/* ═══════════════════════════════════════════════════════════════════════
   Tabler-Bindung FitOrgOS

   Stand 12.09.2026, abgeleitet aus den Inhaberentscheidungen in
   docs/tabler-m02-entscheidungsvorlage.md, Abschnitt 1. Am 12.09.2026 mit
   der M02-Freigabe aus docs/tabler-prototyp/tabler-bindung.entwurf.css
   hierher übernommen; inhaltlich unverändert bis auf zwei Belege, die beim
   Übernehmen nachgeprüft wurden (Pfad der Lieferdatei, Fundstelle von
   --brand-text).

   Noch von keiner Seite eingebunden. Die erste ist die Pilotseite aus M04,
   admin/admin_aenderungsprotokoll.html.

   Ladereihenfolge in einer migrierten Fachseite:
     1. assets/css/fitbox-fonts.css           (Geist, genau einmal)
     2. assets/css/fitbox-tokens.css          (--brand, --brand-text, Linien)
     3. assets/vendor/tabler/1.6.1/tabler.min.css
     4. diese Datei
   NICHT dazu: fitbox-components.css, fitbox-layouts.css, tabler-themes.css,
   dist/js/tabler-theme.js. Zeilenangaben beziehen sich auf dist/css/tabler.css.

   FALLE FUER PROSA-KOMMENTARE, DREIMAL live getroffen (13.09.2026, PR #895,
   #898, #907) - absichtlich UMSCHRIEBEN statt woertlich zitiert, sonst waere
   dieser Warnhinweis selbst ein viertes Beispiel:
     Erstens: die zwei Satzzeichen, die einen CSS-Kommentar SCHLIESSEN,
     koennen in einer Aufzaehlung von CSS-Eigenschaften zufaellig
     nebeneinanderstehen - etwa wenn eine Endung auf das schliessende
     Satzzeichen einer Sternchen-Wildcard trifft und direkt danach ein neuer
     Eigenschaftsname mit Schraegstrich abgetrennt beginnt. Das beendet den
     Kommentar MITTEN IM SATZ; der Browser-Parser verschluckt danach still
     alles bis zur naechsten sauberen Regel (in einem Fall eine ganze Regel),
     OHNE Fehlermeldung. Ein Screenshot zeigt nur "die Regel wirkt nicht",
     nie warum.
     Zweitens: ci/check-tabler-seiten.mjs liest den blossen CSS-Text eines
     Stilblatts auf Vorkommen von Klassennamen. Erwaehnt ein Kommentar zwei
     durch einen Punkt verbundene Klassen wie Baukastens "btn" und ein
     Modifikatorname wie "sekundaer" IN GENAU DIESER Schreibweise, zaehlt der
     Pruefer das als Definition der zweiten Klasse - auch wenn nirgends eine
     echte Regel dafuer steht.
     Drittens: ci/check-css.mjs sucht im gesamten Dateitext (Kommentare nicht
     ausgenommen) nach einem Verweis auf eine eigene Variable und prueft, ob
     sie definiert ist. Ein woertliches Zitat einer echten Regel in der Prosa,
     das an einem Zeilenumbruch mitten im Namen der Variable endet, liest der
     Pruefer als abgebrochenen, undefinierten Verweis - selbst wenn die
     zitierte Variable an anderer Stelle laengst existiert. Beschreiben statt
     zitieren, wo ein solcher Verweis sonst ueber eine Zeile umbrechen wuerde.
     VIERTER Beleg, in einem anderen Pruefer: ci/check-baukasten-abkopplung.mjs
     (13.09.2026 gebaut) las beim ersten Lauf ebenfalls Kommentare mit und
     hielt .az-kpi fuer gebunden, obwohl diese Datei die Klasse nur DREImal
     erwaehnt und keine einzige Regel dafuer traegt - nach dem Kommentar-
     schnitt kamen acht bis dahin verschwiegene Funde heraus.
   DIE REGEL DAHINTER, nicht nur die vier Einzelfaelle: JEDES Skript, das
   Klassen-, Selektor- oder Variablennamen im blossen CSS-TEXT sucht (per
   indexOf/RegExp, nicht per CSSOM/Parser), muss Kommentare VOR der Suche
   wegschneiden - sonst zaehlt Erwaehnung als Definition, und eine echte
   Luecke bleibt unentdeckt gruen. Vier verschiedene Pruefer sind heute genau
   daran gescheitert; das ist keine Reihe von Einzelfaellen mehr, sondern ein
   wiederkehrendes Muster bei jedem neuen textbasierten CSS-Pruefer.
   Gegenprobe nach jeder groesseren Kommentaraenderung in dieser Datei: ein
   kurzes Node-Skript zaehlt geschweifte Klammern auf Null UND die
   Kommentaroeffnungen gegen die Kommentarschluesse (muessen gleich sein) -
   siehe die PR-Beschreibungen von #895/#898 fuer das genaue Kommando. Dazu
   ein Blick ins CSSOM im Browser (document.styleSheets), nicht nur ein
   Screenshot: ein verschluckter Kommentar zeigt sich optisch oft gar nicht,
   weil die naechste Regel zufaellig trotzdem greift.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── 1. Schrift ────────────────────────────────────────────────────────── */
:root {
  --tblr-font-sans-serif: 'Geist', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
}

/* Farbhoheit liegt bei Tabler 1.5.0. Keine Rueckbindung von --tblr-*
   auf FitOrgOS-Rollen: die Legacy-Rollen zeigen umgekehrt auf Tabler.
   Der historische Zusatzname bleibt fuer bestehende Komponenten erhalten. */
:root {
  --tblr-primary-text: var(--tblr-link-color);
  /* Inhaberentscheidung 02.10.2026: deutlicher Tastaturfokus, wie im
     freigegebenen Bildvergleich. Voller Farbton auch fuer ungueltige Felder;
     die Gefahrenfarbe bleibt durch Tablers eigene Fokusregel erhalten. */
  --tblr-focus-ring-width: 3px;
  --tblr-focus-ring-opacity: 1;
  --tblr-focus-ring-opacity-dark: 1;
}
/* Text auf neutralem Grund braucht Tablers themeabhaengige Linkfarbe,
   nicht die in beiden Modi identische Farbe einer gefuellten Schaltflaeche. */
.text-primary, .link-primary { color: var(--tblr-link-color) !important; }
.nav-bordered .nav-link.active,
.nav-bordered .nav-item.show .nav-link { color: var(--tblr-link-color); }
.btn-outline-primary, .btn-outline.btn-primary, .btn-ghost-primary {
  --tblr-btn-color: var(--tblr-link-color);
}

/* ── 4. Dichte: Variante B (kompakt) ───────────────────────────────────────
   Zwei echte Tabler-Tokens, dann die vier festen Stellen, fuer die Tabler
   keinen Token hat. Mehr Selektoren darf diese Datei nicht bekommen. */
:root {
  --tblr-body-font-size: 0.8125rem;   /* tabler.css:121, Vorgabe 0.875rem */
  --tblr-border-radius-scale: 0.5;    /* tabler.css:13881-13887, Vorgabe 1 */
  --tblr-page-padding-y: 0.75rem;     /* tabler.css:13951 */
  --fo-zelle-y: 0.45rem;              /* Tabellenzelle, Vorgabe 0.75rem (tabler.css:2315) */
  --fo-zelle-x: 0.625rem;
  --fo-feld-y: 0.375rem;              /* Feld/Schaltflaeche, Vorgabe 0.5625rem 1rem (2559, 3361-3362) */
  --fo-feld-x: 0.75rem;
  --fo-karte: 1rem;                   /* Kartenpolster, Vorgabe 1.25rem (4518-4519) */
}

.table > :not(caption) > * > * { padding: var(--fo-zelle-y) var(--fo-zelle-x); }
.card-table tr td:first-child,
.card-table tr th:first-child  { padding-inline-start: var(--fo-karte); }
.card-table tr td:last-child,
.card-table tr th:last-child   { padding-inline-end: var(--fo-karte); }

.card {
  --tblr-card-spacer-y: var(--fo-karte);
  --tblr-card-spacer-x: var(--fo-karte);
  --tblr-card-cap-padding-y: calc(var(--fo-karte) * 0.75);
  --tblr-card-cap-padding-x: var(--fo-karte);
}

.btn {
  --tblr-btn-padding-y: var(--fo-feld-y);
  --tblr-btn-padding-x: var(--fo-feld-x);
  --tblr-btn-font-size: var(--tblr-body-font-size);
}

.form-control,
.form-select,
.input-group-text { padding-top: var(--fo-feld-y); padding-bottom: var(--fo-feld-y); font-size: var(--tblr-body-font-size); }
.form-control { padding-inline: var(--fo-feld-x); }
.form-select  { padding-inline-start: var(--fo-feld-x); }
.form-label   { font-size: var(--tblr-body-font-size); }

.modal {
  --tblr-modal-padding: var(--fo-karte);
  --tblr-modal-header-padding: var(--fo-karte);
}

/* Gestapelte Mobiltabelle (Entscheidung 3): die Zeilenaktion schliesst die
   Karte ab und wird kein Blockknopf (tabler.css:24570). */
.table-mobile-md .btn { display: inline-block; }

/* ══ ZENTRALER MELDEKANAL ══════════════════════════════════════════════════
   assets/js/fb-errors.js erzeugt jede Rueckmeldung der Anwendung zur Laufzeit
   als .fb-toast-wrap.fb-global-feedback > .fb-toast (fb-errors.js:153 und
   :178). Gestaltet war das bisher ausschliesslich in fitbox-components.css -
   und genau die laedt eine Tabler-Seite nicht mehr.

   GEFUNDEN AM 12.09.2026 beim Umbau der ersten Fachseite, und zwar NICHT von
   einem Pruefer: ci/ui-contract-lib.mjs fuehrt "fb-toast" als Mussbauteil,
   akzeptiert als Nachweis aber den <script>-Verweis auf fb-errors.js. Das
   belegt, dass der Kanal GELADEN ist, nicht dass er SICHTBAR ist. Ohne diese
   Regeln waere jede Fehlermeldung auf einer Tabler-Seite eine nackte Zeile
   Text unten links im Dokument - kein Rahmen, keine Farbe, kein Kontrast.

   Tabler hat dafuer keine Entsprechung: sein .toast ist ein anderes Bauteil
   mit anderer Anatomie, und fb-errors.js schreibt .fb-toast an 130+ Seiten.
   Die Klasse umzubenennen hiesse, den Baukasten anzufassen. Deshalb steht der
   Kanal hier - uebersetzt auf Tabler-Token, Geometrie wie im Baukasten. ══ */
.fb-toast-wrap {
  position: fixed; inset-inline-end: 1rem; bottom: 1rem; z-index: 1090;
  display: flex; flex-direction: column; gap: .5rem; align-items: flex-end;
  pointer-events: none;
}
.fb-toast-wrap.fb-global-feedback {
  inset-inline-start: 50%; inset-inline-end: auto; transform: translateX(-50%);
  align-items: center;
}
.fb-toast {
  width: max-content; min-width: 220px; max-width: min(380px, calc(100vw - 2rem));
  padding: .625rem .875rem;
  border: var(--tblr-border-width) solid var(--tblr-border-color);
  border-inline-start: 4px solid var(--tblr-primary);
  border-radius: var(--tblr-border-radius);
  background: var(--tblr-bg-surface); color: var(--tblr-body-color);
  box-shadow: var(--tblr-shadow-card);
  font-size: var(--tblr-body-font-size); line-height: 1.4;
  overflow-wrap: anywhere; pointer-events: auto;
  animation: fb-toast-in .2s ease-out;
}
.fb-toast:is(.ok, .success) { border-inline-start-color: var(--tblr-success); }
.fb-toast:is(.warn, .warning) { border-inline-start-color: var(--tblr-warning); }
.fb-toast:is(.err, .danger) { border-inline-start-color: var(--tblr-danger); }
@keyframes fb-toast-in { from { opacity: 0; transform: translateY(6px); } }

@media (max-width: 640px) {
  .fb-toast-wrap { inset-inline: .75rem; bottom: .75rem; align-items: stretch; }
  .fb-toast-wrap.fb-global-feedback { transform: none; }
  .fb-toast { width: 100%; min-width: 0; max-width: none; }
}

/* ══ SYMBOLGROESSE ═════════════════════════════════════════════════════════
   assets/js/fb-icons.js setzt jedes <i data-icon> zur Laufzeit mit einem
   <svg width="24" height="24"> (fb-icons.js:24). Auf Schriftgroesse gebracht
   wurde das ausschliesslich von fitbox-components.css:325-326 - und die laedt
   eine Tabler-Seite nicht mehr.

   Im Browser gemessen am 12.09.2026: alle 13 Symbole der Pilotseite standen
   mit 24x24px in einer 13px-Umgebung, also fast doppelt so gross wie gewollt;
   die Knoepfe wuchsen dadurch auf 37px Hoehe. Aufgefallen ist es nicht sofort,
   weil die Knoepfe einfach mitwachsen und plausibel aussehen - genau die Art
   Verlust, die kein Pruefer meldet.

   Tabler fasst das nackte <svg> nirgends: seine Groessenregeln haengen an der
   Klasse .icon (.btn .icon{width:var(--tblr-btn-icon-size)}), und die vergibt
   fb-icons.js nie. Einzige Ausnahme ist .page-title svg{width:1.5rem}, was
   zufaellig denselben Wert trifft. ══ */
i[data-icon] {
  display: inline-flex; width: 1em; height: 1em; flex: 0 0 auto; vertical-align: -.125em;
}
[data-icon] svg { width: 100%; height: 100%; max-width: 24px; max-height: 24px; }

/* ══ LADEMARKIERUNG ════════════════════════════════════════════════════════
   Entscheidung vom 12.09.2026, Vorarbeit 2 aus Roadmap 5.11: Tablers eigenes
   .spinner-border wird uebernommen statt eine eigene .spinner-Klasse in diese
   Datei zu uebersetzen.

   Die Pilotseite nutzt bereits <span class="spinner-border spinner-border-sm"
   role="status"> (admin_aenderungsprotokoll.html:340) und fuehrt spinner-border
   als Mussbauteil in TABLER_PAGE_REQUIREMENTS - der Wortschatz ist also schon
   gesetzt, nur die grosse Groesse fehlte noch. Tabler liefert selbst nur die
   Voreinstellung (1.5rem) und -sm (1rem); eine -lg-Stufe gibt es nicht. Der
   Baukasten kennt sie: .spinner.lg{width:32px;height:32px} - 32px = 2rem,
   heute an 6 Stellen im Bestand verwendet (studio/index.html eine davon).
   Ein einziger Wert statt einer Wiederholung je Seite, gleiche Bauart wie
   spinner-border-sm selbst.

   Farbe: NICHT hier festgelegt. .spinner-border faerbt ueber currentColor;
   welche Farbe das ist, entscheidet die aufrufende Seite ueber eine normale
   Textfarbklasse (z. B. .text-primary, Abschnitt 3 dieser Datei). Eine
   Sonderregel nur fuer den Spinner wuerde die Farbe ein zweites Mal an
   anderer Stelle festschreiben.

   .loading-state ist eine ANDERE Klasse (Baukasten-Leerzustand mit Text und
   Rahmen, nicht nur ein Ladepunkt) und bleibt hier bewusst unentschieden -
   keine der bisher umgestellten Seiten braucht sie.

   BERICHTIGT AM 12.09.2026: die erste Fassung setzte nur Breite und Hoehe.
   Tablers eigenes Vorbild -sm skaliert die Ringstaerke mit
   (--tblr-spinner-border-width:1px); ohne dieselbe dritte Variable blieb der
   grosse Ring bei Tablers 2px-Voreinstellung, obwohl .spinner.lg im
   Baukasten die 3px von .spinner erbt (fitbox-components.css:1381-1387) -
   duenner als der Bestandswert. 3px folgt derselben Staffelung wie -sm. ══ */
.spinner-border-lg { --tblr-spinner-width: 2rem; --tblr-spinner-height: 2rem; --tblr-spinner-border-width: 3px; }

/* ══ SORTIERBARE SPALTENKOEPFE ═════════════════════════════════════════════
   Derselbe Fall wie der Meldekanal darueber: assets/js/fb-dialog-contract.js
   (Abschnitt "Gemeinsamer Tabellenvertrag", ab Zeile 660) baut jeden
   fachlichen Spaltenkopf zu einem echten Sortierknopf um und vergibt dabei
   .fb-table-sort, .fb-table-sort-label und .fb-table-sort-icon. Gestaltet war
   auch das nur in fitbox-components.css.

   Aufgefallen ist es ueber die Klassenpruefung in ci/check-tabler-seiten.mjs -
   und mit ihr gleich ein zweiter, groesserer Fehler: der Adapter greift
   '.table-wrap table, table[data-fb-sortable="true"]', und der Umbau hatte
   .table-wrap durch Tablers .table-responsive ersetzt. Beide Tabellen hatten
   das Sortieren damit still verloren. Die Seite traegt jetzt
   data-fb-sortable="true" ausdruecklich. ══ */
.fb-table-sort {
  appearance: none; width: 100%; min-width: 0; display: inline-flex;
  align-items: center; justify-content: space-between; gap: .5rem;
  margin: -.375rem -.5rem; padding: .375rem .5rem; border: 0;
  border-radius: var(--tblr-border-radius-sm, var(--tblr-border-radius));
  color: inherit; background: transparent; font: inherit; letter-spacing: inherit;
  text-transform: inherit; text-align: start; white-space: nowrap; cursor: pointer;
}
.fb-table-sort:hover { color: var(--tblr-body-color); background: var(--tblr-bg-surface-secondary); }
.fb-table-sort:focus-visible { outline: 2px solid var(--tblr-primary); outline-offset: 1px; }
.fb-table-sort-icon {
  display: inline-flex; flex: 0 0 14px; width: 14px; height: 14px;
  color: var(--tblr-secondary); opacity: .58;
}
.fb-table-sort-icon > svg { width: 100%; height: 100%; }
th[aria-sort="ascending"] .fb-table-sort-icon,
th[aria-sort="descending"] .fb-table-sort-icon { color: var(--tblr-primary-text); opacity: 1; }

/* ══ WEICHE ABZEICHEN (.bg-*-lt) KONTRASTFEST ══════════════════════════════
   Tabler setzt bei diesen Varianten die Textfarbe auf die VOLLE Farbe und den
   Grund auf 10 % davon (tabler.min.css, Regel .bg-<name>-lt). Das sieht gut
   aus und ist auf kleinem Text zu schwach.

   Im Browser gemessen am 12.09.2026 auf der Pilotseite, Text gegen den
   tatsaechlich gerenderten Kartengrund:
     hell:   gruen 2,49 · orange 2,73 · cyan 2,74 · teal 2,80 · lime 2,24
     dunkel: blau 2,69 · violett 2,80 · rot 2,95 · pink 2,98
   Verlangt sind 4,5 (WCAG AA, normaler Text - ein Abzeichen ist mit 0,857em
   kein grosser Text). Der Baukasten erzwingt denselben Wert ueber
   ci/check-kontrast.mjs; der Pruefer kennt Tablers Klassen nur nicht, deshalb
   war das nirgends rot.

   65 % der Farbe, der Rest Schwarz im Hellen und Weiss im Dunkeln, traegt fuer
   fast alle Varianten. Ein einziger Wert statt 22 einzelner, damit niemand
   spaeter eine Farbe uebersieht.

   BERICHTIGT AM 12.09.2026. Hier stand: "der einheitliche Wert, mit dem ALLE 22
   ausgelieferten Varianten in BEIDEN Themes ueber 4,5 kommen (schlechtester
   Wert danach: 4,86 hell, 4,91 dunkel)". Das war falsch. 4,86 ist lime; Gelb
   liegt darunter. Nachgemessen von ci/check-kontrast.mjs (PR #798, 264 Paare,
   Browser-Gegenprobe mit hoechstens 0,03 Abweichung):

     .bg-warning-lt / .bg-yellow-lt, beide #f59f00, hell:
       bei 65 %  ->  4,40 auf der Karte, 4,24 auf der Seitenflaeche
       bei 60 %  ->  5,01 auf der Karte, 4,82 auf der Seitenflaeche
                     (Browser 5,00 / 4,80)
     Damals auf 60 % gesetzt. Auf dem neuen grauen Workspace (17.09.2026)
     sinkt der Wert auf 4,48:1: deshalb jetzt 55 %. Lime braucht dort
     entsprechend 60 statt 65 %. Dunkle Varianten bleiben unveraendert.

     .bg-primary-lt haengt ueber --tblr-primary an --brand und war gegen Tablers
     Palette gemessen, nicht gegen die Hausfarben. Linie Anthrazit im Dunkeln
     kam damit auf 3,49. Steht jetzt auf 50 % - siehe unten.

   Lehre: ein Mischwert, der an einer festen Palette stimmt, stimmt nicht
   automatisch an einer Farbe, die per Token von aussen kommt.

   Die drei Grauvarianten bekommen die Grundtextfarbe: bei .bg-white-lt und
   .bg-light-lt auf hellem, bei .bg-dark-lt auf dunklem Grund fuehrt JEDE
   Mischung ihrer eigenen Farbe zwangslaeufig unter die Schwelle - dort ist
   die Farbe nicht die Botschaft, der Grundton ist es.

   !important ist noetig, weil Tablers eigene Regel es ebenfalls traegt. ══ */
.bg-azure-lt { color: color-mix(in srgb, var(--tblr-azure) 65%, black) !important; }
.bg-blue-lt { color: color-mix(in srgb, var(--tblr-blue) 65%, black) !important; }
.bg-cyan-lt { color: color-mix(in srgb, var(--tblr-cyan) 65%, black) !important; }
.bg-danger-lt { color: color-mix(in srgb, var(--tblr-danger) 65%, black) !important; }
.bg-green-lt { color: color-mix(in srgb, var(--tblr-green) 65%, black) !important; }
.bg-indigo-lt { color: color-mix(in srgb, var(--tblr-indigo) 65%, black) !important; }
.bg-info-lt { color: color-mix(in srgb, var(--tblr-info) 65%, black) !important; }
.bg-lime-lt { color: color-mix(in srgb, var(--tblr-lime) 60%, black) !important; }
.bg-muted-lt { color: color-mix(in srgb, var(--tblr-muted) 65%, black) !important; }
.bg-orange-lt { color: color-mix(in srgb, var(--tblr-orange) 65%, black) !important; }
.bg-pink-lt { color: color-mix(in srgb, var(--tblr-pink) 65%, black) !important; }
/* 50 statt 65: --tblr-primary haengt an --brand, die je Designlinie wechselt.
   Der einheitliche Wert war an Tablers fester Palette gemessen und trug die
   Hausfarben nicht - Linie Anthrazit im Dunkeln kam auf 3,49.

   Warum EIN Wert fuer alle drei Linien reicht: der Kontrast steigt monoton, je
   kleiner der Farbanteil wird (hell mischt mit Schwarz, dunkel mit Weiss, beide
   Richtungen entfernen den Text von der Flaeche). Es gibt also keine Obergrenze
   aus dem Kontrast, nur aus der Farberkennbarkeit - das ist eine Rechnung, keine
   Entscheidung.

   Warum 50 und nicht 55: 55 % ist der groesste Anteil, der alle zwoelf
   Kombinationen ueber 4,5 bringt - aber nur mit 4,57 (Browser 4,54). Der
   Abstand liegt innerhalb der Messgenauigkeit zwischen statischem Modell und
   Browser (bis 0,03); ein Pruefer, der auf 0,04 Abstand steht, flattert.

   Bei 50 % (statisch, Browser in Klammern):
     fitbox/hell      Karte 10,01 (10,01)   Seite  9,58 (9,60)
     fitbox/dunkel    Karte  6,68 ( 6,64)   Seite  8,09 (8,05)
     salbei/hell      Karte 10,27 (10,26)   Seite  9,83 (9,85)
     salbei/dunkel    Karte  6,72 ( 6,66)   Seite  8,11 (8,08)
     anthrazit/hell   Karte 14,34 (14,40)   Seite 13,79 (13,80)
     anthrazit/dunkel Karte  5,18 ( 5,16)   Seite  6,17 (6,16)  <- engster Fall
   Schlechtester Wert 5,18, Abstand 0,68.

   Je Linie waere mehr Saettigung moeglich (fitbox 73, salbei 73, anthrazit 55).
   Bewusst nicht gemacht: drei Regeln fuer eine Farbe sind genau die
   Zersplitterung, gegen die "ein einziger Wert statt 22" oben argumentiert, und
   der Gewinn waere Saettigung in zwei Linien, die ohnehin bei 10 und 6,7 liegen. */
.bg-primary-lt { color: color-mix(in srgb, var(--tblr-primary) 50%, black) !important; }
.bg-purple-lt { color: color-mix(in srgb, var(--tblr-purple) 65%, black) !important; }
.bg-red-lt { color: color-mix(in srgb, var(--tblr-red) 65%, black) !important; }
.bg-secondary-lt { color: color-mix(in srgb, var(--tblr-secondary) 65%, black) !important; }
.bg-success-lt { color: color-mix(in srgb, var(--tblr-success) 65%, black) !important; }
.bg-teal-lt { color: color-mix(in srgb, var(--tblr-teal) 65%, black) !important; }
/* 55 %: auch auf dem grauen Workspace genuegend Abstand zu 4,5:1. */
.bg-warning-lt { color: color-mix(in srgb, var(--tblr-warning) 55%, black) !important; }
.bg-yellow-lt { color: color-mix(in srgb, var(--tblr-yellow) 55%, black) !important; }

.bg-white-lt,
.bg-light-lt,
.bg-dark-lt { color: var(--tblr-body-color) !important; }

:root[data-theme="dark"] .bg-azure-lt { color: color-mix(in srgb, var(--tblr-azure) 65%, white) !important; }
:root[data-theme="dark"] .bg-blue-lt { color: color-mix(in srgb, var(--tblr-blue) 65%, white) !important; }
:root[data-theme="dark"] .bg-cyan-lt { color: color-mix(in srgb, var(--tblr-cyan) 65%, white) !important; }
:root[data-theme="dark"] .bg-danger-lt { color: color-mix(in srgb, var(--tblr-danger) 65%, white) !important; }
:root[data-theme="dark"] .bg-green-lt { color: color-mix(in srgb, var(--tblr-green) 65%, white) !important; }
:root[data-theme="dark"] .bg-indigo-lt { color: color-mix(in srgb, var(--tblr-indigo) 65%, white) !important; }
:root[data-theme="dark"] .bg-info-lt { color: color-mix(in srgb, var(--tblr-info) 65%, white) !important; }
:root[data-theme="dark"] .bg-lime-lt { color: color-mix(in srgb, var(--tblr-lime) 65%, white) !important; }
:root[data-theme="dark"] .bg-muted-lt { color: color-mix(in srgb, var(--tblr-muted) 65%, white) !important; }
:root[data-theme="dark"] .bg-orange-lt { color: color-mix(in srgb, var(--tblr-orange) 65%, white) !important; }
:root[data-theme="dark"] .bg-pink-lt { color: color-mix(in srgb, var(--tblr-pink) 65%, white) !important; }
:root[data-theme="dark"] .bg-primary-lt { color: color-mix(in srgb, var(--tblr-primary) 50%, white) !important; }
:root[data-theme="dark"] .bg-purple-lt { color: color-mix(in srgb, var(--tblr-purple) 65%, white) !important; }
:root[data-theme="dark"] .bg-red-lt { color: color-mix(in srgb, var(--tblr-red) 65%, white) !important; }
:root[data-theme="dark"] .bg-secondary-lt { color: color-mix(in srgb, var(--tblr-secondary) 65%, white) !important; }
:root[data-theme="dark"] .bg-success-lt { color: color-mix(in srgb, var(--tblr-success) 65%, white) !important; }
:root[data-theme="dark"] .bg-teal-lt { color: color-mix(in srgb, var(--tblr-teal) 65%, white) !important; }
:root[data-theme="dark"] .bg-warning-lt { color: color-mix(in srgb, var(--tblr-warning) 65%, white) !important; }
:root[data-theme="dark"] .bg-yellow-lt { color: color-mix(in srgb, var(--tblr-yellow) 65%, white) !important; }

/* btn-secondary: dunkel weiss auf #9ca3af = 2,43:1, Zaehler darin 1,84/1,44:1
   (gemessen 14.09.2026). Dunkel dunkle Schrift, Zaehler invertiert. */
:root[data-theme="dark"] .btn-secondary { --tblr-secondary-fg: var(--tblr-gray-900); }
:root .btn-secondary .bg-secondary-lt { color: var(--tblr-btn-bg) !important; background-color: var(--tblr-btn-color) !important; }

/* ══ SEITENNAVIGATION DER ADMIN-HUELLE ═════════════════════════════════════
   Die Rail von admin/index.html traegt seit M03 Tablers Anatomie
   (.navbar.navbar-vertical > .navbar-nav > .nav-item > .nav-link). Ihr
   AUSSEHEN ist damit nicht Tablers Voreinstellung geworden - das waere ein
   Bruch gewesen, kein Umbau. Diese Regeln bringen die vorhandene Handschrift
   auf die neue Anatomie.

   Im Browser gegen den alten Stand gemessen und Wert fuer Wert angeglichen
   (12.09.2026): Flaeche #ffffff mit 10px Radius und 10px/8px Polsterung,
   Eintraege 42px hoch mit 6px Radius, 10px/12px Polsterung, 13px/650,
   aktiver Eintrag weiss mit 3px Markenbalken links. Nach der Angleichung:
   keine Abweichung mehr, alle sieben Eintraege exakt 42px.

   Der Balken nimmt --brand und dreht damit die Designlinie mit, statt eine
   Farbe festzuschreiben - dieselbe Regel wie beim Rest dieser Datei.

   Warum hier und nicht in shell-nav.css: dort wuerde sie dashboard.html
   treffen, das dieselben Altklassen benutzt und Tabler NICHT laedt. Diese
   Datei erreicht ausschliesslich Seiten mit data-tabler. ══ */
/* KEIN navbar-expand-* an dieser Sidebar, solange die Huelle ihr eigenes
   Flex-Layout benutzt. Tabler setzt mit .navbar-vertical.navbar-expand-sm
   position:fixed;inset-inline-start:0;top:0;bottom:0;z-index:1030 (Spezifitaet
   0,2,0) und schlaegt damit shell-nav.css' .sidebar{position:relative} (0,1,0).
   Das ist Tablers gewollte Anatomie - aber nur gemeinsam mit .page/.page-wrapper,
   die den Platz per padding-inline-start freihalten. In der halbumgebauten Huelle
   gibt es diesen Platz nicht: am 12.09.2026 live gemessen lag die Navigation ueber
   den linken 208px von Kopfleiste und Inhalt (.main-wrapper links 8px statt 224px).
   Erzwungen von ci/check-tabler-seiten.mjs; die Regel schliesst sich selbst ab,
   sobald .shell und .main-wrapper durch .page und .page-wrapper ersetzt sind. */
:root[data-tabler] .navbar-vertical {
  background: transparent; border: 0; box-shadow: none; padding: 0;
  display: flex; flex-direction: column; height: 100%; min-height: 0;
  /* Tabler setzt ab 576px .navbar-vertical.navbar-expand-sm{align-items:start}.
     Damit nimmt der Menuekasten (.navbar-nav) die Breite seines laengsten
     Eintrags statt der Menuebreite: live am 12.09.2026 gemessen 227px in
     einer 208px breiten Leiste, der Kasten lag 11px ueber der linken Kante
     des Inhaltsfensters (z-index 1030), der 8px-Spalt war weg. Die
     Studio-Huelle (dashboard.html) haelt ihre .rail fest auf 208px. */
  align-items: stretch;
}
:root[data-tabler] .navbar-vertical > .navbar-nav {
  --tblr-nav-link-padding-y: .625rem;
  --tblr-nav-link-padding-x: .75rem;
  --tblr-sidebar-nav-padding-y: .625rem;
  --tblr-sidebar-nav-padding-x: .75rem;
  display: flex; flex-direction: column; gap: 4px;
  background: var(--shell-nav-bg, #ffffff);
  border-radius: var(--tblr-border-radius-lg); padding: .625rem .5rem;
  flex: 1; min-height: 0; overflow-y: auto;
  /* Gleiche Kastenoptik wie die .rail der Studio-Huelle (shell-nav.css):
     1px Rahmen in der Regionenfarbe. Mit Rahmen und 8px Polsterung sind die
     Eintraege 190px breit - exakt der gemessene Studio-Wert. */
  min-width: 0; width: 100%;
  border: 1px solid var(--shell-region-border, #e5e7eb);
}
:root[data-tabler] .navbar-vertical .nav-link {
  position: relative; display: flex; align-items: center; gap: .5rem;
  width: 100%; min-height: 2.625rem; text-align: start;
  background: transparent; border: 0; border-radius: var(--tblr-border-radius);
  font-size: .8125rem; font-weight: 650; color: var(--shell-nav-text, var(--ink, #374151));
}
:root[data-tabler] .navbar-vertical .nav-link:hover {
  background: var(--shell-nav-hover, #f9fafb); color: var(--shell-nav-text, var(--ink, #374151));
}
/* Die aktive Flaeche dreht im Dunkelmodus gemeinsam mit ihrer Textrolle. Ein
   festes Weiss liess dort die helle Standard-Schrift auf #fff verschwinden. */
:root[data-tabler] .navbar-vertical .nav-link.active {
  background: var(--shell-nav-active-bg, #fff); color: var(--shell-nav-active-text, #374151);
}
:root[data-tabler] .navbar-vertical .nav-link.active::before {
  content: ''; position: absolute; inset-inline-start: 0; top: 8px; bottom: 8px;
  width: 3px; border-radius: 2px; background: var(--brand);
}
:root[data-tabler] .navbar-vertical .nav-link-icon { display: inline-flex; flex: 0 0 auto; }
/* Die untere Gruppe bleibt unten - frueher .nav-bottom beziehungsweise
   .nav-secondary in shell-nav.css. */
:root[data-tabler] .navbar-vertical .nav-bottom,
:root[data-tabler] .navbar-vertical .nav-item.nav-secondary { margin-top: auto; }


/* ══ M03 SCHRITT C: TABLERS HUELLENANATOMIE ══════════════════════════════════
   Entscheidung des Inhabers vom 12.09.2026: die Tabler-Anordnung. Navigation
   links ueber die volle Hoehe, Kopfleiste rechts daneben.

   Das Versetzen leistet Tabler selbst:
     .navbar-expand-sm.navbar-vertical ~ .navbar,
     .navbar-expand-sm.navbar-vertical ~ .page-wrapper
       { margin-inline-start: var(--tblr-sidebar-width) }
   Bedingung dafuer ist die Geschwisterbeziehung - deshalb steht die Kopfleiste
   seit Schritt C INNERHALB von .page, hinter der Navigation.

   Hier steht nur, was die Marke ausmacht: der 8px-Rahmen zwischen den Regionen
   und die abgerundeten Flaechen. Warum hier und nicht in shell-chrome.css:
   dort wuerde es dashboard.html und superadmin/index.html treffen, die
   dieselben Altklassen benutzen und Tabler NICHT laden. Diese Datei erreicht
   ausschliesslich Seiten mit data-tabler. ═══════════════════════════════════ */

:root[data-tabler] {
  /* 208px Navigation + 8px Spalt. Damit vergibt Tabler an Kopfleiste und
     Inhalt genau den Versatz, bei dem der Inhalt wie bisher bei 224px
     beginnt - gemessen am Stand vor dem Umbau. */
  --tblr-sidebar-width: 224px;
}

/* Tabler setzt html{scrollbar-gutter:stable}. Die Huelle scrollt nie
   (html, body overflow:hidden), der Rinnstein blieb trotzdem reserviert:
   live am 12.09.2026 gemessen 15px leerer Rand rechts neben dem 8px-Spalt,
   das Inhaltsfenster endete bei 1912 - 15 = 1897. Die Studio-Huelle
   (dashboard.html, ohne Tabler) endet bei 1912. Nur die Huelle, nicht die
   Fachseiten im iframe - die scrollen wirklich und behalten Tablers
   Voreinstellung. */
:root[data-tabler]:has(> body.app-shell) { scrollbar-gutter: auto; }

:root[data-tabler] .page {
  display: flex;
  flex-direction: column;
  min-height: 100%;
  background: var(--shell-workspace-bg, #fff);
}

/* Die Navigation sitzt im 8px-Rahmen statt buendig an der Fensterkante.
   Tabler setzt inset-inline-start/top/bottom auf 0 und width auf die volle
   Sidebarbreite; beides wird hier auf den Markenrahmen zurueckgeholt. */
@media (min-width: 576px) {
  :root[data-tabler] .navbar-vertical.navbar-expand-sm {
    /* height:100% stammt aus Schritt B, als die Navigation noch im Fluss lag.
       Fixiert wuerde sie damit 8px ueber den unteren Rand hinauslaufen - die
       Hoehe soll sich aus top/bottom ergeben. */
    /* Tablers fixierte Navigation ist ab Schritt C ausdruecklich gewollt.
       position und z-index stehen hier wiederholt, obwohl Tabler sie ohnehin
       setzt: so ist die Absicht im eigenen Bestand belegt und nicht nur
       implizit aus dem Fremdpaket uebernommen. shell-nav.css sagt weiter
       position:relative/z-index:100 - das gilt jetzt nur noch fuer
       dashboard.html, das Tabler nicht laedt. */
    position: fixed;
    z-index: 1030;
    height: auto;
    inset-inline-start: var(--shell-region-gap, 8px);
    top: var(--shell-region-gap, 8px);
    bottom: var(--shell-region-gap, 8px);
    width: calc(var(--tblr-sidebar-width) - var(--shell-region-gap, 8px) - var(--shell-region-gap, 8px));
  }
}
/* Die Medienbedingung ist Pflicht, nicht Kosmetik: Tabler fixiert die
   Navigation nur ab 576px (@media min-width:576px um navbar-expand-sm).
   Ohne die Klammer griffe dieser Block auch darunter und setzte den Auszug
   auf top:8px - gemessen am 12.09.2026 lag er damit UEBER der Kopfleiste,
   statt wie vorgesehen darunter (oben 72 statt 8). Unterhalb 576px gelten
   die Auszugswerte aus shell-nav.css. */

/* 576-760px-BRUECKE, 15.09.2026 (aus dashboard.html gehoben, siehe dortigen
   Kommentar). Tablers navbar-expand-sm fixiert die Navigation schon ab
   576px (Regel oben); der gemeinsame Auszug-Vertrag in shell-nav.css bleibt
   aber bis einschliesslich 760px eine Schublade. Ohne diese Bruecke liegt
   der Auszug in dieser Lücke bei top:8px ueber der Kopfzeile und mit
   z-index:1030 ueber dem Hamburger - live an admin/index.html UND
   dashboard.html gemessen (650px, 15.09.2026): beide zeigten vor der
   Bruecke top:8px/width:208px/z-index:1030, beide danach identisch
   top:72px/width:280px/z-index:400. Keine huellenspezifische Geometrie,
   also body.shell-desktop statt der frueheren .page.shell-Voranstellung
   (die admin/index.html strukturell ausgeschlossen haette - .page traegt
   dort kein .shell). Werte und Schaltpunkt entsprechen dem bestehenden
   Drawer-Vertrag in shell-nav.css (760px, an ci/check-shell-bruchstellen.mjs
   gekoppelt). */
@media (min-width:576px) and (max-width:760px){
  :root[data-tabler] body.shell-desktop .sidebar.navbar-vertical.navbar-expand-sm{
    top:calc(var(--header-h) + var(--shell-region-gap,8px) + var(--shell-region-gap,8px));
    bottom:var(--shell-region-gap,8px);
    width:min(280px,calc(100vw - 40px));
    height:auto;
    z-index:400;
  }
}

/* shell-chrome.css gibt der Kopfleiste "margin: 8px 8px 0" und
   "width: calc(100% - 16px)". Die Kurzform setzt margin-inline-start zurueck
   und hebt Tablers Versatz auf; shell-chrome laedt nach Tabler. Deshalb hier
   mit hoeherer Spezifitaet nachgezogen.
   Gescopet auf body.shell-desktop - denselben Traeger, den shell-chrome.css
   selbst fuer .topbar verlangt (Zeile 44 dort). Vorher stand hier nur
   :root[data-tabler], also das Migrationsattribut ohne Huellenbezug: JEDE
   Seite mit eigenem .topbar UND data-tabler bekam den Sidebar-Versatz
   aufgedrueckt, ob sie in der Huelle sass oder nicht. Am 15.09.2026 traf das
   zwei eigenstaendige Seiten (admin/admin_newsletter.html als iframe-
   Dokument mit eigener 46px-Editorleiste, studio/stempeluhr_terminal.html
   als huellenloses Wandterminal) - beide ohne body.shell-desktop, beide
   bekamen trotzdem 224px Linksversatz und passende Breitenkuerzung. Eine
   dritte, bis dahin unbemerkte Seite: superadmin/index.html traegt
   data-tabler und .topbar (dort mit navbar kombiniert), aber body.shell-
   superadmin OHNE shell-desktop - bekam denselben Versatz, rein zufaellig
   unauffaellig, weil die eigene Control-Center-Sidebar ihrerseits 224px
   breit ist (Belegmessung vor diesem Fix: beide vorher-Screenshots und
   Rueck-Messung nach Entfernen der Klasse, siehe PR-Text). */
:root[data-tabler] body.shell-desktop .topbar {
  margin-inline-start: var(--tblr-sidebar-width);
  width: calc(100% - var(--tblr-sidebar-width) - var(--shell-region-gap, 8px));
}

/* ══ DAS LEERE BAND WAR .shell-page-bar, NICHT .topbar ══════════════════════
   Inhaberentscheid 14.09.2026 (Bildschirmfotos aus Produktion). Erster
   Versuch (.topbar-left/-right stauchen) traf das falsche Element - im
   echten Huellenmarkup gemessen (scripts/serve-shell-fixture.mjs,
   #franchise_wissen_uebersicht/#franchise_zentrale_kennzahlen, 1729/1280px):
   das Band ist .shell-page-bar (assets/js/shell-workspaces.js, pageChrome()),
   ein zur Laufzeit erzeugtes <div>, das .page-wrapper (die WEISSE Flaeche
   ueber #page-frame) vorangestellt wird - .topbar (die gruene Leiste) blieb
   davon unberuehrt. Gemessen: .topbar top 8/bottom 54 (46px), .shell-page-bar
   top 63/bottom 127 (64px, eigener border-bottom als Trennlinie), #page-frame
   top 127. "Netzwerk"/"Neu laden" standen in .shell-page-context/
   .shell-page-actions INNERHALB dieses Bands, nicht in .topbar.
   Kontext und Aktionen wandern seither dauerhaft in .topbar (siehe
   pageChrome()); .shell-page-bar traegt nur noch Titel und Brotkrumenpfad
   der Huelle und blendet sich aus, sobald beides leer ist - bei
   Tabler-Kindern, weil deren Titel dort schon seit Designvertrag 6.2 leer
   bleibt (setFramePageInfo()/istTablerKind()). Keine zweite Erkennung: die
   Klasse liest nur den Text, den dieselbe Stelle bereits setzt.
   body.shell-workspaces vorangestellt: shell-chrome.css setzt .shell-workspaces
   .shell-page-bar { display:flex } mit derselben Spezifitaet (zwei Klassen)
   und laedt nach dieser Datei - gleicher Fallstrick wie beim ersten Versuch
   mit .topbar-left/-right, diesmal vorab vermieden statt nachtraeglich
   gefunden (gemessen: ohne body.shell-workspaces blieb display:flex, Band
   64px hoch trotz is-empty-Klasse). */
body.shell-workspaces .shell-page-bar.is-empty { display: none; }

/* Der Inhaltsbereich. body.shell-admin .main-wrapper aus shell-chrome.css
   greift nicht mehr, seit die Klasse page-wrapper heisst - die Regionenoptik
   kommt jetzt von hier. */
:root[data-tabler] .page-wrapper {
  position: relative;
  display: flex;
  flex: 1;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  margin-inline-end: var(--shell-region-gap, 8px);
  margin-block: var(--shell-region-gap, 8px) var(--shell-region-gap, 8px);
  background: var(--shell-workspace-bg, #fff);
  border: 1px solid var(--shell-region-border, #e5e7eb);
  border-radius: var(--tblr-border-radius);
}

/* Der Versatz gilt nur, solange die Navigationsleiste wirklich steht.
   shell-nav.css macht sie ab 760px zur Schublade und schliesst sie
   standardmaessig (.sidebar.collapsed { transform: translateX(-110%) }).
   Tablers navbar-expand-sm versetzt Kopfleiste und Inhalt aber bis 576px
   weiter um die volle Leistenbreite.

   BERICHTIGT AM 12.09.2026. Hier stand 575.98px - Tablers Bruchstelle statt
   der des Bestands. Dazwischen war der Inhalt fuer eine Leiste eingerueckt,
   die nicht da war. Lokal ueber elf Breiten gemessen:
       762px  Leiste bei  l=8     Inhalt l=224   in Ordnung
       761px  Leiste bei  l=8     Inhalt l=224   in Ordnung
       760px  Leiste bei  l=-221  Inhalt l=224   216px leer
       640px  Leiste bei  l=-221  Inhalt l=224   Fachseite 393 von 640px
   Die Navigation war dabei erreichbar - der Schalter "Menue ein- oder
   ausblenden" oeffnet die Schublade, gemessen. Verschenkt war nur die Flaeche.

   DIESE ZAHL IST AN shell-nav.css GEKOPPELT. Wer dort die 760 aendert, muss
   sie hier mitaendern; ci/check-shell-bruchstellen.mjs erzwingt das. */
@media (max-width: 760px) {
  /* Gleicher Huellenbezug wie oben (Zeile ~610) - dieselbe Regel, nur fuer
     die eingeklappte Navigation unter 760px. Ohne body.shell-desktop traf
     sie dieselben zwei huellenlosen Seiten: statt vollen 100% bekam ihr
     .topbar dort einen unerwuenschten 8px-Rand plus 16px-Breitenabzug. */
  :root[data-tabler] body.shell-desktop .topbar {
    margin-inline-start: var(--shell-region-gap, 8px);
    width: calc(100% - var(--shell-region-gap, 8px) - var(--shell-region-gap, 8px));
  }
  :root[data-tabler] .page-wrapper { margin-inline-start: var(--shell-region-gap, 8px); }
}


/* ══ M03 SCHRITT D: DIE KOPFLEISTE ══════════════════════════════════════════
   Sie traegt Tablers .navbar - aber bewusst OHNE navbar-expand-* und ohne
   <header>-Tag. Beides waere ein Fehler, beides gemessen am 12.09.2026:

   1. tabler.min.css enthaelt zwei exakt gespiegelte Regeln:
        html:not([data-bs-navbar-position="vertical"])
          .page:has(> [class*="navbar-expand"]:not(.navbar-vertical))
          > .navbar-vertical { display: none }
        html[data-bs-navbar-position="vertical"]
          .page:has(> .navbar-vertical)
          > [class*="navbar-expand"]:not(.navbar-vertical) { display: none }
      Tabler behandelt vertikale und horizontale Leiste als ALTERNATIVEN, sobald
      beide direkte Kinder von .page sind - nie als Kombination. Mit
      navbar-expand-md an der Kopfleiste verschwand die Navigation vollstaendig
      (display:none, 0x0 gemessen). Tablers eigenes vertikales Layout hat gar
      keine separate Kopfleiste; Marke, Suche und Benutzermenue stecken dort in
      der Sidebar. Die hier gewaehlte Anordnung ist eine Zusammenstellung aus
      Tabler-Teilen, kein Tabler-Layout - und das ist die Inhaberentscheidung
      vom 12.09.2026.

   2. navbar-expand-* steuert ausserdem das Einklappen und braucht tabler.min.js,
      das die Huelle bewusst nicht laedt. Es waere totes Gewicht mit Nebenwirkung.

   3. Ein <header>-Tag statt <div> wuerde die Regel ".in-iframe header" aus dem
      Inline-Stilblock der Huelle treffen und die Leiste im eingebetteten Fall
      verstecken. Tabler gestaltet ueber Klassen, das Tag bringt nichts.

   Was .navbar mitbringt und hier bleibt: flex-wrap:wrap und
   justify-content:space-between. Bei 375, 768 und 1394 gemessen - kein Umbruch,
   kein Ueberlauf, die Kinder schrumpfen ueber min-width:0 aus shell-chrome.css.
   Absichtlich NICHT uebernommen: d-print-none. Der Bestand hat keine einzige
   Druckregel; das waere eine unbestellte Verhaltensaenderung. ═══════════════ */

/* == BREITE: "wide" HEISST UNBEGRENZT, AUCH UNTER TABLER =====================
   Entschieden am 12.09.2026 im Zuge der M05-Vorbereitung.

   Der Bestand kennt vier Breitenprofile (fitbox-layouts.css:26-29); "wide"
   und "full" bedeuten dort --ui-page-max: none, also ausdruecklich KEINE
   Deckelung. Tablers .container-xl deckelt dagegen immer: 1140px ab 1200px
   Viewport, 1320px ab 1400px.

   Auf der Pilotseite treffen beide aufeinander, und sie widersprechen sich:
   admin/admin_aenderungsprotokoll.html:101 erklaert data-ui-width="wide",
   Zeile 102 rendert in .container-xl. Lokal bei 1920px gemessen: Inhalts-
   bereich 1905 breit, Container 1320, also 585px ungenutzt. Im iframe der
   Huelle (dort 1671 breit) sind es rund 350.

   Die Deckelung ist fuer Fliesstext richtig und fuer Datentabellen falsch -
   und "wide" tragen genau die Seiten, die breite Tabellen zeigen. Das
   Breitenprofil der Seite gewinnt deshalb ueber Tablers Voreinstellung.
   Seiten mit "standard" oder "narrow" behalten Tablers Deckelung.

   Warum hier und nicht im Markup: ci/ui-contract-lib.mjs:904 fuehrt
   container-xl als Mussbauteil jeder Tabler-Seite. Die Klasse muss also
   stehenbleiben; korrigiert wird ihr Wert, nicht ihr Vorkommen. Und so gilt
   die Regel ab sofort fuer jede kuenftige Tabler-Seite, statt auf jeder
   einzeln wiederholt zu werden. ======================================== */
/* Die Arbeitsoberflaeche nutzt durchgaengig die verfuegbare Breite. Der
   Container bleibt als Tabler-Bauteil erhalten, sein Bootstrap-Deckel gilt
   jedoch weder im Kopf noch im Inhalt. Das verhindert unterschiedliche
   Seitenraender zwischen Listen, Tabellen und Verwaltungsseiten. */
:root[data-tabler] .container-xl {
  max-width: none !important;
}

/* Master-Detail-Ansichten behalten das Detail nur bei einer Auswahl. Solange
   dessen Karte verborgen ist, belegt die Liste die freie Arbeitsbreite statt
   eine leere rechte Spalte stehen zu lassen. */
@media (min-width: 992px) {
  [data-fb-master-detail]:has(> [data-fb-master-detail-detail] > [hidden]) > [data-fb-master-detail-liste] {
    flex: 0 0 auto;
    width: 100%;
  }

  [data-fb-master-detail]:has(> [data-fb-master-detail-detail] > [hidden]) > [data-fb-master-detail-detail] {
    display: none;
  }
}
/* ══ KENNZAHLENKACHEL (KPI) ════════════════════════════════════════════════
   13.09.2026. EINE Bauform fuer die Rolle "tonfarben-codierte Kennzahlen-
   kachel in einer .row.row-cards-Reihe" - bis hierher unabhaengig dreimal
   erfunden: admin_auswertungen.html .az-kpi (admin-auswertungen-tabler.css),
   superadmin_auswertungen.html .az-kpi (auswertungen-zentral.css, GLEICHER
   Klassenname, ANDERE Definition - die boesartigste Form, weil sie wie
   Einheitlichkeit aussieht), mitarbeiter_auswertungen.html .employee-kpi
   (Inline-<style> der Seite). Inhaberauftrag 13.09.2026: "Ich will keine
   unterschiedlichen Sachen, obwohl sie gleich sind." Wird seitenweise
   abgeloest (Roadmap: admin_auswertungen -> mitarbeiter_auswertungen ->
   superadmin_auswertungen); die Rolle steht erst auf 0, wenn alle drei
   Altdefinitionen entfernt sind, nicht schon, wenn diese hier existiert.

   Randseite links (::before): Praezedenz von zwei der drei Altbauformen.

   Klickbarkeit ist ein PARAMETER (<button> statt <article>/<div>), keine
   eigene Klasse - und keine Geschmacksfrage, sondern eine begruendete
   Unterscheidung: klickbar wird eine Kachelreihe, die Kennzahlen AUS
   ANDEREN Bereichen der Seite zusammenfasst und dorthin verlinkt (z. B. eine
   Uebersichtsreihe); Reihen, die bereits Kennzahlen IHRES EIGENEN, gerade
   offenen Bereichs zeigen, bleiben statisch, weil es nichts gibt, wohin man
   von dort noch springen wuerde. */
/* min-height:100% gleicht Zeilen mit unterschiedlich langem Zusatztext aus -
   ohne das faellt ein <button> (Schritt 2, mitarbeiter_auswertungen.html)
   anders als <article> nicht automatisch auf die volle Reihenhoehe.
   display:flex + .card-body{flex:1 1 auto} ist noetig, damit die
   Akzentlinie (::before an .card-body) diese gestreckte Hoehe auch erreicht -
   .card-body{min-height:100%} allein reicht nicht, weil .fb-kpi selbst keine
   "definite height" hat (nur min-height), Chrome loest Prozenthoehen von
   Kindern darauf nicht auf. Sichtbar erst, wenn eine Nachbarkarte in
   derselben Zeile durch Zeilenumbruch hoeher wird (docs/arbeitsstand/
   fb-kpi-akzentlinie-hoehe.md). */
.fb-kpi { display: flex; flex-direction: column; width: 100%; min-height: 100%; min-width: 0; overflow: hidden; text-align: start; color: inherit; font: inherit; }
button.fb-kpi { appearance: none; padding: 0; cursor: pointer; }
.fb-kpi:focus-visible { outline: 2px solid var(--tblr-primary); outline-offset: 2px; }
.fb-kpi .card-body { position: relative; flex: 1 1 auto; padding: 1rem; }
.fb-kpi .card-body::before { position: absolute; inset: 0 auto 0 0; width: 3px; background: var(--tblr-primary); content: ''; }
.fb-kpi.ok .card-body::before { background: var(--tblr-success); }
.fb-kpi.warn .card-body::before { background: var(--tblr-warning); }
.fb-kpi.danger .card-body::before { background: var(--tblr-danger); }
.fb-kpi.info .card-body::before { background: var(--tblr-info); }
.fb-kpi-icon { display: inline-flex; margin-bottom: .7rem; color: var(--tblr-primary-text); font-size: 1.15rem; }
.fb-kpi-label { display: block; color: var(--tblr-secondary); font-size: .75rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }
.fb-kpi-value { display: block; margin-top: .25rem; color: var(--tblr-body-color); font-size: clamp(1.45rem, 2.4vw, 2rem); font-variant-numeric: tabular-nums; font-weight: 700; letter-spacing: -.025em; line-height: 1.15; }
.fb-kpi-sub { display: block; margin-top: .25rem; color: var(--tblr-secondary); font-size: .8125rem; line-height: 1.35; }
.fb-kpi-arrow { position: absolute; right: var(--fo-karte); bottom: var(--fo-karte); color: var(--tblr-secondary); }

/* superadmin_auswertungen.html benennt Druckbarkeit ausdruecklich als Ziel;
   ihr Vorgaenger .az-kpi (auswertungen-zentral.css) stand deshalb selbst im
   @media print. .fb-kpi kam bisher in KEINEM @media print vor - auch nicht
   fuer mitarbeiter_auswertungen.html, das das seit Schritt 2 ebenfalls
   gebraucht haette. Hier zentral statt seitenweise nachgeholt. */
@media print {
  .fb-kpi { break-inside: avoid; box-shadow: none; }
}

/* ══ DIALOGFAMILIE .fb-dlg AUF TABLER-SEITEN ═══════════════════════════════
   13.09.2026, Auftrag der Koordinationssitzung: Blocker fuer die ganze
   Migration, nicht fuer eine Seite.

   GEMESSEN: .fb-dlg im Markup 68 Dateien, fbHinweis/fbBestaetigen/fbEingabe/
   fbAuswahl-Aufrufer 51 Dateien, Definition bisher ausschliesslich in
   fitbox-components.css ab Zeile 1827 (48 Selektoren) - und die laedt eine
   Tabler-Seite nicht mehr (ci/check-tabler-seiten.mjs, VERBOTEN-Liste, Zeile
   126). Noch ist keine der vier umgestellten Seiten betroffen, weil keine
   einen .fb-dlg verwendet. Gefunden hat es session-1 beim Versuch,
   employee/fahrtkosten.html umzustellen - 20 Treffer dort. Ohne diese Regeln
   waere jeder kuenftige Dialog ein unformatiertes <dialog>: kein Rahmen,
   kein Rueckgrat, Kopf und Fuss nicht abgesetzt.

   assets/js/fb-dialog-contract.js:273 bleibt UNVERAENDERT. Es unterscheidet
   Bestand von Tabler ueber genau eine Weiche (LEGACY_DIALOG_ROOT_SELECTOR:
   '.modal:not(:has(> .modal-dialog)), .modal-card, .modal-box, ...'), und
   .fb-dlg sitzt auf einem echten <dialog>-Knoten - nie auf einem Element,
   das dieser Selektor treffen koennte. Jede Regel unten bleibt deshalb unter
   .fb-dlg verschachtelt; keine nennt .modal, .modal-box oder eine der
   anderen Altnamen nackt.

   ENTSCHIEDEN STATT UEBERSETZT - eine Regel der alten Familie entfaellt
   ersatzlos, weil Tabler sie bereits liefert: .fb-dlg-in h3 setzte bisher
   eigene font-size/font-weight. Tabler traegt jedem nackten h3 schon
   font-size:var(--tblr-font-size-h3) UND
   font-weight:var(--tblr-font-weight-semibold) auf (tabler.min.css,
   allgemeine h1-h6-Regel) - zweite Bauform vermieden. margin:0 bleibt: Tablers
   eigene Regel setzt zusaetzlich margin-bottom:var(--tblr-spacer), das
   zieht die Ueberschrift in der Kopfzeile aus der vertikalen Mitte gegen den
   44px-Knopf (Flex-Querachse zentriert die Randbox, nicht den Inhalt).

   Schatten: Tablers eigener Elevation-Wert (--tblr-shadow-lg) statt des
   alten --shadow-lg (60px Streuung, radikaler als Tablers flache
   Bildsprache) - der Rahmen definiert die Kante bereits, ein zusaetzlich
   dramatischer Schatten waere doppelt gemoppelt.

   BEWUSST NICHT UEBERSETZT, weil Tabler dafuer keinen Begriff hat (house-
   eigene Signatur, kein Fremdpaket-Aequivalent):
     - Schwarzer Doppelrahmen mit gruener Oberkante (--dialog-border-*,
       --dialog-accent*) - das *ist* "Klar gegliedert" (Kommentar
       fitbox-components.css:1821), nicht austauschbar. Beide kommen
       unveraendert aus fitbox-tokens.css, das auf jeder Tabler-Seite laedt
       (Ladereihenfolge oben in dieser Datei).
     - Dunkle Kennzahlenkarte (--dialog-summary-*) - feste Marken-Navy-Werte
       unabhaengig vom Theme, keine Ableitung von --card/--ink, die Tabler
       ersetzen koennte.
     - --modal-gap (Rhythmus zwischen Bloecken IN einem Dialog) - Tabler
       kennt nur Aussenpolster, keinen inneren Blockabstand dieser Art.

   .fb-meldung ist NICHT Teil der urspruenglich genannten Familie, aber eine
   HARTE Voraussetzung von fbHinweis/fbBestaetigen: fb-dialog-contract.js:53
   setzt sie als Textkoerper, sobald ein typ (warn/danger/ok/info, dazu die
   Tabler-Namen warning/success, siehe unten) uebergeben wird. Ohne eigene
   Regeln waere jede Fehler- oder Erfolgsmeldung in einem
   .fb-dlg eine nackte, ungefaerbte Zeile - derselbe Fehler wie beim Toast
   oben, nur in jedem Bestaetigen-Dialog statt im Meldekanal. Die Farben
   uebernehmen die bereits kontrastgepruefte Mischung aus Abschnitt "WEICHE
   ABZEICHEN" oben (65% fuer danger/success/info, 60% fuer warning im
   Hellen - dieselbe #f59f00-Schwaeche) - hier nicht neu gemessen, sondern
   von dort uebernommen.

   GEGENPROBE 13.09.2026, im Browser, an einer Kopie der echten Dialoge aus
   employee/fahrtkosten.html (Zeile 178-274, byte-identisch uebernommen,
   danach verworfen - keine Aenderung an dieser Seite): fahrtDialog (.breit,
   drei .fb-dlg-bereich, .fb-dlg-zusammenfassung mit drei .fb-dlg-kennzahl),
   das tote submitDialog (bestaetigt weiterhin unbenutzt, siehe M06-Inventar),
   fbBestaetigen() normal UND mit gefahr:true, sowie fbHinweis() in allen
   vier .fb-meldung-Typen - je einmal hell und dunkel (data-theme). Alle
   oeffneten und schlossen korrekt, die Kennzahlenkarte blieb im Dunkeln vom
   Dialoggrund unterscheidbar (gemessen: KPI-Karte rgb(16,25,35) gegen
   Dialoggrund rgb(31,41,55)), der 680px-Umbruch stapelte die Fusszeile wie
   vorgesehen. Eckenradius jetzt 4px statt der alten 11px (--tblr-border-
   radius-lg folgt Abschnitt 4's Dichte-Skalierung 0.5 wie Karten und Knoepfe
   - kein Einzelfall nur fuer Dialoge, siehe Screenshot in der Meldung an die
   Koordinationssitzung).

   DABEI KORRIGIERT: die urspruengliche Fassung dieses Kommentars vermutete,
   .fb-dlg .btn.primary bleibe wirkungslos, weil .btn.primary selbst nirgends
   definiert ist (naechster Absatz). Das stimmt nicht - GEMESSEN statt
   vermutet: .fb-dlg .btn.primary setzt Hintergrund, Rahmen und Schrift selbst
   und braucht keine globale .btn.primary-Regel. Im Screenshot ist
   "Speichern & Einreichen" korrekt gruen. Echt betroffen ist .btn.danger:
   fbBestaetigen({gefahr:true}) vergibt "btn danger" (fb-dialog-contract.js:
   110) statt "btn primary", und fuer .btn.danger gibt es - anders als fuer
   .btn.primary - in fitbox-components.css nie eine .fb-dlg-eigene
   Ueberschreibung, nur die globale Regel (:419). Gemessen: der "Loeschen"-
   Knopf im gefahr-Dialog blieb neutral grau statt rot - ein "wirklich
   loeschen"-Knopf ohne Warnfarbe. .fb-meldung.danger daneben (derselbe
   Dialog) war weiterhin korrekt rot, weil dafuer eigene Regeln oben stehen.

   OFFENE LUECKE, gefunden waehrend dieser Arbeit, NICHT Teil dieses Pakets:
   .btn selbst sowie seine Farbvarianten (primaer, sekundaer, gefahr, geist,
   warnung) sind in dieser Datei nirgends definiert (nur .btn mit
   --tblr-btn-padding-* oben in Abschnitt 4, reine Masse, keine Farbe).
   employee/fahrtkosten.html setzt die zweiteilige Schreibweise "btn primaer"
   ("btn" plus Modifikator als GETRENNTE Klassen, nicht Tablers eigene
   Bindestrich-Schreibweise) an sieben Stellen, drei davon in einem
   .fb-dlg-fuss. Der sekundaere Modifikator faellt auf einer Tabler-Seite
   unauffaellig auf Tablers neutrales Grau zurueck (im Screenshot nicht von
   einer eigens gestalteten Sekundaerflaeche zu unterscheiden) - der
   Gefahren-Modifikator dagegen sichtbar falsch, siehe oben. Das betrifft
   jede Schaltflaeche der Seite, nicht nur Dialoge - eine eigene, mindestens
   ebenso grosse Familie wie diese hier, keine Nebenreparatur fuer dieses
   Paket. ══ */

/* ══ NATIVER TABLER-DIALOG (INHABERENTSCHEIDUNG 13.09.2026) ═══════════════
   Die Wurzel bleibt ein echtes <dialog>, weil der Browser Top-Layer,
   Seiten-Inertheit, Escape und Fokusrueckgabe bereitstellt. Tablers
   .modal-Wurzel darf hier nicht stehen: sie setzt display:none und benoetigt
   tabler.min.js. Innen gilt ausschliesslich Tablers Modal-Anatomie.

   Die Zahlen sind der entschiedene Hausstand: Standardbreite 480px, Kopf
   56px, Koerper 16px, Fuss 12px/10px, Grundschrift 14px. Der Radius folgt
   bewusst den echten Hauskarten: --tblr-border-radius-lg ergibt durch die
   Radiusskala 0.5 genau 4px. */
.fo-modal-native {
  --tblr-modal-width: 480px;
  --tblr-modal-padding: 1rem;
  --tblr-modal-margin: 0;
  --tblr-modal-color: var(--tblr-body-color);
  --tblr-modal-bg: var(--tblr-bg-surface);
  --tblr-modal-border-color: var(--tblr-card-border-color, var(--tblr-border-color-translucent));
  --tblr-modal-border-width: var(--tblr-border-width);
  --tblr-modal-border-radius: var(--tblr-border-radius-lg);
  --tblr-modal-inner-border-radius: calc(var(--tblr-modal-border-radius) - var(--tblr-modal-border-width));
  --tblr-modal-box-shadow: var(--tblr-shadow-lg);
  --tblr-modal-header-padding-x: 1.5rem;
  --tblr-modal-header-padding-y: 0;
  --tblr-modal-header-padding: 0 var(--tblr-modal-header-padding-x);
  --tblr-modal-header-border-color: var(--tblr-border-color);
  --tblr-modal-header-border-width: var(--tblr-border-width);
  --tblr-modal-title-line-height: 1.4285714286;
  /* Titel im Koerper (Muster 3): Tablers Abstand, sonst faellt margin auf 0. */
  --tblr-modal-body-title-margin-bottom: 1rem;
  --tblr-modal-footer-gap: .75rem;
  --tblr-modal-footer-padding-y: .75rem;
  --tblr-modal-footer-bg: var(--tblr-bg-surface-tertiary);
  --tblr-modal-footer-border-color: var(--tblr-border-color);
  --tblr-modal-footer-border-width: var(--tblr-border-width);
  width: min(var(--tblr-modal-width), calc(100% - 20px));
  max-width: var(--tblr-modal-width);
  max-height: calc(100dvh - 20px);
  margin: auto;
  padding: 0;
  overflow: visible;
  border: 0;
  background: transparent;
  color: var(--tblr-modal-color);
  font-size: 14px;
}
.fo-modal-native.fo-modal-lg {
  --tblr-modal-width: 720px;
}
.fo-modal-native.fo-modal-xl {
  --tblr-modal-width: 720px;
}
@media (min-width: 1200px) {
  .fo-modal-native.fo-modal-xl {
    --tblr-modal-width: 1140px;
  }
}
.fo-modal-native::backdrop {
  background: color-mix(in srgb, var(--tblr-backdrop-bg) 52%, transparent);
}
.fo-modal-native > .modal-dialog {
  width: 100%; max-width: none; margin: 0; pointer-events: auto;
}
/* Ruhelage mittig (Bindungs-Slot 2, 14.09.2026). Vorher stand hier eine feste
   Hoehe von 100dvh minus 20px: die Schale war damit IMMER fensterhoch, und ein
   kurzer Dialog klebte oben (gemessen dlgMahnen bei 1280x800: 10px Abstand
   oben, 525px unten). Jetzt waechst die Schale mit dem Inhalt; die
   Hoechstmasse auf .modal-dialog und .modal-content bleiben, darum scrollt
   ein langer Dialog weiter im .modal-body und der Fuss bleibt bei 400px
   Fensterhoehe im Bild. Zentriert wird ueber margin:auto der Wurzel oben. */
.fo-modal-native > .modal-dialog-scrollable {
  height: auto;
  max-height: calc(100dvh - 20px);
}
.fo-modal-native .modal-content {
  max-height: calc(100dvh - 20px);
}
/* Hausoptik oben (Inhaberentscheidung 27.09.2026: „das es oben so bläulich
   ist soll bleiben, das dikere x soll ebenfalls bleiben nur die form abstände
   und höhen sollen korrigiert werden“): 4px Akzentkante, Kopf mit 7 % Akzent
   und kraeftiges Schliesskreuz im umrandeten 44px-Feld, wie bisher .fb-dlg.
   Die Kante liegt als Pseudo-Element im Innenrand, weil .modal-content in
   scrollbaren Dialogen overflow:hidden traegt; .modal-status faerbt sie um. */
.fo-modal-native .modal-content::before {
  content: ""; position: absolute; inset-inline: 0; top: 0; height: 4px; z-index: 3;
  background: var(--dialog-accent); pointer-events: none;
  border-radius: var(--tblr-modal-inner-border-radius) var(--tblr-modal-inner-border-radius) 0 0;
}
.fo-modal-native .modal-status { height: 4px; z-index: 4; }
.fo-modal-native .modal-header {
  min-height: 56px;
  /* Tabler legt das Kreuz absolut in die Ecke und reserviert 3.5rem; das
     umrandete Feld steht dagegen im Fluss, 12px vom rechten Rand und
     senkrecht mittig. Der Titel behaelt links den ruhigeren 24px-Einzug. */
  padding-inline-end: .75rem;
  background: color-mix(in srgb, var(--dialog-accent) 7%, var(--tblr-bg-surface));
  border-top-left-radius: var(--tblr-modal-inner-border-radius);
  border-top-right-radius: var(--tblr-modal-inner-border-radius);
}
.fo-modal-native .modal-header > .btn-close {
  flex: 0 0 44px; width: 44px; height: 44px; min-width: 44px; min-height: 44px;
  margin: 0 0 0 auto; padding: 0; border-radius: var(--tblr-border-radius-sm);
  /* position:relative (nicht static) haelt Tablers eigenes .btn-close::before
     (position:absolute, Groesse max(100%,24px)) am Knopf selbst verankert.
     Sonst bleibt Tablers z-index:10 am Flex-Kind wirksam, das ::before misst
     mangels eigenem Positionierungskontext stattdessen gegen .modal-content
     und deckt so den ganzen Dialog ab: jeder Klick im Dialog traf das Kreuz
     (belegt an fbBestaetigen und admin_banken blzDialog, 27.09.2026). */
  -webkit-mask: none; mask: none; position: relative;
  display: grid; place-items: center;
  border: var(--tblr-border-width) solid var(--tblr-border-color);
  background: var(--tblr-bg-surface); color: var(--tblr-body-color); opacity: 1;
  font-size: 22px; font-weight: 800; line-height: 1;
}
.fo-modal-native .modal-header > .btn-close::after { content: "\00D7"; }
.fo-modal-native .modal-header > .btn-close:hover {
  background: var(--tblr-bg-surface-secondary); color: var(--tblr-body-color); opacity: 1;
}
/* Inhaberentscheidung 27.09.2026 (Paket 5, gruppenuebergreifende
   Entscheidung 1, drei Gruppen unabhaengig gemeldet: leistungen,
   mitarbeiterzentrale_personal, abwesenheiten_feedback_kampagnen):
   .fb-dlg-body trug overscroll-behavior:contain, damit ein durchgescrollter
   Dialogkoerper den Seitenhintergrund nicht mitzieht. Beim Wechsel auf
   Tablers .modal-body geht das verloren - hier zentral nachgezogen, keine
   Gruppe aendert das selbst. */
.fo-modal-native .modal-body {
  overscroll-behavior: contain;
}
.fo-modal-native .modal-footer {
  flex-wrap: nowrap;
}
.fo-modal-native .modal-footer > .btn {
  min-width: 0;
}
/* Schmaler Fuss wie .fb-dlg-fuss weiter unten (Bindungs-Slot 2, 14.09.2026):
   unter 680px gestapelt, volle Breite, 44px, Hauptaktion oben. Vorher blieb
   der Fuss einzeilig (flex-wrap:nowrap) und die Knoepfe durften schrumpfen -
   gemessen admin_pt_whatsapp_vorlagen bei 375px: vier Knoepfe je 34px hoch,
   "Duplizieren" und "Speichern" abgeschnitten. margin-inline:0 neutralisiert
   Tablers Kinderabstand und laesst die .ms-auto-Hilfsklasse bei voller
   Breite auf null aufgehen; der Abstand kommt aus gap. */
@media (max-width: 680px) {
  .fo-modal-native .modal-footer {
    flex-direction: column-reverse; align-items: stretch; gap: .5rem;
  }
  .fo-modal-native .modal-footer > .btn {
    width: 100%; min-height: 44px; margin: 0; justify-content: center;
  }
}

.fb-dlg {
  border: var(--dialog-border-width) solid var(--dialog-border-color);
  border-top: 4px solid var(--dialog-accent);
  outline: var(--dialog-border-width) solid var(--dialog-border-color); outline-offset: calc(0px - var(--dialog-border-width));
  border-radius: var(--tblr-border-radius-lg); padding: 0;
  background: var(--tblr-bg-surface); color: var(--tblr-body-color); box-shadow: var(--tblr-shadow-lg);
  max-width: 480px; width: calc(100% - 32px); margin: auto;
  max-height: calc(100dvh - 32px); overflow: auto;
}
.fb-dlg::backdrop { background: color-mix(in srgb, var(--tblr-backdrop-bg) 52%, transparent); }
/* Bestandsdialoge nutzen .mittel; der zentrale Builder vermeidet mit dem
   namespaced Modifier eine Kollision mit fachlichen Klassen gleichen Namens. */
.fb-dlg.mittel,
.fb-dlg.fb-dlg-mittel { max-width: 600px; }
.fb-dlg.breit { max-width: 900px; }
.fb-dlg:has(.fb-dlg-body) { overflow: hidden; }
.fb-dlg:has(.fb-dlg-body) .fb-dlg-in {
  max-height: calc(100dvh - 36px);
  overflow: hidden;
}
.fb-dlg:has(.fb-dlg-body) :is(.fb-dlg-kopf, .fb-dlg-fuss) {
  position: static; flex: 0 0 auto;
}
.fb-dlg:has(.fb-dlg-body) .fb-dlg-body { flex: 1 1 auto; }
.fb-dlg .fb-liste { max-height: 44vh; overflow-y: auto; }
.fb-dlg-in {
  --fb-dlg-pad: clamp(16px, 2vw, 24px);
  display: flex; flex-direction: column; gap: var(--modal-gap);
  padding: var(--fb-dlg-pad);
}
.fb-dlg-body {
  display: flex; flex-direction: column; gap: var(--modal-gap);
  min-width: 0; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  /* Halber g-3-Gutter: .row-Raender passen, Fokusring behaelt 8px. */
  margin-inline: -.5rem; padding-inline: .5rem;
}
.fb-dlg-in h3 { margin: 0; }
.fb-dlg [hidden]:not([hidden="until-found"]) { display: none !important; }

.fb-dlg-kopf,
.fb-dlg-in > :where(:first-child:has(> h3):has(> .icon-btn)) {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  margin: calc(0px - var(--fb-dlg-pad)) calc(0px - var(--fb-dlg-pad)) var(--sp-2);
  padding: 16px var(--fb-dlg-pad);
  background: color-mix(in srgb, var(--dialog-accent) 7%, var(--tblr-bg-surface));
  border-bottom: var(--tblr-border-width) solid var(--tblr-border-color);
  position: sticky; top: 0; z-index: 20;
}
.fb-dlg-kopf h3,
.fb-dlg-kopf h2,
.fb-dlg-in > :first-child:has(> h3):has(> .icon-btn) h3 {
  display: inline-flex; align-items: center; gap: var(--sp-2);
}
.fb-dlg-kopf h3 > i,
.fb-dlg-kopf h2 > i,
.fb-dlg-in > :first-child:has(> h3):has(> .icon-btn) h3 > i {
  color: var(--dialog-accent);
}
.fb-dlg-kopf .icon-btn {
  flex: 0 0 44px; width: 44px; height: 44px; display: grid; place-items: center;
  border: var(--tblr-border-width) solid var(--tblr-border-color); background: var(--tblr-bg-surface);
  font-size: 22px; font-weight: 800;
}
.fb-dlg .btn.primary {
  background: var(--dialog-accent); border-color: var(--dialog-accent-hover);
  color: var(--dialog-accent-ink);
  box-shadow: 0 2px 8px color-mix(in srgb, var(--dialog-accent) 20%, transparent);
}
.fb-dlg .btn.primary:hover { background: var(--dialog-accent-hover); }
.fb-dlg-bereich {
  display: flex; flex-direction: column; gap: var(--sp-3);
  padding: var(--sp-4); border-radius: var(--tblr-border-radius-lg);
  background: var(--tblr-bg-surface-secondary);
}
.fb-dlg-bereich-titel {
  color: var(--tblr-secondary); font-size: .75rem; font-weight: var(--tblr-font-weight-bold);
  letter-spacing: .035em; line-height: 1; text-transform: uppercase;
}
.fb-dlg-zusammenfassung {
  display: grid; grid-template-columns: repeat(3, minmax(0,1fr));
  overflow: hidden; border-left: 4px solid var(--dialog-accent); border-radius: var(--tblr-border-radius-lg);
  background: var(--dialog-summary-bg); color: var(--dialog-summary-ink);
}
.fb-dlg-kennzahl { min-width: 0; padding: 15px 20px; }
.fb-dlg-kennzahl + .fb-dlg-kennzahl { border-left: 1px solid color-mix(in srgb, var(--dialog-summary-ink) 22%, transparent); }
.fb-dlg-kennzahl .label { color: var(--dialog-summary-muted); font-size: .75rem; font-weight: var(--tblr-font-weight-semibold); }
.fb-dlg-kennzahl .value { color: var(--dialog-summary-ink); font-size: 1.25rem; font-weight: 800; margin-top: 4px; }
.fb-dlg-fuss {
  display: flex; gap: var(--sp-2); align-items: center; justify-content: flex-end; flex-wrap: wrap;
  margin: var(--sp-2) calc(0px - var(--fb-dlg-pad)) calc(0px - var(--fb-dlg-pad));
  padding: 13px var(--fb-dlg-pad); background: var(--tblr-bg-surface); border-top: var(--tblr-border-width) solid var(--tblr-border-color);
  position: sticky; bottom: 0; z-index: 20;
}
.fb-dlg-fuss > .fb-dlg-fuss-links { margin-right: auto; }

.fb-dlg-hinweis { display: flex; align-items: flex-start; gap: var(--sp-2); }
.fb-dlg-hinweis > i { flex: 0 0 auto; margin-top: 1px; }
.fb-dlg-text { margin: 0; color: var(--tblr-body-color); line-height: 1.55; white-space: pre-line; }
.fb-dlg-pruefung {
  display: flex; align-items: flex-start; gap: var(--sp-3); padding: var(--sp-3);
  border: var(--tblr-border-width) solid var(--tblr-border-color); border-radius: var(--tblr-border-radius-sm); cursor: pointer;
}
.fb-dlg-pruefung input { flex: 0 0 auto; width: 20px; height: 20px; margin: 0; }

/* fbHinweis/fbBestaetigen-Textkoerper, siehe Begruendung oben.
   Typnamen (27.09.2026): Aufrufer schreiben neben warn/ok auch die
   Tabler-Namen warning/success (studio/kunden_akte.html, admin/admin_tablets.html,
   admin-mitarbeiterzentrale-work.js, dazu Bestandsmarkup in admin_franchise und
   admin_schichtplaner). Ohne eigene Regel fielen sie auf die neutrale graue
   Box zurueck; sie tragen deshalb dieselben, kontrastgeprueften Werte. */
.fb-meldung {
  font-size: var(--tblr-body-font-size); font-weight: var(--tblr-font-weight-semibold); line-height: 1.5;
  border: var(--tblr-border-width) solid var(--tblr-border-color); border-radius: var(--tblr-border-radius-sm); padding: 8px 11px;
  background: var(--tblr-bg-surface-secondary); color: var(--tblr-body-color);
}
.fb-meldung.warn,
.fb-meldung.warning { color: color-mix(in srgb, var(--tblr-warning) 60%, black); background: var(--tblr-warning-lt); border-color: var(--tblr-warning-border-subtle); }
.fb-meldung.danger  { color: color-mix(in srgb, var(--tblr-danger) 65%, black); background: var(--tblr-danger-lt); border-color: var(--tblr-danger-border-subtle); }
.fb-meldung.ok,
.fb-meldung.success { color: color-mix(in srgb, var(--tblr-success) 65%, black); background: var(--tblr-success-lt); border-color: var(--tblr-success-border-subtle); }
.fb-meldung.info    { color: color-mix(in srgb, var(--tblr-info) 65%, black); background: var(--tblr-info-lt); border-color: var(--tblr-info-border-subtle); }
.fb-meldung svg     { width: 14px; height: 14px; vertical-align: -2px; flex-shrink: 0; }
:root[data-theme="dark"] .fb-meldung.warn,
:root[data-theme="dark"] .fb-meldung.warning { color: color-mix(in srgb, var(--tblr-warning) 65%, white); }
:root[data-theme="dark"] .fb-meldung.danger  { color: color-mix(in srgb, var(--tblr-danger) 65%, white); }
:root[data-theme="dark"] .fb-meldung.ok,
:root[data-theme="dark"] .fb-meldung.success { color: color-mix(in srgb, var(--tblr-success) 65%, white); }
:root[data-theme="dark"] .fb-meldung.info    { color: color-mix(in srgb, var(--tblr-info) 65%, white); }
/* Der Textkoerper der zentralen Rueckfrage traegt Aufzaehlungen mit "\n"
   (z. B. statusBlockerText in studio/kunden_akte.html). .fb-dlg-text bricht
   sie schon um; mit typ wird derselbe Absatz zur .fb-meldung und liefe ohne
   diese Regel in eine Zeile. Bewusst an der Kennung und nicht an
   .fb-meldung: die Klasse steht auf vielen Seiten direkt im Markup, dort
   wuerde pre-line die Quelltextumbrueche sichtbar machen. */
#fb-zentraldialog-text { white-space: pre-line; }

@media (max-width: 680px) {
  .fb-dlg { width: calc(100% - 20px); max-height: calc(100dvh - 20px); }
  .fb-dlg-in { --fb-dlg-pad: 14px; }
  .fb-dlg-bereich { padding: var(--sp-3); }
  .fb-dlg-zusammenfassung { grid-template-columns: 1fr; }
  .fb-dlg-kennzahl + .fb-dlg-kennzahl { border-left: 0; border-top: 1px solid color-mix(in srgb, var(--dialog-summary-ink) 22%, transparent); }
  .fb-dlg-fuss > .fb-dlg-fuss-links { margin-right: 0; }
  .fb-dlg-fuss { flex-direction: column-reverse; align-items: stretch; }
  .fb-dlg-fuss .btn { width: 100%; min-height: 44px; justify-content: center; }
}

/* ══ .BTN-VARIANTENACHSE AUF TABLER-SEITEN ══════════════════════════════════
   13.09.2026. Die Koordinationssitzung hat im Browser Testknoepfe eingesetzt
   und gemessen: primary/secondary/danger/ghost/warn rendern auf einer
   Tabler-Seite bislang alle sechs identisch (Tablers neutrales Grau) - ein
   Loeschen-Knopf war von einem Abbrechen-Knopf nicht zu unterscheiden.
   GEGENGEPRUEFT, nicht blind uebernommen: die per-Seite-Zuordnung "ghost bei
   admin_aenderungsprotokoll/mitarbeiter_auswertungen" aus der Meldung war ein
   Messfehler der Gegenseite - beide Seiten benutzen dort bereits Tablers
   eigenes btn-ghost-secondary (Bindestrich), nicht die Baukasten-Schreibweise
   "btn ghost" (Leerzeichen). Real und live betroffen sind alle fuenf
   Tabler-Seiten trotzdem: assets/js/fb-dialog-contract.js erzeugt "btn
   primary"/"btn secondary"/"btn danger" bei JEDEM fbBestaetigen/fbAuswahl-
   Aufruf, unabhaengig vom Markup der Seite selbst.

   ENTSCHIEDEN: ABBILDEN, nicht abloesen. Gemessen: 105 Produktseiten,
   1237 Vorkommen der getrennten Schreibweise "btn primary/secondary/danger/
   ghost/warn" (Skript-Token-Zaehlung, ohne docs/scripts/ci). Dazu kommt
   assets/js/fb-dialog-contract.js, das dieselbe Schreibweise auf JEDER Seite
   erzeugt, die fbBestaetigen/fbAuswahl/fbEingabe aufruft (51 Dateien) -
   Baukasten- UND Tabler-Seiten gleichermassen, weil es ein einziges
   gemeinsames Skript ist. Abloesen hiesse: >1237 Markup-Stellen umschreiben
   UND das gemeinsame Skript nach Seitentyp verzweigen - fuer einen rein
   kosmetischen Klassennamen, der am Ende genauso aussehen soll. Abbilden
   kostet fuenf CSS-Regeln in dieser Datei, keine einzige Markup-Aenderung,
   kein Risiko fuer die 100+ noch nicht umgestellten Seiten.

   MECHANISMUS: Tablers .btn-primary/-danger/-warning setzen ausschliesslich
   die Custom Properties --tblr-btn-bg, --tblr-btn-color sowie je einen Satz
   fuer hover, active und disabled; die Basisregel .btn (und .btn:hover/
   :disabled) liest nur diese Properties.
   Die Regeln unten SETZEN DIESELBEN PROPERTIES MIT DENSELBEN WERTEN wie
   Tablers eigene Varianten (kopiert aus tabler.min.css, nicht neu erfunden) -
   .btn.danger wird dasselbe Rot wie .btn-danger, nicht ein zweites.

   ZWEI ECHTE BEDEUTUNGSUNTERSCHIEDE, nicht nur Namen - beide gemessen, nicht
   geraten:
     - Der Modifikator "secondary" (Baukasten, als "btn secondary" GETRENNTE
       Klasse) ist eine neutrale Flaeche in Kartenfarbe (background:var(--card))
       - keine Farbaussage, nur "Zweitrang". Tablers eigene .btn-secondary ist
       dagegen eine SEMANTISCHE Farbe: volles Grau (--tblr-secondary, #6b7280)
       mit weisser Schrift, genauso kraeftig wie .btn-danger. Eine 1:1-
       Abbildung auf .btn-secondary haette aus jedem neutralen "Abbrechen"
       einen dunkelgrauen Klotz gemacht - eine neue Regression, keine
       Reparatur. Tablers eigenes UNKLASSIFIZIERTES .btn (kein Farbmodifikator)
       liefert die neutrale Flaeche bereits von sich aus (--tblr-bg-surface/
       --tblr-body-color/--tblr-border-color) - siehe Gegenprobe in PR #895,
       wo unstilisierte "Abbrechen"/"Speichern"-Knoepfe schon richtig
       aussahen. Deshalb unten KEIN eigener Eintrag fuer den Modifikator
       "secondary" - ein Eintrag, der nur Tablers eigene Voreinstellung
       wiederholt, waere die zweite Bauform, die vermieden werden soll.
     - .btn.ghost (Baukasten) zeichnet einen Rahmen in Markenfarbe
       (border-color:var(--brand)). Tablers GLEICHNAMIGE .btn-ghost-* ist
       dagegen randlos per Definition (--tblr-btn-border-color:transparent) -
       derselbe Name, ein anderes Bauteil, wie schon bei .modal (siehe
       tabler-modal-namenskollision.md). Das tatsaechlich passende
       Tabler-Vorbild ist .btn-outline-primary (Rahmen in Markenfarbe,
       fuellt beim Hover). Ghost unten kopiert dessen Custom Properties, nicht
       die von .btn-ghost-primary.

   .btn.primary und .btn.danger/.btn.warn wechseln damit von Baukastens
   pastellener Tonflaeche (--danger-bg/--warn-bg, helles Rot/Amber mit
   dunkler Schrift) auf Tablers volle, gesaettigte Farbe mit weisser Schrift -
   bewusst, nicht nur uebernommen: genau das macht einen "Loeschen"-Knopf
   wieder von einem "Abbrechen"-Knopf unterscheidbar (der Befund, der dieses
   Paket ausgeloest hat).

   KONTRAST GEMESSEN IM BROWSER (nicht nur ci/check-kontrast.mjs statisch),
   hell UND dunkel, Text gegen die tatsaechlich gerenderte Flaeche:
     primary  4,42:1  -  dieselbe, bereits bekannte Schwaeche wie .text-
              primary/.link-primary (M05, Entscheidung steht dort aus) -
              hier nicht neu erfunden, nur geerbt.
     danger   4,46:1  -  ENTSCHIEDEN (Koordinationssitzung, 13.09.2026):
              SO LASSEN. Gegengemessen an Tablers eigenem nativen .btn-danger
              (nicht nur an dieser Abbildung): identisch 4,46:1 - der Wert
              gehoert also dem Tabler-Bauteil selbst, nicht dieser Abbildung.
              Wer ihn hier auf 4,5 hoebe, erzeugte zwei verschiedene Rots im
              selben System (eines fuer .btn.danger, eines fuer .btn-danger)
              - genau das, was dieses Paket abschafft. Die Schwaeche bleibt
              deshalb dort gefuehrt, wo sie herkommt: ein eigener, spaeterer
              Posten gegen Tablers eigenes .btn-danger, nicht Teil dieses
              Pakets. Dieselbe Logik wie bei primary oben.
              ABGELOEST 14.09.2026 (Bindungs-Slot 3, Abschnitt GEFAHRKNOPF
              am Dateiende): weisse Schrift fuer beide Schreibweisen
              gemeinsam, 4,66:1, Flaeche unveraendert.
     warn     2,04:1 GEGEN TABLERS EIGENES .btn-warning GEMESSEN, nicht nur
              gegen diese Abbildung - EXAKT DERSELBE WERT dort. Die Schwaeche
              liegt also im Fremdpaket, nicht in dieser Abbildung. Der Text
              unten auf #000 (gemessen 9,84:1) ist deshalb keine Abweichung
              von Tabler, sondern eine UMGEHUNG einer Fremdpaket-Schwaeche -
              Bootstrap-Systeme loesen .btn-warning traditionell aus genau
              diesem Grund seit jeher mit schwarzem statt weissem Text. Ob
              Tablers eigenes .btn-warning selbst nachgezogen wird, ist eine
              andere, spaetere Frage; fuer heute gilt: diese Abbildung ist
              lesbar, die native nicht - das ist die richtige Reihenfolge.
     ghost    4,42:1 hell (--tblr-primary, dieselbe M05-Schwaeche wie primary),
              aber NUR 3,84:1 dunkel, weil das flache Markengruen im
              Dunkelmodus nicht mitzieht. Deshalb unten --tblr-primary-text
              statt --tblr-primary fuer Text UND Rahmen - genau der Token,
              den Abschnitt 3 dieser Datei fuer diesen Fall schon eingefuehrt
              hat (--brand-text). Gemessen danach: 9,55:1 dunkel, 4,42:1
              hell unveraendert.
   secondary/nackt: 11,86:1 dunkel, unveraendert - siehe oben, kein eigener
   Eintrag noetig.

   GEGENPROBE mit dem fbBestaetigen-Fluss, gefahr:true (der Loeschen-Fall):
   vorher rendern "Abbrechen" und "Endgueltig loeschen" ununterscheidbar grau
   (siehe Meldung an die Koordinationssitzung zu PR #895). Nachher: "Loeschen"
   voll rot (--tblr-danger), "Abbrechen" neutral - im Browser bestaetigt, an
   einer Kopie des echten Dialogs (window.fbBestaetigen mit denselben
   Optionen wie in echten Aufrufstellen), nicht an der Quellseite selbst. */
.btn.primary {
  --tblr-btn-color: var(--tblr-primary-fg, #fff);
  --tblr-btn-bg: var(--tblr-primary);
  --tblr-btn-border-color: transparent;
  --tblr-btn-hover-color: var(--tblr-primary-fg);
  --tblr-btn-hover-bg: var(--tblr-primary-darken);
  --tblr-btn-hover-border-color: transparent;
  --tblr-btn-active-color: var(--tblr-primary-fg);
  --tblr-btn-active-bg: var(--tblr-primary-darken);
  --tblr-btn-active-border-color: transparent;
  --tblr-btn-disabled-bg: var(--tblr-primary);
  --tblr-btn-disabled-color: var(--tblr-primary-fg);
}
.btn.danger {
  --tblr-btn-color: var(--tblr-danger-fg, #fff);
  --tblr-btn-bg: var(--tblr-danger);
  --tblr-btn-border-color: transparent;
  --tblr-btn-hover-color: var(--tblr-danger-fg);
  --tblr-btn-hover-bg: var(--tblr-danger-darken);
  --tblr-btn-hover-border-color: transparent;
  --tblr-btn-active-color: var(--tblr-danger-fg);
  --tblr-btn-active-bg: var(--tblr-danger-darken);
  --tblr-btn-active-border-color: transparent;
  --tblr-btn-disabled-bg: var(--tblr-danger);
  --tblr-btn-disabled-color: var(--tblr-danger-fg);
}
.btn.warn,
:where(:root[data-tabler]) .btn-warning {
  /* Textfarbe NICHT aus --tblr-warning-fg (= --tblr-light, dasselbe Off-Weiss
     wie bei primary/danger) - im Browser gemessen 2,04:1 auf --tblr-warning
     (#f59f00), weit unter 4,5:1. Dieselbe Farbe, dieselbe Schwaeche wie im
     Abschnitt "WEICHE ABZEICHEN" oben (dort fuer .bg-warning-lt bereits
     korrigiert) - hier aber ohne Tintenmischung, weil eine volle Flaeche
     einfaches Schwarz statt eines abgestimmten Tons braucht (Bootstraps
     eigenes .btn-warning macht das seit jeher genauso, aus demselben Grund).
     Gemessen: Schwarz auf #f59f00 = 9,84:1.

     SEIT BINDUNGS-SLOT 2 (14.09.2026) AUCH FUER TABLERS EIGENES .btn-warning -
     die oben offen gelassene "spaetere Frage". Gemessen auf studio/mtt.html
     (#markScanBtn) und admin/admin_mtt.html (synthetischer Knopf, btn-sm):
     Off-Weiss auf #f59f00 2,04:1 hell wie dunkel, Hover/Aktiv 1,79:1 hell
     (die Tabler-Abdunklung ist 80 % deckend und wird auf Weiss HELLER) und
     3,01:1 dunkel. Dieselbe Tinte an beiden Schreibweisen, damit es keine
     zwei Warnknoepfe gibt. :where() haelt die Spezifitaet bei einer Klasse:
     Tablers Umriss- und Geistform in der Zwei-Klassen-Schreibweise stehen
     damit weiter ueber dieser Regel und behalten ihre eigenen Werte. Die
     Flaechenwerte unten sind dieselben wie in Tablers Warnknopf, sie aendern
     dort nichts. */
  --tblr-btn-color: #000;
  --tblr-btn-bg: var(--tblr-warning);
  --tblr-btn-border-color: transparent;
  --tblr-btn-hover-color: #000;
  --tblr-btn-hover-bg: var(--tblr-warning-darken);
  --tblr-btn-hover-border-color: transparent;
  --tblr-btn-active-color: #000;
  --tblr-btn-active-bg: var(--tblr-warning-darken);
  --tblr-btn-active-border-color: transparent;
  --tblr-btn-disabled-bg: var(--tblr-warning);
  --tblr-btn-disabled-color: #000;
}
.btn.ghost {
  /* Text und Rahmen aus --tblr-primary-text, nicht --tblr-primary: im
     Dunkeln gemessen 3,84:1 auf der Seitenflaeche mit dem flachen
     Markengruen - --tblr-primary-text ist genau der Fall, fuer den
     Abschnitt 3 dieser Datei ihn schon eingefuehrt hat (--brand-text,
     45% Marke in Weiss gemischt). Der gefuellte Hover-/Aktiv-Zustand bleibt
     bei --tblr-primary/-fg wie .btn.primary - dieselbe, bereits bekannte
     4,42:1-Schwaeche, keine neue. */
  --tblr-btn-color: var(--tblr-primary-text);
  --tblr-btn-bg: transparent;
  --tblr-btn-border-color: var(--tblr-primary-text);
  --tblr-btn-hover-color: var(--tblr-primary-fg, #fff);
  --tblr-btn-hover-bg: var(--tblr-primary);
  --tblr-btn-hover-border-color: transparent;
  --tblr-btn-active-color: var(--tblr-primary-fg, #fff);
  --tblr-btn-active-bg: var(--tblr-primary);
  --tblr-btn-active-border-color: var(--tblr-primary-text);
  --tblr-btn-disabled-color: var(--tblr-primary-text);
  --tblr-btn-disabled-border-color: var(--tblr-primary-text);
}

/* ══ BESTANDSDIALOGE (.modal/.info-card-Familie) AUF TABLER-SEITEN ═════════
   13.09.2026. Blockiert vier fertige PRs am admin_zeiterfassung-Turm (#884,
   #888, #890, #897): deren Dialoge brechen nach dem Wechsel auf diese Datei
   - #settleModal stand dauerhaft offen im Dokumentfluss, uiDialogShell()s
   vier Dialoge gingen nie sichtbar auf und schwaerzten stattdessen die
   ganze Seite.

   DREI NAMENSKOLLISIONEN IN DERSELBEN FAMILIE, alle drei gemessen, nicht
   vermutet - WER HIER NOCHMAL ETWAS UMBENENNT, MUSS SIE KENNEN:
     1. .modal ist im Bestand die DIALOGKARTE selbst. In Tabler ist .modal
        der Vollbild-Overlay-Container (display:none in Ruhelage), die
        Karte darin heisst .modal-dialog (siehe
        tabler-modal-namenskollision.md).
     2. .modal-backdrop ist im Bestand nur EINER von elf Schreibweisen fuer
        den Hintergrund (fitbox-components.css, Abschnitt "Popups"). In
        Tabler ist es die einzige, offizielle Backdrop-Klasse - und OHNE die
        zusaetzliche Klasse .show weder ein display:none-Grundzustand noch
        eine reduzierte Deckkraft (gemessen in tabler.min.css: nur
        .modal-backdrop.show setzt eine Opacity-Variable). admin_zeiter-
        fassung.html vergibt der Reihe "modal-backdrop active" (nie "show")
        - daraus folgt der beobachtete blickdichte Vollbild-Layer.
     3. .modal-dialog existiert im Bestand gar nicht - erst durch die
        Kollision mit (1) wird der Name ueberhaupt relevant.

   ENTSCHIEDEN: NICHT ueber eine Verneinung mit einer Existenzpruefung auf
   das Kindelement unterscheiden (so unterscheidet fb-dialog-contract.js:273
   selbst, und das war der erste Vorschlag der Koordinationssitzung dazu),
   SONDERN ueber das Attributpaar, das der Bestand fuer genau diesen Zweck
   SCHON HAT: data-fb-dialog-standardisiert="true" (Karte) und
   data-fb-dialog-hintergrund="true" (Hintergrund). fitbox-components.css
   loest das seit PR #771 attributgesteuert statt klassenbasiert, aus
   demselben Grund, aus dem es hier zaehlt: elf Klassennamen fuer denselben
   Hintergrund zu jagen ist die schlechtere Wette als an einem Attribut zu
   haengen, das jede Schreibweise gleichermassen setzt.

   Beide Attribute stehen bei #settleModal STATISCH im Markup
   (admin_zeiterfassung.html:225-226) und werden von uiDialogShell()
   SYNCHRON beim Erzeugen gesetzt (Zeile 678 und 682) - nie erst
   nachtraeglich per Beobachter. Ein echter Tabler-Dialog (siehe
   admin_aenderungsprotokoll.html, .modal mit echtem .modal-dialog-Kind und
   tabler.min.js) traegt keines der beiden Attribute - weder fb-dialog-
   contract.js noch irgendeine Fachseite setzt sie dort. Die Unterscheidung
   ist damit dieselbe wie beim urspruenglichen Vorschlag (Bestand gegen
   echten Tabler-Dialog), aber ohne eine Frage nach Browserunterstuetzung
   oder Spezifitaet zu stellen - und vor allem: es ist KEINE neue Bauform.
   Diese Regeln uebernehmen das Muster, das die Datei schon benutzt, statt
   ein zweites danebenzustellen.

   Jede Regel unten haengt deshalb an [data-fb-dialog-standardisiert="true"]
   bzw. [data-fb-dialog-hintergrund="true"] - NIE an .modal oder
   .modal-backdrop allein. assets/js/fb-dialog-contract.js UND
   admin_zeiterfassung.html selbst bleiben unangetastet: die beiden
   Attribute und diese CSS-Regeln bilden zusammen einen Vertrag, wer eines
   aendert, muss das andere mitziehen (dieselbe Regel wie bei .fb-dlg oben).

   .info-backdrop/.info-card (== #settleModal-Aussenhuelle UND das zentrale
   Info-Modal aus assets/js/fb-errors.js, openInfoModal()) sind NICHT Teil
   dieser Kollision - beide Namen sind in Tabler unbekannt, derselbe
   einfache Fall wie .fb-dlg. Gehoert trotzdem hierher, gleicher Blocker.
   GEFUNDEN BEIM BAUEN: dashboard.html hat exakt diese Familie schon einmal
   lokal in seinem eigenen <style>-Block nachgebaut (eigene, aber sehr
   aehnliche Tabler-Token-Wahl) - ein Beleg, dass die Luecke real und schon
   einmal unabhaengig gefunden wurde, und ein spaeterer, kleiner
   Aufraeumposten (dashboards eigene Kopie wird redundant, sobald diese hier
   auf main ist), nicht Teil dieses Pakets.

   NICHT MITGENOMMEN: .info-section/.info-list/.info-step (Inhaltsformate
   innerhalb des Info-Modals, nicht die Dialogschale) - keine der beiden
   dringenden Seiten braucht sie, gehoeren notfalls zum naechsten Fund.

   ZWEI WEITERE FUNDE, NUR IM BROWSER SICHTBAR, NICHT AM CSSOM-ZAEHLCHECK
   (beide durch die Gegenprobe unten gefangen, vor dem Commit korrigiert):
     - display:flex direkt in die erste Fassung der Hintergrund-Regel
       geschrieben (statt an .open/.show/.active gebunden) hielt #settleModal
       nach dem Schliessen weiter sichtbar - das Attribut wird nie entfernt,
       nur die Klasse .show. Siehe Kommentar direkt an der Regel.
     - Tablers eigene .modal-Grundregel setzt position:fixed, inset-inline-
       start:0, top:0, width:100%, height:100% - genau die Rolle, in der
       Tabler diesen Klassennamen sieht (Vollbild-Container). Ohne eigene
       position:static auf der Karte lag sie links oben am Fensterrand fest,
       ausserhalb des Flusses der zentrierenden Hintergrund-Flexbox. Siehe
       Kommentar direkt an der Regel. ══ */

:is(.backdrop, .modal-bd, .modal-backdrop, .modal-overlay, .modal-bg,
    .confirm-backdrop, .info-backdrop, .kc-modal-bd)[data-fb-dialog-hintergrund="true"] {
  position: fixed; inset: 0; z-index: var(--z-modal);
  align-items: center; justify-content: center; padding: var(--sp-4);
  background: rgba(15,23,42,.45); backdrop-filter: blur(4px);
  /* RUHELAGE, nachgetragen am 13.09.2026. Sie fehlte hier - das
     Baukasten-Original (fitbox-components.css) hat sie in seiner
     Grundregel. Ohne sie steht der Hintergrund PERMANENT im Bild,
     sobald das Attribut gesetzt ist, unabhaengig von .open/.show/.active.

     Wie es passieren konnte: der Kommentar direkt darunter begruendet
     ausfuehrlich, warum display:FLEX NICHT in diese Regel gehoert (sonst
     kollidiert es mit Tablers eigenem .modal-backdrop.show). Beim
     Umschreiben genau dieser einen Zeile ist das display:NONE mit
     verschwunden. Es war keine Entscheidung, sondern eine Auslassung -
     und der Kommentar hat sie nicht aufgefangen, weil er begruendet, was
     NICHT drinsteht, statt zu pruefen, was fehlt.

     Gefunden im BROWSER, nicht am Quelltext, und auch nicht von einem
     Waechter: kein Pruefer misst Kaskade. Bisher unbemerkt, weil die
     einzigen zwei Seiten mit dem Attribut es nicht traf -
     admin_zeiterfassung.html hat eine eigene .info-backdrop-Regel mit
     display:none, und admin_inbox.html ist noch nicht data-tabler, laedt
     also weiterhin fitbox-components.css. admin_rechte.html waere die
     erste Seite gewesen, die wirklich hineinlaeuft.

     Tabler hilft hier nicht aus: sein eigenes .modal-backdrop hat in der
     Grundregel ebenfalls kein display:none (nur .show setzt opacity). */
  display: none;
}
/* GEMESSEN BEIM BAUEN, im Browser: display:flex direkt in die Regel oben zu
   schreiben (statt an .open/.show/.active zu haengen, wie fitbox-components.
   css:1042 es tut) sah nach der kleineren Regel aus - war aber falsch.
   #settleModal traegt data-fb-dialog-hintergrund="true" STATISCH UND
   DAUERHAFT im Markup (nicht nur waehrend es offen ist) und schaltet seine
   Sichtbarkeit ausschliesslich ueber classList.add/remove('show'). Mit
   display:flex fest in der Regel oben blieb der Dialog nach dem Schliessen
   sichtbar - das Attribut allein reichte nicht als Ruhezustand, weil es nie
   entfernt wird. uiDialogShell()s Hintergrund braucht das nicht (wird erst
   bei .active >> = beim Erzeugen >> in den DOM gehaengt und beim Schliessen
   per remove() wieder entfernt), aber die Regel muss fuer BEIDE Faelle
   gelten. Deshalb hier zusaetzlich an dieselben drei Klassen gebunden, mit
   denen der Bestand das schon loest - NICHT ungegated (das wuerde Tablers
   eigenes .modal-backdrop.show treffen, das dieselbe Klasse "show" traegt). */
:is(.backdrop, .modal-bd, .modal-backdrop, .modal-overlay, .modal-bg,
    .confirm-backdrop, .info-backdrop, .kc-modal-bd)[data-fb-dialog-hintergrund="true"]:is(.open, .show, .active) {
  display: flex;
  opacity: 1; /* Tablers .modal-backdrop.show setzt sonst .32 - samt Karte */
}
:is(.modal, .modal-card, .modal-box, .confirm-card, .info-card,
    .kc-modal)[data-fb-dialog-standardisiert="true"] {
  /* GEMESSEN BEIM BAUEN: .modal ist genau die Klasse, mit der Tabler seinen
     eigenen Vollbild-Overlay-Container meint (siehe Kommentar oben) - dessen
     Grundregel setzt position:fixed, inset-inline-start:0, top:0, width:100%,
     height:100%. Diese Karte hier ist aber KEIN Overlay-Container, sondern
     die Karte selbst; ohne die vier Zeilen unten erbte sie Tablers Fixierung
     und lag links oben am Fensterrand fest, ausserhalb des Fluss der
     Hintergrund-Flexbox - "zentriert" nur der Absicht nach, nicht im
     gerenderten Ergebnis. Erst mit position:static kehrt die Karte in den
     Fluss des Hintergrunds zurueck, dessen align-items/justify-content sie
     dann tatsaechlich zentrieren. */
  position: static; inset-inline-start: auto; top: auto; height: auto;
  width: min(720px,100%); max-height: 92vh; padding: 0;
  display: flex; flex-direction: column; overflow: hidden;
  background: var(--tblr-bg-surface); color: var(--tblr-body-color);
  border: var(--dialog-border-width) solid var(--dialog-border-color);
  border-top: 4px solid var(--dialog-accent);
  outline: var(--dialog-border-width) solid var(--dialog-border-color); outline-offset: calc(0px - var(--dialog-border-width));
  border-radius: var(--tblr-border-radius-lg); box-shadow: var(--tblr-shadow-lg);
  overscroll-behavior: contain;
}
:is(.modal, .modal-card, .modal-box, .confirm-card, .info-card,
    .kc-modal)[data-fb-dialog-standardisiert="true"] > .modal-head {
  flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-3); margin: 0; padding: var(--modal-pad);
  position: sticky; top: 0; z-index: 2;
  color: var(--tblr-body-color); background: color-mix(in srgb, var(--dialog-accent) 7%, var(--tblr-bg-surface));
  border: 0; border-bottom: var(--tblr-border-width) solid var(--tblr-border-color);
}
:is(.modal, .modal-card, .modal-box, .confirm-card, .info-card,
    .kc-modal)[data-fb-dialog-standardisiert="true"] > .modal-head
    :is(h2, h3, .modal-title, .modal-head-title, .confirm-title, .info-head-title, .kc-modal-t) {
  margin: 0; color: var(--tblr-body-color);
  font: var(--type-title-weight) var(--type-card-title-size)/1.2 var(--font);
  letter-spacing: 0; text-transform: none;
}
:is(.modal, .modal-card, .modal-box, .confirm-card, .info-card,
    .kc-modal)[data-fb-dialog-standardisiert="true"] > .modal-head .modal-titelgruppe {
  min-width: 0; display: flex; align-items: center; gap: var(--sp-3);
}
:is(.modal, .modal-card, .modal-box, .confirm-card, .info-card,
    .kc-modal)[data-fb-dialog-standardisiert="true"] > .modal-head .confirm-icon {
  flex: 0 0 auto; width: 32px; height: 32px; margin: 0;
}
:is(.modal, .modal-card, .modal-box, .confirm-card, .info-card,
    .kc-modal)[data-fb-dialog-standardisiert="true"] > .modal-head .modal-x {
  flex: 0 0 44px; width: 44px; height: 44px; min-width: 44px; min-height: 44px;
  margin: 0; padding: 0; display: grid; place-items: center;
  appearance: none; cursor: pointer; line-height: 1; font-size: 22px; font-weight: 800;
  color: var(--tblr-secondary); background: var(--tblr-bg-surface); border: var(--tblr-border-width) solid var(--tblr-border-color);
  border-radius: var(--tblr-border-radius-sm); box-shadow: none;
}
:is(.modal, .modal-card, .modal-box, .confirm-card, .info-card,
    .kc-modal)[data-fb-dialog-standardisiert="true"] > .modal-head .modal-x:hover {
  color: var(--tblr-body-color); background: var(--tblr-bg-surface-secondary);
}
:is(.modal, .modal-card, .modal-box, .confirm-card, .info-card,
    .kc-modal)[data-fb-dialog-standardisiert="true"] > .modal-body {
  flex: 1 1 auto; min-width: 0; min-height: 0; overflow: auto;
  display: flex; flex-direction: column; gap: var(--modal-gap);
  margin: 0; padding: var(--modal-pad); color: var(--tblr-body-color); background: var(--tblr-bg-surface);
}
:is(.modal, .modal-card, .modal-box, .confirm-card, .info-card,
    .kc-modal)[data-fb-dialog-standardisiert="true"] > .modal-foot {
  flex: 0 0 auto; display: flex; align-items: center; justify-content: flex-end;
  gap: var(--sp-2); flex-wrap: wrap; margin: 0; padding: 13px var(--modal-pad);
  position: sticky; bottom: 0; z-index: 2;
  background: var(--tblr-bg-surface); border: 0; border-top: var(--tblr-border-width) solid var(--tblr-border-color);
}
:is(.modal, .modal-card, .modal-box, .confirm-card, .info-card,
    .kc-modal)[data-fb-dialog-standardisiert="true"] .btn.primary {
  color: var(--dialog-accent-ink); background: var(--dialog-accent);
  border-color: var(--dialog-accent-hover);
  box-shadow: 0 2px 8px color-mix(in srgb, var(--dialog-accent) 20%, transparent);
}
:is(.modal, .modal-card, .modal-box, .confirm-card, .info-card,
    .kc-modal)[data-fb-dialog-standardisiert="true"] .btn.primary:hover {
  color: var(--dialog-accent-ink); background: var(--dialog-accent-hover);
}

@media (max-width: 680px) {
  :is(.backdrop, .modal-bd, .modal-backdrop, .modal-overlay, .modal-bg,
      .confirm-backdrop, .info-backdrop, .kc-modal-bd)[data-fb-dialog-hintergrund="true"] { padding: 10px; }
  :is(.modal, .modal-card, .modal-box, .confirm-card, .info-card,
      .kc-modal)[data-fb-dialog-standardisiert="true"] { width: 100%; max-height: calc(100dvh - 20px); }
  :is(.modal, .modal-card, .modal-box, .confirm-card, .info-card,
      .kc-modal)[data-fb-dialog-standardisiert="true"] > .modal-foot,
  :is(.modal, .modal-card, .modal-box, .confirm-card, .info-card,
      .kc-modal)[data-fb-dialog-standardisiert="true"] .form-actions {
    flex-direction: column-reverse; align-items: stretch;
  }
  :is(.modal, .modal-card, .modal-box, .confirm-card, .info-card,
      .kc-modal)[data-fb-dialog-standardisiert="true"] > .modal-foot .btn,
  :is(.modal, .modal-card, .modal-box, .confirm-card, .info-card,
      .kc-modal)[data-fb-dialog-standardisiert="true"] .form-actions .btn {
    width: 100%; min-height: 44px; justify-content: center;
  }
}

/* Reines Info-Modal (fb-errors.js, openInfoModal/closeInfoModal): eigene,
   schmalere Familie - .info-head traegt hier die Markenfarbe direkt statt
   der neutralen "Klar gegliedert"-Kopfzeile, weil ein Hilfe-Popup bewusst
   anders aussehen soll als ein Bestaetigungsdialog. Padding/align-items
   unten sind bereits der ENDWERT aus zwei Regeln in fitbox-components.css
   (Grunddefinition plus eine spaetere "Seitenhilfen"-Ueberschreibung) - zu
   einer Regel zusammengezogen, weil hier keine Historie zu bewahren ist. */
.info-backdrop {
  position: fixed; inset: 0; z-index: 2000; display: none;
  align-items: center; justify-content: center; overflow: hidden;
  padding: clamp(10px, 3dvh, 24px);
  background: rgba(13,27,42,.55); backdrop-filter: blur(8px);
}
.info-backdrop.show { display: flex; animation: infoFadeIn .2s ease; }
@keyframes infoFadeIn { from { opacity: 0; } to { opacity: 1; } }
.info-card {
  display: flex; flex-direction: column; overflow: hidden;
  width: min(640px,100%); max-height: min(720px, calc(100dvh - 20px));
  background: var(--tblr-bg-surface); color: var(--tblr-body-color);
  border-radius: var(--tblr-border-radius-lg); box-shadow: var(--tblr-shadow-lg);
}
.info-head {
  display: flex; align-items: center; gap: 14px; padding: 18px 22px;
  color: #fff; background: linear-gradient(135deg, var(--brand) 0%, var(--brand-dark) 100%);
}
.info-head > div:nth-child(2) { flex: 1; min-width: 0; }
.info-head-icon { font-size: 28px; flex-shrink: 0; }
.info-head-title { flex: 1; min-width: 0; margin: 0; font-size: 17px; font-weight: 800; letter-spacing: -.01em; }
.info-head-sub { font-size: 12px; opacity: .85; margin-top: 2px; }
.info-close {
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 44px; width: 44px; height: 44px; padding: 0;
  appearance: none; cursor: pointer; line-height: 1; font-size: 22px; font-weight: 700;
  color: #fff; background: rgba(255,255,255,.15); border: 1px solid rgba(255,255,255,.22);
  border-radius: var(--tblr-border-radius-sm);
}
.info-close:hover { background: rgba(255,255,255,.28); }
.info-body { flex: 1 1 auto; min-height: 0; padding: 20px 24px 24px; overflow-y: auto; overscroll-behavior: contain; }
.info-backdrop .modal-foot { flex: 0 0 auto; padding: 0 24px 20px; }

/* ══ FORMULARTEILE AUS assets/js/fb-dialog-contract.js AUF TABLER-SEITEN ════
   13.09.2026. fbEingabe() (Funktion anzeigen(), art:'eingabe') baut fuer
   jedes Eingabefeld <label class="field"><span class="field-label">...
   <input class="ctrl">, auf JEDER Seite, die ihn aufruft - Baukasten- und
   Tabler-Seiten gleichermassen, gemessen ueber ci/check-baukasten-
   abkopplung.mjs: field-label auf 20, field auf 18, ctrl auf 17 Tabler-
   Seiten ungestaltet. Ohne diese Regeln sieht ein Eingabefeld in JEDEM
   fbEingabe-Dialog auf einer Tabler-Seite aus wie ein blankes Browserfeld
   von 1995 (roher Rahmen, kein Radius, keine Tabler-Fokusoptik) - derselbe
   "halber Umbau"-Fehler wie bei .fb-dlg ohne .fb-meldung.

   .ctrl UEBERSETZT AUF TABLERS EIGENE .form-control-BUCHSTABEN (Werte aus
   tabler.min.css kopiert, nicht neu erfunden), NICHT auf die alten --feld-*-
   Tokens - dieselbe Begruendung wie bei .btn: ein Eingabefeld in einem
   Dialog soll aussehen wie jedes andere Eingabefeld der Seite, nicht wie
   ein zweites, nur leicht anders gefaerbtes. Tablers .form-control setzt
   seine Werte anders als .btn DIREKT statt ueber Custom Properties - hier
   also keine Handvoll Variablen, sondern die volle Regel.

   .field-label bekommt --tblr-font-weight-medium und --tblr-body-font-size,
   nicht --tblr-font-weight-semibold: Tablers eigenes .form-label (Abschnitt 1
   dieser Datei setzt dessen Schriftgroesse bereits auf denselben Wert) tut
   exakt das schon - derselbe Bauteiltyp bekommt hier bewusst
   dieselbe Schriftrolle, damit ein Feldlabel im Dialog nicht kraeftiger
   wirkt als eines auf der Seite selbst.

   .fb-fehler UND .field-help sind NICHT Teil der urspruenglichen Messung
   (die galt fb-dialog-contract.js), aber direkt benachbart und je nur eine
   Seite betroffen - mitgenommen statt fuer zwei Klassen ein drittes Mal die
   Datei zu sperren. .fb-fehler nutzt dieselbe kontrastgepruefte Mischung
   wie .fb-meldung.danger oben (identische Werte, kein zweites Rot).

   UNGUELTIG-BRUECKE (Paket AM, 15.09.2026, Messung
   PAKET-AL-FORMULARFELD-UMFANG-15-09.md). .ctrl reagiert bisher nur auf
   [aria-invalid="true"] (Zeile unten), Tablers eigenes .form-control/
   .form-select nur auf die Klasse .is-invalid - keins der beiden Systeme
   versteht das Signal des anderen. Das ist kein kosmetischer Unterschied:
   admin_postausgang.html, employee/passwort_aendern.html und
   studio/verkauf.html setzen aria-invalid heute aktiv im eigenen JS - ein
   kuenftiger Wechsel zu .form-control ohne Anpassung der Validierung wuerde
   die Fehlermarkierung lautlos unsichtbar machen. aria-invalid ist das
   semantische Signal (Vorleseprogramme werten es aus), is-invalid eine rein
   visuelle Konvention - die Brueckenregeln binden die Darstellung an die
   Bedeutung in BEIDE Richtungen: .ctrl bekommt zusaetzlich .is-invalid als
   Ausloeser, .form-control/.form-select bekommen [aria-invalid="true"] als
   Ausloeser, mit denselben Werten wie .ctrl[aria-invalid="true"] unten (eine
   uebersetzte Regel, keine neue Bauform). */
.field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.field label, .field > label, label.field, .field-label {
  display: block; font-weight: var(--tblr-font-weight-medium); font-size: var(--tblr-body-font-size);
  line-height: 1.25; letter-spacing: .01em; text-transform: none; color: var(--tblr-secondary);
}
.field-help { color: var(--tblr-secondary); font-size: .75rem; line-height: 1.4; }

.ctrl {
  display: block; appearance: none; width: 100%;
  padding: .5625rem 1rem;
  font: 400 var(--tblr-body-font-size, .875rem)/1.25rem var(--tblr-body-font-family);
  color: var(--tblr-body-color);
  background-color: var(--tblr-bg-forms); background-clip: padding-box;
  border: var(--tblr-border-width) solid var(--tblr-border-color);
  border-radius: var(--tblr-border-radius);
  box-shadow: var(--tblr-shadow-input);
  transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;
}
/* FOKUSRING (Tabler 1.6.1, 02.10.2026). Tabler 1.6 zeichnet den Fokus als
   Umriss (outline) aus den Tokens --tblr-focus-ring-width/-color/-offset statt
   als Schatten (box-shadow) wie 1.5.0. .ctrl und .fb-feld (unten) uebernehmen
   Tablers .form-control:focus mit freigegebenem Umriss 3px, Versatz -1px,
   Ring aus --tblr-focus-ring-color, Randton aus --tblr-primary), damit eigene
   und Tabler-Felder gleich aussehen. */
.ctrl:focus {
  color: var(--tblr-body-color); background-color: var(--tblr-bg-forms);
  border-color: color-mix(in oklab, var(--tblr-white) 50%, var(--tblr-primary));
  --tblr-focus-ring-offset: -1px;
  outline: var(--tblr-focus-ring, var(--tblr-focus-ring-width) solid var(--tblr-focus-ring-color));
  outline-offset: var(--tblr-focus-ring-offset);
}
.ctrl:disabled { background-color: var(--tblr-bg-forms-disabled); color: var(--tblr-secondary); opacity: 1; cursor: default; }
.ctrl::placeholder { color: var(--tblr-tertiary); opacity: 1; }
.ctrl[aria-invalid="true"], .ctrl.is-invalid {
  border-color: var(--tblr-danger);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--tblr-danger) 12%, transparent);
}
:where([data-tabler]) :is(.form-control, .form-select)[aria-invalid="true"] {
  border-color: var(--tblr-danger);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--tblr-danger) 12%, transparent);
}
/* Ungueltig UND fokussiert: Ring in Gefahrenfarbe, wie Tablers eigenes
   .form-control.is-invalid:focus (Formel 1:1). Ohne diese Regel stuende ein
   Markenring ueber dem roten Rand und dem roten Hof. Der rote Hof (3px, 12 %)
   faellt im Fokus weg, sonst stuenden Umriss und Hof zugleich da (Doppelrahmen,
   Gegenpruefung 02.10.2026); Tablers .form-control.is-invalid:focus hat ihn auch nicht. */
.ctrl[aria-invalid="true"]:focus, .ctrl.is-invalid:focus,
:where([data-tabler]) :is(.form-control, .form-select)[aria-invalid="true"]:focus {
  --tblr-focus-ring-color: light-dark(color-mix(in oklab, var(--tblr-danger) calc(var(--tblr-focus-ring-opacity) * 100%), var(--tblr-body-bg)), color-mix(in oklab, var(--tblr-danger) calc(var(--tblr-focus-ring-opacity-dark) * 100%), var(--tblr-body-bg)));
  box-shadow: var(--tblr-shadow-input);
}
textarea.ctrl { resize: vertical; min-height: calc(1.25rem + 1.125rem + calc(var(--tblr-border-width) * 2)); line-height: 1.45; }

/* Die Regel "FOKUSRING MARKENFARBE" (Paket AM, 15.09.2026) ist mit Tabler 1.6.1
   entfallen: Sie bog die hart kodierte Randfarbe rgb(131,183,232) von
   Tabler 1.5.0 auf --tblr-primary um. 1.6.1 mischt den Rand selbst aus
   --tblr-primary (50 % Weiss), die Markenfarbe kommt also von allein an. Bei
   Feldern mit Fokus deckt der 2-px-Ring den 1-px-Rand ohnehin ab, uebrig bleibt
   nur der Randton an den Rundungen. Gemessen (Pixelvergleich, 4-fach): mit und
   ohne die alte Regel unterscheiden sich nur die vier Ecken, hoechstens 35 von
   255 Stufen. */

.fb-fehler {
  font-size: .8125rem; font-weight: var(--tblr-font-weight-semibold); margin: 0;
  padding: 7px 10px; border-radius: var(--tblr-border-radius-sm);
  color: color-mix(in srgb, var(--tblr-danger) 65%, black);
  background: var(--tblr-danger-lt); border: var(--tblr-border-width) solid var(--tblr-danger-border-subtle);
}
:root[data-theme="dark"] .fb-fehler { color: color-mix(in srgb, var(--tblr-danger) 65%, white); }

/* ══ AUSWAHLLISTE (.pick-list/.pick-item/.pick-count) AUF TABLER-SEITEN ═════
   13.09.2026. employee/fahrtkosten.html wurde auf Tabler umgestellt und hielt
   die Adressvorschlagsliste danach nur ueber eine SEITENLOKALE Kopie dieser
   Familie am Leben (PR #916, ausdruecklich als Zwischenloesung markiert) -
   der eigentliche Fund war, dass zehn weitere Baukasten-Seiten und drei
   geteilte Skripte dieselbe Familie nutzen, ohne dass sie irgendwo zentral
   fuer Tabler stand. KEINE Namenskollision: .pick-list/.pick-item/.pick-count
   kommen in Tabler nicht vor, reines Nachbauen wie bei .fb-dlg.

   GEMESSEN, welche drei Seiten .pick-* SELBST erwaehnen, und was das wirklich
   bedeutet - keine davon ausser fahrtkosten hat je die Basis kopiert:
     - studio/cash_pos.html: .category-strip .pick-list/.pick-item ist eine
       ECHTE, bewusste Abweichung im eigenen Kontext (horizontale Pillenleiste
       statt Spalte, eigene Rundung/Groesse) - ein Vorfahrenselektor, keine
       Neudefinition der Basis. Bleibt unberuehrt.
     - studio/kunden_akte.html (33 Markup-Vorkommen, mit Abstand am meisten):
       definiert GAR KEINE eigene .pick-*-Regel, nur eine Touch-Ziel-Ausnahme
       ueber denselben Vorfahren-Mechanismus (.kd-nav .pick-item{min-height:
       44px!important}). Reiner Verbraucher - der Pruefstein dafuer, dass die
       Bindung unten vollstaendig ist, auch ohne dass diese Seite je etwas
       beigetragen hat.
     - employee/fahrtkosten.html: die einzige ECHTE Kopie, unten mit dieser
       Bindung entfernt (siehe die Seite selbst).

   NICHT MITGENOMMEN: .pick-item.inactive .person-pick-avatar
   (fitbox-components.css). Trotz .pick-item im Selektor gehoert die Regel zu
   einem ANDEREN, groesseren Bauteil (.picker-panel/.person-pick-*, "Auswahl-
   spalte einer Master-Detail-Seite") - sie faerbt nur einen .person-pick-
   avatar anders, WENN er zufaellig innerhalb eines .pick-item.inactive
   auftritt. Kein Teil dieser Familie, gehoert zu einer eigenen, spaeteren
   .picker-panel-Bindung.

   .pick-item.active vs. .pick-item:hover:not(.active) sehen auf den ersten
   Blick fast gleich aus (beide background:var(--brand-soft), beide border-
   color:var(--brand-line)), unterscheiden sich aber in color/font-weight -
   der einzige sichtbare Hinweis beim Durchpfeilen mit der Tastatur, welcher
   Eintrag gerade ausgewaehlt ist. Im Browser mit echten Pfeiltasten geprueft,
   nicht nur per Hover mit der Maus (siehe Commit). */
.pick-list { display: flex; flex-direction: column; gap: 6px; }
.pick-item {
  appearance: none; width: 100%; text-align: left;
  display: flex; align-items: center; gap: 9px;
  background: var(--tblr-bg-surface); color: var(--tblr-secondary);
  border: var(--tblr-border-width) solid var(--tblr-border-color); border-radius: var(--tblr-border-radius-lg);
  padding: 9px 12px; font: var(--tblr-font-weight-semibold) .75rem/1.2 var(--tblr-body-font-family);
  cursor: pointer; transition: all .12s;
}
.pick-item:hover:not(.active) {
  background: var(--brand-soft); border-color: var(--brand-line); color: var(--tblr-body-color);
}
.pick-item.active {
  background: var(--brand-soft); border-color: var(--brand-line);
  color: var(--auf-brand-acc); font-weight: var(--tblr-font-weight-bold);
}
/* Flach: die Liste sitzt schon in einer Karte, ein zweiter Rahmen je Eintrag
   waere ein Kasten im Kasten. */
.pick-list.flach { gap: 2px; }
.pick-list.flach .pick-item { border-color: transparent; background: none; border-radius: var(--tblr-border-radius-sm); }
.pick-list.flach .pick-item:hover:not(.active) { background: var(--brand-soft); border-color: transparent; }
/* Die Zeile darueber setzt background:none fuer ALLE Eintraege der flachen
   Liste - mit hoeherer Spezifitaet als .pick-item.active. Ohne diese Regel
   verliert der aktive Eintrag seine Markierung. */
.pick-list.flach .pick-item.active { background: var(--brand-soft); border-color: transparent; }
/* Zaehler rechts im Eintrag */
.pick-item .pick-count {
  margin-left: auto; font-size: .75rem; font-weight: 700;
  background: color-mix(in srgb, var(--tblr-body-color) 8%, transparent); color: var(--tblr-secondary);
  padding: 1px 7px; border-radius: var(--tblr-border-radius-pill); font-variant-numeric: tabular-nums;
}
.pick-item.active .pick-count { background: color-mix(in srgb, var(--brand) 15%, transparent); color: var(--auf-brand-acc); }

/* ══ AUSWAHLSPALTE (.picker-panel/.person-pick-*) AUF TABLER-SEITEN ═════════
   13.09.2026, direkte Fortsetzung der .pick-list/.pick-item-Bindung oben.
   Rollenfrage zuerst beantwortet: .picker-panel/.person-pick-* ist NICHT
   dieselbe Rolle wie .pick-*, sondern zwei WEITERE, eigene Rollen, die
   zusammen mit ihr eine Auswahlspalte einer Master-Detail-Seite bilden:
     - .pick-item/.pick-list (oben): die Auswahlzeile selbst - Zustand
       (hover/active). Bereits gebunden.
     - .picker-panel (+ -search/-tabs/-list, hier): die Kartenhuelle DRUM-
       HERUM - Kopf, Suche, Tabs, Scrollbereich. Ohne Bezug zur Zeile selbst:
       admin_support.html und admin_mail_postfach.html nutzen denselben
       Rahmen fuer Supportfaelle bzw. Nachrichten-Threads, nicht fuer
       Personen.
     - .person-pick-* (hier): der PERSONEN-Inhalt EINER Zeile (Avatar, Name,
       Metazeile, Zaehler) - ein Inhaltsschema, das innerhalb eines
       .pick-item sitzt, wenn das Ausgewaehlte eine Person ist. Heute
       einziger Verbraucher: admin_mitarbeiterzentrale.html.
   Drei Rollen, ein Satz je Unterschied - kein Fall fuer eine Rueckfrage.

   TRAEGER (fitbox-components.css, Zeilen 788-822), alle DREI Baukasten-
   Seiten, KEINE davon bisher data-tabler:
     - admin/admin_mitarbeiterzentrale.html: .picker-panel MIT .person-pick-*
       (Mitarbeiterliste). Ausserdem strukturell bewacht durch
       ci/ui-contract-lib.mjs semanticComponentContractErrors() - derselbe
       Vertrag verlangt schon heute .picker-panel/.person-pick-* an den
       echten Auswahl-/Zeilenknoten, nicht nur irgendwo auf der Seite.
     - admin/admin_support.html, admin/admin_mail_postfach.html:
       .picker-panel OHNE .person-pick-* (eigene Zeileninhalte:
       .su-item-* bzw. .mp-item-*).
   KEINE Namenskollision: .picker-panel/.person-pick-* kommen in Tabler
   nicht vor.

   NICHT MITGENOMMEN, weil je eine ANDERE, groessere Rolle mit eigenem
   Zuschnitt - an den Master gemeldet, nicht stillschweigend mitgebaut oder
   stillschweigend liegengelassen:
     - .card-head/.card-body/.card-sub/.card-note (fitbox-components.css):
       die generische Kartenkopf-/-koerper-Anatomie JEDER .card, nicht nur
       von .picker-panel - noch VOLLSTAENDIG ungebunden. .picker-panel
       .card-head h2/h3 unten setzt nur eine Zusatzregel fuer die
       Kopfzeilen-Anordnung, ersetzt die fehlende Basis nicht. Blockiert
       die drei Traeger oben erst beim eigenen Tabler-Umbau, nicht diese
       Bindung hier.
     - .ui-master-detail (fitbox-layouts.css, ANDERE Datei): das
       Zweispalten-Grid, das .picker-panel neben die Detailflaeche stellt.
       Gehoert zur "ui-*-Layouthelfer"-Familie (.ui-toolbar, .ui-stack,
       .ui-kpi-grid, ...) - komplett eigene, ebenfalls noch ungebundene
       Rolle, in KEINEM Abschnitt dieser Datei bisher vertreten.

   GEPRUEFT: da keine der drei Traegerseiten schon data-tabler ist, gab es
   keine echte Seite fuer die Browser-Gegenprobe wie bei .pick-*. Stattdessen
   ein Silo mit dem ECHTEN Markup aus admin_mitarbeiterzentrale.html unter
   data-tabler + tabler.min.css + dieser Datei (siehe PR-Beschreibung).

   NACHTRAG (vor dem Merge, Fund des Master): .person-pick-title/-meta sind
   SPANs, .person-pick-copy (ihr Elter) ist kein Flex-Container - beide
   bleiben also inline, solange kein display:block gesetzt ist. Auf einem
   Inline-Element wirkt von overflow/text-overflow/white-space nur
   white-space; die Ellipse entsteht ohne display:block gar nicht. Der
   erste Silo-Versuch zeigte das nicht, weil er still von der seiteneigenen
   Regel admin-mitarbeiterzentrale.css .mz-picker .person-pick-title,
   .person-pick-meta{display:block} mitgetragen wurde - eine TEILWEISE
   Ueberlagerung, die aussah, als waere die Rolle versorgt. display:block
   steht jetzt direkt in dieser Bindung; die seiteneigene Zeile ist damit
   ueberfluessig und entfernt. Im Silo OHNE jede Seitenregel nachgemessen:
   die Ellipse entsteht jetzt aus der Bindung allein. */
.picker-panel { display: flex; flex-direction: column; min-width: 0; overflow: hidden; }
.picker-panel.sticky {
  position: sticky; top: var(--sp-4); max-height: calc(100dvh - (2 * var(--sp-4)));
}
.picker-panel .card-head { flex: 0 0 auto; }
.picker-panel .card-head h2,
.picker-panel .card-head h3 { display: inline-flex; align-items: center; gap: var(--sp-2); }
.picker-panel-search { flex: 0 0 auto; padding: var(--sp-3); border-bottom: var(--tblr-border-width) solid var(--tblr-border-color); }
.picker-panel-search .ctrl { width: 100%; }
.picker-panel-tabs { flex: 0 0 auto; width: auto; margin: var(--sp-2) var(--sp-3) 0; }
.picker-panel-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
@media (max-width: 820px) {
  .picker-panel.sticky { position: static; max-height: none; }
  .picker-panel-list { max-height: 280px; }
}

.person-pick-avatar {
  display: grid; place-items: center; flex: 0 0 32px; width: 32px; height: 32px;
  border-radius: var(--tblr-border-radius-pill); color: var(--auf-brand-acc); background: var(--brand-soft);
  font-size: .75rem; font-weight: var(--tblr-font-weight-bold);
}
.pick-item.inactive .person-pick-avatar { color: var(--tblr-secondary); background: var(--tblr-bg-surface-secondary); }
.person-pick-copy { flex: 1 1 auto; min-width: 0; }
.person-pick-title {
  display: block; overflow: hidden; color: var(--tblr-body-color); font-size: var(--tblr-body-font-size);
  font-weight: var(--tblr-font-weight-bold); text-overflow: ellipsis; white-space: nowrap;
}
.person-pick-meta {
  display: block; overflow: hidden; color: var(--tblr-secondary); font-size: .75rem;
  text-overflow: ellipsis; white-space: nowrap;
}
.person-pick-count {
  flex: 0 0 auto; min-width: 22px; padding: 2px 7px; text-align: center;
  color: var(--tblr-primary-fg, #fff); background: var(--tblr-primary); border-radius: var(--tblr-border-radius-pill);
  font-size: var(--tblr-badge-font-size); font-weight: var(--tblr-font-weight-bold); font-variant-numeric: tabular-nums;
}

/* ══ KARTENANATOMIE (.card-head/.card-body.flush/.card-sub/.card-note) AUF
   TABLER-SEITEN ═══════════════════════════════════════════════════════════
   13.09.2026, Fund waehrend der .picker-panel-Bindung (siehe PR #932):
   .card selbst hat in dieser Datei nur eine Dichteanpassung (Tabler-eigene
   --tblr-card-*-Variablen), aber die vier Baukasten-eigenen Kartensub-
   Bauteile sind vier verschiedene Rollen mit unterschiedlichem
   Bindungsbedarf:
     - .card-body: KEIN Fund. Tabler kennt denselben Namen und stylt ihn
       schon selbst (Innenabstand ueber --tblr-card-spacer-y/-x, hier
       schon dichteangepasst) - nur der .flush-Modifikator (padding:0)
       fehlt als Zusatzregel.
     - .card-head: eigene Rolle (Kopfzeile mit Titel+Aktion). Tabler hat
       KEIN gleichnamiges Gegenstueck - .card-header (mit "er") ist ein
       anderer Name, strukturell aber fast identisch (beide flex, Innen-
       abstand+Untergrund+Randlinie unten). Gebunden statt umbenannt, aus
       demselben Grund wie bei .picker-panel: Umbenennen wuerde Markup auf
       93 Baukasten-Seiten anfassen, Binden ist ein reiner CSS-Schnitt.
     - .card-sub: eigene Text-Rolle (Untertitel), kein Tabler-Gegenstueck
       unter diesem Namen (nur .card-subtitle, ein anderer Name).
     - .card-note: ECHTE, wenn auch schmale Namenskollision - siehe unten.

   GEMESSEN: .card-head kommt auf KEINER der 24 aktuellen Tabler-Seiten
   vor (eine erste Zaehlung per Teilstringsuche zeigte 11 Treffer - reiner
   Fehlalarm, ".card-header" enthaelt ".card-head" als Praefix). .card-body
   ist ueberall vertreten, unbedenklich (siehe oben).

   .card-note KOLLIDIERT ECHT: Tabler kennt denselben Namen fuer eine
   GELBE NOTIZKARTE, deren Regel ausschliesslich zwei Custom Properties
   setzt (--tblr-card-bg/--tblr-card-border-color) - die nur .card SELBST
   liest. Auf dem tatsaechlichen Traeger, einem blossen <p>, bewirkt
   Tablers Regel exakt nichts; der Absatz rendert unversorgt als
   Fliesstext. HEUTE SCHON live: employee/fahrtkosten.html:298, ein
   <p class="card-note"> im Bestaetigungsdialog "Fahrt einreichen?", ohne
   lokale Definition - ein kleiner, aber echter, schon bestehender
   Darstellungsfehler, den ci/check-baukasten-abkopplung.mjs vorher nicht
   erfasst hatte (nicht weil das Skript keine HTML-Klassen liest, sondern
   weil "Tabler kennt den Namen" dort bislang pauschal als Deckung galt -
   fuer .card-note falsch, fuer andere Faelle wie .modal-body richtig;
   seit PR #934 gemessen: genau dieser eine Fall im ganzen Bestand). Weil
   beide .card-note-Regeln dieselbe Spezifitaet haben, MUSS die Bindung
   unten per Ladereihenfolge gewinnen - im Silo nachgemessen, nicht nur
   angenommen (siehe Commit).

   ENTSCHIEDEN: --card-header-bg und --font bleiben unveraendert - beide
   sind Haus-Token aus fitbox-tokens.css, die auf JEDER Seite (Baukasten
   wie Tabler) gleich aufloesen (Geist laedt laut Ladereihenfolge oben
   auch auf Tabler-Seiten). --tblr-card-inner-border-radius (Tablers
   EIGENE Kennzahl fuer genau diesen Fall - ein Kappenrand, der zur
   Aussenrundung der Karte passt, vererbt von der umgebenden .card) ersetzt
   die alte, feste Randrundung. Die bisherige Titelgroesse (16px) entspricht
   Tablers eigenem .card-title (font-size:1rem) - kein Umweg noetig. */
.card-head {
  display: flex; justify-content: space-between; align-items: center;
  padding: 13px 16px; background: var(--card-header-bg);
  border-bottom: var(--tblr-border-width) solid var(--tblr-border-color);
  border-radius: var(--tblr-card-inner-border-radius) var(--tblr-card-inner-border-radius) 0 0; gap: 10px;
}
.card-head h2, .card-head h3,
.card-head:is(h2, h3) {
  font: var(--tblr-font-weight-bold) 1rem/1.2 var(--font); color: var(--tblr-body-color); margin: 0;
}
.card-body.flush { padding: 0; }
.card-sub { color: var(--tblr-secondary); font-size: .75rem; line-height: 1.4; }
.card-note { color: var(--tblr-secondary); font-size: .75rem; line-height: 1.5; }
.card-body.flush > .card-note { padding: var(--sp-3) var(--sp-4); }

/* ══ VOLLBILD-LADEBLENDE (.loading-overlay, vormals #fitLoading) AUF
   TABLER-SEITEN ═══════════════════════════════════════════════════════════
   13.09.2026, vom Master gemessen und zugewiesen. Rollenfrage selbst
   nachgeprueft, nicht nur uebernommen: JA, dieselbe Rolle auf allen sieben
   Traegern - Vollbild-Blende waehrend einer Aktion, Kreisel+Text zentriert,
   per .show geschaltet. Aber DREI verschiedene Bauformen dafuer, nicht
   eine:
     - 5x BYTEIDENTISCH, ID-basiert: employee/onboarding.html,
       passwort_aendern.html, employee_absence.html, fahrtkosten.html,
       meine_schichten.html. Kein Kartenbau, Kreisel+Text sitzen direkt auf
       der Blende.
     - 1x fast identisch, andere ID (#globalLoading statt #fitLoading), mit
       zusaetzlichem backdrop-filter:blur(4px) und eigenem z-index (1000
       statt 1050): admin/admin_zeiterfassung.html (seit #928, erste
       Tabler-Seite mit dieser Rolle).
     - 1x strukturell anders, bereits klassenbasiert (.loading-overlay):
       admin/admin_aenderungsprotokoll.html - mit eigenem .card/.card-body
       INNERHALB der Blende und z-index 1080.

   ENTSCHIEDEN: die Mehrheits-Bauform gewinnt (kein Kartenbau, Kreisel+Text
   direkt auf der Blende) - admin_aenderungsprotokoll.html verliert seinen
   .card/.card-body-Umbau, seine eigene Seite ist entsprechend vereinfacht.
   z-index 1080 (der hoechste der drei bisherigen Werte) uebernommen: sitzt
   damit sicher ueber Tablers eigenem .modal (1055) und .modal-backdrop
   (1050), aber unter .fb-toast-wrap (1090) - eine Fehlermeldung bleibt
   also auch waehrend des Ladens sichtbar. backdrop-filter:blur(4px) (bisher
   nur admin_zeiterfassung.html) auf alle sieben Traeger ausgeweitet - eine
   reine Ergaenzung ohne Kontrast- oder Funktionsrisiko, kein Traeger
   verliert dadurch etwas.

   KONTRASTANKER, nicht selbst gefunden sondern von session-5-8d72ec
   uebernommen und hier festgehalten: die alte Bauform hat KEINE eigene
   Karte fuer Kreisel+Text - die Blende selbst muss darum hell (im
   Dunkelmodus dunkel) sein, sonst waere die geerbte Standardtextfarbe auf
   einer dunklen Blende unlesbar. --tblr-body-bg ist bereits selbst
   dunkelmodusfest (light-dark(), siehe tabler.min.css) und ersetzt damit
   sowohl das literale rgba(255,255,255,.72)/rgba(20,22,26,.72) als auch
   die zwei seiteneigenen Dunkelmodus-Ueberschreibungen ([data-theme=dark]
   und @media prefers-color-scheme) - eine Regel statt drei, ohne den
   Kontrastanker zu verlieren. Im Silo in beiden Themes nachgemessen (siehe
   Commit), nicht nur angenommen.

   SPEZIFITAET: #fitLoading/#globalLoading sind ID-Selektoren (1,0,0), eine
   Klassenregel (0,1,0) schlaegt sie NICHT. Die Ablösung entfernt darum die
   ID-Regeln aus allen sieben Seiten vollstaendig, statt eine wirkungslose
   Klassenregel danebenzustellen - die HTML-ID selbst bleibt stehen (wird
   von showLoading()/hideLoading() weiterhin per getElementById
   angesprochen), nur ihre CSS-Regel faellt weg. */
.loading-overlay {
  position: fixed; inset: 0; display: none; align-items: center; justify-content: center;
  flex-direction: column; gap: .5rem;
  background: color-mix(in srgb, var(--tblr-body-bg) 72%, transparent);
  backdrop-filter: blur(4px);
  z-index: 1080;
}
.loading-overlay.show { display: flex; }

/* ══ .ui-stack AUF TABLER-SEITEN ═════════════════════════════════════════
   13.09.2026. Fund waehrend der Messung der "ui-*-Layouthelfer"-Familie
   (fitbox-layouts.css) - GETRENNT von deren Rest gebunden, nicht mit
   hineingenommen, weil dieser eine Fall eine andere Kategorie ist: eine
   REPARATUR mit lebender Folge, kein Vorrat ohne aktuellen Traeger.

   admin/admin_zeiterfassung.html traegt .ui-stack SCHON HEUTE (Zeile 291,
   <main class="page ui-stack" ...>) und haelt es seit seinem eigenen
   Tabler-Umbau (Teil 4) nur ueber eine SEITENLOKALE Kopie am Leben - die
   Seite sagt das selbst im Kommentar ("HAUPTLAYOUT: .ui-stack seit Teil 4
   hier lokal statt zentral"). Derselbe Fall wie employee/fahrtkosten.html
   vor #925: ein Stopgap, der einen zentralen Eigentuemer braucht. Die
   restliche ui-*-Familie hat dagegen HEUTE keinen einzigen Tabler-Traeger
   (gemessen ueber alle 26 aktuellen Seiten) - reine Vorsorge, an anderer
   Stelle gebunden, wo sie sich im Silo statt an einer echten Seite
   gegenpruefen laesst.

   KEINE Namenskollision: .ui-stack kommt in Tabler nicht vor.

   ── 15.09.2026, Gegenpruefung (Paket CJ): die Schmalkante ist ersatzlos
   entfallen, absichtlich, nicht vergessen ────────────────────────────────
   Bis hierher stand zusaetzlich "@media(max-width:760px){.ui-stack{gap:
   14px}}". BEIDE Zahlen lagen neben der Leiter: 760px ist keine
   Tabler-Bruchkante (Bootstrap kennt 576/768/992/1200/1400), und 14px
   steht nicht auf der Abstandsleiter (docs/ui-baukasten-standard.md 4.1:
   0/4/8/16/24/32/40).

   HERKUNFT, nicht Nachlaessigkeit: 760/14 waren eine originalgetreue
   Kopie aus fitbox-layouts.css' --ui-section-gap-System (dort an
   [data-ui-page] gebunden). DORT ist 760/761 tatsaechlich eine
   lueckenlose Kante (siehe fitbox-layouts.css:190,199 - min-width:761px
   schliesst nahtlos an max-width:760px an). Der Wert hat sich beim Umzug
   hierher nicht geaendert - seine Begruendung ist unterwegs verloren
   gegangen: [data-ui-page] ist ein eigenes, von Tabler unabhaengiges
   Seitenbehaelter-System mit einer eigenen Kante bei 760/1100px, NICHT
   Tablers Rasterkante. .ui-stack lebt jetzt in der Tabler-Bindung und
   muss sich an Tablers eigener md-Kante (768px, dort wechselt das Raster
   von gestapelt auf nebeneinander) orientieren, nicht an der Kante des
   Systems, aus dem es kopiert wurde.

   GEMESSEN, der eigentliche Fund: auf Seiten, die zusaetzlich zu
   .ui-stack echte Tabler-Rasterspalten tragen (admin_franchise.html,
   studio/kunden_akte.html: col-md-*), schaltete das Raster erst bei 768px
   um, waehrend .ui-stack schon ab 761px seinen Breitabstand zeigte -
   zwischen 761 und 767px lief das Raster noch gestapelt, .ui-stack aber
   bereits im Breitmodus. Per getBoundingClientRect() an echten
   Geschwisterelementen bestaetigt (14,0px bei <=760px, 16,0px ab 761px,
   exakt an dieser Kante, nicht bei 768).

   WARUM KEINE SCHMALVARIANTE MEHR, statt die Kante nur zu verschieben:
   der einzige Leitersprossenwert in der Naehe von 14 ist 16 (Abstand 2
   Sprossen zu 8, aber nur 1 zu 16) - und 16px ist bereits der Breitwert
   --sp-4. Eine Schmalvariante auf 8px waere keine Rundung mehr gewesen,
   sondern eine neu erfundene, nie getroffene Design-Entscheidung (halbe
   Breite auf schmalen Schirmen), begruendet nur mit dem Anschein von
   Regeltreue. Der ladder-treue Zielwert faellt mit dem Ausgangswert
   zusammen - eine Unterscheidung, deren einziges korrektes Ziel der
   Ausgangswert selbst ist, war nie eine Unterscheidung.

   GEPRUEFT vor dem Entfernen: --sp-4 lokal auf keiner der Seiten mit
   .ui-stack ueberschrieben (0 Treffer fuer "--sp-4:" projektweit, nicht
   nur auf den 33 Nicht-Fixture-Seiten mit .ui-stack; Stichprobe von sechs
   Seiten gegen die gerenderte Kaskade bestaetigt --sp-4=16px ueberall, wo
   .ui-stack selbst rendert).

   UNGESCHUeTZT: kein Waechter prueft diese Regel. ci/check-abstandsleiter.mjs
   (Paket CB) liest nur seitenlokale <style>-Bloecke, nie gemeinsame
   Stilblaetter wie diese Datei - Paket CK (Nachtbericht 15.09.2026) soll
   das nachruesten. Bis dahin verhindert nichts, dass hier wieder eine
   Schmalkante mit einer Zahl neben der Leiter eingefuehrt wird - wer das
   vorhat, lese diesen Kommentar zuerst. */
.ui-stack { display: flex; flex-direction: column; gap: var(--sp-4); min-width: 0; }

/* ══ .fb-feld / .fb-switch AUF TABLER-SEITEN ═══════════════════════════════
   13.09.2026, vom Master gemessen und zugewiesen: .fb-feld 23 Seiten/244
   Vorkommen, .fb-switch 15 Seiten - beide bisher nur zentral in
   fitbox-components.css gestaltet (.fb-feld ab Zeile 1745, .fb-kontext
   .fb-feld Zeile 1715; .fb-switch ab Zeile 680), das eine Tabler-Seite
   nicht mehr laedt. Eine Sitzung hat die Kopie beim eigenen Umbau schon
   zweimal seitenlokal nachgebaut und beim dritten Mal nachgefragt statt
   zu kopieren - Grund fuer diese Bindung statt eines Umbaus auf Tablers
   .form-label/.form-control: 244 Stellen umzuschreiben waere eine
   Umbenennungsaktion mit Risiko, keine Bindung rettet dagegen Bestand
   (Designvertrag).

   BINDEN HEISST NICHT KOPIEREN - derselben Machart wie .field/.ctrl (PR
   #909) gefolgt, nicht die alten --feld-*-Token woertlich uebernommen:
     - .fb-feld selbst (display:flex/column/gap 5px/min-width:0) ist
       BUCHSTAEBLICH identisch mit dem bereits gebundenen .field - eine
       Rolle, zwei Namen auf verschiedenen Seiten.
     - .fb-feld > label bekommt dieselbe Rolle wie .field label:
       --tblr-font-weight-medium statt des urspruenglichen
       --type-ui-weight (600/semibold) - Tablers eigenes .form-label
       setzt bereits genau diese Schriftrolle, ein Feldlabel soll hier
       nicht kraeftiger wirken als eines auf der Seite selbst (dieselbe
       Begruendung wie bei .field-label). Ebenso --tblr-body-font-size
       statt des urspruenglichen --type-label-size (12px) - eine bewusste,
       kleine Vergroesserung, aus demselben Grund.
     - .fb-feld :is(input, select) UEBERNIMMT .ctrl WOERTLICH (Werte aus
       tabler.min.css, nicht neu erfunden) statt der alten --feld-h/-pad/
       -line/-r/-bg-Token: ein Eingabefeld in einem .fb-feld soll aussehen
       wie jedes andere Eingabefeld der Seite (dieselbe Begruendung wie
       bei .ctrl selbst). Das urspruengliche --feld-h/-pad wird dabei
       nicht mehr gebraucht - .ctrl's eigene Massgrundlage sorgt fuer
       dieselbe Zeilenhoehe, kein width:100% noetig (.fb-feld ist
       display:flex;flex-direction:column ohne align-items - der native
       Flex-Stretch macht das Kind schon breit, wie im Original).
     - .fb-switch: --ink2/--type-ui-weight/--text-base auf dieselbe Weise
       wie oben auf --tblr-secondary/--tblr-font-weight-medium/
       --tblr-body-font-size uebersetzt. Track/Thumb sind STRUKTURELL
       anders als Tablers eigenes .form-switch (SVG-Hintergrundbild statt
       Track+::after) - dessen Regeln passen nicht auf diese Markup-Form,
       darum Tokenuebersetzung statt Bauteilwechsel: --border-strong (fuer
       Rand UND Fuellung des Aus-Zustands) wird zu --tblr-secondary (kein
       eigenes "strong border"-Aequivalent etabliert, --tblr-secondary ist
       hier die durchgaengig gewaehlte Entsprechung fuer geraden Baukasten-
       Grauton), --shadow-sm zu Tablers echtem --tblr-shadow-sm, --brand/
       --brand-dark zu --tblr-primary/--tblr-primary-darken (dieselbe
       Kombination wie beim Hover von .btn.primary).
     - .fb-kontext selbst (die Werkzeugleiste drumherum) bleibt AUSSEN VOR -
       eigene, breitere Rolle, nicht Teil dieses Auftrags. Nur die enge
       .fb-kontext .fb-feld{min-width:0}-Zusatzregel ist mitgenommen, weil
       sie nichts an .fb-kontext selbst aussagt, nur .fb-feld ergaenzt.
     - .check-row (fitbox-components.css, direkt neben .fb-switch) ist ein
       ANDERES Bauteil (einfache Checkbox-Zeile, kein Schalter) - nicht
       Teil dieses Auftrags, nicht mitgenommen.

   GEMESSEN im Browser, nicht aus der Kaskadenreihenfolge geschlossen
   (siehe Commit fuer die Werte): admin/admin_support.html (data-tabler
   seit #963) gegen eine Baukasten-Seite mit .fb-feld, in Hell UND Dunkel. */
.fb-feld { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.fb-feld > label {
  display: block; font-weight: var(--tblr-font-weight-medium); font-size: var(--tblr-body-font-size);
  line-height: 1.25; letter-spacing: .01em; text-transform: none; color: var(--tblr-secondary);
}
.fb-feld > .wozu { color: var(--tblr-secondary); font-size: .75rem; line-height: 1.4; }
.fb-feld :is(input, select) {
  display: block; appearance: none; width: 100%;
  padding: .5625rem 1rem;
  font: 400 var(--tblr-body-font-size, .875rem)/1.25rem var(--tblr-body-font-family);
  color: var(--tblr-body-color);
  background-color: var(--tblr-bg-forms); background-clip: padding-box;
  border: var(--tblr-border-width) solid var(--tblr-border-color);
  border-radius: var(--tblr-border-radius);
  box-shadow: var(--tblr-shadow-input);
  transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;
}
/* Fokus wie .ctrl:focus oben (Umriss aus den Tabler-Tokens, Tabler 1.6.1). */
.fb-feld :is(input, select):focus {
  color: var(--tblr-body-color); background-color: var(--tblr-bg-forms);
  border-color: color-mix(in oklab, var(--tblr-white) 50%, var(--tblr-primary));
  --tblr-focus-ring-offset: -1px;
  outline: var(--tblr-focus-ring, var(--tblr-focus-ring-width) solid var(--tblr-focus-ring-color));
  outline-offset: var(--tblr-focus-ring-offset);
}
.fb-feld.stark > label { color: var(--tblr-body-color); font-weight: var(--tblr-font-weight-bold); }
.fb-kontext .fb-feld { min-width: 0; }

.fb-switch {
  position: relative; display: inline-flex; align-items: center; gap: var(--sp-2);
  min-height: 32px; cursor: pointer; color: var(--tblr-secondary);
  font: var(--tblr-font-weight-medium) var(--tblr-body-font-size)/1.35 var(--tblr-body-font-family);
}
.fb-switch > input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.fb-switch > .track {
  width: 42px; height: 24px; border-radius: var(--tblr-border-radius-pill); flex: 0 0 auto;
  position: relative; background: var(--tblr-secondary); border: 1px solid var(--tblr-secondary);
  transition: background .18s, border-color .18s;
}
.fb-switch > .track::after {
  content: ''; position: absolute; top: 2px; left: 2px; width: 18px; height: 18px;
  border-radius: 50%; background: var(--tblr-bg-surface); box-shadow: var(--tblr-shadow-sm);
  transition: transform .18s;
}
.fb-switch > input:checked + .track { background: var(--tblr-primary); border-color: var(--tblr-primary-darken); }
.fb-switch > input:checked + .track::after { transform: translateX(18px); }
.fb-switch > input:focus-visible + .track { outline: var(--tblr-focus-ring, var(--tblr-focus-ring-width) solid var(--tblr-focus-ring-color)); outline-offset: var(--tblr-focus-ring-offset); }
.fb-switch > input:disabled + .track { opacity: .5; cursor: not-allowed; }

/* ══ .fb-hinweis AUF TABLER-SEITEN ══════════════════════════════════════════
   13.09.2026: 21 Seiten nutzen .fb-hinweis im Markup, bisher nur zentral in
   fitbox-components.css Zeile 1814 gestaltet. Zwei davon sind bereits
   data-tabler (admin_buchhaltung.html, admin_support.html) und laden
   fitbox-components.css nicht mehr - beide hatten deshalb die Regel
   wortgleich seitenlokal nachgebaut, derselbe Fall wie vorher bei .fb-feld/
   .fb-switch.

   DIREKTE TOKENUEBERSETZUNG, kein Bauteilwechsel: Tabler hat mit
   .form-hint zwar ein eigenes Bauteil fuer denselben Zweck (color:
   var(--tblr-secondary)), das passt hier aber NICHT - .form-hint setzt
   display:block, .fb-hinweis wird jedoch auch auf <span> fuer INLINE-Text
   verwendet (admin_bankabgleich.html: <span class="fb-hinweis"
   id="importZustand">), block wuerde dort umbrechen und die Zeile
   veraendern. Deshalb dieselbe Uebersetzung wie schon bei .fb-feld > .wozu
   (identischer Ausgangswert): --text-sm (12px) zu .75rem, --muted zu
   var(--tblr-secondary) - keine Strukturaenderung, nur die zwei Tokens.

   GEMESSEN im Browser, Hell UND Dunkel: admin/admin_support.html (data-
   tabler) gegen eine Baukasten-Seite mit .fb-hinweis. */
.fb-hinweis { font-size: .75rem; color: var(--tblr-secondary); line-height: 1.5; }

/* ══ ZWEISPALTER MIT KLEBENDER SEITENSPALTE (Designvertrag §10.1) ══════════
   14.09.2026, Master zugewiesen (Auftrag E, docs/tabler-arbeitsflaeche-
   zielform.md). Kein Dialogteil - Codex arbeitet an dieser Datei am
   Dialogteil, diese beiden Regeln stehen bewusst am Dateiende, weit weg
   davon.

   §10.1 (docs/ui-baukasten-standard.md:743-770, gemessen am Postfach-
   Zweispalter): Tablers eigenes .sticky-top setzt schon position:sticky;
   top:0 auf JEDER Breite (tabler.min.css, kein @media noetig - anders als
   .sticky-lg-top, das erst ab 992px existiert; deshalb .sticky-top im
   Markup verwenden, nie .sticky-lg-top). Was fehlt, ist eine Hoehe: ohne
   Bindung wird eine Karte mit vielen Eintraegen hoeher als das Fenster und
   klebt dann nicht mehr.

   ALLGEMEIN AUF .sticky-top GEBUNDEN, NICHT AUF .card.sticky-top: die
   Postfach-Messung nennt eine einzelne Karte mit einer langen, rollenden
   Liste. admin_offene_posten.html (erster Bau dieser Bindung) hat eine
   andere Form - die Seitenspalte ist ein gestapeltes Buendel MEHRERER
   kleiner Karten, keine einzelne Liste. Eine Bindung auf .card.sticky-top
   allein haette dort nichts zu binden (der klebende Wrapper ist keine
   .card). Die zweite Regel unten (> .list-group) betrifft nur ein direktes
   .list-group-Kind - fuer admin_offene_posten.html greift sie nicht (kein
   .list-group in der Seitenspalte), fuer einen kuenftigen Postfach-Umbau
   greift sie unveraendert.

   GILT FUER JEDE .sticky-top (Master, 14.09.2026: 0 Verwendungen im
   Bestand ausserhalb von vendor/docs, die Regel trifft heute nichts
   Bestehendes). Wer eine klebende Kopf- oder Tabellenleiste baut, braucht
   eine EIGENE Klasse, nicht .sticky-top - top:1rem und max-height waeren
   dort vermutlich falsch (eine Kopfleiste klebt typischerweise bei top:0
   ohne Hoehenbegrenzung). Bei der ersten solchen Verwendung wird diese
   Regel auf den Zweispalter-Fall eingegrenzt (z. B. auf eine Klasse wie
   .zweispalter-neben statt des allgemeinen Tabler-Namens). */
.sticky-top {
  top: 1rem; max-height: calc(100dvh - 2rem);
  display: flex; flex-direction: column; gap: var(--sp-4);
}
.sticky-top > .list-group { flex: 1 1 auto; min-height: 0; overflow-y: auto; }

/* ══ .fb-ergebnis / .fb-summe / .fb-summe-tx / .fb-tuen AUF TABLER-SEITEN ═══
   14.09.2026, Master zugewiesen. Gemessene Ablehnung, dieselbe Machart wie
   .fb-hinweis: Tabler hat keine Ergebniszeile (fuehrende Zahl + Nebentext +
   Aktionen) und kein tabular-nums (0 Treffer in tabler.min.css) - Tokens
   uebersetzt, keine Strukturaenderung. Bis zu dieser Bindung stand eine
   Verbatim-Kopie in admin_rechnungen.html (PR #1002); sie entfaellt mit
   diesem Commit (siehe dort).

   .fb-summe: Designvertrag §5.4 "Kennzahl" nennt die Zielwerte bereits
   explizit (24/32/600, tabular-nums; Bestand 28px/800 "beides nicht in
   Tabler") - direkt aus Tablers eigenen Ueberschriften-Tokens uebernommen
   (--tblr-font-size-h1/--tblr-line-height-h1/--tblr-font-weight-headings
   = 1.5rem/2rem/600), keine eigene Zahl erfunden.
   .fb-summe-tx / .fb-summe-tx b: --ink2/--type-ui-weight/--text-base auf
   dieselbe Weise wie bei .fb-switch (oben) uebersetzt: --tblr-secondary /
   --tblr-font-weight-medium / --tblr-body-font-size.
   .fb-ergebnis / .fb-tuen: reine Layoutregeln (flex/gap/border), keine
   Farb- oder Schriftrolle - --sp-4/--sp-2 bleiben woertlich (fitbox-
   tokens.css ist weiter geladen), nur --border wird zu Tablers eigenem
   --tblr-border-color, damit die Trennlinie zu Tablers eigenen Kartenrahmen
   passt. */
.fb-ergebnis {
  display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap;
  padding-top: var(--sp-4); margin-top: var(--sp-4); border-top: 1px solid var(--tblr-border-color);
}
.fb-ergebnis.oben { padding-top: 0; margin-top: 0; border-top: none; }
.fb-summe {
  font-family: var(--tblr-body-font-family); font-size: var(--tblr-font-size-h1);
  line-height: var(--tblr-line-height-h1); font-weight: var(--tblr-font-weight-headings);
  letter-spacing: -.02em; font-variant-numeric: tabular-nums;
}
.fb-summe-tx { font-size: var(--tblr-body-font-size); color: var(--tblr-secondary); margin-top: 2px; }
.fb-summe-tx b { color: var(--tblr-secondary); font-weight: var(--tblr-font-weight-medium); font-variant-numeric: tabular-nums; }
.fb-tuen { margin-left: auto; display: flex; gap: var(--sp-2); flex-wrap: wrap; }

/* ══ .fb-betrag AUF TABLER-SEITEN ═══════════════════════════════════════════
   14.09.2026, Master zugewiesen: binden statt durch <strong> ersetzen (wie
   zunaechst in PR #1002 an admin_rechnungen.html gebaut, dort mit diesem
   Commit auf .fb-betrag zurueckgestellt) - Betraege brauchen tabular-nums,
   damit Geldspalten untereinanderstehen, Tabler hat dafuer keine Fertigform.
   Basisform 700 ist ein erlaubtes Tabler-Gewicht (Designvertrag Abschnitt
   5.3), keine Uebersetzung noetig. .fb-betrag.aus (gedaempfter Betrag, z. B.
   "noch nicht faellig"): Wortlaut von Master vorgegeben. */
.fb-betrag { font-size: var(--tblr-body-font-size); font-weight: var(--tblr-font-weight-bold); font-variant-numeric: tabular-nums; }
.fb-betrag.aus { color: var(--tblr-secondary); font-weight: normal; }

/* ══ TOUCHPROFIL 44PX AUF TABLER-SEITEN (BINDUNGS-SLOT 2) ══════════════════
   14.09.2026. Der Baukasten fuehrt die 44px-Treffhoehe global in
   fitbox-components.css (Abschnitt "Touchprofil", Media-Bedingung
   max-width 820px ODER grobe Zeigereingabe). Eine Tabler-Seite laedt diese
   Datei nicht mehr - die Grenze war auf allen data-tabler-Seiten still weg.
   GEMESSEN vorher bei 440px, admin_aenderungsprotokoll: Knoepfe 34px, der
   kleine Zurueck-Knopf der Blaetterleiste 30px, Felder 34px, Reiter 36,7px;
   studio/mtt: Segmentschalter 36px.

   DIESELBE MEDIA-BEDINGUNG wie im Baukasten, damit Telefon, Tablet und
   Touch-Laptop dieselbe Antwort bekommen. Bei 1280px mit Maus greift sie
   nicht (vorher=nachher gemessen).

   BEWUSST OHNE !important, anders als der Baukasten: dort sollte die Grenze
   alte Seitendialekte schlagen. Hier wuerde !important auch GROESSERE
   Mindesthoehen der Seite ueberschreiben. :where() haelt die Spezifitaet bei
   Tablers eigener (eine Klasse beim Knopf und Feld, zwei beim Reiter); die
   Datei laedt nach Tabler und gewinnt deshalb gleichrangig. Mehrzeilige
   Felder (textarea mit Elementtyp im Tabler-Selektor) behalten Tablers
   eigene Mindesthoehe - sie sind ohnehin hoeher.

   Die Schliessen-Kreuzchen der Dialoge sind in der nativen Schale schon
   44px (Abschnitt NATIVER TABLER-DIALOG) und in Tablers Kopf 56px; die
   Regel unten deckt die uebrigen Fundorte ab. border-box, weil Tablers
   Kreuzchen sein Innenpolster sonst AUF die 44px aufschlaegt.

   .ctrl ERGAENZT (Paket AM, 15.09.2026, Messung
   PAKET-AL-FORMULARFELD-UMFANG-15-09.md): .ctrl (fb-dialog-contract.js'
   uebersetztes Eingabefeld, siehe Formularteile-Block oben) stand nicht in
   dieser Liste und blieb bei Touchbreite auf 40px statt auf 44px zu
   springen - 4px unter der Grenze, auf allen 16 .ctrl-Seiten. .field NICHT
   ergaenzt: es ist der Flex-Wrapper um Label+Feld (display:flex;
   flex-direction:column), kein Feldtraeger mit eigener Bedienhoehe - seine
   Hoehe ergibt sich aus den Kindern. */
@media (max-width: 820px), (pointer: coarse) {
  :where([data-tabler]) :is(.btn, .form-control, .form-select, .ctrl),
  :where([data-tabler]) :is(.nav-tabs, .nav-segmented) .nav-link {
    min-height: 44px;
  }
  :where([data-tabler]) .btn-icon { min-width: 44px; }
  :where([data-tabler]) .btn-close { box-sizing: border-box; min-width: 44px; min-height: 44px; }
}

/* ══ REITER UND SEGMENTSCHALTER IM DUNKELN (BINDUNGS-SLOT 2) ═══════════════
   14.09.2026, gemessen aus der gerenderten Kaskade (Canvas-Aufloesung der
   light-dark()-Werte, nicht aus dem Token-Text).

   REITER (.nav-tabs): der gemeldete Befund "aktiver Reiter bleibt im Dunkeln
   hell" liess sich NICHT nachstellen - auf fuenf Seiten (vertraege, mtt,
   kurse, betreuung, aenderungsprotokoll) und eingebettet in der Huelle hat
   der aktive Reiter dunkel die Seitenflaeche #111827, Text 14,33:1. Wer den
   Token per getPropertyValue liest, sieht allerdings die unaufgeloeste
   light-dark()-Kette, deren ERSTER Wert hell ist - vermutlich die Quelle.
   ECHT dagegen: der INAKTIVE Reiter. Tabler setzt die Linkfarbe fest auf
   Grau 500, im Hellen und Dunklen gleich; dunkel gemessen 3,67:1 auf der
   Seitenflaeche und 3,04:1 in einer Karte. --tblr-secondary ist dieselbe
   Farbe im Hellen (hell also unveraendert) und hellt im Dunkeln auf Grau 400
   auf.

   SEGMENTSCHALTER (.nav-segmented): Wanne und aktives Segment waren im
   Dunkeln dieselbe Flaeche (rgb 31,41,55, Kontrast 1,00:1), unterscheidbar
   nur ueber Rahmen und Schrift. Tabler legt die Wanne auf die dritte
   Flaechenstufe; die ist hell eine Stufe unter der Karte, dunkel aber gleich
   der Karte. Die zweite Flaechenstufe hat im Hellen denselben Wert wie die
   dritte (hell unveraendert) und liegt im Dunkeln unter der Karte - dasselbe
   Verhaeltnis "Wanne tiefer als aktives Segment" wie im Hellen. */
:where([data-tabler]) .nav-tabs { --tblr-nav-link-color: var(--tblr-secondary); }
:where([data-tabler]) .nav-segmented { --tblr-nav-bg: var(--tblr-bg-surface-secondary); }

/* ══ SCHRITTLEISTE, SCHLIESSEN-KREUZ, WARN- UND GEFAHRKNOEPFE, STAPELKOERPER
   IN DIALOGEN (BINDUNGS-SLOT 3) ═════════════════════════════════════════════
   14.09.2026. Alle Zahlen aus der gerenderten Kaskade: headless Chrome,
   getComputedStyle, Farben per Canvas in sRGB aufgeloest, Deckkraft des
   Elements eingerechnet, Text gegen die tatsaechlich darunterliegende
   Flaeche. Hell und dunkel, 375/440/1280 px; Dialoge zusaetzlich 500x400 und
   700x380. Vorher = origin/main b531871d2, Probeseite mit echtem CSS plus
   echte Seiten mit synthetischer Sitzung, externes Netz gesperrt.
   Jede Regel haengt an data-tabler (Gegenprobe: dieselbe Probeseite ohne das
   Attribut misst vorher=nachher identisch) bzw. an den Dialog-Stapelkoerpern.

   SCHRITTLEISTE (.steps). Tabler faerbt noch nicht erreichte Schritte mit
   --tblr-disabled-color (Koerpertext zu 40 % deckend) und legt Punkt und
   Verbindungslinie auf die Linienfarbe. Gemessen vorher, Karte / Seite:
   Beschriftung 2,11 / 2,09:1 hell, 3,15 / 3,30:1 dunkel; Punkt und Linie
   1,24 / 1,18:1 hell, 1,42 / 1,72:1 dunkel. --tblr-secondary ist dieselbe
   Tonstufe, die Slot 2 schon den inaktiven Reitern gibt. Nachher Beschriftung,
   Punkt und Linie gleich: 4,83 / 4,63:1 hell, 5,78 / 6,99:1 dunkel. Erreichte
   Schritte (Markengruen) unveraendert: 4,62 / 4,42:1 hell, 3,17 / 3,84:1
   dunkel.
   Mit Zaehler (.steps-counter): die weisse Ziffer im offenen Punkt stand hell
   bei 1,24:1; auf dem neuen grauen Punkt waere sie dunkel auf 2,54:1 gefallen
   (vorher 10,31:1). Darum nimmt sie dort die Kartenflaeche als Farbe: hell
   weiterhin Weiss (4,83:1), dunkel die dunkle Flaeche (5,78:1).
   BEKANNT: offener und erreichter Punkt unterscheiden sich jetzt vor allem im
   Farbton, kaum in der Helligkeit (Grau 4,83 gegen Gruen 4,62:1 auf Weiss).
   Den Zustand traegt weiter die Beschriftung: erreichte Schritte in
   Koerpertext (10,31:1), offene in Grau (4,83:1), der aktive fett. */
:where([data-tabler]) .steps { --tblr-steps-inactive-color: var(--tblr-secondary); }
:where([data-tabler]) .step-item.active ~ .step-item { color: var(--tblr-secondary); }
:where([data-tabler]) .steps-counter .step-item.active ~ .step-item::before { color: var(--tblr-bg-surface); }

/* SCHLIESSEN-KREUZ (.btn-close). Tabler zeichnet das Symbol in Textfarbe bei
   40 % Deckkraft. Gemessen in Ruhe vorher: 2,11:1 auf der Karte und im Kopf
   eines nativen Dialogs (admin_franchise_partnernutzung, detailDialog), 2,09:1
   auf der Seitenflaeche; dunkel 3,16 / 3,31:1. Nachher bei 70 %: 4,33 /
   4,23:1 hell, 6,59 / 7,52:1 dunkel - grafisch ueber 3:1 auch auf leicht
   getoenten Flaechen. Hover hebt jetzt auf volle Deckkraft (vorher 75 %,
   also kaum mehr als die neue Ruhe): 10,31:1 hell, 11,86:1 dunkel. Fokus
   bleibt Tablers eigener Zustand (volle Deckkraft plus Markenring).
   Trefferflaeche unveraendert 44x44 px (Slot 2 und Dialogkopf).
   NICHT behoben: in einer Hinweisbox mit Schliessen-Kreuz faerbt Tabler das
   Symbol in der Hinweisfarbe - Info-Blau auf Hellblau erreicht selbst bei
   voller Deckkraft nur 2,83:1 (Ruhe nachher 2,03:1). Auf keiner Seite im
   Einsatz; ein eigener Posten, falls die Bauform kommt. */
:where([data-tabler]) .btn-close {
  --tblr-btn-close-opacity: .7;
  --tblr-btn-close-hover-opacity: 1;
}

/* UMRISS-WARNKNOPF (.btn-outline-warning, auch die Zwei-Klassen-Schreibweise).
   Tabler setzt Text und Rahmen auf das volle Warn-Gelb: 2,13:1 auf Weiss.
   Hover und Aktiv fuellen die Flaeche und schreiben Off-Weiss darauf: 2,04:1
   hell wie dunkel. Im Einsatz auf admin_kassenprodukte (Schnellzugriff-Schalter
   im Wechsel mit dem gefuellten Warnknopf) und admin_zeiterfassung.
   Nachher dieselbe Tintenmischung wie die weiche Warnplakette weiter oben
   (60 % Gelb in Schwarz hell, 65 % in Weiss dunkel), Text und Rahmen gleich:
   5,42:1 hell, 8,95:1 dunkel (vorher 6,88:1). Hover/Aktiv schwarz auf Gelb wie
   der gefuellte Warnknopf aus Slot 2: 9,84:1. Gesperrt (Tabler-Deckkraft 40 %,
   von der Kontrastpflicht ausgenommen) 1,36 wird 1,79:1 hell, 2,22 wird
   2,62:1 dunkel. Zwei Selektoren statt einer Liste in einem :is(), damit die
   Ein-Klassen-Schreibweise ihre Spezifitaet von einer Klasse behaelt. */
:where([data-tabler]) .btn-outline-warning,
:where([data-tabler]) .btn-outline.btn-warning {
  --tblr-btn-color: color-mix(in srgb, var(--tblr-warning) 60%, black);
  --tblr-btn-border-color: color-mix(in srgb, var(--tblr-warning) 60%, black);
  --tblr-btn-hover-color: #000;
  --tblr-btn-active-color: #000;
  --tblr-btn-disabled-color: color-mix(in srgb, var(--tblr-warning) 60%, black);
  --tblr-btn-disabled-border-color: color-mix(in srgb, var(--tblr-warning) 60%, black);
}
:where([data-tabler][data-theme="dark"]) .btn-outline-warning,
:where([data-tabler][data-theme="dark"]) .btn-outline.btn-warning {
  --tblr-btn-color: color-mix(in srgb, var(--tblr-warning) 65%, white);
  --tblr-btn-border-color: color-mix(in srgb, var(--tblr-warning) 65%, white);
  --tblr-btn-disabled-color: color-mix(in srgb, var(--tblr-warning) 65%, white);
  --tblr-btn-disabled-border-color: color-mix(in srgb, var(--tblr-warning) 65%, white);
}
/* GEFUELLTE WARNPLAKETTE (.badge mit .bg-warning, ohne eigene Textklasse).
   Tablers Plakette schreibt in --tblr-secondary; auf Warn-Gelb 2,27:1 hell
   und 1,19:1 dunkel. Nachher schwarz wie der Warnknopf: 9,84:1 in beiden
   Modi. Die weiche Plakette (bg-warning-lt, Hausstand) bleibt unberuehrt. */
:where([data-tabler]) .badge.bg-warning { --tblr-badge-color: #000; }

/* GEFAHRKNOPF (.btn-danger und die Baukasten-Schreibweise mit .danger).
   Loest den oben im Abschnitt zur Knopf-Variantenachse als "spaeterer Posten"
   gefuehrten Wert ein, fuer BEIDE Schreibweisen gemeinsam - es bleibt ein Rot.
   Ruhe vorher Off-Weiss (--tblr-light) auf #d63939: 4,46:1 hell wie dunkel.
   Nachher reines Weiss auf UNVERAENDERTER Flaeche: 4,66:1 - keine
   Farbverschiebung der Flaeche. Hover und Aktiv mischten Tabler bisher mit
   80 % Deckkraft; das wird auf Weiss HELLER (#de6161, 3,36:1), nur dunkel
   dunkler (#b13639, 5,81:1). Nachher deckend 90 % Rot in Schwarz (#c13333,
   entspricht Tablers eigenem deckendem Abdunklungswert): 5,54:1 in beiden
   Modi. Gesperrt 1,80 / 2,44:1 wird 1,83 / 2,51:1 (ausgenommen, 40 %).
   Umriss- und Geistform in Tablers Zwei-Klassen-Schreibweise stehen ueber
   dieser Regel und behalten ihre Werte (gemessen: Umriss-Rot unveraendert). */
:where([data-tabler]) .btn-danger,
:where([data-tabler]) .btn.danger {
  --tblr-btn-color: #fff;
  --tblr-btn-hover-color: #fff;
  --tblr-btn-active-color: #fff;
  --tblr-btn-disabled-color: #fff;
  --tblr-btn-hover-bg: color-mix(in srgb, var(--tblr-danger) 90%, black);
  --tblr-btn-active-bg: color-mix(in srgb, var(--tblr-danger) 90%, black);
}

/* STAPELKOERPER IN DIALOGEN: KINDER SCHRUMPFEN NICHT. Beide Koerper sind
   Flex-Spalten mit eigenem Scrollen (.fb-dlg-body oben im Dialogabschnitt,
   der native Dialogkoerper ueber .ui-stack). Ein Kind, das selbst ein
   Scrollbehaelter ist (Querlauf-Huelle einer Tabelle, Liste mit
   Hoechstmass, Kennzahlband mit overflow:hidden), hat als Flex-Kind die
   Mindesthoehe null und wurde gestaucht, statt dass der Koerper scrollt.
   GEMESSEN vorher, jeweils Fuss im Fenster:
     admin_franchise_partnernutzung detailDialog, zwei Tabellen (244/103 px
       Inhalt): 16 und 6,7 px bei 375x800, 0 und 0 px bei 500x400 und
       700x380, sogar 202,6 und 85,3 px bei 1280x800. Nachher ueberall
       244,3 und 102,8 px, der Koerper scrollt.
     admin_tablets deviceDialog (.fb-dlg-body OHNE .ui-stack): Kennzahlband
       und Faktenliste 0 und 2 px bei 500x400/700x380, bei 375x800 51,9 und
       67,5 statt 164,4 und 209,7 px - Status, Eingerichtet und Zuletzt aktiv
       waren abgeschnitten. Nachher volle Hoehe, Koerper 478/733 px.
     admin_franchise_rueckkanal threadDialog (acht Platzhalter-Nachrichten):
       Nachrichtenliste 0 px bei 500x400/700x380, nachher 192 bzw. 182 px mit
       eigenem Scrollen bis zu ihrem Hoechstmass.
     admin_pt_email_vorlagen templateDialog: Kontrollkaestchen-Zeilen 17,5
       statt 21 px bei 375 und 700 px Breite.
   Sweep ueber 31 Tabler-Seiten, 89 Dialoge bei 700x380 (ohne Daten): Hoehen
   aendern sich nur an diesen Stellen; alle uebrigen Koerper messen
   vorher=nachher.
   BEWUSST ALLE Kinder, nicht nur Tabellen: gestaucht wird jedes Kind mit
   Scroll-Rolle, egal wie es heisst - der Sweep fand neben der Tabelle vier
   weitere Namen. Nicht-scrollende Kinder sind ohnehin nie kleiner als ihr
   Inhalt, fuer sie aendert sich nichts. Spezifitaet null: eine Seite, die ein
   Kind ausdruecklich schrumpfen oder fuellen laesst, gewinnt weiter. */
:where(.fo-modal-native .modal-body.ui-stack, .fb-dlg-body) > * {
  flex-shrink: 0;
}

/* ══ .STATUS-DOT MIT WARNTON IM HELLMODUS UNTER 3:1 (BINDUNGS-SLOT 4) ═══════
   15.09.2026. WCAG 1.4.11 (nicht-Text, Schwelle 3:1), nicht die Textschwelle
   4,5:1 - darum eigene, sanftere Mischung als die -lt-Klassen oben (60/65%).
   Gemessen: echtes headless Chrome, injizierte Testelemente in der echten
   Kaskade zweier Kontexte (Kopfzeilen-Widget auf Seitenflaeche, Karte/Liste/
   Tabelle auf reinem Weiss), WCAG-Kontrastformel. Positivkontrolle: dieselbe
   Messung traf unabhaengig exakt die Zahlen einer Vormessung vom Vortag.

   bg-warning/status-yellow (identischer Grundton --tblr-warning=--tblr-yellow,
   #f59f00): 2,04:1 Kopfzeile, 2,13:1 Karte - in JEDEM gemessenen Kontext
   unter 3:1, keine Ausnahme.
   status-orange (--tblr-orange, #f76707): 2,91:1 Kopfzeile, 3,04:1
   Karte/Liste/Tabelle - im zweiten Kontext technisch schon ueber der
   Schwelle, aber mit 0,04 Abstand keine belastbare Marge (Rundung/
   Kantenglaettung koennten sie kippen). Regel deshalb bewusst fuer BEIDE
   Kontexte ohne Ausnahme - derselbe Punkt soll nicht je nach Vorlage
   unterschiedlich aussehen.

   27 Seiten betroffen (18 mit dem Ton direkt im Markup/Skript, 9 nur ueber
   eine Ternary- oder Nachschlagetabellen-Variable erreichbar - der Selektor
   greift in beiden Faellen gleich, weil er auf die gerenderte Klasse zielt,
   nicht auf den Quelltext). Gegen Verwechslung mit Rot/Gruen gerechnet:
   color-mix mit Schwarz dreht den Farbton nicht, der Abstand zu Rot
   (24 Grad) bleibt unveraendert, nur die Helligkeit sinkt - keine der 27
   Seiten mischt beide Warntoene im selben Ampelsystem.
   Dunkelmodus unveraendert (8,31/5,83:1, beide bereits ueber der Schwelle) -
   bewusst KEIN dark-Override.

   Nachher (80%/90% Schwarzmischung, Kopfzeilen-Kontext, schlechtester Fall):
   Warnung/Gelb rgb(196,127,0) 3,14:1, Orange rgb(222,93,6) 3,54:1 - beide mit
   spuerbarem Sicherheitsabstand statt Rasierklingenschaerfe.

   ZWEI VERSCHIEDENE MECHANISMEN UNTER DEMSELBEN .status-dot, in diesem
   Bestand gemischt eingesetzt: Tablers Komponentensystem
   (.status-yellow/.status-orange setzen nur die Custom Property
   --tblr-status-color, .status-dot liest sie ohne !important) UND Tablers
   Utility-Klasse .bg-warning (setzt background-color direkt, MIT
   !important wie schon .bg-warning-lt oben). Ohne das eigene !important bei
   .bg-warning haette die Regel dort lautlos nichts bewirkt - im Browser
   gemessen, nicht angenommen.

   --tblr-warning/--tblr-orange sind FESTE Hexwerte, nicht light-dark() -
   dieselbe Mischformel traf darum zuerst auch den Dunkelmodus (dort
   unnoetig dunkler, 5,4/4,79:1 statt der schon ausreichenden 8,31/5,83:1).
   Eigener dark-Override darunter setzt fuer [data-theme="dark"] auf den
   unveraenderten Tabler-Grundwert zurueck - im Browser gemessen, nicht nur
   angenommen. */
:where([data-tabler]) .status-dot.bg-warning {
  background-color: color-mix(in srgb, var(--tblr-warning) 80%, black) !important;
}
:where([data-tabler]) .status-dot.status-yellow {
  background-color: color-mix(in srgb, var(--tblr-warning) 80%, black);
}
:where([data-tabler]) .status-dot.status-orange {
  background-color: color-mix(in srgb, var(--tblr-orange) 90%, black);
}
:where([data-tabler][data-theme="dark"]) .status-dot.bg-warning {
  background-color: var(--tblr-warning) !important;
}
:where([data-tabler][data-theme="dark"]) .status-dot.status-yellow {
  background-color: var(--tblr-warning);
}
:where([data-tabler][data-theme="dark"]) .status-dot.status-orange {
  background-color: var(--tblr-orange);
}

/* ══ SEITENUEBERSCHRIFT: HAUSABWEICHUNG NACHGEZOGEN ═══════════════════════
   15.09.2026, Paket AM (fuenfter Punkt). docs/ui-baukasten-standard.md:392
   entscheidet ausdruecklich: "Farbe ist ausdruecklich die Ueberschriften-
   farbe ... Tabler setzt color: inherit, die Pilotueberschrift steht
   dadurch in Fliesstextfarbe #374151, gemessen." Diese Entscheidung war nie
   gebaut - tabler-bindung.css erwaehnte --tblr-heading-color bis heute
   nicht. .card-title und h1-h6 nutzen den Token schon (tabler.css, ohne
   Zutun dieser Datei); .page-title war die einzige Ausnahme mit dem
   ungebundenen color:inherit aus Tablers eigener Regel.
   GEMESSEN: 97 Seiten, 102 Vorkommen, ausnahmslos in einem .page-header.
   Hell: #374151 -> #111827, Kontrast 9,86:1 -> 16,98:1 (Verbesserung).
   Dunkel: KEINE Aenderung - Tabler setzt --tblr-heading-color innerhalb
   [data-bs-theme=dark]/[data-theme=dark] selbst auf inherit (tabler.css),
   die Kaskade landet also dunkel wieder beim genau gleichen inherit-Pfad
   wie vorher. Im Browser an mindestens drei der 97 Regelfaelle vorher/
   nachher bestaetigt, hell und dunkel - dunkel ausnahmslos unveraendert.

   AUFLAGE: superadmin/index.html (eigenes Farbsystem, "eigener Wortschatz",
   docs/tabler-gesamtmigration-roadmap.md:955) zeigt .page-title heute in
   --control-ink (#0f172a), und zwar GENAU WEIL .page-title bisher inherit
   war (html,body{color:var(--control-ink)} in control.css:12, keine eigene
   .page-title-Farbregel). Diese zentrale Zeile kappt die Vererbung.
   Gegenbindung dafuer in superadmin/control.css (nicht hier - dieselbe
   Form wie bei angebot_extern: die zentrale Regel sagt den Normalfall, die
   Seite mit eigener Linie sagt ihren eigenen). control.css laedt NACH
   dieser Datei (superadmin/index.html:22-23) - gleiche Selektorspezifitaet,
   spaetere Ladeposition gewinnt, keine hoehere Spezifitaet noetig. Im
   Browser bestaetigt: computed color auf superadmin/index.html vor und
   nach dieser Zeile IDENTISCH (#0f172a), solange die Gegenbindung steht. */
.page-title {
  color: var(--tblr-heading-color);
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
}
