/* ISOCAL Relay - Gemeinsame Styles
 *
 * FARB-SSOT aller drei UIs (Admin-Panel, Kunden-Portal, Dispatcher-Portal).
 * Farbschema: ISOLY (Teal/Lime), Stand 2026-07-31.
 * Quelle der Marken-Tokens: ISOLY-DESIGN-TOKENS.md (Variante A).
 *
 * WICHTIGSTE AENDERUNG SEIT 2026-07-29: Obere Leiste und Dialogkoepfe sind
 * LIME mit TEAL-Schrift (vorher Teal-900 mit Weiss) - Anwender-Entscheidung
 * 2026-07-31, damit App und Weboberflaechen dasselbe Gesicht haben. Daran
 * haengen die Logo-Varianten: helle Flaeche verlangt isoly-logo.svg, nicht die
 * Reverse-Fassung. Begruendung und Kontrastwerte stehen bei .navbar-isocal.
 *
 * ── Zwei Regeln, die beim Aendern von Farben gelten ──────────────────────
 *
 * 1. DER AKZENT RICHTET SICH NACH DER FLAECHE, NICHT NACH DEM ELEMENT.
 *    Auf hellem Grund ist der Akzent Teal-600 (--isoly-accent), auf dunklem
 *    Grund Lime (--isoly-accent-on-dark). Wer ein Akzent-Token setzt, prueft
 *    ZUERST den Hintergrund der umgebenden Flaeche.
 *    Lime ist FLAECHENfarbe, nie Textfarbe auf Hell: Lime auf Weiss = 1,41:1.
 *
 * 2. JEDER KONTRASTWERT IST NACHGERECHNET (WCAG 2.1, Relativluminanz), nicht
 *    aus der Tokens-Doku uebernommen - deren Kontrasttabelle weicht 4-8 % ab.
 *    Belegte Abweichung: die Doku nennt Teal-500 #2C8F63 als "accent-onLight";
 *    nachgerechnet sind das nur 4,03:1 auf Weiss (unter AA). Dieses Projekt
 *    verwendet daher Teal-600 #1D6B4F (6,43:1 auf Weiss, 5,99:1 auf Teal-50).
 *
 * Herleitung der abgeleiteten Funktionsfarben: Die Funktionsfarben der
 * Tokens-Doku reissen als Text bzw. mit weisser Schrift das AA-Minimum
 * (Warnung #E8A13C 2,19:1, Fehler #D8503F 4,09:1, Info #3A8F86 3,85:1).
 * Sie wurden deshalb entlang EINER Achse abgeleitet: Farbton und Saettigung
 * bleiben fix, allein die HSL-Helligkeit wurde gesenkt, bis 4,5:1 gegen den
 * DUNKELSTEN Untergrund erreicht war, auf dem die Farbe vorkommt. Genommen
 * wurde jeweils der erste (= minimal veraenderte) passende Wert.
 * Vollstaendige Kontrast-Tabelle: docs/handoff/HANDOFF_2026-07-29_isoly-farbschema.md
 */

/* ── Schrift: Quicksand, lokal gehostet ───────────────────────────────────
 * Variable Font (300-700) in einer Datei je Subset, SIL Open Font License.
 * Bewusst KEIN Google-Fonts-CDN: kein Drittanbieter-Request aus dem Portal.
 * Pfad relativ zur CSS-Datei -> loest hinter dem Apache-Reverse-Proxy
 * genauso auf wie bei Direktzugriff (siehe .claude/rules/reverse-proxy-pfade.md).
 * latin-ext wird per unicode-range nur geladen, wenn es gebraucht wird.
 */
@font-face {
  font-family: 'Quicksand';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('fonts/quicksand-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Quicksand';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('fonts/quicksand-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  /* ── ISOLY-Marken-Skala (unveraendert aus den Design-Tokens) ─────────── */
  --teal-900: #0A3836;
  --teal-800: #0C433E;
  --teal-700: #13524A;
  --teal-600: #1D6B4F;
  --teal-500: #2C8F63;
  --teal-400: #5B736C;
  --teal-300: #B7CFC7;
  --teal-150: #E2EBE6;
  --teal-50:  #F4F8F5;
  --lime-500: #A3ED6B;
  --lime-600: #8AD94F;

  /* RGB-Tripel fuer rgba() und fuer die --bs-*-rgb-Variablen -
   * rgba() kann kein Hex-Token aufnehmen */
  --teal-900-rgb: 10, 56, 54;
  --teal-700-rgb: 19, 82, 74;
  --teal-600-rgb: 29, 107, 79;
  --teal-500-rgb: 44, 143, 99;
  --teal-50-rgb:  244, 248, 245;

  /* Weiss als Token: Text auf gesaettigter Markenflaeche. Bewusst ein
   * eigener Name statt #fff im Komponentencode - so ist an jeder Stelle
   * lesbar, dass es sich um eine Kontrastentscheidung handelt. */
  --isoly-on-brand: #FFFFFF;

  /* ── Semantische Rollen (Light Theme, kein Dark Mode) ────────────────── */
  --isoly-bg:             var(--teal-50);    /* Seitenhintergrund */
  --isoly-surface:        #FFFFFF;           /* Karten */
  --isoly-text:           var(--teal-900);   /* 12,01:1 auf Seitengrund */
  --isoly-text-muted:     var(--teal-400);   /* 4,76:1 auf Seitengrund */
  --isoly-border:         var(--teal-150);
  --isoly-radius:         14px;

  /* Akzent - Flaeche entscheidet, siehe Regel 1 im Kopf */
  --isoly-accent:         var(--teal-600);   /* auf HELL  6,43:1 auf Weiss */
  --isoly-accent-on-dark: var(--lime-500);   /* auf DUNKEL 9,12:1 auf Teal-900 */

  /* CTA: Lime-Flaeche mit Teal-Text (9,12:1), Hover Lime-600 (7,43:1) */
  --isoly-cta-bg:       var(--lime-500);
  --isoly-cta-bg-hover: var(--lime-600);
  --isoly-cta-fg:       var(--teal-900);

  /* ── Funktionsfarben: FLAECHE und TEXT sind getrennte Rollen ──────────
   * Die Flaechen-Variante traegt weissen bzw. Teal-Text; die Text-Variante
   * ist so weit abgedunkelt, dass sie auf Weiss, auf Teal-50 UND auf dem
   * eigenen Tint mindestens 4,5:1 erreicht. Beide Rollen zu vermischen war
   * der Hauptfehlerweg - deshalb zwei Token statt einem.
   */
  --isoly-success:      var(--teal-600);  /* + weiss = 6,43:1 */
  --isoly-success-text: #227C5C;          /* weiss 5,11 · teal-50 4,77 · Tint 4,52 */
  --isoly-success-tint: #DFF6EE;

  --isoly-danger:       #D54230;          /* + weiss = 4,51:1 (aus #D8503F abgedunkelt) */
  --isoly-danger-text:  #BD3726;          /* weiss 5,62 · teal-50 5,24 · Tint 4,51 */
  --isoly-danger-tint:  #F6E2DF;

  --isoly-warning:       #E8A13C;         /* + Teal-900-Text = 5,88:1 (NUR mit dunklem Text!) */
  --isoly-warning-hover: #E59421;         /* -6 % Helligkeit, Teal-900-Text noch 5,26:1 */
  --isoly-warning-text:  #976012;         /* weiss 5,25 · teal-50 4,90 · Tint 4,53 */
  --isoly-warning-tint:  #F6EDDF;

  --isoly-info:         #22828F;          /* + weiss = 4,51:1 */
  --isoly-info-text:    #1F7783;          /* weiss 5,22 · teal-50 4,87 · Tint 4,54 */
  --isoly-info-tint:    #DFF3F6;

  --isoly-neutral:      #6D7883;          /* + weiss = 4,50:1, neutrales Grau */

  /* RGB-Tripel der Funktionsfarben fuer die --bs-*-rgb-Variablen */
  --isoly-danger-rgb:   213, 66, 48;
  --isoly-warning-rgb:  232, 161, 60;
  --isoly-info-rgb:     34, 130, 143;
  --isoly-neutral-rgb:  109, 120, 131;

  /* Die alten Token mit dem Praefix aus der Blau-Zeit wurden mit diesem
   * Milestone ersatzlos entfernt: alle sieben Konsumenten (dispatcher.css,
   * live-status.html, machines.html) sind auf die --isoly-*-Namen
   * umgestellt. Aliase waeren hier nicht nur toter Code, sondern
   * irrefuehrend - ein Token, dessen Name "blue" enthaelt und das einen
   * Gruenwert traegt, ist eine Falle fuer den naechsten Leser.
   * (Kein Alt-Praefix als Literal in diesem Kommentar - sonst schlaegt die
   * Verifikations-Grep aus dem Handoff auf den Kommentar selbst an.)
   */

  /* ── Bootstrap-5.3-Variablen ueberschreiben ───────────────────────────
   * Die Utility-Klassen (.bg-*, .text-*, .border-*) lesen zur Laufzeit die
   * --bs-*-rgb-Variablen. Werden sie hier gesetzt, folgen alle Utilities
   * automatisch - auch solche, die erst zur Laufzeit aus JS zusammengesetzt
   * werden (z. B. `bg-${farbe}`) und per Grep nicht auffindbar waeren.
   * Buttons und Alerts lesen sie NICHT (Sass-generiert) und sind unten
   * einzeln ueberschrieben.
   */
  /* Konsequent per var() auf die Token oben verweisen statt die Hexwerte
   * zu wiederholen - sonst muesste ein kuenftiger Farbwechsel an zwei
   * Stellen greifen, genau das Risiko, das die Token-SSOT vermeiden soll. */
  --bs-primary:       var(--teal-600);      --bs-primary-rgb:   var(--teal-600-rgb);
  --bs-secondary:     var(--isoly-neutral); --bs-secondary-rgb: var(--isoly-neutral-rgb);
  --bs-success:       var(--isoly-success); --bs-success-rgb:   var(--teal-600-rgb);
  --bs-danger:        var(--isoly-danger);  --bs-danger-rgb:    var(--isoly-danger-rgb);
  --bs-warning:       var(--isoly-warning); --bs-warning-rgb:   var(--isoly-warning-rgb);
  --bs-info:          var(--isoly-info);    --bs-info-rgb:      var(--isoly-info-rgb);
  --bs-light:         var(--teal-50);       --bs-light-rgb:     var(--teal-50-rgb);
  --bs-dark:          var(--teal-900);      --bs-dark-rgb:      var(--teal-900-rgb);

  --bs-link-color:       var(--teal-600);  --bs-link-color-rgb:       var(--teal-600-rgb);
  --bs-link-hover-color: var(--teal-700);  --bs-link-hover-color-rgb: var(--teal-700-rgb);

  --bs-body-color: var(--teal-900);  --bs-body-color-rgb: var(--teal-900-rgb);
  --bs-body-bg:    var(--teal-50);   --bs-body-bg-rgb:    var(--teal-50-rgb);
  --bs-border-color: var(--isoly-border);

  /* .text-muted (62 Verwendungen) liest in Bootstrap 5.3 NICHT
   * --bs-secondary-rgb, sondern --bs-secondary-color - ohne diese Zeile
   * bliebe es Bootstrap-Neutralgrau statt Teal-400.
   * Teal-400: 5,10:1 auf Weiss, 4,76:1 auf Teal-50. */
  --bs-secondary-color: #5B736C;

  --bs-font-sans-serif: 'Quicksand', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
}

html {
  color-scheme: light;
}

body {
  background-color: var(--isoly-bg) !important;
  color: var(--isoly-text) !important;
  /* Quicksand wirkt in 400 zu duenn - Body-Schnitt ist 500 (Tokens-Doku) */
  font-family: 'Quicksand', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  font-weight: 500;
}

/* Explizite Textfarben - Bootstrap 5.3 CSS-Variablen koennen
   bei Override von --bs-primary Textfarben auf weiss setzen */
.card, .card-body, .modal-body {
  color: var(--isoly-text);
}

/* ── Modal-Header: Lime-Leiste mit Teal-Schrift (9,12:1) ───────────────
 *
 * UMGESTELLT 2026-07-31 auf Anwender-Entscheidung. Vorher Teal-900 mit
 * weisser Schrift. Anlass: Die App wurde auf Lime-Flaeche mit Teal-Schrift
 * umgestellt - obere Leiste und alle Dialoge -, und die Weboberflaechen
 * ziehen nach, damit App und Portal nicht auseinanderlaufen.
 *
 * Selbst nachgerechnet (WCAG, nicht aus der Tokens-Doku uebernommen):
 *   teal-900 auf lime-500   9,12:1   (Text-Schwelle 4,5:1 - reichlich)
 *   schwarz  auf lime-500  14,88:1   (Bootstrap-Standard des Schliesskreuzes)
 */
.modal-header {
  background-color: var(--isoly-cta-bg);
  color: var(--isoly-cta-fg);
}

.modal-header .modal-title {
  color: var(--isoly-cta-fg);
}

/* KEIN Weiss-Filter mehr. Er faerbte das Kreuz fuer die dunkle Leiste weiss;
 * auf Lime waere es mit 1,41:1 praktisch unsichtbar. Das Bootstrap-Original
 * ist schwarz und erreicht hier 14,88:1 - der Filter muss also nicht ersetzt,
 * sondern nur entfernt werden. */
.modal-header .btn-close {
  filter: none;
}

.form-label, label {
  color: var(--isoly-text);
}

.form-control, .form-select {
  color: var(--isoly-text);
}

h1, h2, h3, h4, h5, h6 {
  color: var(--isoly-text);
  font-weight: 700;
}

h3, h4, h5, h6 {
  font-weight: 600;
}

p, li, td, th {
  color: inherit;
}

/* ── Navbar ─────────────────────────────────────────────────────────────
 *
 * LIME-LEISTE MIT TEAL-SCHRIFT. Anwender-Entscheidung 2026-07-31.
 *
 * Sie ERSETZT die Entscheidung vom 2026-07-29 (Teal-900 mit weisser Schrift).
 * Deren Begruendung steht hier, weil sie nicht falsch war, sondern ueberholt:
 *
 *   "Die Portale sind dichte Datentabellen-UIs, in denen eine Lime-Leiste
 *    dauerhaft dominieren wuerde; ausserdem faerbt der bestehende Logo-Filter
 *    die PNG-Logos WEISS - auf Lime waeren sie mit 1,41:1 unsichtbar."
 *
 * Was sich geaendert hat: Die App wurde auf Lime mit Teal-Schrift umgestellt,
 * obere Leiste und alle Dialoge. App und Weboberflaechen sollen dasselbe
 * Gesicht haben - das wiegt schwerer als die Sorge um die Dominanz.
 *
 * DER LOGO-EINWAND WAR TECHNISCH RICHTIG und ist deshalb MITGELOEST, nicht
 * uebergangen:
 *   - Admin-Panel und Kunden-Portal trugen isoly-logo-reverse.svg, die
 *     Marken-Variante fuer DUNKLE Flaechen (weiss + Lime). Auf Lime waere sie
 *     mit 1,41:1 verschwunden. Sie sind auf isoly-logo.svg umgestellt - die
 *     normale Variante des Markenpakets, gemacht fuer helle Flaechen.
 *   - Das Dispatcher-Portal traegt ein blaues Fremd-PNG, das per Filter weiss
 *     gefaerbt wurde. Es steht jetzt in einem weissen Kasten (Anwender-
 *     Entscheidung); der Filter entfaellt. Siehe dispatcher/css/dispatcher.css.
 *
 * Kontraste selbst nachgerechnet (WCAG), Flaeche lime-500:
 *   teal-900  9,12:1   teal-800  7,88:1   teal-700  6,38:1   teal-600  4,55:1
 *   weiss     1,41:1   <- deshalb muss ALLES Weisse hier weichen
 */

.navbar-isocal {
  background-color: var(--isoly-cta-bg) !important;
  box-shadow: 0 2px 4px rgba(0,0,0,0.15);
}

/* Die Schriftfarbe steht hier AUSDRUECKLICH und nicht implizit.
 *
 * Beinahe-Fehler beim Umstellen auf Lime: Das Markup trug Bootstraps Klasse
 * `navbar-dark`, und die setzt `--bs-navbar-brand-color: #fff`. Die Flaeche war
 * umgestellt, der Markenname blieb weiss - 1,41:1 auf Lime, also praktisch
 * unsichtbar. Aufgefallen ist es erst im regel-check.
 *
 * Behoben an zwei Stellen, und beide sind noetig:
 *   1. Im Markup steht jetzt `navbar-light` - semantisch richtig, die Leiste
 *      IST hell.
 *   2. Diese Zeile. Sie ist die tragende: Ohne sie haengt die Lesbarkeit an
 *      einer Fremdklasse, die jemand beim naechsten Umbau versehentlich
 *      zuruecksetzt - und der Fehler faellt niemandem auf, weil er nur die
 *      Marke betrifft, nicht die Bedienung.
 */
.navbar-isocal .navbar-brand {
  color: var(--isoly-cta-fg) !important;
  font-weight: 700;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* KEIN Filter hier - und seit 2026-07-31 auch keine Reverse-Variante mehr.
 *
 * Die Geschichte dieser Stelle in drei Schritten, weil jeder davon eine
 * Falle hinterlassen hat:
 *
 *   bis 2026-07-30  `filter: brightness(0) invert(1)` faerbte das blaue
 *                   ISOCAL-PNG fuer die dunkle Leiste weiss.
 *   ab  2026-07-30  isoly-logo-reverse.svg - die Marken-Variante fuer DUNKLE
 *                   Flaechen (weiss + Lime). Der Filter musste weg, er haette
 *                   das Lime im Wuerfel herausgebrannt.
 *   ab  2026-07-31  isoly-logo.svg - die NORMALE Variante, denn die Leiste ist
 *                   jetzt hell. Die Reverse-Fassung waere auf Lime mit 1,41:1
 *                   verschwunden; sie ist fuer dunkle Flaechen gemacht, und der
 *                   Name sagt es.
 *
 * Merksatz fuer die naechste Aenderung: Die Logo-Variante haengt an der
 * HELLIGKEIT DER FLAECHE, nicht am Geschmack. Wer die Leiste wieder abdunkelt,
 * muss beide Stellen zusammen anfassen - Farbe und Logo.
 *
 * Das Dispatcher-Portal traegt ein blaues Fremd-PNG und loest es anders: weisser
 * Kasten statt Filter (dispatcher/css/dispatcher.css). Seitenspezifischer Behelf
 * gehoert zur Seite (.claude/rules/frontend-shared-code.md).
 *
 * Hoehe 28px ergibt bei Seitenverhaeltnis 838:234 eine Breite von 100px und
 * haelt damit die Marken-Mindestbreite von 96px ein (Tokens-Doku, Abschnitt
 * Schutzraum). Nicht weiter verkleinern - darunter nur das Symbol verwenden.
 */
.navbar-isocal .navbar-brand img {
  height: 28px;
}

/* Ruhender Zustand teal-700 (6,38:1), aktiver teal-900 (9,12:1) und fett.
 * Der Unterschied liegt damit auf ZWEI Merkmalen - Farbe und Strichstaerke -,
 * nicht auf Farbe allein: Wer Farbunterschiede schlecht wahrnimmt, erkennt die
 * aktive Seite trotzdem. Beide Werte liegen ueber der Textschwelle von 4,5:1. */
.navbar-isocal .nav-link {
  color: var(--teal-700) !important;
  transition: color 0.15s;
}

.navbar-isocal .nav-link:hover {
  color: var(--isoly-cta-fg) !important;
}

/* Aktive Seite auf HELLER Flaeche -> das dunkelste Teal. Hier stand vorher
 * --isoly-accent-on-dark (Lime), richtig fuer die dunkle Leiste von damals -
 * auf der Lime-Leiste waere Lime auf Lime mit 1,00:1 vollstaendig unsichtbar.
 * Der Token-Name sagt es bereits: "on-dark". */
.navbar-isocal .nav-link.active {
  color: var(--isoly-cta-fg) !important;
  font-weight: 600;
}

/* Der Benutzername ist Beiwerk, aber lesbar: teal-700 statt einer
 * Deckkraft-Abstufung. Ein durchscheinendes Teal auf Lime laege je nach Wert
 * unter der Schwelle, ohne dass man es der Zeile ansieht. */
.navbar-isocal .nav-link-user {
  color: var(--teal-700) !important;
  font-size: 0.85rem;
}

/* ── Cards ──────────────────────────────────────────────────────────────
 * Radius 14px bewusst nur auf Karten/Modals/Alerts - Buttons, Inputs und
 * Badges behalten den Bootstrap-Radius (kleinstes Layout-Risiko).
 */

.card {
  box-shadow: 0 1px 3px rgba(0,0,0,0.08);
  border: 1px solid var(--isoly-border);
  border-radius: var(--isoly-radius);
}

.modal-content {
  border-radius: var(--isoly-radius);
}

.stat-card {
  border-left: 4px solid var(--isoly-accent);
  transition: transform 0.15s;
}

.stat-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}

.stat-card .stat-value {
  font-size: 2rem;
  font-weight: 700;
  color: var(--isoly-accent);   /* 6,43:1 auf weisser Karte */
}

.stat-card .stat-label {
  font-size: 0.875rem;
  color: var(--isoly-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-weight: 600;
}

/* Zweite Zahl in einer Kachel - z. B. "7 offen" unter der Gesamtzahl. */
.stat-card .stat-sub {
  font-size: 0.8rem;
  color: var(--isoly-text-muted);
  margin-top: 0.15rem;
}

/* Hervorhebung, solange etwas offen ist. Das Token traegt 5,25:1 auf Weiss
   (dokumentiert an seiner Definition) und liegt damit ueber der
   WCAG-Schwelle 4,5:1 fuer Fliesstext. */
.stat-card .stat-sub.hat-offene {
  color: var(--isoly-warning-text);
  font-weight: 600;
}

/* Kachel als Link (Admin-Dashboard).
   Der Anker bringt KEINE eigene Optik mit - Rahmen, Schatten und der
   Hover-Effekt stecken in der Karte darin. height:100% ist noetig, damit das
   h-100 der Karte eine Bezugshoehe hat und alle Kacheln einer Reihe gleich
   hoch bleiben, auch wenn eine davon drei Zeilen traegt.
   Ohne href - die Rolle darf das Ziel nicht oeffnen - bleibt der Anker ein
   gewoehnlicher Block: keine Zeigerhand, kein Tab-Stopp. */
.stat-card-link {
  display: block;
  height: 100%;
  text-decoration: none;
  color: inherit;
}

.stat-card-link:focus-visible {
  outline: 2px solid var(--isoly-accent);
  outline-offset: 2px;
  border-radius: var(--isoly-radius);
}

/* ── Buttons ────────────────────────────────────────────────────────────
 * Primaer = CTA: Lime-Flaeche mit Teal-900-Text (9,12:1). Bootstrap-Buttons
 * lesen --bs-primary NICHT (Sass-generierte --bs-btn-*), daher explizit.
 */

.btn-primary {
  background-color: var(--isoly-cta-bg) !important;
  border-color: var(--isoly-cta-bg) !important;
  color: var(--isoly-cta-fg) !important;
  font-weight: 600;
}

.btn-primary:hover, .btn-primary:focus {
  background-color: var(--isoly-cta-bg-hover) !important;
  border-color: var(--isoly-cta-bg-hover) !important;
  color: var(--isoly-cta-fg) !important;
}

.btn-primary:active {
  background-color: var(--isoly-cta-bg-hover) !important;
  border-color: var(--teal-900) !important;
  color: var(--isoly-cta-fg) !important;
}

/* Outline-Primaer steht auf hellem Grund -> Teal-600, nicht Lime (1,41:1) */
.btn-outline-primary {
  color: var(--isoly-accent) !important;
  border-color: var(--isoly-accent) !important;
}

.btn-outline-primary:hover {
  background-color: var(--isoly-accent) !important;
  border-color: var(--isoly-accent) !important;
  color: var(--isoly-on-brand) !important;
}

/* Sekundaer = dunkle Teal-Flaeche mit weissem Text (12,87:1) */
.btn-secondary {
  background-color: var(--teal-900) !important;
  border-color: var(--teal-900) !important;
  color: var(--isoly-on-brand) !important;
}

.btn-secondary:hover, .btn-secondary:focus, .btn-secondary:active {
  background-color: var(--teal-800) !important;
  border-color: var(--teal-800) !important;
  color: var(--isoly-on-brand) !important;
}

.btn-outline-secondary {
  color: var(--isoly-neutral) !important;
  border-color: var(--isoly-neutral) !important;
}

.btn-outline-secondary:hover {
  background-color: var(--isoly-neutral) !important;
  color: var(--isoly-on-brand) !important;
}

.btn-success {
  background-color: var(--isoly-success) !important;
  border-color: var(--isoly-success) !important;
  color: var(--isoly-on-brand) !important;
}

.btn-success:hover, .btn-success:focus {
  background-color: var(--teal-700) !important;
  border-color: var(--teal-700) !important;
}

.btn-outline-success {
  color: var(--isoly-success-text) !important;
  border-color: var(--isoly-success) !important;
}

.btn-outline-success:hover {
  background-color: var(--isoly-success) !important;
  color: var(--isoly-on-brand) !important;
}

.btn-danger {
  background-color: var(--isoly-danger) !important;
  border-color: var(--isoly-danger) !important;
  color: var(--isoly-on-brand) !important;
}

.btn-danger:hover, .btn-danger:focus {
  background-color: var(--isoly-danger-text) !important;
  border-color: var(--isoly-danger-text) !important;
}

.btn-outline-danger {
  color: var(--isoly-danger-text) !important;
  border-color: var(--isoly-danger) !important;
}

.btn-outline-danger:hover {
  background-color: var(--isoly-danger) !important;
  color: var(--isoly-on-brand) !important;
}

/* Warnung traegt DUNKLEN Text - weiss auf #E8A13C waere 2,19:1 */
.btn-warning {
  background-color: var(--isoly-warning) !important;
  border-color: var(--isoly-warning) !important;
  color: var(--teal-900) !important;
}

/* Hover-Herleitung: Die Warnflaeche traegt Teal-900-Text. Abdunkeln SENKT
 * diesen Kontrast (5,88:1 bei -0 %, 4,22:1 bei -12 % Helligkeit - die
 * AA-Grenze liegt bei -12 %). Gewaehlt ist -6 % Helligkeit bei gleichem
 * Farbton und gleicher Saettigung: sichtbarer Hover mit Abstand zur Grenze. */
.btn-warning:hover, .btn-warning:focus {
  background-color: var(--isoly-warning-hover) !important;
  border-color: var(--isoly-warning-hover) !important;
  color: var(--teal-900) !important;
}

.btn-outline-warning {
  color: var(--isoly-warning-text) !important;
  border-color: var(--isoly-warning) !important;
}

.btn-outline-warning:hover {
  background-color: var(--isoly-warning) !important;
  color: var(--teal-900) !important;
}

.btn-info {
  background-color: var(--isoly-info) !important;
  border-color: var(--isoly-info) !important;
  color: var(--isoly-on-brand) !important;
}

.btn-info:hover, .btn-info:focus {
  background-color: var(--isoly-info-text) !important;
  border-color: var(--isoly-info-text) !important;
  color: var(--isoly-on-brand) !important;
}

/* Fokus-Ring in Markenfarbe statt Bootstrap-Blau */
.btn:focus, .btn:focus-visible {
  box-shadow: 0 0 0 0.25rem rgba(var(--teal-500-rgb), 0.4);
}

/* ── Badges ─────────────────────────────────────────────────────────────
 * Badges sind Kleinschrift (0,72-0,85rem) -> es gilt das Fliesstext-Minimum
 * 4,5:1, NICHT die 3:1-Grossschrift-Schwelle.
 */

.badge-online, .bg-success {
  background-color: var(--isoly-success) !important;
}

.badge-offline {
  background-color: var(--isoly-neutral) !important;
}

/* Warn-Badges brauchen dunklen Text (Bootstrap setzt teils weiss) */
.bg-warning {
  color: var(--teal-900) !important;
}

/* ── Tables ─────────────────────────────────────────────────────────────── */

.table td, .table th {
  vertical-align: middle;
}

.table thead th {
  background-color: var(--teal-150);
  color: var(--teal-700);          /* 7,40:1 auf Teal-150 */
  font-weight: 600;
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  border-bottom: 2px solid var(--isoly-accent);
}

.table tbody tr:hover {
  background-color: rgba(var(--teal-600-rgb), 0.04);
}

/* Bootstrap-Kontextzeile (Protokoll-Aenderungen) */
.table-info {
  --bs-table-bg: var(--teal-150);
  --bs-table-color: var(--teal-900);   /* 10,58:1 */
}

/* ── Login ──────────────────────────────────────────────────────────────── */

.login-container {
  max-width: 420px;
  margin: 60px auto;
}

.login-container .card {
  border-top: 4px solid var(--isoly-accent);
}

.login-logo {
  text-align: center;
  margin-bottom: 1.5rem;
}

.login-logo img {
  height: 48px;
}

.login-title {
  color: var(--teal-700);          /* 8,40:1 auf hellem Kartengrund */
  font-weight: 700;
}

/* ── Secrets/Codes ─────────────────────────────────────────────────────── */

.secret-display {
  font-family: 'Courier New', monospace;   /* Quicksand hat keinen Mono-Schnitt */
  font-size: 1.1rem;
  background: var(--isoly-warning-tint);
  border: 2px solid var(--isoly-warning);
  padding: 1rem;
  border-radius: 0.5rem;
  word-break: break-all;
  color: var(--isoly-text);
}

.secret-display .label {
  font-family: inherit;
  font-size: 0.8rem;
  color: var(--isoly-danger-text);   /* 4,84:1 auf Warn-Tint */
  font-weight: 600;
  display: block;
  margin-bottom: 0.25rem;
}

.code-display {
  font-family: 'Courier New', monospace;
  font-size: 1.5rem;
  letter-spacing: 0.15em;
}

/* ── Alerts ─────────────────────────────────────────────────────────────
 * Bootstrap-Alerts sind Sass-generiert und folgen den --bs-*-rgb nicht.
 * Hinweis: Die Tint-FLAECHEN von success und info liegen mit dE 5,4 nah
 * beieinander (beides helle Teal-Toene). Getragen wird die Unterscheidung
 * hier von Text (dE 18,1) und Rand (dE 20,2) - beide klar getrennt.
 */

.alert {
  border-radius: var(--isoly-radius);
}

/* Rahmen-Herleitung: gleicher Farbton wie die Funktionsfarbe, Saettigung auf
 * hoechstens 55 % begrenzt, Helligkeit gesenkt bis 3:1 gegen die eigene
 * Tint-Flaeche (Rahmen sind Nicht-Text, dort gilt 3:1, nicht 4,5:1).
 * Erreichte Werte je Rahmen gegen seine Flaeche: 3,03 / 3,02 / 3,00 / 3,00. */
.alert-success {
  --bs-alert-bg: var(--isoly-success-tint);
  --bs-alert-color: var(--isoly-success-text);
  --bs-alert-border-color: #2D9C74;
}

.alert-danger {
  --bs-alert-bg: var(--isoly-danger-tint);
  --bs-alert-color: var(--isoly-danger-text);
  --bs-alert-border-color: #CE6457;
}

.alert-warning {
  --bs-alert-bg: var(--isoly-warning-tint);
  --bs-alert-color: var(--isoly-warning-text);
  --bs-alert-border-color: #B47F34;
}

.alert-info {
  --bs-alert-bg: var(--isoly-info-tint);
  --bs-alert-color: var(--isoly-info-text);
  --bs-alert-border-color: #3097A5;
}

/* ── Tabs / Pills / Formularschalter ────────────────────────────────────
 * Diese drei ziehen ihre Farbe NICHT aus der a{}-Regel bzw. den Utilities
 * und waeren sonst die letzten verbliebenen Bootstrap-Blau-Stellen.
 */

.nav-tabs .nav-link {
  color: var(--isoly-accent);
}

.nav-tabs .nav-link.active {
  color: var(--teal-900);
  border-bottom-color: var(--isoly-surface);
}

.nav-pills {
  --bs-nav-pills-link-active-bg: var(--isoly-accent);
}

.form-check-input:checked {
  background-color: var(--isoly-accent);
  border-color: var(--isoly-accent);
}

.form-check-input:focus {
  border-color: var(--teal-500);
  box-shadow: 0 0 0 0.25rem rgba(var(--teal-500-rgb), 0.25);
}

/* ── Toast / Overlay ───────────────────────────────────────────────────── */

.toast-container {
  position: fixed;
  top: 1rem;
  right: 1rem;
  z-index: 9999;
}

#loading-overlay {
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  background: rgba(255,255,255,0.8);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 9999;
}

#loading-overlay .spinner-border {
  color: var(--isoly-accent) !important;
}

/* ── Links ──────────────────────────────────────────────────────────────
 * Teal-600 = Akzent auf hellem Grund (6,43:1 auf Weiss, 5,99:1 auf Teal-50).
 */

a {
  color: var(--isoly-accent);
}

a:hover {
  color: var(--teal-700);
}

/* ── Forms ──────────────────────────────────────────────────────────────── */

.form-control:focus, .form-select:focus {
  border-color: var(--teal-500);
  box-shadow: 0 0 0 0.2rem rgba(var(--teal-500-rgb), 0.25);
}

/* ══════════════════════════════════════════════════════════════════════════
   Lightbox — Bild gross anzeigen, ohne es herunterzuladen
   Markup erzeugt public/shared/lightbox.js selbst.

   Die Farbwerte hier sind DOKUMENTIERTE AUSNAHMEN der Token-Regel: Ein
   Verdunkelungs-Overlay und ein weisser Schliessen-Knopf darauf sind neutrale
   Flaechen ohne Markenbezug (.claude/rules/frontend-shared-code.md, Abschnitt
   "Dokumentierte Ausnahmen"). Ein Marken-Teal als Overlay wuerde das Bild
   einfaerben - genau das, was man beim Betrachten nicht will.
   ══════════════════════════════════════════════════════════════════════════ */

.lb-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, .88);
  z-index: 2000;            /* ueber Bootstrap-Modals (1055) */
  padding: 24px;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
.lb-overlay.lb-offen { display: flex; }

.lb-rahmen { position: relative; max-width: 94vw; max-height: 86vh; }

.lb-rahmen img {
  max-width: 94vw;
  max-height: 86vh;
  object-fit: contain;
  border-radius: 6px;
  display: block;
}

.lb-unterschrift {
  margin-top: 12px;
  text-align: center;
  color: rgba(255, 255, 255, .75);
  font-size: .9rem;
  max-width: 94vw;
  word-break: break-word;
}

.lb-schliessen {
  position: absolute;
  top: -14px;
  right: -14px;
  width: 34px;
  height: 34px;
  border: none;
  border-radius: 50%;
  background: #fff;
  color: #222;
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 2px 10px rgba(0, 0, 0, .5);
}
.lb-schliessen:hover { background: #eee; }

/* Anklickbare Vorschau: zeigt, dass mehr dahintersteckt */
.lb-vorschau { cursor: zoom-in; transition: opacity .12s ease; }
.lb-vorschau:hover { opacity: .85; }

/* ══════════════════════════════════════════════════════════════════════════
   Feedback-Detailansicht — Gliederung
   Die Ansicht bringt Kopfdaten, Beschreibung, Verlauf, Anhaenge und Kontext
   untereinander. Ohne sichtbare Trennung liest sich das als eine lange Wand.
   ══════════════════════════════════════════════════════════════════════════ */

/* FIGUR UND GRUND statt staerkerer Rahmen.
 *
 * Der Anwender: "Die Sektionen haben kaum einen Kontrast." Gemessen stimmt das
 * genau - der Rahmen teal-150 erreicht auf weissem Grund 1,22:1, die Schwelle
 * fuer nicht-textliche Elemente liegt bei 3,0:1.
 *
 * Sein Vorschlag war eine Lime-Umrandung. Nachgerechnet loest die das NICHT:
 *   teal-150 (bisher)  1,22:1      lime-500  1,41:1      lime-600  1,73:1
 *   teal-300           1,65:1      teal-400  5,10:1      teal-600  6,43:1
 * Lime waere 0,19 besser als der Istzustand - sichtbar waere es damit nicht.
 *
 * Die Ursache ist nicht die Rahmenfarbe, sondern dass ALLES WEISS AUF WEISS
 * steht: Dialoggrund weiss, Abschnitte weiss, Rahmen fast weiss. Deshalb
 * bekommt der Dialog einen getoenten Grund und die Abschnitte werden zu
 * weissen Karten darauf - dasselbe Muster wie .card auf --isoly-bg im
 * uebrigen Panel.
 *
 * DIE UNBEQUEME ZAHL, die hier zuerst fehlte: Weiss gegen teal-50 sind
 * 1,07:1 - NIEDRIGER als der verworfene Rahmen mit 1,22:1. Ueber das
 * Farbverhaeltnis allein waere die neue Loesung also schlechter, und sie
 * auszulassen waere selektiv gewesen.
 *
 * Was sie trotzdem traegt, ist der SCHLAGSCHATTEN. Tiefe ist ein anderer
 * Wahrnehmungskanal als Helligkeitsunterschied: Eine Kante mit Schatten liest
 * das Auge als Ebene, unabhaengig davon, wie nah die beiden Flaechen
 * beieinanderliegen. Genau deshalb funktioniert das Kartenmuster in hellen
 * Paletten ueberhaupt - und deshalb waere ein staerkerer Rahmen (teal-400,
 * 5,10:1) zwar messbar besser, aber optisch ein Kasten statt einer Karte.
 *
 * Der Lime-Akzent kommt trotzdem - als KANTE, wo er auffaellt, statt als
 * Umrandung, wo er verschwaende. 4 Pixel gesaettigtes Lime an der linken Seite
 * sieht man; ein 1 Pixel breiter Rahmen ringsum nicht.
 */
#ticket-modal .modal-body { background: var(--isoly-bg); }

.fb-abschnitt {
  margin-top: 1.25rem;
  background: var(--isoly-surface);
  border-radius: var(--isoly-radius);
  border-left: 4px solid var(--isoly-cta-bg);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .10);
  padding: 1rem 1.15rem;
}
.fb-abschnitt:first-child { margin-top: 0; }

/* Ueberschrift OHNE eigenen Balken - der Abschnitt traegt die Lime-Kante.
 *
 * Hier stand bis zur Umstellung ein Verlaufsbalken (Lime nach Teal) als
 * ::before-Pseudoelement, samt Herleitung. Er ist entfallen, weil zwei
 * Lime-Balken uebereinander - Kante am Abschnitt, Balken an der Ueberschrift -
 * miteinander konkurrierten, statt zu fuehren. Die Messwerte von damals gelten
 * unveraendert und stehen jetzt bei .fb-abschnitt, wo die Kante sitzt.
 *
 * Die Ueberschrift arbeitet stattdessen mit Groesse und einer Trennlinie
 * darunter - beides Mittel, die nicht von Farbkontrast abhaengen. */
.fb-abschnitt > h5 {
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--isoly-text);
  margin: 0 0 .85rem;
  padding-bottom: .5rem;
  border-bottom: 1px solid var(--isoly-border);
}

/* Kopfdaten - eine weisse Karte wie die Abschnitte, nur ohne Lime-Kante.
 *
 * BEINAHE-FEHLER, vom regel-check gefunden: Hier stand kurzzeitig eine
 * Toenung mit der Begruendung "auf der weissen Karte traegt die Toenung". Das
 * war schlicht falsch - .fb-kopf liegt GAR NICHT in einem .fb-abschnitt. Die
 * Kopfdaten werden in feedback-tickets.js direkt in #detail-body eingesetzt,
 * nicht ueber abschnitt(). Und #detail-body traegt seit dieser Umstellung
 * selbst --isoly-bg. Toenung auf gleicher Toenung, ohne Rahmen, ohne Schatten:
 * der Block waere vollstaendig verschwunden - schlechter als der Zustand, den
 * die Umstellung beheben sollte.
 *
 * Die Lehre: Eine Aussage ueber die UMGEBUNG eines Elements gehoert am DOM
 * geprueft, nicht angenommen. Im CSS sieht man dem Selektor nicht an, wo er
 * landet.
 *
 * Keine Lime-Kante: Die Kopfdaten sind Nachschlagewerk, kein Abschnitt. Der
 * Unterschied soll sichtbar bleiben. */
.fb-kopf {
  background: var(--isoly-surface);
  border-radius: var(--isoly-radius);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .10);
  padding: .8rem 1rem;
}
.fb-kopf dt { font-weight: 600; color: var(--isoly-text-muted); }
.fb-kopf dd { color: var(--isoly-text); }
.fb-kopf dl { margin-bottom: 0; }

/* Beschreibung: der Text des Melders, abgesetzt wie ein Zitat.
 * Die Lime-Kante wiederholt das Motiv der Abschnitte eine Ebene tiefer -
 * so ist erkennbar, dass hier jemand SPRICHT, nicht dass etwas aufgelistet
 * wird. */
.fb-beschreibung {
  background: var(--isoly-bg);
  border-left: 3px solid var(--isoly-cta-bg);
  border-radius: 0 var(--isoly-radius) var(--isoly-radius) 0;
  padding: .8rem 1rem;
}

/* ── Nachrichtenverlauf ──────────────────────────────────────────────── */

/* Kein eigener Rahmen mehr - der Abschnitt IST die Karte. */
.fb-strang { padding: 0; }

.fb-blase { max-width: 82%; border-radius: var(--isoly-radius); padding: .7rem .9rem; }

/* Der Anwender links auf heller Markenflaeche, der Bearbeiter rechts auf
   Weiss mit Rand: Die Richtung ist an Seite UND Farbe erkennbar - wer nur
   eines von beidem wahrnimmt, erkennt sie trotzdem. */
/* BEIDE Blasen sind gefuellt, seit die Abschnitte weisse Karten sind: Eine
 * weisse Blase auf weisser Karte waere nur noch am Rahmen erkennbar gewesen -
 * und der erreicht 1,22:1. Als Flaechen lesen sich beide als Form, unabhaengig
 * vom Kontrastverhaeltnis, und die Richtung bleibt an Seite UND Farbe
 * erkennbar. */
.fb-blase-melder     { background: var(--teal-150); color: var(--isoly-text); }
.fb-blase-bearbeiter { background: var(--isoly-bg); border: 1px solid var(--isoly-border); }

.fb-blase-kopf {
  display: flex;
  align-items: center;
  gap: .4rem;
  font-size: .8rem;
  color: var(--isoly-text-muted);
  margin-bottom: .35rem;
}

/* Statuswechsel: eine Linie durch den Strang, damit er als Einschnitt
   gelesen wird und nicht als weitere Nachricht */
.fb-status-zeile {
  display: flex;
  align-items: center;
  gap: .75rem;
  margin: .9rem 0;
  color: var(--isoly-text-muted);
  font-size: .85rem;
}
.fb-status-zeile::before,
.fb-status-zeile::after {
  content: "";
  flex: 1;
  border-top: 1px solid var(--isoly-border);
}

/* Eingabebereich abgesetzt vom Verlauf */
.fb-composer { border-top: 2px solid var(--isoly-border); margin-top: 1rem; padding-top: 1rem; }

/* ── Initialen-Avatare ───────────────────────────────────────────────── */

/*
 * Ein Kreis mit bis zu zwei Initialen als visueller Anker beim Ueberfliegen.
 *
 * DIE FARBE IST SCHMUCK, DIE INITIALEN SIND DIE AUSSAGE. Bei drei Farben
 * teilen sich Personen zwangslaeufig eine - deshalb steht der Name immer
 * daneben, und der Avatar traegt `aria-hidden`, damit eine Vorlesehilfe ihn
 * nicht zusaetzlich buchstabiert.
 *
 * DIE PALETTE IST GEMESSEN, NICHT GEWAEHLT (2026-08-01):
 *
 *   - Nur die Marken-Rampe kommt in Frage. Die Funktionsfarben sind belegt:
 *     Ein roter Avatar saehe aus wie ein Fehler, ein gruener wie "erledigt".
 *   - teal-500 faellt raus: weisser Text erreicht darauf keine 4,5:1.
 *   - teal-600 faellt raus, obwohl der Kontrast stimmt - es ist mit dE 0,0
 *     EXAKT die Farbe der BEHOBEN-Plakette.
 *   - teal-800 faellt raus: nur dE 3,9 von teal-900 entfernt, in einer
 *     Dreier-Palette also verschenkt.
 *
 * Es bleiben (weisser Text / Abstand untereinander / Abstand zur naechsten
 * Statusfarbe):
 *
 *   teal-900  12,87:1    dE >= 8,8      dE 18,9 zu BEHOBEN
 *   teal-700   9,00:1                   dE 10,7 zu BEHOBEN
 *   teal-400   5,10:1                   dE 12,5 zu ABGELEHNT
 *
 * Verwechslung mit einer Statusplakette ist zusaetzlich durch die FORM
 * ausgeschlossen: Kreis mit zwei Buchstaben gegen Pille mit einem Wort.
 */
.fb-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;               /* darf im Flex-Kontext nicht schrumpfen */
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .02em;
  line-height: 1;
  color: var(--isoly-on-brand);
  user-select: none;            /* beim Kopieren einer Zeile nicht mitnehmen */
}
.fb-avatar-klein { width: 1.5rem; height: 1.5rem; font-size: .65rem; }

.fb-avatar-0 { background: var(--teal-900); }
.fb-avatar-1 { background: var(--teal-700); }
.fb-avatar-2 { background: var(--teal-400); }

/* ── Kennzahlen der Feedback-Uebersicht ──────────────────────────────── */

/*
 * Eine Reihe kleiner Kacheln ueber der Liste.
 *
 * WARUM `flex-wrap` UND KEIN FESTES RASTER: Die Zahl der Kacheln ist nicht
 * konstant - "ohne jede Antwort" erscheint nur, wenn es solche Meldungen gibt
 * (die Kachel ist die ehrliche Gegenzahl zum Mittelwert und waere bei 0 nur
 * Rauschen). Ein Raster mit fester Spaltenzahl liesse an dieser Stelle eine
 * Luecke.
 *
 * Die Kacheln sind ABSICHTLICH nicht anklickbar. Ein Filtersprung waere
 * naheliegend, aber die Kennzahlen gelten fuer den GESAMTEN Bestand, die
 * Filter nur fuer die Liste - ein Klick auf "12 neu" fuehrte zu einer Liste,
 * deren Kopfzahl dann etwas anderes zaehlt als die Kachel darueber. Das waere
 * eine eigene Entscheidung, kein Nebeneffekt.
 */
.fb-statistik {
  display: flex;
  flex-wrap: wrap;
  gap: .75rem;
}

.fb-kachel {
  flex: 1 1 8.5rem;          /* mitwachsend, aber nie schmaler als lesbar */
  min-width: 8.5rem;
  background: var(--isoly-surface);
  border-radius: var(--isoly-radius);
  border-left: 4px solid var(--isoly-border);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .10);
  padding: .7rem .9rem;
}
/* Die Reaktionszeit-Kacheln tragen Text statt einer Zahl und brauchen mehr
   Platz - sonst bricht "2 Tage 3 h" mitten im Wert um. */
.fb-kachel-weit { flex: 2 1 14rem; min-width: 14rem; }

.fb-kachel-wert {
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1.1;
  color: var(--isoly-text);
}
.fb-kachel-titel {
  font-size: .8rem;
  font-weight: 600;
  color: var(--isoly-text);
  margin-top: .1rem;
}
.fb-kachel-unten {
  font-size: .75rem;
  color: var(--isoly-text-muted);
  margin-top: .15rem;
}

/* Zwei Kanten heben etwas hervor - dieselbe Sprache wie bei den Karten:
   Lime fuer "frisch da", Rot fuer "jemand wartet". Die Kante traegt die
   Aussage NICHT allein; die Beschriftung sagt dasselbe in Worten. */
.fb-kachel-neu     { border-left-color: var(--lime-500); }
.fb-kachel-warnung { border-left-color: var(--isoly-danger); }

/* ── Chat-Uebersicht der Meldungen ───────────────────────────────────── */

/*
 * Die Uebersicht ist eine Folge von Karten statt einer Tabelle - der Text der
 * Meldung ist direkt lesbar, der Verlauf klappt auf.
 *
 * DIE KARTE MUSS ALLES TRAGEN, was die acht Tabellenspalten zeigten (Nummer,
 * Typ, Titel, Melder, Instanz, Zeit, Anhaenge, Status). Nur deshalb ist es
 * vertretbar, die Tabelle ersatzlos aufzugeben statt einen zweiten Renderpfad
 * zu pflegen.
 *
 * Der Aufbau ist deshalb eine LESEREIHENFOLGE, keine Spaltenordnung:
 *   Kopf    - Kennungen und Zustand (Nummer, Typ, Status, Zeit)
 *   Titel   - worum es geht
 *   Quelle  - wer, wo, wie viele Anhaenge
 *   Text    - was zuletzt gesagt wurde
 *   Fuss    - aufklappen / bearbeiten
 */
.fb-liste { display: flex; flex-direction: column; gap: .85rem; }

.fb-karte {
  background: var(--isoly-surface);
  border-radius: var(--isoly-radius);
  border-left: 4px solid var(--isoly-border);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .10);
  padding: .9rem 1.1rem;
  cursor: pointer;
}
.fb-karte:hover { box-shadow: 0 2px 8px rgba(0, 0, 0, .14); }

/* Unbeantwortet: die Kante wechselt auf Rot - dasselbe Signal wie die
 * Plakette, aber am Rand der ganzen Karte und damit beim Ueberfliegen einer
 * langen Liste sichtbar, ohne jede Zeile zu lesen. Die Plakette bleibt
 * trotzdem: Farbe allein darf nie der einzige Traeger einer Aussage sein. */
.fb-karte-offen { border-left-color: var(--isoly-danger); }

.fb-karte-kopf {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .4rem;
  margin-bottom: .45rem;
}
.fb-nr { color: var(--isoly-text-muted); font-size: .82rem; }
/* Die Zeit rutscht ans Ende der Zeile - sie ist Beiwerk, kein Kennzeichen. */
.fb-karte-zeit { margin-left: auto; color: var(--isoly-text-muted); font-size: .8rem; }

.fb-karte-titel {
  font-size: 1.02rem;
  font-weight: 700;
  color: var(--isoly-text);
  margin: 0 0 .2rem;
}
/* Flex, damit der Avatar neben dem Text steht und nicht darueber. Der Text
   darf umbrechen, der Kreis nicht schrumpfen - deshalb `flex: 0 0 auto` am
   Avatar und kein `white-space` hier. */
.fb-karte-quelle {
  display: flex;
  align-items: center;
  gap: .55rem;
  font-size: .82rem;
  color: var(--isoly-text-muted);
  margin-bottom: .5rem;
}

/* Wer den zitierten Beitrag gesagt hat. Steht bewusst UEBER dem Zitat und
   nicht in der Quellenzeile: Dort steht der Melder der Meldung, hier der
   Sprecher dieses einen Beitrags - nach der ersten Antwort sind das zwei
   verschiedene Personen. */
.fb-karte-sprecher {
  font-size: .8rem;
  font-weight: 600;
  color: var(--isoly-text);
  margin-bottom: .15rem;
}

/* Die Vorschau ist ABSICHTLICH nicht auf eine Zeilenzahl beschnitten: Gekuerzt
 * wird bereits in der Datenbank (VORSCHAU_ZEICHEN), und zweimal zu kuerzen
 * hiesse, dass die Auslassungspunkte auf einmal an einer anderen Stelle stehen
 * als der tatsaechliche Schnitt. Ein Rand nach links setzt sie als Zitat ab. */
.fb-karte-text {
  border-left: 3px solid var(--isoly-border);
  padding-left: .7rem;
  color: var(--isoly-text);
}
.fb-karte-text > :last-child { margin-bottom: 0; }

.fb-karte-fuss {
  display: flex;
  gap: 1rem;
  align-items: center;
  margin-top: .6rem;
}

/* Beide Knoepfe als Text, nicht als Flaeche: In einer Liste aus Karten waeren
   zwei gefuellte Schaltflaechen je Eintrag lauter als der Inhalt. */
.fb-aufklappen,
.fb-bearbeiten {
  background: none;
  border: 0;
  padding: 0;
  font-size: .85rem;
  font-weight: 600;
  color: var(--isoly-accent);       /* 6,43:1 auf Weiss */
  cursor: pointer;
}
.fb-aufklappen:hover,
.fb-bearbeiten:hover { text-decoration: underline; }
.fb-bearbeiten { margin-left: auto; color: var(--isoly-text-muted); }
.fb-pfeil { display: inline-block; font-size: .7rem; }

.fb-karte-strang {
  margin-top: .8rem;
  padding-top: .8rem;
  border-top: 1px solid var(--isoly-border);
}

/* ── Status- und Typ-Plaketten ───────────────────────────────────────── */

/*
 * ZWEI ACHSEN, ZWEI FORMEN - und das ist der eigentliche Entwurf, nicht die
 * Farbwahl.
 *
 * Eine Meldung traegt gleichzeitig einen BEARBEITUNGSSTATUS (wo sie im
 * Arbeitsablauf steht) und einen TYP (was fuer eine Meldung sie ist). Beides
 * gefuellt einzufaerben ergibt neun Farbflaechen nebeneinander und ist
 * schlechter lesbar als der farblose Zustand davor, nicht besser. Deshalb:
 *
 *   Status = gefuellte PILLE   (rund, gesaettigt)
 *   Typ    = getoente TAFEL    (eckig, blass, mit Marker links)
 *
 * An der Form sieht man ohne Lesen, welche Achse man vor sich hat. Die Farbe
 * unterscheidet dann INNERHALB der Achse.
 *
 * ── Warum der Typ nicht ebenfalls gesaettigt ist ──
 *
 * Nachgemessen (CIEDE2000, Werkzeug zuvor gegen die Testdaten von
 * Sharma/Wu/Dalal 2005 geprueft): Die blassen Toenungen unterscheiden sich
 * UNTEREINANDER nicht ausreichend - WUNSCH gegen FRAGE liegt bei dE 5,4, weit
 * unter den geforderten 15. Der Signaltraeger ist deshalb nicht die Flaeche,
 * sondern der gesaettigte TEXT: dort ist das schwaechste Paar dE 18,1.
 *
 * Genau darum uebernimmt der Marker links die Textfarbe (currentColor) und
 * nicht die gesaettigte Familienfarbe. Der zuerst geplante Weg - Marker in
 * --isoly-warning auf --isoly-warning-tint - erreicht 1,89:1 und waere bei
 * FACHAUSSAGE praktisch unsichtbar gewesen. Ueber currentColor gilt fuer den
 * Marker automatisch derselbe Wert wie fuer den Text, also >= 4,5:1 in allen
 * vier Faellen.
 *
 * ── Die gemessenen Werte ──
 *
 * Alle Farben stammen aus dem Token-Block oben; es entsteht KEIN neuer
 * Farbwert. Text auf Flaeche, Soll >= 4,5:1 (Plaketten sind Kleinschrift - die
 * 3:1-Schwelle fuer Grossschrift gilt hier NICHT):
 *
 *   NEU          lime-500  + teal-900       9,12:1
 *   ANGENOMMEN   info      + on-brand       4,51:1
 *   IN_ARBEIT    warning   + teal-900       5,88:1
 *   BEHOBEN      success   + on-brand       6,43:1
 *   ABGELEHNT    neutral   + on-brand       4,50:1
 *   unbeantwortet danger   + on-brand       4,51:1
 *   FEHLER/WUNSCH/FRAGE/FACHAUSSAGE: die -text/-tint-Paare, 4,51-4,54:1
 *
 * Unterscheidbarkeit innerhalb der Status-Gruppe: schwaechstes Paar
 * ANGENOMMEN/ABGELEHNT dE 16,3. Innerhalb der Typ-Gruppe (am Text gemessen):
 * WUNSCH/FRAGE dE 18,1.
 *
 * ── Zwei behobene Befunde des Ist-Zustands ──
 *
 * 1. NEU und BEHOBEN waren dieselbe Farbe, dE 0,0. Beide liefen ueber
 *    Bootstrap-Klassen, und der Token-Block bildet --bs-primary UND
 *    --bs-success auf --teal-600 ab. Die beiden Enden des Arbeitsablaufs sahen
 *    identisch aus. Das faellt niemandem als Fehler auf - man haelt es fuer
 *    Absicht.
 * 2. ANGENOMMEN trug "bg-info text-dark" und erreichte 2,85:1.
 *    --isoly-info ist fuer WEISSEN Text gerechnet (4,51:1).
 *
 * Deshalb eigene Klassen statt Bootstrap: Wer eine Bootstrap-Klasse waehlt,
 * waehlt eine Farbe, die er nicht sieht - sie steht drei Indirektionen
 * entfernt im Token-Block.
 *
 * 3. Das Kennzeichen "unbeantwortet" trug dasselbe Amber wie IN_ARBEIT
 *    (dE 0,0) - und beide stehen unmittelbar nebeneinander. Es ist eine dritte
 *    Achse (ein Merker, kein Zustand) und bekommt Rot: gegen alle fuenf Status
 *    ist das schwaechste Paar dE 31,9.
 */
.fb-badge {
  display: inline-block;
  padding: .28em .62em;
  font-size: .78rem;
  font-weight: 600;
  line-height: 1.25;
  white-space: nowrap;
  border-radius: 999px;          /* Pille - die Status-Form */
}

/* Status: gefuellt.
 *
 * NEU nimmt --lime-500/--teal-900 direkt und NICHT das CTA-Paar, obwohl das
 * heute dieselben Werte traegt: Das CTA-Paar bedeutet "hier klicken". Wer es
 * eines Tages verschiebt, verschoebe sonst stillschweigend einen Status mit. */
.fb-st-neu        { background: var(--lime-500);      color: var(--teal-900); }
.fb-st-angenommen { background: var(--isoly-info);    color: var(--isoly-on-brand); }
.fb-st-in-arbeit  { background: var(--isoly-warning); color: var(--teal-900); }
.fb-st-behoben    { background: var(--isoly-success); color: var(--isoly-on-brand); }
.fb-st-abgelehnt  { background: var(--isoly-neutral); color: var(--isoly-on-brand); }

/* Merker: der Anwender wartet auf Antwort. */
.fb-offen { background: var(--isoly-danger); color: var(--isoly-on-brand); }

/* Typ: getoente Tafel mit Marker. Eckig statt rund - der Formunterschied. */
.fb-typ {
  border-radius: 4px;
  border-left: 3px solid currentColor;
  padding-left: .5em;
}
.fb-ty-fehler      { background: var(--isoly-danger-tint);  color: var(--isoly-danger-text); }
.fb-ty-wunsch      { background: var(--isoly-success-tint); color: var(--isoly-success-text); }
.fb-ty-frage       { background: var(--isoly-info-tint);    color: var(--isoly-info-text); }
.fb-ty-fachaussage { background: var(--isoly-warning-tint); color: var(--isoly-warning-text); }

/* Unbekannter Wert - bewusst als Stoerung erkennbar, nicht als gueltiger Wert.
 *
 * Die Vorgaenger-Fassung fiel auf dieselbe Klasse zurueck wie ABGELEHNT. Ein
 * fehlerhafter Wert saehe damit aus wie eine regulaer abgelehnte Meldung, und
 * niemand kaeme auf die Idee nachzusehen. Der gestrichelte Rahmen sagt
 * "hier stimmt etwas nicht" - genau die Sorte stiller Verwechslung, die den
 * Job-Status schon einmal getroffen hat (sql/024). */
.fb-unbekannt {
  background: var(--isoly-surface);
  color: var(--isoly-text-muted);
  border: 1px dashed var(--isoly-border);
}

/* ── Anhaenge ────────────────────────────────────────────────────────── */

/* Kacheln: getoente Flaeche mit LIME-KANTE - der Wunsch des Anwenders, aber
 * als Kante statt als Umrandung.
 *
 * Eine Lime-Umrandung ringsum erreicht auf hellem Grund 1,41:1 und waere
 * praktisch unsichtbar; dieselben vier Pixel an EINER Seite sieht man. Die
 * Trennung von der weissen Karte darunter macht die Toenung, nicht die Kante -
 * die Kante ist der Akzent.
 *
 * overflow:hidden haelt das Vorschaubild in der abgerundeten Ecke. */
.fb-anhang {
  background: var(--isoly-bg);
  border-left: 4px solid var(--isoly-cta-bg);
  border-radius: 0 var(--isoly-radius) var(--isoly-radius) 0;
  overflow: hidden;
  height: 100%;
}
/* VOLLSTAENDIGE Anzeige: `contain` statt `cover`. Mit `cover` fuellte das Bild
   die Kachel und wurde dabei mittig beschnitten - bei einem hohen
   Handy-Screenshot sah man nur den Mittelstreifen, also gerade nicht das, was
   der Melder zeigen wollte.

   Die feste Hoehe bleibt trotzdem: `contain` zeigt das Bild vollstaendig
   INNERHALB der Kachel, der Rest bleibt Hintergrundflaeche. Das Raster bleibt
   damit gleichmaessig - die Kacheln unterschiedlich hoch werden zu lassen waere
   nicht noetig, um alles zu sehen.

   HERLEITUNG DER 220 PIXEL - der bindende Fall ist das Hochformat:

   Bei `contain` bestimmt die Hoehe die Darstellung, sobald das Bild hoeher als
   breit ist. Fuer ein Handy-Seitenverhaeltnis von 9:19,5 (verbreitetes Format
   aktueller Geraete) gilt: Breite = Hoehe * 9 / 19,5.

   ZIEL: mindestens 100 Pixel Bildbreite. Das ist eine Lesbarkeits-Entscheidung
   und als solche benannt - darunter ist ein Screenshot ein Farbfleck, in dem
   sich weder Gliederung noch Dialogfenster ausmachen lassen, und eine Vorschau,
   die nichts erkennen laesst, erfuellt ihren Zweck nicht.

   Daraus folgt die Hoehe:  100 * 19,5 / 9 = 216,7  ->  aufgerundet 220.

   Gegenprobe mit 220:
     9:19,5 (Hochformat Handy)   220 * 9/19,5 = 101,5 px   ->  Ziel erfuellt
     9:16   (aeltere Geraete)    220 * 9/16   = 123,8 px   ->  reichlich
     16:9   (Querformat)         Breite bindet, volle Kachelbreite ~300 px
   Randfall winziges Bild (z. B. 40x40): wird nicht hochskaliert, `contain`
   vergroessert nicht - es steht mittig auf der Flaeche.

   Der alte Wert 170 verfehlte das Ziel deutlich: 170 * 9/19,5 = 78,5 px. */
.fb-anhang-bild {
  width: 100%;
  height: 220px;
  object-fit: contain;
  display: block;
  /* Weiss, nicht die Kachelfarbe: Bei `contain` bleibt neben einem Hochformat
     Flaeche frei. Waere sie wie die Kachel getoent, verschwaemme das Bild mit
     ihr - so steht es wie in einem Passepartout. */
  background: var(--isoly-surface);
  border-bottom: 1px solid var(--isoly-border);
}
.fb-anhang-fuss { padding: .6rem .75rem; }
