/**
 * JP FM — plugin-eigene Feinheiten (letzte Schicht).
 *
 * Diese Datei gehört dem Plugin, nicht dem Design-System-Paket: sie wird von
 * keinem Paket geliefert und von keinem überschrieben (siehe
 * FM_Core_Frontend_Assets::enqueue_design_system(), der Enqueue prüft nur, ob
 * die Datei existiert). Hier gehört alles hin, was dauerhaft lokal bleiben soll.
 *
 * Reihenfolge der Schichten:
 * tailadmin → fm-tokens → fm-app → …Modul-CSS… → fm-design-system
 * → fm-design-system-extensions → **fm-overrides**
 *
 * @package Facility Management
 */

/* ============================================================================
   Kompatibilitäts-Aliase (Punkt 8 – Modul-Stylesheet-Bereinigung)

   Mehrere Modul-Layer lesen Farben über Alias-Namen (--fm-card-bg, --fm-color-*,
   --fm-hover, --fm-title, --fm-warning-bg …), die nirgends definiert waren – dort
   griff bisher ein hart kodierter Fallback (u. a. weiße Flächen #fff), der den
   Dunkelmodus (Punkt 7) durchbrach. Diese Zeilen mappen die Aliase auf die
   kanonischen Tokens; damit erben die betroffenen Module Marke UND Dunkelmodus,
   ohne dass jede Moduldatei einzeln angefasst werden muss (gleiche Strategie wie
   der Alias-Block in fm-tokens.css). Nicht gemappt: --fm-surface-sunken (bewusst
   `transparent`) und Layout-Aliase (--fm-letter-*, --fm-help-tour-inset).
   ============================================================================ */
#fm-app {
	--fm-card-bg: var(--fm-surface);
	--fm-title: var(--fm-heading);
	--fm-hover: var(--fm-surface-2);
	--fm-warning-bg: var(--fm-warning-soft);
	--fm-mail-toast-accent: var(--fm-primary);

	/* Parallele --fm-color-*-Namenskonvention einzelner Layer. */
	--fm-color-surface: var(--fm-surface);
	--fm-color-surface-muted: var(--fm-surface-2);
	--fm-color-border: var(--fm-border);
	--fm-color-text-muted: var(--fm-body);
	--fm-color-primary: var(--fm-primary);
	--fm-color-primary-soft: var(--fm-primary-soft);
	--fm-color-danger: var(--fm-danger);
	--fm-color-danger-soft: var(--fm-danger-soft);
	--fm-color-success: var(--fm-success);
	--fm-color-warning: var(--fm-warning);
}

/* --------------------------------------------------------------------------
 * `fm-row-actions`: ein Klassenname, zwei verschiedene Bauteile
 * --------------------------------------------------------------------------
 *
 * `fm-notifications.css` definiert `.fm-row-actions` als **Knopfreihe**
 * (`display:flex; align-items:center`) — und genau so benutzen es die sechs
 * Templates des Moduls `notifications`. Die Datei wird global geladen (die
 * Glocke in der Topbar steht auf jeder App-Seite), die Regel gilt also überall.
 *
 * `contracts/templates/detail-review.php` verwendet denselben Namen für ein
 * **Aufklapp-Element**: `<details class="fm-row-actions">` mit `<summary>` und
 * `.fm-row-actions-body`. Durch das geerbte `display:flex` werden
 * Zusammenfassung und Inhalt zu Flex-Geschwistern — das aufgeklappte Prüf-Panel
 * stand rechts NEBEN dem Knopf statt darunter und war auf seine `min-width`
 * von 16rem gequetscht (gemessen bei 1280 px: Panel links 619 bei Knopfende
 * 614, Breite 256 statt 613; der Knopf mittig daneben).
 *
 * Der Element-Selektor trifft nur den `<details>`-Fall; die Knopfreihen der
 * Benachrichtigungen bleiben unverändert flex. Spezifität (1,1,1) schlägt die
 * (0,1,0) aus `fm-notifications.css`.
 *
 * Sauber wäre, eines der beiden Bauteile umzubenennen — dann kann diese Regel
 * ersatzlos entfallen.
 */
#fm-app details.fm-row-actions {
	display: block;
}

/* --------------------------------------------------------------------------
 * `screen-reader-text`: die WordPress-Klasse ohne WordPress-Stylesheet
 * --------------------------------------------------------------------------
 *
 * Im Plugin gilt `fm-sr-only` (definiert in tailadmin.css:75). An acht Stellen
 * steht stattdessen die WordPress-Klasse `screen-reader-text` — und die trägt
 * in dieser Umgebung KEINE Regel:
 *
 *   1. kein Stylesheet des Plugins definiert sie;
 *   2. das Theme `fm-blank` entfernt die Frontend-Stylesheets, die sie sonst
 *      mitbrächten (`wp-block-library`, `wp-block-library-theme`,
 *      `global-styles`, `classic-theme-styles`);
 *   3. `admin-bar.css` würde sie mitbringen, aber `FM_Core_Login` blendet die
 *      Adminleiste per Vorgabe aus (`show_admin_bar` → false).
 *
 * Der nur für Screenreader gedachte Text stand damit sichtbar auf der Seite.
 * Diese Regel spiegelt `fm-sr-only` eins zu eins.
 *
 * Betroffen: notifications/overview.php:160,
 * notifications/notification-settings.php:65,
 * purchasing/supplier-detail.php:352, time-accounts/cockpit.php:56,63,159,238.
 * Die achte Stelle liegt in der PWA, die diese Datei nicht lädt — dafür steht
 * dieselbe Regel in fm-pwa.css.
 *
 * Ziehen die Templates auf `fm-sr-only` nach, kann die Regel ersatzlos weg.
 */
#fm-app .screen-reader-text {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

/* --------------------------------------------------------------------------
 * Zwei Merkmale, die die Systemschicht benutzt, aber niemand definiert
 * --------------------------------------------------------------------------
 *
 * `fm-design-system-extensions.css` greift dreimal auf `--fm-fg` und einmal auf
 * `--fm-fs-base` zu — **ohne Rückfallwert**. Beide Merkmale sind in keiner
 * CSS-Datei des Plugins definiert. Ein `var()` ohne Definition und ohne
 * Rückfall macht die ganze Deklaration ungültig; die Eigenschaft fällt auf
 * `unset` zurück, bei `color` also auf den geerbten Wert.
 *
 * Gemessen (1280 px, Elternfarbe absichtlich rot gesetzt):
 *
 *   .fm-readonly-value  ohne Erweiterungsschicht: rgb(13,27,42)  = --fm-heading
 *                       mit  Erweiterungsschicht: rgb(255,0,0)   = geerbt
 *
 * Das ist eine echte Verschlechterung: `fm-design-system.css:1132` setzt für
 * dieses Element `color: var(--fm-heading)`, und die spätere, ungültige
 * Deklaration hebt sie auf. Bei `.fm-detail-subtitle` (Runde 54) und `.fm-label`
 * (Runde 82) gibt es keine Basisregel — dort bleibt die Farbangabe schlicht
 * wirkungslos.
 *
 * Statt drei Paketregeln lokal zu ändern (das Paket ersetzt die Datei jede
 * Runde), werden hier die fehlenden Merkmale nachgereicht. Custom Properties
 * werden am Element aufgelöst, nicht in Dateireihenfolge — die Definition wirkt
 * deshalb auch auf die früher geladene Erweiterungsschicht.
 *
 * `--fm-fg` erhält `--fm-heading`: alle drei Fundstellen sind kräftiger Text
 * (Zwischenüberschrift, Feldbeschriftung, Antwortwert), und für
 * `.fm-readonly-value` stellt es genau den vorherigen Zustand wieder her.
 * `--fm-fs-base` erhält `--fm-fs-md` (1 rem), die Grundschriftgröße der Skala.
 *
 * Definiert das Paket die Merkmale eines Tages selbst, kann dieser Block weg.
 */
#fm-app {
	--fm-fg: var(--fm-heading);
	--fm-fs-base: var(--fm-fs-md);
}

/* --------------------------------------------------------------------------
 * `fm-field-error` neben dem `hidden`-Attribut  (aus Runde 153 hierher gezogen)
 * --------------------------------------------------------------------------
 *
 * `#fm-app .fm-field-error` setzt in `fm-design-system.css` `display: flex`. Eine
 * Autorenregel schlägt die Browser-Vorgabe `[hidden] { display: none }` unabhängig
 * von der Spezifität — ein Element mit der Klasse UND dem `hidden`-Attribut bleibt
 * also sichtbar. Getroffen hat es die Überschreitungswarnung der Splittbuchung
 * (`data-fm-split-warn`), die `fm-banking.js` allein über `warnEl.hidden` schaltet:
 * im Browser gemessen `display: flex` und 111 px Höhe trotz `hidden`, die Warnung
 * stand also dauerhaft da. Dieselbe Bremse liegt bereits als Einzelregel in
 * `fm-mail-center.css` (Zeilen 336, 845) — hier an der Klasse selbst, damit die
 * Falle nicht bei jeder weiteren Verwendung neu zuschlägt.
 *
 * Stand bis 29.07.2026 in `fm-design-system-extensions.css`. Die Regel gehört
 * dem Plugin, nicht dem Paket: der Paketfassung fehlt sie, ein wörtlich
 * übernommener Paketstand hätte sie also gelöscht und den Fehler zurückgebracht.
 */
#fm-app .fm-field-error[hidden] {
	display: none;
}

/* --------------------------------------------------------------------------
 * `fm-code-inline`: lange technische Werte dürfen umbrechen
 * --------------------------------------------------------------------------
 *
 * Die Klasse kam mit den Runden 211–218 und hatte bis hierhin keine einzige
 * Regel. Sie ist aber NICHT wirkungslos gewesen: sie sitzt in allen acht
 * Fundstellen auf einem `<code>`-Element, und `#fm-app code`
 * (`fm-design-system.css:70`) gestaltet das bereits — Monospace, getönter
 * Hintergrund, Radius. Die Klasse war also schlicht überflüssig.
 *
 * Ein echtes Problem bleibt trotzdem: die Werte dahinter sind Message-IDs,
 * Prüfsummen, SMTP-Hosts und maskierte Lizenzschlüssel — Zeichenketten ohne
 * Leerzeichen, die nirgends umbrechen dürfen und deshalb aus ihrer Zelle
 * laufen. Gemessen an einer Message-ID in einem 182 px breiten Container:
 *
 *   ohne Regel            Code-Element 350 px  →  168 px Überlauf
 *   mit overflow-wrap     Code-Element 175 px  →  bricht auf zwei Zeilen
 *
 * Genau das macht die Klasse jetzt — und nur an den Stellen, die sie tragen;
 * kurze `<code>`-Schnipsel im Fließtext bleiben unangetastet.
 */
#fm-app .fm-code-inline {
	overflow-wrap: anywhere;
}

/* --------------------------------------------------------------------------
 * `mfr-migration`: die Klassen des Assistenten, für die es keine Komponente gibt
 * --------------------------------------------------------------------------
 *
 * Das Modul wurde nie gestaltet — sein Markup trug 20 Klassen ohne jede Regel.
 * Der größere Teil ist am Template gelöst (auf bestehende Komponenten abgebildet:
 * fm-form-row→fm-field, fm-report-list→fm-detail-list, die Kacheln in
 * step-check auf components/card-kpi.php, fm-visually-hidden→fm-sr-only,
 * fm-table-scroll→fm-table-wrap). Was hier steht, hat kein Gegenstück im System:
 * es sind Bauteile, die es nur in diesem Assistenten gibt.
 *
 * Warum in fm-overrides.css und nicht in der Erweiterungsschicht: die gehört dem
 * Design-System-Paket (CLAUDE.md §7.1), und dieses Modul liefert es nicht mit.
 */

/* Quellenauswahl (Schritt 1): Karten nebeneinander, auf dem Telefon gestapelt. */
#fm-app .fm-source-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
	gap: var(--fm-s-4);
	margin: 0;
	padding: 0;
	list-style: none;
}

#fm-app .fm-source-card {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: var(--fm-s-3);
	padding: var(--fm-s-4);
	border: 1px solid var(--fm-stroke);
	border-radius: var(--fm-r-lg);
	background: var(--fm-surface);
}

#fm-app .fm-source-card.is-selected {
	border-color: var(--fm-primary-500);
	box-shadow: inset 0 0 0 1px var(--fm-primary-500);
}

#fm-app .fm-source-name {
	font-weight: 600;
	color: var(--fm-heading);
}

/* Verbindungstest (Schritt 2): je Prüfpunkt Bezeichnung, Ergebniswort, Meldung.
   fm-check-list/-item/-label liegen in der Systemschicht; hier fehlen nur das
   Ergebnis-Abzeichen, die Meldung und die drei Zustandsfarben. */
#fm-app .fm-check-badge {
	padding: 0.1rem 0.5rem;
	border-radius: 999px;
	background: var(--fm-muted-soft);
	color: var(--fm-muted-fg);
	font-size: var(--fm-fs-xs);
	font-weight: 600;
}

#fm-app .fm-check {
	flex-wrap: wrap;
}

#fm-app .fm-check-msg {
	flex: 1 1 100%;
	color: var(--fm-muted-fg);
	font-size: var(--fm-fs-sm);
}

#fm-app .fm-check-ok .fm-check-badge {
	background: var(--fm-success-soft);
	color: var(--fm-success-text);
}

/* Der dritte Zustand heisst `skip` (Pruefung nicht durchgefuehrt), nicht
   `warn` — die Zustaende kommen aus step-connection.php:88. */
#fm-app .fm-check-skip .fm-check-badge {
	background: var(--fm-warning-soft);
	color: var(--fm-warning-text);
}

#fm-app .fm-check-fail .fm-check-badge {
	background: var(--fm-danger-soft);
	color: var(--fm-danger-text);
}

/* Bereichsauswahl (Schritt 3): der Text im Kästchen ist der dehnbare Teil,
   damit die Zeile bei langen Bereichsnamen sauber umbricht. */
#fm-app .fm-checkbox-label {
	flex: 1 1 auto;
}

/* Pflichtfeldstern. Rein visuell — die Vorlesbarkeit hängt an `required` und
   `aria-required`, deshalb trägt der Stern im Markup `aria-hidden`. */
#fm-app .fm-req {
	color: var(--fm-danger-text);
	font-weight: 600;
}

/* Lauf (Schritt 6): Fortschrittstext über dem Balken, darunter je Bereich eine
   Zeile mit Bereichsname links und den drei Zahlen rechts. */
#fm-app .fm-progress-text {
	margin: 0 0 var(--fm-s-2);
	color: var(--fm-muted-fg);
	font-size: var(--fm-fs-sm);
}

#fm-app .fm-count-list {
	display: grid;
	gap: var(--fm-s-2);
	margin: var(--fm-s-4) 0 0;
	padding: 0;
	list-style: none;
}

#fm-app .fm-count-list > li {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--fm-s-2);
	padding-bottom: var(--fm-s-2);
	border-bottom: 1px solid var(--fm-stroke-soft);
}

#fm-app .fm-count-area {
	font-weight: 500;
	color: var(--fm-heading);
}

#fm-app .fm-count-nums {
	color: var(--fm-muted-fg);
	font-size: var(--fm-fs-sm);
	font-variant-numeric: tabular-nums;
}

/* Assistentenrahmen: Abstand zwischen Schrittleiste und Schrittinhalt, und die
   Beschriftung in der Schrittleiste (fm-wizard-steps/-step/-step-num sind
   definiert, -label fehlte). */
#fm-app .fm-wizard-body {
	margin-top: var(--fm-s-5);
}

#fm-app .fm-wizard-step-label {
	font-size: var(--fm-fs-sm);
	line-height: 1.3;
}

/* --------------------------------------------------------------------------
 * `fm-timeline-time`: der Zeitstempel im Verlauf
 * --------------------------------------------------------------------------
 *
 * Die Klasse sitzt im Verlaufs-Panel des Klärfalls auf dem `<span>` mit der
 * Uhrzeit, direkt vor dem `<strong>` mit der Aktion. Sie hatte keine Regel —
 * der Zeitstempel erschien damit in Größe und Farbe wie die Aktion selbst,
 * obwohl er die nachrangige Angabe ist. Das Geschwister-Element für den
 * Benutzernamen trägt dafür bereits `fm-muted`.
 *
 * Zwei Dinge stellt die Regel her:
 *   - Zurücknahme (kleiner, gedämpft) — die Aktion bleibt die Hauptinformation.
 *   - `font-variant-numeric: tabular-nums` — Ziffern gleicher Breite, damit die
 *     Aktionen untereinander an derselben Stelle beginnen. Ohne das wandert der
 *     Aktionstext je nach Uhrzeit um mehrere Pixel hin und her (proportionale
 *     Ziffern: „11:11" ist schmaler als „08:44").
 *
 * Bewusst KEINE feste Breite: die Zeitformate der Installationen unterscheiden
 * sich (12h/24h, mit und ohne Datum), eine feste Spalte würde abschneiden.
 */
#fm-app .fm-timeline-time {
	font-size: var(--fm-fs-sm);
	color: var(--fm-body);
	font-variant-numeric: tabular-nums;
	margin-right: 0.5rem;
}

/* ==========================================================================
   Auslastungsmatrix (Disposition → Auslastung): Ampel-Zellen mit Prozentbalken.
   Grün bis 80 %, gelb bis 100 %, rot darüber; grau bei nicht verfügbarer Ressource.
   ========================================================================== */
#fm-app .fm-util-matrix th.fm-util-col {
	text-align: center;
	white-space: nowrap;
	font-size: var(--fm-fs-sm);
}

#fm-app .fm-util-cell {
	text-align: center;
	vertical-align: middle;
	min-width: 60px;
}

#fm-app .fm-util-num {
	display: block;
	font-weight: 600;
	font-variant-numeric: tabular-nums;
}

#fm-app .fm-util-bar-track {
	height: 4px;
	margin-top: 4px;
	border-radius: 2px;
	background: var(--fm-muted-bg);
	overflow: hidden;
}

#fm-app .fm-util-bar {
	display: block;
	height: 100%;
	background: currentColor;
}

#fm-app .fm-util-ok {
	color: var(--fm-success);
}

#fm-app .fm-util-high {
	color: var(--fm-warning);
}

#fm-app .fm-util-over {
	color: var(--fm-danger);
}

#fm-app .fm-util-off {
	color: var(--fm-muted);
}

/* Preisblatt-Reihenfolge: sortierbare Liste (Drag-and-Drop + Nummerneingabe) — #19.
   Ohne JavaScript bleibt sie als Nummernliste voll bedienbar; der Griff wird erst
   per JS ziehbar (progressive enhancement). */
#fm-app .fm-sort-list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: var(--fm-s-2);
}
#fm-app .fm-sort-item {
	display: flex;
	align-items: center;
	gap: var(--fm-s-3);
	padding: var(--fm-s-2) var(--fm-s-3);
	border: 1px solid var(--fm-border);
	border-radius: var(--fm-radius);
	background: var(--fm-surface);
}
#fm-app .fm-sort-item.is-dragging {
	opacity: 0.5;
}
#fm-app .fm-sort-handle {
	color: var(--fm-muted);
	font-size: 1.15rem;
	line-height: 1;
	cursor: grab;
	touch-action: none;
	-webkit-user-select: none;
	user-select: none;
	/* Touch-Ziel ≥ 44 px (Außendienst, CLAUDE.md §7). */
	min-width: 44px;
	min-height: 44px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
}
#fm-app .fm-sort-handle[draggable="true"]:active {
	cursor: grabbing;
}
#fm-app .fm-sort-label {
	flex: 1 1 auto;
}
#fm-app .fm-sort-number {
	flex: 0 0 4.5rem;
	width: 4.5rem;
}

/* ============================================================================
   Dunkles Farbschema (Punkt 7)

   Aktivierung über das Attribut #fm-app[data-fm-theme="dark"] (gesetzt server-
   seitig aus dem Cookie fm_theme für FOUC-Freiheit, danach vom Umschalter in
   public/js/fm-app.js). Die Systemschicht liest Farben ausschließlich über die
   --fm-*-Tokens; deshalb genügt es, hier die Neutral-/Flächentoken und die harten
   Soft-Flächen umzudefinieren — Karten, Tabellen, Formulare, Badges usw. erben den
   Dunkelmodus automatisch (docs/INTEGRATION.md §5b).

   Neutral-Achse: niedrige Nummern = dunkle Flächen (n-0 bleibt die HELLSTE Fläche,
   damit Karten sich vom Hintergrund abheben), hohe Nummern = heller Text. Marke
   (--fm-primary*) bleibt unverändert (Aktion/Fokus tragen auf hell wie dunkel).
   ============================================================================ */
#fm-app[data-fm-theme="dark"] {
	/* Neutral – Flächen (0 = Karte, 100 = App-Hintergrund) bis Text (900). */
	--fm-n-0: #1b2635;
	--fm-n-50: #151f2c;
	--fm-n-100: #0f1824;
	--fm-n-150: #202d3d;
	--fm-n-200: #2b3a4d;
	--fm-n-300: #3a4b60;
	--fm-n-400: #6b7c93;
	--fm-n-500: #94a3b8;
	--fm-n-700: #cbd5e1;
	--fm-n-900: #f1f5f9;

	/* „white" wird hier als Fläche gelesen (Karten), nicht als reine Farbe. */
	--fm-white: var(--fm-n-0);

	/* Weiche Blau-/Akzentflächen: hell (#e8f1ff) würde auf dunkel blenden.
	   Die Basisstufen 50/100 werden mit umdefiniert — sie werden im System
	   ausschließlich als FLÄCHE benutzt (aktive/ausgewählte Zustände wie
	   `fm-choice:has(input:checked)`, aktive Subnav, markierte Tabellenzeilen).
	   Ohne das bleibt dort die helle Fläche stehen, während der Text hell
	   bleibt → unlesbar. */
	--fm-primary-50: #16283f;
	--fm-primary-100: #1d3453;
	--fm-primary-soft: #16283f;
	--fm-accent-soft: #16283f;
	--fm-accent-border: #24405f;

	/* Status: dunkle, entsättigte Flächen + heller Text/Rahmen (Kontrast ≥ 4.5:1). */
	--fm-success-soft: #14301f;
	--fm-success-line: #1c4a30;
	--fm-success-text: #4ade80;
	--fm-warning-soft: #38290c;
	--fm-warning-line: #57411a;
	--fm-warning-text: #fbbf24;
	--fm-danger-soft: #3a1719;
	--fm-danger-line: #5c2428;
	--fm-danger-text: #f87171;
	--fm-info-soft: #16283f;
	--fm-info-line: #24405f;
	--fm-info-text: #93b4f6;
	--fm-neutral-soft: var(--fm-n-150);

	/* Elevation: auf dunklem Grund tragen nur klar schwarze Schatten. */
	--fm-e-1: 0 1px 2px rgba(0, 0, 0, 0.4);
	--fm-e-2: 0 4px 12px rgba(0, 0, 0, 0.5);
	--fm-e-3: 0 14px 34px rgba(0, 0, 0, 0.6);
	--fm-shadow: var(--fm-e-1);
	--fm-shadow-lg: var(--fm-e-2);

	/* Fokusring eine Spur kräftiger, damit er auf dunkel gleich gut sitzt. */
	--fm-focus-ring: 0 0 0 3px rgba(59, 130, 246, 0.45);

	color-scheme: dark;
}

/* Gewöhnliche Textlinks im Dunkelmodus.

   Die Systemschicht setzt `#fm-app a { color: var(--fm-primary-600) }` (#2563eb).
   Da die Marke oben bewusst NICHT umdefiniert wird, stand dieses Blau auch auf
   der dunklen Kartenfläche (#1b2635) — gemessen 2,95:1 und damit unter dem
   WCAG-AA-Minimum von 4,5:1 für Fließtext (Hellmodus: 5,17:1). --fm-primary-300
   (#93b4f6) hebt das auf 7,34:1, ohne die Marke anzufassen.

   Warum `:where()` und nicht `#fm-app[data-fm-theme="dark"] a`:
   Der Attributselektor zählt als Klasse, die Regel läge also bei (1,1,1) und
   würde — aus dieser Datei als letzter Quelle — JEDE Komponente überstimmen, die
   ihre Linkfarbe selbst setzt. Gemessen wären das 22 Bausteine, darunter
   `.fm-btn-primary` (weiß auf Blau, 5,17:1 → 2,49:1), `.fm-btn-danger`
   (4,83:1 → 2,32:1), die Sidebar (`--fm-sidebar-text` → Blau im Navy),
   `.fm-table a` (neutral, Blau erst bei Hover), `.fm-card-kpi-link`
   (`color: inherit`) sowie Subnav, Chips, Breadcrumb, Portal-Nav und
   Benutzermenü. `:where()` steuert 0 zur Spezifität bei: die Regel landet auf
   (1,0,1) — exakt der Position von `#fm-app a`, das sie ersetzen soll — und
   gewinnt allein über die spätere Quellposition. Alles, was seine Farbe über
   eine Klasse (≥ 1,1,0) selbst bestimmt, bleibt unberührt. Beim Ändern diese
   Konstruktion bitte erhalten; die vermeintliche Vereinfachung auf den reinen
   Attributselektor bricht die genannten Bausteine.

   Der Hover-Zustand steht weiter unten im Block mit den weichen Blauflächen. */
#fm-app:where([data-fm-theme="dark"]) a {
	color: var(--fm-primary-300);
}

/* Text auf weichen Blauflächen.

   Die Systemschicht paart `--fm-primary-50/100` (Fläche) fest mit
   `--fm-primary-700/900` (Text) — im Hellmodus helle Fläche + dunkle Schrift.
   Im Dunkelmodus sind die Flächen jetzt dunkel; die Textstufen dürfen aber
   NICHT global umdefiniert werden, weil `--fm-primary-700` an anderer Stelle
   als Fläche dient (`fm-btn-primary:hover`, `--fm-accent-strong` in
   fm-lexoffice-migration.css). Deshalb wird die Schrift hier komponentenweise
   aufgehellt. */
#fm-app[data-fm-theme="dark"] code,
#fm-app[data-fm-theme="dark"] .fm-code,
#fm-app[data-fm-theme="dark"] .fm-avatar,
#fm-app[data-fm-theme="dark"] .fm-btn-subtle,
#fm-app[data-fm-theme="dark"] a.fm-chip-active,
#fm-app[data-fm-theme="dark"] .fm-chip-active,
#fm-app[data-fm-theme="dark"] .fm-btn-link,
#fm-app[data-fm-theme="dark"] .fm-kpi-icon,
#fm-app[data-fm-theme="dark"] a:hover {
	color: var(--fm-primary-300);
}

#fm-app[data-fm-theme="dark"] .fm-btn-subtle:hover,
#fm-app[data-fm-theme="dark"] .fm-btn-link:hover {
	color: #c7dbff;
}

/* Umschalter für das Farbschema (Topbar). Touch-Ziel ≥ 40 px, tokenbasiert. */
#fm-app .fm-theme-toggle {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 40px;
	height: 40px;
	min-width: 40px;
	padding: 0;
	border: 1px solid var(--fm-stroke);
	border-radius: var(--fm-r-md);
	background: var(--fm-surface);
	color: var(--fm-text);
	cursor: pointer;
	transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
#fm-app .fm-theme-toggle:hover {
	background: var(--fm-surface-2);
	color: var(--fm-heading);
}
#fm-app .fm-theme-toggle:focus-visible {
	outline: none;
	box-shadow: var(--fm-focus-ring);
}
#fm-app .fm-theme-toggle .fm-icon {
	width: 20px;
	height: 20px;
}
/* Hell zeigt den Mond (klick → dunkel), Dunkel zeigt die Sonne (klick → hell). */
#fm-app .fm-theme-icon-to-light {
	display: none;
}
#fm-app[data-fm-theme="dark"] .fm-theme-icon-to-dark {
	display: none;
}
#fm-app[data-fm-theme="dark"] .fm-theme-icon-to-light {
	display: inline-flex;
}

/* ---------------------------------------------------------------------------
   Kombinierte Mehr-Routen-Ansicht (Disposition → „Touren nebeneinander").
   Routen eines Tages als nebeneinanderliegende Lanes; der Container scrollt
   horizontal in sich (der Seitenkörper nie). Nur Tokens, keine festen Werte.
   --------------------------------------------------------------------------- */
#fm-app .fm-routes-hint {
	margin: 0 0 var(--fm-s-3);
	font-size: 0.9rem;
}

#fm-app .fm-routes-lanes {
	display: flex;
	gap: var(--fm-s-4);
	align-items: flex-start;
	overflow-x: auto;
	padding-bottom: var(--fm-s-3);
	-webkit-overflow-scrolling: touch;
}

#fm-app .fm-route-lane {
	flex: 0 0 20rem;
	max-width: 20rem;
	display: flex;
	flex-direction: column;
	gap: var(--fm-s-2);
	padding: var(--fm-s-3);
	border: 1px solid var(--fm-border);
	border-radius: var(--fm-radius);
	background: var(--fm-surface);
}

#fm-app .fm-route-lane-head {
	display: flex;
	flex-direction: column;
	gap: var(--fm-s-1);
	padding-bottom: var(--fm-s-2);
	border-bottom: 1px solid var(--fm-border);
}

#fm-app .fm-route-lane-title {
	margin: 0;
	font-size: 1rem;
	line-height: 1.3;
}

#fm-app .fm-route-lane-badges {
	display: flex;
	flex-wrap: wrap;
	gap: var(--fm-s-1);
}

#fm-app .fm-route-lane-time {
	margin: 0;
	font-size: 0.85rem;
	color: var(--fm-muted);
	font-variant-numeric: tabular-nums;
}

#fm-app .fm-route-lane-conflicts {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: var(--fm-s-1);
}

#fm-app .fm-lane-stops {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: var(--fm-s-2);
	counter-reset: fm-lane-stop;
}

#fm-app .fm-lane-stop {
	display: flex;
	flex-direction: column;
	gap: var(--fm-s-2);
	padding: var(--fm-s-2);
	border: 1px solid var(--fm-border);
	border-radius: var(--fm-radius);
	background: var(--fm-bg);
}

#fm-app .fm-lane-stop-empty {
	border-style: dashed;
	background: var(--fm-muted-bg);
}

#fm-app .fm-lane-stop-main {
	display: flex;
	flex-direction: column;
	gap: 2px;
	min-width: 0;
}

#fm-app .fm-lane-stop-name {
	font-weight: 600;
	word-break: break-word;
}

#fm-app .fm-lane-stop-name::before {
	counter-increment: fm-lane-stop;
	content: counter(fm-lane-stop) ". ";
	color: var(--fm-muted);
}

#fm-app .fm-lane-stop-meta {
	font-size: 0.82rem;
	color: var(--fm-muted);
}

#fm-app .fm-lane-move {
	display: flex;
	flex-wrap: wrap;
	gap: var(--fm-s-2);
	align-items: center;
}

#fm-app .fm-lane-move-target {
	flex: 1 1 8rem;
	min-width: 0;
	min-height: 44px;
}

#fm-app .fm-lane-move .fm-btn {
	min-height: 44px;
}

@media ( max-width: 640px ) {
	#fm-app .fm-route-lane {
		flex-basis: 85vw;
		max-width: 85vw;
	}
}

/* ---------------------------------------------------------------------------
   Standardtour-Vorlagen: Editor der geordneten Objekt-Stopps (Objekt / Leistung
   / Notiz je Zeile) + kompaktes Inline-Datumsfeld beim Ausrollen. Nur Tokens.
   --------------------------------------------------------------------------- */
#fm-app .fm-template-stops {
	display: flex;
	flex-direction: column;
	gap: var(--fm-s-2);
}

#fm-app .fm-template-stop-row {
	display: grid;
	grid-template-columns: 2fr 1.5fr 2fr;
	gap: var(--fm-s-2);
	align-items: end;
}

#fm-app .fm-template-stop-row .fm-field {
	margin: 0;
}

#fm-app .fm-input-inline {
	width: auto;
	max-width: 11rem;
	min-height: 44px;
}

@media ( max-width: 640px ) {
	#fm-app .fm-template-stop-row {
		grid-template-columns: 1fr;
	}
}

/* Barcode-/QR-Scanner (Einkauf/Lager „Scannen"). Eigene Komponente → hier, nicht im
   Design-System-Paket (CLAUDE.md §7). Nur Tokens, keine festen Farbwerte. */
#fm-app .fm-input-group {
	display: flex;
	gap: var(--fm-s-2);
	align-items: stretch;
}

#fm-app .fm-input-group .fm-input {
	flex: 1 1 auto;
	min-width: 0;
}

#fm-app .fm-scan-viewport {
	position: relative;
	width: 100%;
	max-width: 32rem;
	margin: 0 auto var(--fm-s-3);
	border-radius: var(--fm-radius);
	overflow: hidden;
	background: var(--fm-muted-bg);
	aspect-ratio: 4 / 3;
}

#fm-app .fm-scan-video {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

#fm-app .fm-scan-frame {
	position: absolute;
	inset: 18% 12%;
	border: 3px solid var(--fm-primary);
	border-radius: var(--fm-radius);
	pointer-events: none;
	box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0.28);
}

/* ------------------------------------------------------------------ */
/* Kontrollpunkt-QR-Etiketten (Objektmodul) + Scan-Viewport.          */
/* Eigene Komponente (§7.1): nur #fm-app-Präfix, Tokens, kein !important. */
/* ------------------------------------------------------------------ */

#fm-app .fm-qr-labels {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
	gap: var(--fm-s-3);
	margin-top: var(--fm-s-3);
}

#fm-app .fm-qr-label {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--fm-s-2);
	padding: var(--fm-s-3);
	border: 1px solid var(--fm-border);
	border-radius: var(--fm-radius);
	background: var(--fm-surface);
	text-align: center;
	break-inside: avoid;
}

#fm-app .fm-qr-label-img svg {
	display: block;
	width: 100%;
	height: auto;
	max-width: 9rem;
}

#fm-app .fm-qr-label-meta {
	display: grid;
	gap: 0.15rem;
	word-break: break-word;
}

#fm-app .fm-qr-label-name {
	font-weight: 600;
}

#fm-app .fm-qr-label-object {
	color: var(--fm-muted-fg);
	font-size: var(--fm-fs-sm);
}

#fm-app .fm-qr-label-code {
	font-family: var(--fm-font-mono, ui-monospace, monospace);
	font-size: var(--fm-fs-sm);
	letter-spacing: 0.05em;
}

#fm-app .fm-qr-scan-viewport {
	position: relative;
	width: 100%;
	max-width: 28rem;
	margin: 0 auto var(--fm-s-3);
	border-radius: var(--fm-radius);
	overflow: hidden;
	background: var(--fm-muted-bg);
	aspect-ratio: 4 / 3;
}

#fm-app .fm-qr-scan-video {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

@media print {
	#fm-app .fm-no-print {
		display: none;
	}

	#fm-app .fm-qr-labels {
		grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
	}

	#fm-app .fm-qr-label {
		border-color: #000;
	}
}

/* ============================================================================
   Handlungsleiste in einem Hinweis (`fm-notice-actions`)

   Ein Hinweis, der sagt „legen Sie zuerst X an", ist ohne Weg dorthin eine
   Sackgasse (LV-Import-Landing ohne Kunden). Ein Knopf im Hinweis brauchte eine
   eigene Zeile mit Abstand — `fm-notice` definiert für seine Kindelemente keine.
   Bewusst nur Abstand + Umbruch, keine Farben: der Knopf behält seine Variante,
   der Hinweis seinen Akzent.
   ========================================================================== */

#fm-app .fm-notice-actions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--fm-s-2);
	margin: var(--fm-s-3) 0 0;
}

/* ============================================================================
   Folgeentscheidung innerhalb eines Feldes (`fm-choice` im `fm-field`)

   Ein Kästchen, das sich auf das Feld darüber bezieht (LV-Import: „Hinterlegten
   Schlüssel entfernen" unter dem OpenAI-Schlüssel), stieß ohne Abstand an das
   Eingabefeld bzw. den Hilfetext: `fm-choice` bringt als eigenständige Kachel
   keinen oberen Rand mit, `fm-field-hint` endet ohne unteren. Als eigenes
   `fm-field` daneben wäre die Zuordnung verloren gegangen (im `fm-grid-2` sogar
   in der Nachbarspalte gelandet). Bewusst nur Abstand — Rahmen, Farbe und
   Trefferfläche der Kachel bleiben unverändert.
   ========================================================================== */

#fm-app .fm-field > .fm-input + .fm-choice,
#fm-app .fm-field > .fm-field-hint + .fm-choice {
	margin-top: var(--fm-s-3);
}

/* ============================================================================
   Spaltensortierung in Übersichtstabellen (`public/js/fm-table-sort.js`)

   Das Skript hängt in jede sortierbare Überschrift einen echten `<button>` —
   damit ist die Bedienung mit Tastatur und Screenreader ohne Zusatzarbeit
   richtig (Enter/Leertaste, Zustand über `aria-sort` an der Zelle).

   Der Knopf übernimmt die Innenabstände der Zelle und füllt sie vollständig
   aus; die Zelle selbst gibt ihre Polsterung dafür ab. Sonst wäre nur die
   Beschriftung anklickbar und das Touch-Ziel deutlich unter 44 px.

   Schriftbild wird durchgehend geerbt (`font: inherit` u. a.), damit die
   Versalien-/Laufweiten-Einstellung aus `fm-design-system.css` erhalten bleibt
   und ein Kopf mit Knopf neben einem ohne nicht auffällt.
   ========================================================================== */

#fm-app .fm-table th.fm-th-sortable {
	padding: 0;
}

#fm-app .fm-table th.fm-th-sortable .fm-th-sort {
	display: flex;
	align-items: center;
	justify-content: flex-start;
	gap: var(--fm-s-1);
	width: 100%;
	min-height: 44px; /* Touch-Ziel (CLAUDE.md §7.2), wie in fm-design-system.css. */
	margin: 0;
	padding: 0.55rem var(--fm-cell-x);
	border: 0;
	border-radius: 0;
	background: none;
	font: inherit;
	letter-spacing: inherit;
	text-transform: inherit;
	color: inherit;
	text-align: inherit;
	cursor: pointer;
}

/* Zahlenspalten sind rechtsbündig – der Knopfinhalt folgt. */
#fm-app .fm-table th.fm-num .fm-th-sort,
#fm-app .fm-table th.fm-text-right .fm-th-sort {
	justify-content: flex-end;
}

#fm-app .fm-table th.fm-th-sortable .fm-th-sort:hover {
	background: var(--fm-n-150);
	color: var(--fm-primary-600);
}

/* Der globale Fokusring steht 2 px AUSSERHALB – in einer randlosen, klebenden
   Kopfzelle wird er dort abgeschnitten. Deshalb nach innen gelegt. */
#fm-app .fm-table th.fm-th-sortable .fm-th-sort:focus-visible {
	outline: 2px solid var(--fm-focus-color);
	outline-offset: -2px;
}

#fm-app .fm-table th.fm-th-sortable .fm-th-sort-icon {
	flex: none;
	font-size: var(--fm-fs-sm);
	line-height: 1;
	opacity: 0.35;
}

#fm-app .fm-table th.fm-th-sortable .fm-th-sort-icon::before {
	content: "↕";
}

#fm-app .fm-table th.fm-th-sortable:hover .fm-th-sort-icon {
	opacity: 0.7;
}

#fm-app .fm-table th[aria-sort="ascending"] .fm-th-sort-icon,
#fm-app .fm-table th[aria-sort="descending"] .fm-th-sort-icon {
	opacity: 1;
	color: var(--fm-primary-600);
}

#fm-app .fm-table th[aria-sort="ascending"] .fm-th-sort-icon::before {
	content: "↑";
}

#fm-app .fm-table th[aria-sort="descending"] .fm-th-sort-icon::before {
	content: "↓";
}

/* Hinweis unter einer Tabelle mit Seitennavigation: sortiert wird die Seite,
   nicht der Gesamtbestand. Erscheint erst nach dem ersten Sortieren. */
#fm-app .fm-table-sort-hint {
	margin: var(--fm-s-2) 0 0;
}

/* Auswahlleiste für die mobile Kartenansicht.

   Standardmäßig AUS: solange der Tabellenkopf sichtbar ist, ist er die
   Bedienung — eine zweite daneben wäre Redundanz. Sichtbar wird die Leiste
   ausschließlich in demselben Fenster, in dem `fm-mobile-usability.css` die
   `.fm-table-cards`-Tabelle zur Kartenliste macht und ihren `<thead>`
   ausblendet. Deshalb ist die Grenze hier bewusst identisch: **640 px**, nicht
   768 px — der 768er-Block derselben Datei vergrößert nur Touch-Ziele, der
   Kartenumbruch steht im 640er-Block. Eine Abweichung ließe entweder beide
   Bedienungen gleichzeitig stehen (641–768 px) oder keine.
   Bewacht von `tests/table-sort-smoke.php` (Abschnitt 6): der Test liest beide
   Grenzen aus den Dateien und vergleicht sie. */
#fm-app .fm-table-sort-bar {
	display: none;
}

@media ( max-width: 640px ) {
	#fm-app .fm-table-sort-bar {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		gap: var(--fm-s-2);
		margin-bottom: var(--fm-s-3);
	}

	/* Beschriftung auf eigener Zeile, damit Auswahlfeld und Richtungsknopf
	   nebeneinander die volle Breite bekommen. */
	#fm-app .fm-table-sort-bar .fm-table-sort-label {
		flex: 1 0 100%;
		margin-bottom: 0;
	}

	#fm-app .fm-table-sort-bar .fm-table-sort-select {
		flex: 1 1 10rem;
		min-width: 0;
		min-height: 44px;
	}

	#fm-app .fm-table-sort-bar .fm-table-sort-dir {
		flex: 0 0 auto;
		min-height: 44px;
	}
}

@media print {
	#fm-app .fm-table th.fm-th-sortable .fm-th-sort-icon,
	#fm-app .fm-table-sort-hint,
	#fm-app .fm-table-sort-bar {
		display: none;
	}

	#fm-app .fm-table th.fm-th-sortable .fm-th-sort {
		min-height: 0;
		padding: 0;
	}
}

/* ==========================================================================
   .fm-form — Formular-Grundgerüst (fehlende Komponente, §7.1)

   `fm-form` steht 68× in 50 Dateien (66× am <form>), hatte aber projektweit
   KEINE Regel: die Klasse wurde still ungestylt ausgeliefert. `.fm-form-actions`
   gibt es, `.fm-form` gab es nicht. Aufgefallen über die CSS-Abdeckungsprüfung
   in tests/time-accounts-smoke.php.

   BEWUSST KEIN Abstand zwischen den Feldern: den bringt `.fm-field` schon selbst
   mit (`margin-bottom: var(--fm-s-5)` in fm-design-system.css). Ein `gap` oder
   ein Kind-Rand hier würde die Rhythmik verdoppeln — genau die Falle, wegen der
   die Komponente nicht einfach „nachgereicht" werden konnte.

   Bleibt der eine Fall, den das Feld nicht selbst lösen kann: endet ein Formular
   mit einem Feld statt mit `.fm-form-actions`, hängt dessen unterer Rand als
   Leerraum am Formularende (Filterleisten, Schnellmasken). `.fm-form-actions`
   bleibt unangetastet — seine negativen Ränder lassen die Aktionsleiste bewusst
   bis an die Kartenkante laufen.
   ========================================================================== */

#fm-app .fm-form > .fm-field:last-child {
	margin-bottom: 0;
}

/* ---------------------------------------------------------------------------
   Abhängige Auswahl im Rahmenvertragsformular (fm-contract-form-scope.js)
   ---------------------------------------------------------------------------
   Das Skript blendet nicht passende Objektzeilen und – wenn keine übrig bleibt –
   das ganze Auswahlgitter über das `hidden`-Attribut aus. Gegen die Regel
   `#fm-app .fm-checkbox-grid { display: grid }` (fm-design-system-extensions.css)
   verliert das UA-Stylesheet, das leere Gitter bliebe als Lücke stehen. Die
   Zeilenregel steht vorsorglich daneben, falls ein Paketstand `fm-checkbox-row`
   später ein eigenes `display` gibt. */
#fm-app .fm-checkbox-grid[hidden],
#fm-app .fm-checkbox-row[hidden] {
	display: none;
}

/* ==========================================================================
   .fm-modal — Rückfrage-Dialog (fehlende Komponente, §7.1)

   Basis ist das native <dialog>: Backdrop, Fokusfalle, Escape-zum-Schließen und
   die Top-Layer-Stapelung liefert der Browser. Nachgebaut wird davon nichts —
   das Skript public/js/fm-modal.js ruft nur showModal()/close().

   ZWEI Darstellungen, bewusst:
   - `:modal` (mit JS, showModal) → mittiger Dialog über abgedunkeltem Hintergrund.
   - `[open]:not(:modal)` (OHNE JS, `open` serverseitig gesetzt) → normale Karte im
     Seitenfluss. Ein <dialog open> steht sonst als position:absolute-Kasten über
     dem Inhalt; ohne diese Regel wäre der No-JS-Weg unbenutzbar.

   Farben ausschließlich über --fm-*-Tokens ⇒ Dunkelmodus erbt automatisch. Der
   Backdrop trägt bewusst einen festen Schleier: ::backdrop liegt im Top-Layer und
   soll auf hell wie dunkel gleich abdunkeln.
   ========================================================================== */

#fm-app dialog.fm-modal {
	/* 36rem: gerade breit genug, dass die drei Entscheidungen (springen / neu /
	   abbrechen) am Schreibtisch in EINE Zeile passen. Schmaler bricht „Abbrechen"
	   allein um und liest sich wie eine nachrangige vierte Möglichkeit. */
	max-width: min(36rem, calc(100vw - var(--fm-s-8)));
	width: 100%;
	padding: 0;
	border: 0;
	border-radius: var(--fm-r-lg);
	background: transparent;
	color: var(--fm-text);
}

#fm-app dialog.fm-modal::backdrop {
	background: rgba(13, 27, 42, 0.55);
}

#fm-app dialog.fm-modal .fm-modal-panel {
	padding: var(--fm-s-6);
	border: 1px solid var(--fm-stroke);
	border-radius: var(--fm-r-lg);
	background: var(--fm-surface);
	box-shadow: var(--fm-e-3);
}

#fm-app dialog.fm-modal .fm-modal-head {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: var(--fm-s-3);
	margin-bottom: var(--fm-s-3);
}

#fm-app dialog.fm-modal .fm-modal-title {
	margin: 0;
	font-size: var(--fm-fs-lg);
	color: var(--fm-heading);
}

#fm-app dialog.fm-modal .fm-modal-close {
	flex: 0 0 auto;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 44px;
	min-height: 44px;
	margin: calc(var(--fm-s-2) * -1) calc(var(--fm-s-2) * -1) 0 0;
	border-radius: var(--fm-r-md);
	color: var(--fm-muted-fg);
	font-size: var(--fm-fs-xl);
	line-height: 1;
	text-decoration: none;
}

#fm-app dialog.fm-modal .fm-modal-close:hover {
	background: var(--fm-surface-2);
	color: var(--fm-heading);
}

#fm-app dialog.fm-modal .fm-modal-list {
	margin: var(--fm-s-4) 0 0;
	padding: 0;
	list-style: none;
	display: flex;
	flex-direction: column;
	gap: var(--fm-s-2);
}

#fm-app dialog.fm-modal .fm-modal-list li {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--fm-s-2);
	padding: var(--fm-s-3);
	border: 1px solid var(--fm-stroke-soft);
	border-radius: var(--fm-r-md);
	background: var(--fm-surface-2);
}

#fm-app dialog.fm-modal .fm-modal-actions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--fm-s-2);
	margin-top: var(--fm-s-6);
}

/* Ohne JS: als Karte im Fluss statt als schwebender Kasten. */
#fm-app dialog.fm-modal[open]:not(:modal) {
	position: static;
	max-width: none;
	margin: var(--fm-s-5) 0;
}

@media (max-width: 640px) {
	#fm-app dialog.fm-modal .fm-modal-actions .fm-btn {
		flex: 1 1 100%;
		justify-content: center;
	}
}

/* --------------------------------------------------------------------------
   Dashboard-Kachelauswahl (Bearbeitungsmodus ?fm_dash_edit=1)
   Neue Plugin-Komponente (kein Paket-Bestandteil), daher hier in den
   Overrides: Im Auswahlmodus wird jede KPI-Kachel zum <label> um die eigene
   Checkbox (templates/components/card-kpi.php, 'select_mode') — die ganze
   Kachel ist Touch-Ziel. Abgewählte Kacheln werden gedimmt (progressive
   Enhancement über :has(); ohne Support bleibt die Checkbox maßgeblich).
   -------------------------------------------------------------------------- */
#fm-app .fm-kpi-select {
	position: relative;
	margin: 0;
	cursor: pointer;
}

#fm-app .fm-kpi-select .fm-kpi-select-box {
	position: absolute;
	top: var(--fm-s-3);
	right: var(--fm-s-3);
	width: 1.25rem;
	height: 1.25rem;
	margin: 0;
	accent-color: var(--fm-primary-600);
	cursor: pointer;
}

/* Tastatur-Fokus der überlagerten Checkbox an der Kachel sichtbar machen. */
#fm-app .fm-kpi-select:focus-within {
	border-color: var(--fm-primary-300);
	box-shadow: var(--fm-e-2);
}

/* Abgewählte Kachel dimmen — reine Nettigkeit, Funktion hängt nicht daran. */
#fm-app .fm-kpi-select:has(.fm-kpi-select-box:not(:checked)) {
	opacity: 0.55;
	border-style: dashed;
}

/* ---------------------------------------------------------------------------
   Seitenkopf mit Aktionen: mobiler Abstand (`fm-page-head-row`)
   ---------------------------------------------------------------------------
   Zur Einordnung, weil die Klasse leicht als kaputt gelesen wird: Das
   Flex-Layout stammt aus der Basisschicht `fm-tailadmin`
   (public/vendor/tailadmin/tailadmin.css — `display: flex`, `flex-wrap`,
   `justify-content: space-between`, `align-items: flex-start`). In
   fm-design-system.css:302 steht nur noch `gap`, was das Layout NICHT aufhebt:
   nicht erneut deklarierte Eigenschaften bleiben aus der früheren Schicht
   gültig. Titelblock und Aktionsknopf stehen also nebeneinander — hier ist
   bewusst kein `display: flex` nachgetragen, das wäre eine zweite Quelle der
   Wahrheit für eine bereits wirksame Regel.

   Kaputt war allein der mobile Abstand: fm-mobile-usability.css setzt unter
   `@media ( max-width: 640px )` ein `gap: 0.5rem`, doch fm-design-system.css:302
   setzt danach unbedingt `gap: var(--fm-s-4)` (1rem). Media Queries erhöhen die
   Spezifität nicht, und die Design-System-Schicht lädt später (eigener Hook mit
   Prio 100 gegen Prio 10 der Modul-Stylesheets) — der engere mobile Abstand war
   damit wirkungslos (gemessen bei 375px: 16px statt der gewollten 8px).

   Diese Schicht lädt als letzte und stellt die Absicht wieder her. Bewusst nur
   `gap`: `flex-direction: column` und `align-items: flex-start` aus
   fm-mobile-usability.css greifen weiterhin, weil das Design-System sie nicht
   überschreibt. */
@media ( max-width: 640px ) {
	#fm-app .fm-page-head-row {
		gap: var(--fm-s-2);
	}
}

/* Angebotsformular: aufklappbare interne Kalkulation je Positionszeile (A3,
   v1.72.165). Neue Komponente — im Design-System-Paket gibt es keinen
   Positions-Kalkblock, deshalb gehört sie laut §7.1 in diese Schicht.
   Natives <details> = No-JS-fähig; das JS spiegelt nur live. */
#fm-app .fm-offer-item-calc {
	margin: var(--fm-s-1) 0 var(--fm-s-2);
	padding: var(--fm-s-2);
	border: 1px solid var(--fm-border);
	border-radius: var(--fm-radius);
	background: var(--fm-surface-2);
}

#fm-app .fm-offer-item-calc > summary {
	cursor: pointer;
	font-size: var(--fm-fs-sm);
	color: var(--fm-muted-fg);
	display: flex;
	align-items: center;
	gap: var(--fm-s-2);
	min-height: 44px; /* Touch-Ziel (Außendienst) */
}

#fm-app .fm-offer-calc-grid {
	display: grid;
	grid-template-columns: repeat( auto-fill, minmax( 150px, 1fr ) );
	gap: var(--fm-s-2);
	margin-top: var(--fm-s-2);
}

#fm-app .fm-offer-calc-field .fm-field-label {
	font-size: var(--fm-fs-sm);
	color: var(--fm-muted-fg);
}

#fm-app .fm-offer-calc-derived {
	margin: var(--fm-s-2) 0 0;
	font-size: var(--fm-fs-sm);
	color: var(--fm-muted-fg);
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--fm-s-1) var(--fm-s-2);
}

/* --------------------------------------------------------------------------
 * Navigations-Favoriten „Mein Arbeitsbereich" (Sidebar-Stern + Favoriten-Block).
 * Warum hier: eigene Komponente des Plugins, das Design-System-Paket kennt sie
 * nicht. Das Toggle-Formular umschließt die ganze Gruppenliste und muss
 * layoutneutral bleiben; der Stern braucht ein 44-px-Touch-Ziel, ohne die
 * schlanke Nav-Zeile aufzublähen (negative Ränder gleichen das aus).
 * ----------------------------------------------------------------------- */
#fm-app .fm-nav-fav-scope {
	display: contents;
}

#fm-app .fm-nav-row {
	display: flex;
	align-items: center;
}

#fm-app .fm-nav-row .fm-nav-link {
	flex: 1 1 auto;
	min-width: 0;
}

#fm-app .fm-nav-fav {
	min-width: 44px;
	min-height: 44px;
	margin: -4px 0;
	padding: 0;
	background: none;
	border: 0;
	border-radius: var(--fm-r-sm);
	color: var(--fm-sidebar-muted, var(--fm-muted-fg));
	display: grid;
	place-items: center;
	cursor: pointer;
	opacity: 0;
}

#fm-app .fm-nav-fav svg {
	width: 16px;
	height: 16px;
}

#fm-app .fm-nav-row:hover .fm-nav-fav,
#fm-app .fm-nav-row:focus-within .fm-nav-fav,
#fm-app .fm-nav-fav[aria-pressed="true"] {
	opacity: 1;
}

#fm-app .fm-nav-fav[aria-pressed="true"] svg path {
	fill: currentColor;
}

#fm-app .fm-nav-fav:focus-visible {
	outline: 2px solid currentColor;
	outline-offset: -2px;
}

/* Touch-Geräte haben kein Hover: Stern immer zeigen. */
@media (hover: none) {
	#fm-app .fm-nav-fav {
		opacity: 1;
	}
}

/* „Bearbeiten …"-Zeile am Ende des Favoriten-Blocks: bewusst gedämpft. */
#fm-app .fm-nav-link-edit {
	font-size: var(--fm-fs-sm);
	opacity: 0.75;
}

/* A6 (v1.72.166): Turnus-Block je Positionszeile teilt sich die Grid-Optik mit
   dem Kalkblock; Wochentags-Häkchen laufen inline. */
#fm-app .fm-offer-item-turnus {
	margin: var(--fm-s-1) 0 var(--fm-s-2);
	padding: var(--fm-s-2);
	border: 1px solid var(--fm-border);
	border-radius: var(--fm-radius);
	background: var(--fm-surface-2);
}

#fm-app .fm-offer-item-turnus > summary {
	cursor: pointer;
	font-size: var(--fm-fs-sm);
	color: var(--fm-muted-fg);
	min-height: 44px;
	display: flex;
	align-items: center;
}

#fm-app .fm-choice-inline {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	margin-right: var(--fm-s-2);
	min-height: 44px;
}

/* Bearbeitungsansicht: Menüpunkt-Zeile (Icon außerhalb der Nav braucht ein Maß). */
#fm-app .fm-nav-fav-entry {
	display: inline-flex;
	align-items: center;
	gap: var(--fm-s-2);
}

#fm-app .fm-nav-fav-entry .fm-icon {
	width: 18px;
	height: 18px;
	flex: 0 0 auto;
}

/* Vertrags-Leitstand (Paket V2, v1.72.171): Phasen-Stepper + offene Punkte auf
   der Vertragsakte. Generalisierte Fassung des Lexoffice-Wizard-Musters
   (.fm-lex-steps lädt nur auf der Migrationsansicht — bewusst eigene, hier
   global verfügbare Klassen; §7.1: neue Komponenten in diese Schicht). */
#fm-app .fm-guidance-steps {
	display: flex;
	gap: var(--fm-s-2);
	overflow-x: auto;
	list-style: none;
	margin: 0 0 var(--fm-s-3);
	padding: 0;
}

#fm-app .fm-guidance-step {
	display: flex;
	align-items: center;
	gap: var(--fm-s-1);
	white-space: nowrap;
	font-size: var(--fm-fs-sm);
	color: var(--fm-muted-fg);
	min-height: 44px;
}

#fm-app .fm-guidance-step-dot {
	width: 10px;
	height: 10px;
	border-radius: 50%;
	background: var(--fm-border);
	flex: none;
}

#fm-app .fm-guidance-step.is-done .fm-guidance-step-dot { background: var(--fm-success, #16a34a); }
#fm-app .fm-guidance-step.is-current .fm-guidance-step-dot,
#fm-app .fm-guidance-step.is-blocked .fm-guidance-step-dot { background: var(--fm-primary); }
#fm-app .fm-guidance-step.is-blocked .fm-guidance-step-dot { background: var(--fm-warning, #d97706); }
#fm-app .fm-guidance-step.is-current .fm-guidance-step-label,
#fm-app .fm-guidance-step.is-blocked .fm-guidance-step-label { color: var(--fm-heading); font-weight: 600; }

#fm-app .fm-guidance-next {
	display: flex;
	align-items: center;
	gap: var(--fm-s-2);
	flex-wrap: wrap;
	margin-bottom: var(--fm-s-2);
}

#fm-app .fm-guidance-open > summary {
	cursor: pointer;
	font-size: var(--fm-fs-sm);
	color: var(--fm-muted-fg);
	min-height: 44px;
	display: flex;
	align-items: center;
}

#fm-app .fm-guidance-items {
	list-style: none;
	margin: var(--fm-s-1) 0 0;
	padding: 0;
	display: grid;
	gap: var(--fm-s-1);
}

#fm-app .fm-guidance-item {
	display: flex;
	align-items: center;
	gap: var(--fm-s-2);
	flex-wrap: wrap;
}

#fm-app .fm-guidance-settled .fm-card-body {
	display: flex;
	align-items: center;
	gap: var(--fm-s-2);
}

/* V4 (v1.72.173): erklärte Statusaktionen — Button + Kurzbeschreibung als
   Raster, damit die Erklärzeile ihrem Knopf zugeordnet bleibt. */
#fm-app .fm-status-actions {
	display: grid;
	grid-template-columns: repeat( auto-fill, minmax( 220px, 1fr ) );
	gap: var(--fm-s-2);
	margin-bottom: var(--fm-s-2);
}

#fm-app .fm-status-action .fm-field-hint {
	margin-top: 2px;
}

/* Ein-/Austritts-Checklisten in der Personalakte (#27): das Designsystem hat
   keine Checklisten-Komponente — Zeile mit Inline-Toggle-Formular links und
   durchgestrichenem Text für erledigte Punkte. */
#fm-app .fm-checklist-item {
	display: flex;
	align-items: baseline;
	gap: var(--fm-space-2, 8px);
	padding: var(--fm-space-1, 4px) 0;
}

#fm-app .fm-checklist-item .fm-inline-form {
	flex: 0 0 auto;
}

#fm-app .fm-checklist-done {
	text-decoration: line-through;
	color: var(--fm-color-text-muted, #6b7280);
}

/* Objekt-Terminal (Kiosk, #38): großes Stempel-Formular für geteilte Tablets —
   Touch-Ziele weit über 44 px, eine Spalte, maximale Lesbarkeit. */
#fm-app .fm-kiosk { max-width: 560px; margin: 0 auto; }
#fm-app .fm-kiosk-input { font-size: 1.6rem; padding: 18px 16px; text-align: center; letter-spacing: .12em; }
#fm-app .fm-kiosk-submit { width: 100%; font-size: 1.3rem; padding: 20px; margin-top: 8px; }
#fm-app .fm-kiosk-result { font-size: 1.2rem; }

/* API & Webhooks (#49): Einmal-Anzeige frischer Geheimnisse gut kopierbar. */
#fm-app .fm-api-secret { display: block; margin-top: 6px; padding: 10px 12px; font-size: 1.05rem; word-break: break-all; user-select: all; background: var(--fm-surface-2); border-radius: 4px; }

/* ============================================================================
   Touch-Ziele ≥ 44 px (Befund B-022, CLAUDE.md §7.2)

   Die Regel „Touch-Ziele ≥ 44 px" stand bisher nur in CLAUDE.md und war
   technisch nirgends verankert. Live gemessen am 21.08.2026 in einem
   375-px-Rahmen auf fm_view=time: 14 von 58 Bedienelementen im Formular und
   7 von 50 in der Wochenansicht lagen darunter — betroffen waren gemeinsame
   Komponentenklassen, der Mangel galt also für ALLE mobilen Ansichten:

     .fm-btn-sm      34 px   (Wochennavigation — das Hauptbedienelement mobil)
     .fm-btn-xs      28 px
     .fm-chip        34 px   (Statusfilter)
     .fm-btn-text    34 px
     .fm-burger      40 px   .fm-search-toggle 40 px   .fm-theme-toggle 40 px
     .fm-btn         42 px   .fm-btn-icon 42 px
     .fm-input/.fm-select/.fm-textarea 42 px

   Gelöst im Design-System statt je Modul, und bewusst NUR dort, wo mit dem
   Finger bedient wird: am Zeigergerät (pointer: coarse) sowie auf schmalen
   Rahmen — Letzteres, damit die Messung im Desktop-Browser bei 375 px
   reproduzierbar bleibt (dort meldet der Browser `pointer: fine`). Am
   Arbeitsplatz mit Maus bleibt die kompakte Dichte erhalten.

   Kein `!important` nötig: gleiche Spezifität wie die Systemregeln, diese
   Datei lädt zuletzt. Eine @media-Bedingung erhöht die Spezifität NICHT —
   sie gewinnt hier allein über die Quellreihenfolge.
   ============================================================================ */
#fm-app {
	--fm-touch-min: 44px;
}

@media (pointer: coarse), (max-width: 768px) {

	#fm-app .fm-btn,
	#fm-app .fm-btn-sm,
	#fm-app .fm-btn-xs,
	#fm-app .fm-btn-text,
	#fm-app a.fm-chip,
	#fm-app .fm-chip,
	#fm-app .fm-input,
	#fm-app .fm-select,
	#fm-app .fm-textarea {
		min-height: var(--fm-touch-min);
	}

	/* Quadratische Icon-Flächen: auch in der Breite, sonst bleibt das Ziel schmal. */
	#fm-app .fm-btn-icon,
	#fm-app .fm-burger,
	#fm-app .fm-search-toggle,
	#fm-app .fm-theme-toggle {
		width: var(--fm-touch-min);
		height: var(--fm-touch-min);
		min-width: var(--fm-touch-min);
		min-height: var(--fm-touch-min);
	}

	/* Kästchen und Auswahlknöpfe: Ziel ist das Label, nicht das 18-px-Kästchen. */
	#fm-app .fm-choice {
		min-height: var(--fm-touch-min);
		align-items: center;
	}

	/* Navigation und Menüs (Nachtrag B-028, live gemessen 22.08.2026).
	   Diese Klassen setzen KEINE Höhe, ihre 38 px entstehen aus dem Padding —
	   der Prüfstand konnte sie deshalb nicht sehen, und die erste Runde ließ sie
	   aus. Aufgefallen sind sie erst beim Nachmessen mit GEÖFFNETER Schublade:
	   allein die Seitenleiste stellte 43 Ziele unter 44 px, und sie ist mobil das
	   Hauptnavigationselement. `align-items: center` hält den Text mittig, wenn
	   die Mindesthöhe über die Zeilenhöhe hinausgeht.

	   `a.fm-user-menu-item` steht bewusst ZUSÄTZLICH da und ist keine Dopplung:
	   fm-design-system.css setzt dort `min-height: 2.5rem` über eine Selektorliste,
	   die mit `#fm-app a.fm-user-menu-item` beginnt (Zeile 1465). Der Typ-Selektor
	   hebt die Spezifität an, und die Quellreihenfolge allein reicht dann nicht mehr —
	   ohne diese Zeile bleibt der Eintrag bei 40 px. Nachgemessen, nicht vermutet. */
	#fm-app .fm-nav-link,
	#fm-app .fm-nav-group-title,
	#fm-app .fm-cat-link,
	#fm-app .fm-subnav-link,
	#fm-app .fm-user-menu-item,
	#fm-app a.fm-user-menu-item,
	#fm-app .fm-bell-toggle {
		display: flex;
		align-items: center;
		min-height: var(--fm-touch-min);
	}
}

/* ---------------------------------------------------------------------------
   Vertragsvorlagen: Platzhalterübersicht (Redaktionshilfe).

   Der Platzhaltertext ist ohne JavaScript reiner Lesetext zum Abschreiben.
   Erst `fm-contract-template-placeholders.js` setzt `is-interactive` auf den
   Block — deshalb hängt JEDE Klick-Affordanz (Zeiger, Fokusring, Schwebezustand)
   an dieser Klasse. Ohne sie darf nichts nach Knopf aussehen, sonst verspricht
   die Oberfläche eine Bedienung, die es gerade nicht gibt.

   Keine neue Komponente: `fm-badge`, `fm-table`, `fm-accordion` kommen aus dem
   Designsystem, hier steht nur die Marke selbst.
   --------------------------------------------------------------------------- */
#fm-app .fm-placeholder-token {
	display: inline-block;
	font-family: var(--fm-font-mono);
	font-size: var(--fm-fs-xs);
	padding: var(--fm-s-1) var(--fm-s-2);
	border: 1px solid var(--fm-border);
	border-radius: var(--fm-r-sm);
	background: var(--fm-surface-2);
	white-space: nowrap;
}

#fm-app .fm-placeholder-help.is-interactive .fm-placeholder-token {
	cursor: pointer;
	border-color: var(--fm-accent-border);
	background: var(--fm-accent-soft);
	color: var(--fm-accent-strong);
}

#fm-app .fm-placeholder-help.is-interactive .fm-placeholder-token:hover,
#fm-app .fm-placeholder-help.is-interactive .fm-placeholder-token:focus-visible {
	background: var(--fm-accent);
	border-color: var(--fm-accent);
	color: var(--fm-n-0);
}

/* Touch-Ziel nur dort, wo mit dem Finger bedient wird — dieselbe Bedingung wie
   der Block weiter oben. Am Arbeitsplatz mit Maus bleibt die Zeile kompakt,
   sonst zerreißt eine Tabelle mit 15 Platzhaltern die Seite. */
@media (pointer: coarse), (max-width: 768px) {

	#fm-app .fm-placeholder-help.is-interactive .fm-placeholder-token {
		display: inline-flex;
		align-items: center;
		min-height: var(--fm-touch-min);
	}
}

/* Kartenmodus (< 640 px): die Platzhalter-Zelle traegt Marke + bis zu zwei
   Abzeichen. Die Systemregel legt `td` als Flexzeile ohne Umbruch an; mit dem
   `white-space: nowrap` der Marke sprengte die Zeile den Kartenrahmen und die
   Tabelle bekam einen waagerechten Rollbalken (nachgemessen bei 375 px:
   513 px Inhalt in 408 px Rahmen, waehrend die Bedingungstabelle ohne Marke
   sauber blieb). Die Marke selbst darf NICHT umbrechen — sie soll abschreibbar
   bleiben —, also weichen die Abzeichen in die naechste Zeile aus. */
@media ( max-width: 640px ) {

	#fm-app .fm-table-cards td.fm-placeholder-cell {
		flex-wrap: wrap;
		justify-content: flex-start;
		gap: var(--fm-s-1) var(--fm-s-2);
	}

	#fm-app .fm-table-cards td.fm-placeholder-cell::before {
		flex: 1 0 100%;
	}
}

/* Lange Komposita der Erklaerungstexte („Eigentuemergemeinschaften",
   „Preisaenderungsklausel") passen auf dem Handy in keine Kartenspalte und
   erzwangen einen waagerechten Rollbalken im Tabellenrahmen (nachgemessen bei
   375 px: 29 px bzw. 42 px). Umbruch im Wort loest das; die Marke selbst ist
   davon ausgenommen, sie bleibt in einer Zeile abschreibbar. */
#fm-app .fm-placeholder-table td,
#fm-app .fm-placeholder-table th {
	overflow-wrap: break-word;
	word-break: break-word;
}

/* Sechs Gruppentabellen untereinander: ohne feste Aufteilung rechnet jede ihre
   eigenen Spaltenbreiten aus, und die Trennlinien springen von Block zu Block.
   Feste Aufteilung ist hier gefahrlos, weil die Zellen im Wort umbrechen
   duerfen (Regel oben). Im Kartenmodus (< 640 px) ist die Tabelle ohnehin
   aufgeloest — deshalb nur oberhalb davon. */
@media ( min-width: 641px ) {

	#fm-app .fm-placeholder-table {
		table-layout: fixed;
	}

	#fm-app .fm-placeholder-table th:nth-child(1),
	#fm-app .fm-placeholder-table td:nth-child(1) {
		width: 30%;
	}

	#fm-app .fm-placeholder-table th:nth-child(2),
	#fm-app .fm-placeholder-table td:nth-child(2) {
		width: 45%;
	}

	/* Bedingungen: zwei Spalten, Name schmaler als die Herkunftserklaerung. */
	#fm-app .fm-placeholder-table-2col th:nth-child(1),
	#fm-app .fm-placeholder-table-2col td:nth-child(1) {
		width: 40%;
	}

	#fm-app .fm-placeholder-table-2col th:nth-child(2),
	#fm-app .fm-placeholder-table-2col td:nth-child(2) {
		width: 60%;
	}
}
