/* ============================================================
   Output Management — Design System
   Emerald admin look, light + dark, Inter + JetBrains Mono
   ============================================================ */

:root {
  /* Emerald primary (Tailwind emerald-600/500) */
  --color-primary:        #2e3861;
  --color-primary-hover:  #232b4d;
  --color-primary-soft:   #eef0f7;
  --color-primary-ring:   rgba(46, 56, 97, 0.22);
  --color-accent:         #ec613f;
  --color-accent-hover:   #d9502f;
  --color-accent-soft:    #fdeeea;

  /* Surfaces — LIGHT */
  --color-bg-app:         #f8fafc;   /* slate-50 */
  --color-bg-subtle:      #f1f5f9;   /* slate-100 */
  --color-surface:        #ffffff;
  --color-surface-hover:  #f8fafc;
  --color-border:         #e2e8f0;   /* slate-200 */
  --color-border-strong:  #cbd5e1;   /* slate-300 */
  /* Bewusst NICHT im Dark-Theme überschrieben: feste dunkle Fläche für Vorschauen, die
     unabhängig vom aktuellen App-Theme eine dunkle Umgebung simulieren (z. B. die
     Dark-Logo-Vorschau in TenantLogoSlotTile). Entspricht --color-surface im Dark-Theme. */
  --color-surface-inverse: #111827;

  --color-text-primary:   #0f172a;   /* slate-900 */
  --color-text-secondary: #334155;   /* slate-700 */
  --color-text-muted:     #64748b;   /* slate-500 */
  --color-text-faint:     #94a3b8;   /* slate-400 */
  /* Nur für Text auf --color-bg-subtle (z. B. Tabellenkopf): --color-text-muted allein reicht dort
     nicht für AA (4.34:1 statt 4.5:1), weil die Fläche etwas dunkler als Weiß ist. */
  --color-text-muted-on-subtle: #475569;   /* slate-600 */

  /* Semantic colors */
  --color-success:        #1e7d50;
  --color-success-bg:     #d8efe1;
  --color-success-text:   #0f5436;

  --color-warning:        #d97706;
  --color-warning-bg:     #fef3c7;
  --color-warning-text:   #92400e;

  --color-danger:         #dc2626;
  --color-danger-bg:      #fee2e2;
  --color-danger-text:    #991b1b;

  --color-info:           #2563eb;
  --color-info-bg:        #dbeafe;
  --color-info-text:      #1e40af;

  --color-neutral-bg:     #f1f5f9;
  --color-neutral-text:   #475569;

  /* Soft tints & borders for status badges/tiles */
  --color-success-soft:   rgba(22, 163, 74, .08);
  --color-danger-soft:    rgba(220, 38, 38, .08);
  --color-info-soft:      rgba(59, 130, 246, .08);
  --color-warning-soft:   rgba(234, 179, 8, .18);
  --color-success-border: rgba(22, 163, 74, .4);
  --color-danger-border:  #fecaca;
  --color-info-border:    rgba(59, 130, 246, .35);

  /* Shadows */
  --shadow-xs: 0 1px 2px 0 rgba(15, 23, 42, 0.04);
  --shadow-sm: 0 1px 3px 0 rgba(15, 23, 42, 0.06), 0 1px 2px -1px rgba(15, 23, 42, 0.04);
  --shadow-md: 0 4px 6px -1px rgba(15, 23, 42, 0.07), 0 2px 4px -2px rgba(15, 23, 42, 0.05);
  --shadow-lg: 0 10px 15px -3px rgba(15, 23, 42, 0.08), 0 4px 6px -4px rgba(15, 23, 42, 0.05);

  /* Radii */
  --radius-card:   12px;
  --radius-input:  8px;
  --radius-btn:    8px;
  --radius-pill:   9999px;

  /* Layout */
  --sidebar-w: 240px;
  --topbar-h:  64px;
  /* Höhe der fixen Wizard-Leiste (MailingWizardNav). Einzige Quelle für alle, die um sie
     herum rechnen: das padding-bottom von .page-wizard und die Höhe der gepinnten
     PDF-Vorschau in MailingLettersView. Die Leiste erzwingt den Wert selbst per
     min-height — vorher war er an drei Stellen unabhängig geraten.
     Herleitung: Treiber ist die Button-Höhe (.ds-btn, 36px) + 2×8px Padding + 1px
     Border-Top = 53px; der einzeilige Stepper (Label + Hinweiszeile ~30px) liegt darunter.
     Der Token rundet auf 56px auf, die Leiste hält ihn per min-height. Ändert sich Stepper,
     Button oder Padding, neu messen — min-height hält die Leiste nur nach unten, nicht
     nach oben; ist sie real höher, steht Inhalt hinter ihr. */
  --wizard-nav-h: 56px;

  --font-ui:   'Poppins', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

html[data-theme="dark"] {
  --color-primary:        #6e7aae;
  --color-primary-hover:  #8a96c4;
  --color-primary-soft:   rgba(110, 122, 174, 0.16);
  --color-primary-ring:   rgba(110, 122, 174, 0.32);
  --color-accent:         #ff8b69;
  --color-accent-hover:   #ffa787;
  --color-accent-soft:    rgba(255, 97, 54, 0.16);

  --color-bg-app:         #0a0f14;
  --color-bg-subtle:      #0f172a;
  --color-surface:        #111827;
  --color-surface-hover:  #1f2937;
  --color-border:         #1f2937;
  --color-border-strong:  #334155;

  --color-text-primary:   #f1f5f9;
  --color-text-secondary: #cbd5e1;
  --color-text-muted:     #94a3b8;
  --color-text-faint:     #64748b;
  /* Im Dark-Theme reicht --color-text-muted auf --color-bg-subtle bereits für AA — keine
     Verstärkung nötig. */
  --color-text-muted-on-subtle: var(--color-text-muted);

  --color-success:        #10b981;
  --color-success-bg:     rgba(16, 185, 129, 0.14);
  --color-success-text:   #6ee7b7;

  --color-warning:        #f59e0b;
  --color-warning-bg:     rgba(245, 158, 11, 0.14);
  --color-warning-text:   #fcd34d;

  --color-danger:         #ef4444;
  --color-danger-bg:      rgba(239, 68, 68, 0.14);
  --color-danger-text:    #fca5a5;

  --color-info:           #3b82f6;
  --color-info-bg:        rgba(59, 130, 246, 0.14);
  --color-info-text:      #93c5fd;

  --color-neutral-bg:     rgba(148, 163, 184, 0.12);
  --color-neutral-text:   #cbd5e1;

  /* Soft tints & borders for status badges/tiles */
  --color-success-soft:   rgba(16, 185, 129, .12);
  --color-danger-soft:    rgba(239, 68, 68, .12);
  --color-info-soft:      rgba(59, 130, 246, .14);
  --color-warning-soft:   rgba(245, 158, 11, .16);
  --color-success-border: rgba(16, 185, 129, .45);
  --color-danger-border:  rgba(239, 68, 68, .45);
  --color-info-border:    rgba(59, 130, 246, .45);

  --shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.4);
  --shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.4), 0 1px 2px -1px rgba(0, 0, 0, 0.3);
  --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.45), 0 2px 4px -2px rgba(0, 0, 0, 0.3);
  --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -4px rgba(0, 0, 0, 0.3);
}

* { box-sizing: border-box; }
html, body, #root { height: 100%; }
body {
  margin: 0;
  font-family: var(--font-ui);
  font-size: 14px;
  line-height: 1.5;
  color: var(--color-text-primary);
  background: var(--color-bg-app);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-feature-settings: 'cv11', 'ss01', 'ss03';
}

.mono { font-family: var(--font-mono); font-feature-settings: normal; }

a { color: inherit; text-decoration: none; }
button { font-family: inherit; }

/* Scrollbars */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--color-border-strong); border-radius: 10px; border: 2px solid var(--color-bg-app); }
::-webkit-scrollbar-thumb:hover { background: var(--color-text-faint); }

/* ------------------------------------------------------------
   ds-card
   ------------------------------------------------------------ */
.ds-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-xs);
}
/* Kopfzeile jeder Karte. Verbindlicher Aufbau — kein Inline-Style, keine eigene Kopf-Klasse je
   Seite:

       <div class="ds-card-header">
           <div class="ds-card-title"><Icon Name="shield" /><h2>Sicherheit</h2></div>
           <button class="ds-btn ds-btn-sm">…</button>   @* optional, landet rechts *@
       </div>

   Die Titelgruppe schiebt alles Folgende per margin-right:auto nach rechts. Bewusst kein
   justify-content:space-between mehr: das riss Kopfzeilen mit drei Kindern (Titel, Badge, Knopf)
   gleichmäßig auseinander und schob bei Icon+Überschrift als Geschwistern die Überschrift an den
   rechten Rand. Ohne Wrapper liegt jetzt alles linksbündig — falsch aussehen kann es, zerreißen
   nicht. */
.ds-card-header {
  padding: 16px 20px;
  border-bottom: 1px solid var(--color-border);
  display: flex;
  align-items: center;
  gap: 12px;
}
.ds-card-header h2, .ds-card-header h3 { margin: 0; font-size: 14px; font-weight: 600; color: var(--color-text-primary); letter-spacing: -0.005em; }
.ds-card-title {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  margin-right: auto;
}
/* Das Icon steht als stilles Ordnungsmerkmal vor dem Titel, nicht als Blickfang: gedämpfte Farbe,
   und es schrumpft nie (sonst quetscht ein langer Titel es zusammen). */
.ds-card-title > svg { flex: 0 0 auto; color: var(--color-text-muted); }
.ds-card-body { padding: 20px; }

/* Quadratische Symbolplakette im Primärton — der Kopf einer Aktionskachel: die Upload-Dropzone
   (FileExplorer) und die beiden Quellen-Kacheln der Anlagen-Karte tragen dieselbe. Steht global,
   weil Scoped CSS sie zwischen zwei Komponenten nicht teilen kann und die Plakette sonst zweimal
   gepflegt werden müsste. Die Kantenlänge kommt über --ds-tile-badge-size vom Aufrufer; den
   Schwebe-Effekt beim Überfahren setzt .ds-tile (weiter unten, nach .ds-btn). Eigene Hover-Ziele
   außerhalb einer Kachel — etwa die Dropzone im Drag-over-Zustand — bringen ihn selbst mit. */
.ds-tile-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--ds-tile-badge-size, 30px);
  height: var(--ds-tile-badge-size, 30px);
  border-radius: 9px;
  background: var(--color-primary-soft);
  color: var(--color-primary);
  transition: transform 0.15s;
}

/* Karte einer Anmeldeseite im EmptyLayout (Mandantenwahl, Umschalter): schmale, mittige Karte mit
   erklärendem Vorspann und Fußzeile. Steht global statt als Scoped CSS je Seite, weil beide Seiten
   exakt dasselbe Gerüst zeigen — eine Änderung an der Breite oder am Vorspann müsste sonst an zwei
   Stellen nachgezogen werden. */
.ds-auth-card { max-width: 420px; width: 100%; }
.ds-auth-intro { font-size: 13px; color: var(--color-text-secondary); line-height: 1.6; margin: 0 0 16px; }
.ds-auth-alert { margin-bottom: 16px; }
.ds-auth-footer { margin-top: 18px; text-align: center; font-size: 12.5px; color: var(--color-text-muted); }

/* ------------------------------------------------------------
   ds-btn
   ------------------------------------------------------------ */
.ds-btn {
  --btn-bg: var(--color-surface);
  --btn-fg: var(--color-text-primary);
  --btn-border: var(--color-border);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 36px;
  padding: 0 14px;
  font-size: 13px;
  font-weight: 500;
  color: var(--btn-fg);
  background: var(--btn-bg);
  border: 1px solid var(--btn-border);
  border-radius: var(--radius-btn);
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s, color 0.12s, transform 0.05s;
  white-space: nowrap;
  user-select: none;
}
/* Hover setzt --btn-bg statt background: sonst überschreibt diese Regel wegen höherer
   Spezifität den Fill der Solid-Varianten (primary/success/danger-solid) mit der Flächenfarbe. */
.ds-btn:hover:not(:disabled) { --btn-bg: var(--color-surface-hover); }
.ds-btn:active:not(:disabled) { transform: translateY(0.5px); }
.ds-btn:focus-visible { outline: 2px solid var(--color-primary-ring); outline-offset: 2px; }
.ds-btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* bewusst weiß: --btn-fg auf den Solid-Button-Varianten liegt auf der jeweiligen Statusfarbe
   (--btn-bg) und ist damit themenunabhängig; ebenso color-mix(...#000) für den Hover-Verdunkler. */
.ds-btn-primary {
  --btn-bg: var(--color-primary);
  --btn-fg: #ffffff;
  --btn-border: var(--color-primary);
}
.ds-btn-primary:hover:not(:disabled) { --btn-bg: var(--color-primary-hover); --btn-border: var(--color-primary-hover); }
/* Im Dark-Theme ist --color-primary bewusst aufgehellt (Lesbarkeit von Links/Icons auf dunklem
   Grund) und reicht als Fläche hinter weißem Buttontext nicht für AA (4.15:1). Nur die
   Button-Fläche bekommt hier einen dunkleren Ton, damit das helle --color-primary an seinen
   übrigen Einsatzstellen unangetastet bleibt. */
html[data-theme="dark"] .ds-btn-primary {
  --btn-bg: #565f88;
  --btn-border: #565f88;
}
/* Der generische Hover (Zeile darüber) hellt im Dark-Theme auf — bei einer ohnehin zu hellen
   Fläche würde das den Kontrast beim Hover weiter verschlechtern (2.9:1). Hier stattdessen wie im
   Light-Theme: dunkler statt heller. */
html[data-theme="dark"] .ds-btn-primary:hover:not(:disabled) {
  --btn-bg: #454e78;
  --btn-border: #454e78;
}

.ds-btn-ghost {
  --btn-bg: transparent;
  --btn-border: transparent;
  --btn-fg: var(--color-text-secondary);
}
.ds-btn-ghost:hover:not(:disabled) { --btn-bg: var(--color-surface-hover); }

.ds-btn-danger {
  --btn-bg: var(--color-surface);
  --btn-fg: var(--color-danger);
  --btn-border: var(--color-border);
}
.ds-btn-danger:hover:not(:disabled) { --btn-bg: var(--color-danger-bg); --btn-border: var(--color-danger); }

/* Gefüllte Ja/Nein-Varianten für eindeutige Entscheidungs-Aktionen (z. B. Freigeben/Ablehnen). */
.ds-btn-success {
  --btn-bg: var(--color-success);
  --btn-fg: #ffffff;
  --btn-border: var(--color-success);
}
.ds-btn-success:hover:not(:disabled) {
  --btn-bg: color-mix(in oklab, var(--color-success) 86%, #000);
  --btn-border: color-mix(in oklab, var(--color-success) 86%, #000);
}

.ds-btn-danger-solid {
  --btn-bg: var(--color-danger);
  --btn-fg: #ffffff;
  --btn-border: var(--color-danger);
}
.ds-btn-danger-solid:hover:not(:disabled) {
  --btn-bg: color-mix(in oklab, var(--color-danger) 86%, #000);
  --btn-border: color-mix(in oklab, var(--color-danger) 86%, #000);
}

.ds-btn-lg { height: 42px; padding: 0 18px; font-size: 14px; }
.ds-btn-sm { height: 30px; padding: 0 10px; font-size: 12px; }
.ds-btn-block { width: 100%; }

/* Aktionskachel: Symbolplakette (.ds-tile-badge) oben, eine kurze Beschriftung darunter, volle
   Spaltenbreite. Trägt nur die Geometrie — die drei Quellen der Anlagen-Karte sehen damit gleich
   aus, obwohl sie aus zwei verschiedenen Komponenten kommen (UploadDropzone und die beiden
   Schaltflächen der Karte) und Scoped CSS zwischen ihnen nichts teilen kann.

   Zur Kachel gehört auch ihre Fläche: der gedämpfte Grund hebt sie als Bedienfeld vom Weiß der
   Karte ab, und die Dropzone daneben trägt ihn ohnehin. Gesetzt wird er über --btn-bg statt über
   background, damit Schwebe- und Sperr-Zustand weiter aus .ds-btn kommen (die dortigen Regeln
   setzen dieselbe Variable und haben die höhere Spezifität). An der Dropzone, die kein .ds-btn
   ist, läuft die Variable ins Leere — sie bringt ihren Grund selbst mit.

   Bewusst NICHT hier: der Rahmen. Die Dropzone bleibt gestrichelt — dass man nur auf sie etwas
   fallen lassen kann, ist der einzige Unterschied der drei, der etwas bedeutet.

   Die Klasse steht bewusst NACH .ds-btn und nicht oben bei .ds-tile-badge: beides sind
   Ein-Klassen-Selektoren, bei gleicher Spezifität entscheidet die Quell-Reihenfolge. Weiter oben
   gewännen height, padding und white-space von .ds-btn. */
.ds-tile {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  width: 100%;
  height: auto;
  padding: 8px 10px;
  border-radius: var(--radius-card);
  font-size: 12.5px;
  font-weight: 400;
  text-align: center;
  white-space: normal;
  --ds-tile-badge-size: 24px;
  --btn-bg: var(--color-bg-subtle);
}
/* Schwebe-Effekt der Plakette einmal für alle Kacheln — vorher stand er in jeder Komponente. */
.ds-tile:hover:not(:disabled) .ds-tile-badge { transform: translateY(-1px); }

/* Reiterleiste aus ds-btn-Segmenten (aktiv = ds-btn-primary, sonst ds-btn-ghost) — Vorbild
   .sales-period-filter, hier als eigene Klasse, weil mehrere Seiten mit role="tablist" sie nutzen. */
.ds-tabs {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin: 16px 0;
}

/* ------------------------------------------------------------
   ds-input, ds-label, ds-select
   ------------------------------------------------------------ */
.ds-label {
  display: block;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--color-text-secondary);
  margin-bottom: 6px;
  letter-spacing: 0;
}
.ds-label-hint { color: var(--color-text-muted); font-weight: 400; }

/* Von/Bis-Paar in einer umbrechenden Filterleiste: beide Felder brechen nur gemeinsam in die
   nächste Zeile. Sonst stand „bis" allein unter dem Rest, getrennt von seinem „von". Die Felder
   behalten ihre eigene flex-Basis; der Abstand entspricht dem gap der Filterleisten (12 px). */
.ds-filter-range { display: flex; gap: 12px; flex: 0 0 auto; }

.ds-input, .ds-select, .ds-textarea {
  width: 100%;
  height: 38px;
  padding: 0 12px;
  font-size: 13.5px;
  font-family: inherit;
  color: var(--color-text-primary);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-input);
  transition: border-color 0.12s, box-shadow 0.12s;
  outline: none;
}
.ds-textarea { height: auto; padding: 10px 12px; min-height: 80px; resize: vertical; line-height: 1.5; }
.ds-input:focus, .ds-select:focus, .ds-textarea:focus {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px var(--color-primary-ring);
}
.ds-input::placeholder, .ds-textarea::placeholder { color: var(--color-text-faint); }
.ds-select { appearance: none; background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2 4l4 4 4-4' stroke='%2364748b' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 12px center; padding-right: 32px; }
/* Kompakte Variante für dichte Filter-/Toolbar-Zeilen (z. B. Schreiben-Liste, Worker-Log). */
.ds-select-sm,
.ds-input-sm {
    font-size: 11.5px;
    padding: 2px 6px;
    height: 26px;
}
/* Die Kurzform-Regel darüber überschreibt das padding-right von .ds-select — der Pfeil lag dadurch
   auf der Zahl, sichtbar an der Seitengrößen-Auswahl des Pagers (width:auto). Hier nur der Platz
   für den Pfeil, näher an der Kante als in der großen Variante. */
.ds-select.ds-select-sm {
    padding-right: 26px;
    background-position: right 8px center;
}

/* ------------------------------------------------------------
   ds-badge
   ------------------------------------------------------------ */
.ds-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 10px;
  height: 22px;
  font-size: 11.5px;
  font-weight: 500;
  line-height: 1;
  border-radius: var(--radius-pill);
  background: var(--color-neutral-bg);
  color: var(--color-neutral-text);
  white-space: nowrap;
  letter-spacing: 0.01em;
}
.ds-badge-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: 0 0 auto; }
.ds-badge-success { background: var(--color-success-bg); color: var(--color-success-text); }
.ds-badge-warning { background: var(--color-warning-bg); color: var(--color-warning-text); }
.ds-badge-danger  { background: var(--color-danger-bg);  color: var(--color-danger-text); }
.ds-badge-info    { background: var(--color-info-bg);    color: var(--color-info-text); }

/* ------------------------------------------------------------
   ds-alert
   ------------------------------------------------------------ */
.ds-alert {
  display: flex;
  gap: 12px;
  padding: 12px 14px;
  border-radius: var(--radius-input);
  background: var(--color-neutral-bg);
  color: var(--color-text-secondary);
  font-size: 13px;
  border: 1px solid transparent;
}
.ds-alert-info    { background: var(--color-info-bg);    color: var(--color-info-text); }
.ds-alert-warning { background: var(--color-warning-bg); color: var(--color-warning-text); }
.ds-alert-danger  { background: var(--color-danger-bg);  color: var(--color-danger-text); }
.ds-alert-success { background: var(--color-success-bg); color: var(--color-success-text); }

/* Sidebar-nav-Stile (.ds-nav-item, .ds-nav-icon) leben in NavItem.razor.css;
   .ds-nav-section in Sidebar.razor.css. */

/* ------------------------------------------------------------
   Tables
   ------------------------------------------------------------ */
.ds-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}
.ds-table thead th {
  text-align: left;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--color-text-muted-on-subtle);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 10px 16px;
  border-bottom: 1px solid var(--color-border);
  background: var(--color-bg-subtle);
  /* Kopftexte dürfen umbrechen. Mit nowrap war die Mindestbreite der Tabelle die Summe
     aller ungebrochenen Spaltentitel — ab ~1366px ragte sie über den Kartenrand hinaus
     (Admin-Logs, Benutzerlisten). Ein Scroll-Wrapper ist keine Alternative: er schneidet
     das absolut positionierte Zeilenmenü (.dropdown-menu) ab. */
  white-space: normal;
}
.ds-table tbody td {
  padding: 12px 16px;
  border-bottom: 1px solid var(--color-border);
  color: var(--color-text-secondary);
  vertical-align: middle;
  /* anywhere, nicht break-word: nur anywhere senkt die Min-Content-Breite der Zelle, sonst
     zieht die Tabelle weiter auf das längste unbrechbare Token (E-Mail, GUID, Log-Zeile) auf.
     Zellen mit fachlichem white-space:nowrap (Zeitstempel, Beträge) bleiben unberührt —
     nowrap unterbindet den Umbruch unabhängig von overflow-wrap. */
  overflow-wrap: anywhere;
}
.ds-table tbody tr { transition: background 0.1s; }
.ds-table tbody tr:hover { background: var(--color-surface-hover); }
/* Der Pointer ist opt-in: nur Zeilen mit echter Primäraktion (@onclick auf dem <tr>)
   tragen .is-clickable. Vorher galt er unbedingt für jede ds-table und versprach auch
   in reinen Lese-/Log-Tabellen einen Klick, der nichts tat. */
.ds-table tbody tr.is-clickable { cursor: pointer; }
/* Der `@onclick` auf dem `<tr>` allein ist reine Maus-Bedienung: die Zeile bekommt keinen Fokus,
   Enter/Space lösen nichts aus. Ein `tabindex`/`role="button"` auf dem `<tr>` wäre die falsche
   Reparatur — es zerstört die Tabellensemantik (der Screenreader kündigt dann keine Zeile mehr an)
   und liefert immer noch keinen Mittelklick bzw. „in neuem Tab öffnen".
   Deshalb gilt für jede `.is-clickable`-Zeile: fokussierbar ist ein echtes Element IN der Zeile,
   und zwar in der identifizierenden (meist ersten) Spalte, damit es in Leserichtung zuerst kommt.
     • Navigationsziel      →  `<a class="ds-row-link" href>`
     • Dialog/Toggle/Auswahl →  `<button class="ds-row-trigger">`
   Der Zeilen-`@onclick` bleibt als Maus-Komfort; :focus-within hebt die ganze Zeile hervor, sonst
   sähe man beim Tabben nicht, in welcher Zeile der Fokus steht. */
.ds-table tbody tr.is-clickable:focus-within { background: var(--color-surface-hover); }
.ds-table tbody tr.is-clickable:focus-within td:first-child { box-shadow: inset 2px 0 0 var(--color-primary); }
.ds-table tbody tr:last-child td { border-bottom: 0; }
.ds-table .num { font-family: var(--font-mono); font-size: 12px; color: var(--color-text-muted); font-variant-numeric: tabular-nums; }

/* Spaltenklassen gegen das Kollabieren der Textspalten. Das overflow-wrap:anywhere oben senkt die
   Mindestbreite jeder Textspalte auf ein Zeichen. Badge-, Datums- und Aktionsspalten sind nowrap
   und behalten ihre volle Breite. In einer vollen Tabelle landen Name und E-Mail deshalb bei der
   Breite ihres Spaltentitels („r/a/u/c/h…"). Die Klassen stehen am <td>; die Spalte übernimmt die
   größte Mindestbreite ihrer Zellen.
     .col-name   Personen- und Mandantennamen: Wörter bis ~9 Zeichen bleiben ganz
     .col-title  Titel, Profil- und Objektnamen, Bezeichner wie „OnPremConnector"
     .col-email  E-Mail-Adressen: brechen erst oberhalb der Mindestbreite, 15 bis 17 Zeichen je Zeile
     .col-date   Datum/Uhrzeit: nie in der Ziffernfolge getrennt; die Uhrzeit darf in Zeile 2
     .col-wrap   Badges und Zeilen-Buttons dieser Spalte dürfen umbrechen (lange Badge-Texte)
   Die Mindestbreiten gelten erst ab 1280 px Viewport, der garantierten Breite: darunter hat eine
   Tabelle mit neun oder zehn Spalten nicht genug Platz dafür. Dort brechen die Textspalten dann
   lieber, als dass die Tabelle über die Karte hinausragt. Der Breakpoint rechnet mit der Shell
   (Inhalt = Viewport − 304 px), gedacht für Listen auf .page. Die Breiten schließen das
   Zell-Padding ein (box-sizing: border-box). */
@media (min-width: 1280px) {
  .ds-table td.col-name  { min-width: 100px; }
  .ds-table td.col-title { min-width: 160px; }
  .ds-table td.col-email { min-width: 140px; }
}
.ds-table td.col-date { overflow-wrap: normal; }
.ds-table td.col-wrap .ds-badge {
  white-space: normal;
  height: auto;
  min-height: 22px;
  padding-top: 3px;
  padding-bottom: 3px;
  line-height: 1.25;
}
.ds-table td.col-wrap .ds-btn {
  white-space: normal;
  height: auto;
  min-height: 30px;
  padding-top: 4px;
  padding-bottom: 4px;
  line-height: 1.25;
  text-align: left;
}
.ds-table td.col-wrap .row-actions-inline { flex-wrap: wrap; }

/* Listen mit acht und mehr Spalten: 16 px statt 32 px Abstand zwischen den Spalten, die Außenkanten
   bleiben bei 16 px, bündig mit der Kartenkopfzeile. Das bringt einer Zehn-Spalten-Tabelle rund
   150 px mehr Platz, sonst passen Name und E-Mail bei 1280 px nicht mehr daneben. */
.ds-table-dense thead th,
.ds-table-dense tbody td { padding-left: 8px; padding-right: 8px; }
.ds-table-dense thead th:first-child,
.ds-table-dense tbody td:first-child { padding-left: 16px; }
.ds-table-dense thead th:last-child,
.ds-table-dense tbody td:last-child { padding-right: 16px; }

/* Die beiden Zellen-Trigger einer .is-clickable-Zeile (siehe Kommentar oben). Beide übernehmen die
   Typografie ihrer Zelle, sind also optisch unverändert Zellinhalt — aber fokussierbar. */
.ds-row-link { color: inherit; text-decoration: none; }
.ds-row-link:focus-visible { outline: 2px solid var(--color-primary-ring); outline-offset: 2px; border-radius: 3px; }
.ds-row-trigger {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  text-align: inherit;
  cursor: pointer;
}
.ds-row-trigger:focus-visible { outline: 2px solid var(--color-primary-ring); outline-offset: 2px; border-radius: 3px; }
/* Mehrzeiliger Zellinhalt (z. B. Name über Anschrift): die Kinder füllen die Zelle wie vorher die
   `<div>`s, damit `text-overflow: ellipsis` weiter greift. */
.ds-row-trigger.is-stacked { display: flex; flex-direction: column; align-items: stretch; gap: 0; width: 100%; min-width: 0; }
.ds-table-filter-row th { padding: 6px 16px; background: var(--color-bg-subtle); border-bottom: 1px solid var(--color-border); }
.ds-table-filter-input {
  width: 100%; box-sizing: border-box;
  height: 28px; padding: 0 8px;
  font-size: 12px; font-family: inherit;
  border: 1px solid var(--color-border); border-radius: 6px;
  background: var(--color-surface); color: var(--color-text-primary);
  outline: none; transition: border-color 0.15s;
}
.ds-table-filter-input::placeholder { color: var(--color-text-muted); }
.ds-table-filter-input:focus { border-color: var(--color-primary); }
.ds-table-filter-select {
  width: 100%; box-sizing: border-box;
  height: 28px; padding: 0 24px 0 8px;
  font-size: 12px; font-family: inherit;
  border: 1px solid var(--color-border); border-radius: 6px;
  background: var(--color-surface); color: var(--color-text-primary);
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 8px center;
  outline: none; cursor: pointer; transition: border-color 0.15s;
}
.ds-table-filter-select:focus { border-color: var(--color-primary); }

/* Kebab-Menü pro Tabellenzeile (.row-actions / .dropdown-menu / .dropdown-item). Verhalten
   (Schließen bei Außen-/Eintrag-Klick) liefert das globale wwwroot/js/dropdown.js für
   `details.dropdown`. Global statt seitengebunden, weil mehrere Seiten dasselbe Muster
   nutzen (UserManagement, GroupManagement, PlatformAccounts, …). */
.row-actions {
  position: relative;
  display: inline-block;
}
.row-actions-trigger {
  list-style: none;
  display: inline-grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 6px;
  color: var(--color-text-muted);
  cursor: pointer;
  transition: background 0.1s, color 0.1s;
}
.row-actions-trigger::-webkit-details-marker { display: none; }
.row-actions-trigger:hover {
  background: var(--color-surface-hover);
  color: var(--color-text-primary);
}
.row-actions[open] > .dropdown-menu { display: block; }
.row-actions:not([open]) > .dropdown-menu { display: none; }
.dropdown-menu {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  min-width: 220px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 10px;
  box-shadow: var(--shadow-lg);
  padding: 6px;
  z-index: 50;
  text-align: left;
  animation: row-dd-in 0.12s ease-out;
}
@keyframes row-dd-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}
.dropdown-menu form { margin: 0; }
.dropdown-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  border: 0;
  background: transparent;
  border-radius: 6px;
  font-family: inherit;
  font-size: 13px;
  color: var(--color-text-primary);
  text-align: left;
  cursor: pointer;
  user-select: none;
}
.dropdown-item:hover:not(:disabled) { background: var(--color-surface-hover); }
.dropdown-item:disabled { opacity: 0.5; cursor: not-allowed; }
.dropdown-item-danger { color: var(--color-danger); }
.dropdown-separator {
  height: 1px;
  background: var(--color-border);
  margin: 6px 4px;
}

/* Inline-Variante derselben Aktionsspalte (1–2 harmlose Aktionen, siehe RowActions.razor). */
.row-actions-inline {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  justify-content: flex-end;
}

/* Layout-Stile (.app-shell, .app-sidebar, .app-topbar, .app-content,
   .sidebar-brand) leben in MainLayout/Sidebar/Topbar.razor.css. */

/* Breitenstufen einer Seite — vier Stück, mehr gibt es nicht. Ad-hoc-Breiten per Inline-Style
   sind ein Befund: sie ließen dieselbe Art Seite mal 900, mal 1000, mal 1200 px breit werden.

     .page            1400 px  Listen und breite Tabellen (Standard)
     .page-medium     1100 px  Karten- und Einstellungsseiten mit Fließtext
     .page-narrow      720 px  einspaltige Formulare (Profil, Neue Aussendung, Kopplung)
     .page-wide      2400 px  Seiten, die deutlich mehr als .page brauchen (Aussendungs-Detail) */
.page {
  max-width: 1400px;
  margin: 0 auto;
  padding: 28px 32px 32px;
}
.page-medium {
  max-width: 1100px;
}
.page-narrow {
  max-width: 720px;
}
/* Viel Breite für Seiten, die den Platz brauchen (z. B. Aussendungs-Detail mit
   Liste + PDF-Vorschau) — aber nicht unbegrenzt. Padding der .page bleibt erhalten.

   2400 px ist bewusst so gewählt, dass die Grenze im Alltag nicht greift: die Inhaltsbreite ist
   Fensterbreite minus Sidebar (240) minus Padding (64), bei 2560 px Fenster also 2256 px. Bis rund
   2700 px Fensterbreite ändert der Deckel deshalb gar nichts — er fängt nur echte 3840-CSS-px ab
   (4K ohne Skalierung, Ultrawide). Dort lief die Seite sonst auseinander: die Empfänger-Spalte der
   Schreiben-Liste wurde 1333 px breit für einen kurzen Namen, während die Nebenspalten bei ihren
   100-140 px blieben, und die Karten der Konfiguration wurden 872 px breit.
   Wer wirklich mehr Breite braucht, löst das über einen eigenen Scroll-Container, nicht hierüber. */
.page-wide {
  max-width: 2400px;
}
/* Nur für Seiten mit der fixen Wizard-Leiste am unteren Rand (MailingWizardNav): schafft
   den Freiraum, damit kein Inhalt hinter der Leiste verschwindet. Stand früher global auf
   .page — die 80px kosteten jede andere Seite Höhe, obwohl nur diese eine Seite die
   Leiste hat. */
.page-wizard {
  padding-bottom: calc(var(--wizard-nav-h) + 24px);
}
/* Seite füllt die (definite) Höhe von .app-content, statt nur so hoch zu werden wie ihr
   Inhalt. Voraussetzung für Seiten, deren Panels intern scrollen sollen: .app-shell ist
   ein 100vh-Grid, damit ist die Höhenkette bis hierher durchgehend definit. Die Seite
   gibt die Resthöhe unter dem Seitenkopf per flex an ihr Hauptelement weiter — das
   braucht dort dann flex:1 und min-height:0. Vorbild: .letter-detail-page. */
.page-fill {
  height: 100%;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 24px;
}
.page-title {
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--color-text-primary);
  margin: 0;
}
/* FocusOnNavigate (Routes.razor) setzt nach jeder Navigation den Fokus programmatisch
   auf die h1, damit Tastatur-/Screenreader-Nutzer am Hauptinhalt landen. Eine Überschrift
   ist nie selbst bedienbar (tabindex="-1"), der Fokusrahmen zeigt dort also nichts an und
   stört nur. Chrome setzt bei programmatischem Fokus nach Tastaturbedienung zusätzlich
   :focus-visible — deshalb beide Zustände abschalten. Der Fokus selbst bleibt gesetzt. */
h1:focus, h1:focus-visible { outline: none; }
.page-subtitle {
  font-size: 13.5px;
  color: var(--color-text-muted);
  margin: 4px 0 0;
}

/* Dashboard KPI cards (farbig, mit Icon-Chip) */
.kpi-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
}
.kpi-card {
  display: flex;
  align-items: center;
  gap: 14px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-border);
  border-radius: var(--radius-card);
  padding: 16px 18px;
  box-shadow: var(--shadow-xs);
}
.kpi-card--info    { border-left-color: var(--color-info); }
.kpi-card--primary { border-left-color: var(--color-primary); }
.kpi-card--success { border-left-color: var(--color-success); }
.kpi-card--warning { border-left-color: var(--color-warning); }
.kpi-card--danger  { border-left-color: var(--color-danger); }

.kpi-icon {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 10px;
  background: var(--color-neutral-bg);
  color: var(--color-neutral-text);
}
.kpi-card--info    .kpi-icon { background: var(--color-info-bg);      color: var(--color-info-text); }
.kpi-card--primary .kpi-icon { background: var(--color-primary-soft); color: var(--color-primary); }
.kpi-card--success .kpi-icon { background: var(--color-success-bg);   color: var(--color-success-text); }
.kpi-card--warning .kpi-icon { background: var(--color-warning-bg);   color: var(--color-warning-text); }
.kpi-card--danger  .kpi-icon { background: var(--color-danger-bg);    color: var(--color-danger-text); }

.kpi-body { min-width: 0; }
.kpi-label {
  font-size: 12px;
  font-weight: 500;
  color: var(--color-text-muted);
}
.kpi-value {
  font-size: 28px;
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--color-text-primary);
  font-variant-numeric: tabular-nums;
}
.kpi-hint {
  font-size: 11.5px;
  color: var(--color-text-faint);
}

@media (max-width: 900px) {
  .kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* Unter ~560 px stehen zwei Kacheln so eng, dass Label und Zahl umbrechen — dann lieber
   einspaltig. */
@media (max-width: 560px) {
  .kpi-grid { grid-template-columns: minmax(0, 1fr); }
}

/* Topbar (.topbar-tenant, .tenant-*, .avatar, .dropdown*, .breadcrumb)
   leben in Topbar.razor.css. */

/* Progress bar */
.progress {
  position: relative;
  height: 8px;
  background: var(--color-bg-subtle);
  border-radius: var(--radius-pill);
  overflow: hidden;
}
.progress-fill {
  height: 100%;
  background: var(--color-primary);
  border-radius: var(--radius-pill);
  transition: width 0.4s ease-out;
}
.progress-fill.indeterminate {
  width: 35% !important;
  animation: progress-indet 1.4s ease-in-out infinite;
}
@keyframes progress-indet {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(350%); }
}

/* Spinner */
.spinner {
  width: 14px; height: 14px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
  display: inline-block;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Search box */
.search-box {
  position: relative;
  flex: 1;
  max-width: 320px;
}
.search-box input {
  width: 100%;
  height: 34px;
  padding: 0 12px 0 34px;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-surface);
  color: var(--color-text-primary);
  font-size: 13px;
  outline: none;
  transition: border-color 0.12s, box-shadow 0.12s;
}
.search-box input:focus { border-color: var(--color-primary); box-shadow: 0 0 0 3px var(--color-primary-ring); }
.search-box svg { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: var(--color-text-muted); }

/* Focus styling */
input:focus-visible, select:focus-visible, textarea:focus-visible,
button:focus-visible {
  outline-offset: 2px;
}

/* Nur für Screenreader: Text, der optisch nicht erscheinen darf, aber im Accessible Name bzw.
   im Vorlesefluss gebraucht wird (z. B. die Live-Region des Zeitraum-Umschalters im Dashboard).
   Bewusst NICHT `display:none`/`visibility:hidden` — beides nimmt den Text auch aus dem
   Accessibility-Tree. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Skeleton-Loading: schimmernde Platzhalter, bis die Daten geladen sind. Liegt global (nicht
   scoped), weil es zwei Seiten nutzen — Aussendungs-Detail und Dashboard; dieselbe Begründung,
   aus der die .kpi-*-Kacheln hier stehen. */
.skeleton-block {
  position: relative;
  overflow: hidden;
  background: var(--color-bg-subtle);
  border-radius: var(--radius-input);
}
.skeleton-block::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(
    90deg,
    transparent,
    var(--color-surface-hover) 50%,
    transparent
  );
  animation: skeleton-shimmer 1.4s ease-in-out infinite;
}
@keyframes skeleton-shimmer {
  100% { transform: translateX(100%); }
}
@media (prefers-reduced-motion: reduce) {
  .skeleton-block::after { animation: none; }
}

/* ============================================================
   Konzept-update — additional shell components
   ============================================================ */

/* Tenant-Pill und Breadcrumb leben in Topbar.razor.css. */

/* Prozess-Stepper der Wizard-Leiste (.mailing-stepper, .mailing-step*) lebt in
   MailingWizardNav.razor.css — die Komponente ist seine einzige Nutzerin. */

/* Ablehnungs-Hinweis des Freigebers — gelbe Karte über der Konfiguration im Entwurf. Steht im
   Hinweis-Stapel der Detailseite (.mailing-notices, MailingDetail.razor.css), der Abstände und
   das kompakte Polster setzt. */
.mailing-rejection-note {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 14px 18px;
  border: 1px solid var(--color-warning-text);
  border-left-width: 4px;
  border-radius: var(--radius-card, 12px);
  background: var(--color-warning-bg);
  color: var(--color-warning-text);
}
.mailing-rejection-note strong {
  display: block;
  font-size: 13.5px;
  margin-bottom: 2px;
}
.mailing-rejection-note p {
  margin: 0;
  font-size: 13px;
  line-height: 1.45;
  white-space: pre-wrap;
}
.mailing-rejection-note-label {
  font-weight: 600;
  white-space: nowrap;
}

/* Kompakte Konfig-Zusammenfassung (Status „AwaitingReview"): Versandziel · Trennung · … */
.mailing-config-summary {
  display: flex;
  align-items: center;
  gap: 10px 18px;
  flex-wrap: wrap;
}
.mailing-config-summary-item {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  font-size: 12.5px;
}
.mailing-config-summary-item .label {
  color: var(--color-text-muted);
}
.mailing-config-summary-item .value {
  color: var(--color-text-primary);
  font-weight: 500;
}

/* Aufklappbare Konfiguration (Status „AwaitingReview"+): native Disclosure-Marker ausblenden,
   eigener Chevron im Summary-Markup. */
.mailing-config-collapse > summary { list-style: none; }
.mailing-config-collapse > summary::-webkit-details-marker { display: none; }

/* ============================================================
   Native <dialog>-Modals — Backdrop, Surface, Header, Footer
   ============================================================ */
dialog.ds-dialog {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-card);
    background: var(--color-surface);
    color: var(--color-text-primary);
    padding: 0;
    box-shadow: var(--shadow-lg);
    max-width: min(92vw, 1100px);
    max-height: 88vh;
    overflow: hidden;
}
/* WICHTIG: Browser-Default ist `display: none` für geschlossene <dialog>.
   Flex-Layout nur im offenen Zustand, sonst zeigen sie sich inline. */
dialog.ds-dialog[open] {
    display: flex;
    flex-direction: column;
}
dialog.ds-dialog::backdrop {
    background: rgba(15, 23, 42, 0.45);
    backdrop-filter: blur(2px);
}
html[data-theme="dark"] dialog.ds-dialog::backdrop {
    background: rgba(0, 0, 0, 0.6);
}
.ds-dialog-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 14px 20px;
    border-bottom: 1px solid var(--color-border);
}
.ds-dialog-header h2 {
    margin: 0;
    font-size: 14px;
    font-weight: 600;
    color: var(--color-text-primary);
    letter-spacing: -0.005em;
}
.ds-dialog-body {
    padding: 20px;
    overflow: auto;
    flex: 1 1 auto;
}
.ds-dialog-footer {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    padding: 12px 20px;
    border-top: 1px solid var(--color-border);
    background: var(--color-bg-subtle);
}
.ds-dialog-close {
    background: transparent;
    border: 0;
    cursor: pointer;
    color: var(--color-text-muted);
    padding: 4px;
    border-radius: var(--radius-input);
    line-height: 0;
}
.ds-dialog-close:hover { background: var(--color-surface-hover); color: var(--color-text-primary); }

/* ============================================================
   Sidebar — Collapsed-Variante
   Selectoren liegen hier (statt im isolierten Sidebar.razor.css),
   weil die [data-sidebar-collapsed]-Marke am .app-shell hängt. Der Sidebar-Kopf gehört bewusst
   NICHT dazu: er stylt sich über .sidebar-brand-collapsed selbst (Sidebar.razor.css).
   ============================================================ */
.app-shell[data-sidebar-collapsed="true"] .ds-nav-section {
    display: none;
}
.app-shell[data-sidebar-collapsed="true"] .ds-nav-item .ds-nav-label,
.app-shell[data-sidebar-collapsed="true"] .ds-nav-item .ds-nav-badge {
    display: none;
}
.app-shell[data-sidebar-collapsed="true"] .ds-nav-item {
    justify-content: center;
}
.app-shell[data-sidebar-collapsed="true"] .sidebar-footer {
    display: none;
}
/* Septeo-Hinweis bleibt im eingeklappten Zustand sichtbar, aber nur als Signet ohne Text. */
.app-shell[data-sidebar-collapsed="true"] .sidebar-septeo {
    justify-content: center;
    padding: 10px 0;
}
.app-shell[data-sidebar-collapsed="true"] .sidebar-septeo-text {
    display: none;
}

.sidebar-toggle {
    margin-left: auto;
    background: transparent;
    border: 0;
    color: var(--color-text-muted);
    padding: 4px;
    border-radius: var(--radius-input);
    cursor: pointer;
    line-height: 0;
    display: grid;
    place-items: center;
}
.sidebar-toggle:hover {
    background: var(--color-surface-hover);
    color: var(--color-text-primary);
}
.sidebar-toggle-icon {
    transition: transform 0.16s ease-out;
}

/* ============================================================
   Toaster (bottom-right)
   ============================================================ */
.ds-toaster {
    position: fixed;
    right: 20px;
    bottom: 20px;
    display: flex;
    flex-direction: column;
    gap: 8px;
    z-index: 1100;
    pointer-events: none;
}
.ds-toast {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    min-width: 280px;
    max-width: 420px;
    padding: 10px 12px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-input);
    background: var(--color-surface);
    box-shadow: var(--shadow-lg);
    font-size: 13px;
    color: var(--color-text-primary);
    pointer-events: auto;
    animation: toast-in 0.16s ease-out;
}
@keyframes toast-in {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: translateY(0); }
}
.ds-toast-msg { flex: 1; line-height: 1.4; }
.ds-toast-close {
    background: transparent;
    border: 0;
    color: var(--color-text-muted);
    cursor: pointer;
    padding: 2px;
    line-height: 0;
    border-radius: 4px;
}
.ds-toast-close:hover { background: var(--color-surface-hover); color: var(--color-text-primary); }

.ds-toast-success { border-left: 3px solid var(--color-success); }
.ds-toast-success > svg:first-of-type { color: var(--color-success); }
.ds-toast-warning { border-left: 3px solid var(--color-warning); }
.ds-toast-warning > svg:first-of-type { color: var(--color-warning); }
.ds-toast-danger  { border-left: 3px solid var(--color-danger); }
.ds-toast-danger  > svg:first-of-type { color: var(--color-danger); }
.ds-toast-info    { border-left: 3px solid var(--color-info); }
.ds-toast-info    > svg:first-of-type { color: var(--color-info); }

/* ============================================================
   Drawer (right side panel — inspection surface)
   ============================================================ */
.ds-drawer-shell {
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 900;
}
.ds-drawer-scrim {
    position: absolute;
    inset: 0;
    background: rgba(15, 23, 42, 0.32);
    opacity: 0;
    transition: opacity 0.16s ease-out;
}
html[data-theme="dark"] .ds-drawer-scrim {
    background: rgba(0, 0, 0, 0.55);
}
.ds-drawer {
    position: absolute;
    top: 0;
    right: 0;
    height: 100%;
    width: min(420px, 90vw);
    background: var(--color-surface);
    border-left: 1px solid var(--color-border);
    box-shadow: var(--shadow-lg);
    transform: translateX(100%);
    transition: transform 0.18s ease-out;
    display: flex;
    flex-direction: column;
    pointer-events: auto;
}
.ds-drawer-shell.ds-drawer-open { pointer-events: auto; }
.ds-drawer-shell.ds-drawer-open .ds-drawer-scrim { opacity: 1; }
.ds-drawer-shell.ds-drawer-open .ds-drawer { transform: translateX(0); }
.ds-drawer-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 14px 20px;
    border-bottom: 1px solid var(--color-border);
    background: var(--color-bg-subtle);
}
.ds-drawer-header h2 { margin: 0; font-size: 14px; font-weight: 600; color: var(--color-text-primary); }
.ds-drawer-close {
    background: transparent;
    border: 0;
    color: var(--color-text-muted);
    cursor: pointer;
    padding: 4px;
    line-height: 0;
    border-radius: var(--radius-input);
}
.ds-drawer-close:hover { background: var(--color-surface-hover); color: var(--color-text-primary); }
.ds-drawer-body { padding: 16px 20px; overflow: auto; flex: 1 1 auto; }

/* ============================================================
   Hinweis-Leiste (Banners above content)
   ============================================================ */
.ds-banner-stack {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 12px 24px 0;
}
.ds-banner {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-input);
    font-size: 12.5px;
    color: var(--color-text-primary);
    background: var(--color-surface);
}
.ds-banner-msg { flex: 1; line-height: 1.4; }
.ds-banner-action {
    font-size: 12px;
    font-weight: 500;
    color: var(--color-primary);
    text-decoration: none;
    padding: 4px 10px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-btn);
    background: var(--color-surface);
}
.ds-banner-action:hover { background: var(--color-surface-hover); }
.ds-banner-close {
    background: transparent;
    border: 0;
    color: var(--color-text-muted);
    cursor: pointer;
    padding: 2px;
    line-height: 0;
    border-radius: 4px;
}
.ds-banner-close:hover { background: var(--color-surface-hover); color: var(--color-text-primary); }

.ds-banner-info    { border-left: 3px solid var(--color-info); }
.ds-banner-info    > svg:first-of-type { color: var(--color-info); }
.ds-banner-success { border-left: 3px solid var(--color-success); }
.ds-banner-success > svg:first-of-type { color: var(--color-success); }
.ds-banner-warning { border-left: 3px solid var(--color-warning); }
.ds-banner-warning > svg:first-of-type { color: var(--color-warning); }
.ds-banner-danger  { border-left: 3px solid var(--color-danger); }
.ds-banner-danger  > svg:first-of-type { color: var(--color-danger); }

/* ============================================================
   Meta-Label (kleine Caps-Label über Wert-Feldern in Cards)
   ============================================================ */
.ds-meta-label {
    font-size: 11px;
    font-weight: 500;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

/* ============================================================
   RichTextEditor (Mail-Body WYSIWYG, contenteditable)
   Eigenes, themenfähiges Styling (nur CSS-Variablen) — die
   Dropdown-Klassen aus dem Topbar sind component-scoped und
   greifen hier nicht, daher unter .rte nachgebildet.
   ============================================================ */
.rte {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-input);
    background: var(--color-surface);
    overflow: visible;
}
.rte-toolbar {
    display: flex;
    align-items: center;
    gap: 4px;
    flex-wrap: wrap;
    padding: 6px 8px;
    border-bottom: 1px solid var(--color-border);
    background: var(--color-bg-subtle);
    border-top-left-radius: var(--radius-input);
    border-top-right-radius: var(--radius-input);
}
.rte-tool {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
    min-width: 30px;
    height: 30px;
    padding: 0 8px;
    border: 1px solid transparent;
    border-radius: 6px;
    background: transparent;
    color: var(--color-text-secondary);
    cursor: pointer;
    font: inherit;
    font-size: 12.5px;
    list-style: none;
}
.rte-tool:hover {
    background: var(--color-surface-hover);
    color: var(--color-text-primary);
}
.rte-tool::-webkit-details-marker { display: none; }
.rte-tool-text { white-space: nowrap; }
.rte-sep {
    width: 1px;
    align-self: stretch;
    margin: 4px 2px;
    background: var(--color-border);
}
.rte-placeholder { position: relative; }
.rte-placeholder > .dropdown-menu {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: 10px;
    box-shadow: var(--shadow-lg);
    padding: 6px;
    z-index: 50;
}
.rte-placeholder:not([open]) > .dropdown-menu { display: none; }
.rte-placeholder > .dropdown-menu .dropdown-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    width: 100%;
    padding: 8px 10px;
    border: 0;
    border-radius: 6px;
    background: transparent;
    font: inherit;
    font-size: 13px;
    text-align: left;
    color: var(--color-text-primary);
    cursor: pointer;
}
.rte-placeholder > .dropdown-menu .dropdown-item:hover {
    background: var(--color-surface-hover);
}
.rte-token {
    font-size: 11px;
    color: var(--color-text-muted);
}
.rte-free {
    border-top: 1px solid var(--color-border);
    margin-top: 4px;
    padding: 8px 10px 4px;
}
.rte-hint {
    font-size: 11px;
    color: var(--color-text-muted);
    margin-top: 6px;
}
.rte-linkbar {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 8px;
    border-bottom: 1px solid var(--color-border);
    background: var(--color-bg-subtle);
}
.rte-surface {
    min-height: 220px;
    max-height: 480px;
    overflow-y: auto;
    padding: 14px 16px;
    font-size: 14px;
    line-height: 1.6;
    color: var(--color-text-primary);
    outline: none;
}
.rte-surface.is-readonly {
    background: var(--color-bg-subtle);
    color: var(--color-text-secondary);
    cursor: default;
}
.rte-surface:empty::before {
    content: "Mailtext hier eingeben …";
    color: var(--color-text-faint);
}
.rte-surface a { color: var(--color-primary); }
.rte-surface ul { padding-left: 22px; margin: 8px 0; }
.rte-surface p { margin: 0 0 10px; }

/* Absender im Mailinhalt — read-only (zentral pro Mail-Domain gepflegt) */
.mail-from-readonly {
    padding: 10px 14px;
    background: var(--color-bg-subtle);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-input);
    font-size: 13px;
    color: var(--color-text-secondary);
}
.ds-field-hint {
    margin-top: 6px;
    font-size: 11.5px;
    color: var(--color-text-muted);
}
/* Betreff-Zeile mit „Platzhalter einfügen"-Dropdown rechts neben dem Label */
.mailinhalt-label-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    margin-bottom: 4px;
}
.mailinhalt-label-row .ds-label { margin-bottom: 0; }

/* ------------------------------------------------------------
   Log-Viewer-Zeilen (.live-log-*) — geteilt zwischen dem Live-Worker-Log-Panel
   (MailingWorkerLogPanel) und dem Fortschritts-Dialog (MailingProgressDialog). Beide
   Komponenten rendern dieselbe terminal-artige Zeilenoptik; weil zwei Komponenten betroffen
   sind, liegen die Zeilen-Klassen global statt scoped. Die jeweilige Hülle (Card/Dialog-Body)
   bleibt komponenten-lokal.
   ------------------------------------------------------------ */
.live-log-empty {
    padding: 18px;
    font-size: 12px;
    color: var(--color-text-muted);
    font-style: italic;
    font-family: inherit;
}
.live-log-row {
    display: grid;
    grid-template-columns: 80px 36px auto 1fr;
    column-gap: 8px;
    padding: 4px 12px;
    border-bottom: 1px solid var(--color-border);
    align-items: baseline;
}
.live-log-row:last-child { border-bottom: 0; }
.live-log-time {
    color: var(--color-text-faint);
    font-size: 10.5px;
}
.live-log-level {
    font-weight: 600;
    font-size: 10px;
    letter-spacing: 0.05em;
    padding: 1px 4px;
    border-radius: 3px;
    text-align: center;
}
.live-log-step {
    color: var(--color-text-muted);
    font-size: 10.5px;
}
.live-log-msg {
    color: var(--color-text-primary);
    word-break: break-word;
    white-space: pre-wrap;
}
.live-log-ex {
    grid-column: 1 / -1;
    margin: 4px 0 0;
    color: var(--color-danger, #dc2626);
    font-size: 10.5px;
    white-space: pre-wrap;
    word-break: break-all;
    max-height: 180px;
    overflow: auto;
    padding: 6px 8px;
    background: var(--color-danger-soft);
    border-radius: var(--radius-input);
}
.live-log-row.is-error  .live-log-level { background: var(--color-danger-bg); color: var(--color-danger); }
.live-log-row.is-warning .live-log-level { background: var(--color-warning-bg); color: var(--color-warning-text); }
.live-log-row.is-debug   .live-log-level { background: rgba(120, 120, 120, 0.15); color: var(--color-text-muted); }
.live-log-row.is-info    .live-log-level { background: var(--color-primary-soft); color: var(--color-primary); }

/* Klickbare Step-/Letter-Chips als Filter-Trigger im Log-Row */
.live-log-step-btn,
.live-log-lid-btn {
    border: 1px solid var(--color-border);
    background: var(--color-bg-subtle);
    color: var(--color-text-muted);
    font-size: 10.5px;
    padding: 1px 6px;
    border-radius: 3px;
    cursor: pointer;
    line-height: 1.3;
}
.live-log-step-btn:hover,
.live-log-lid-btn:hover {
    background: var(--color-primary-soft);
    color: var(--color-primary);
    border-color: var(--color-primary);
}
.live-log-lid-btn {
    justify-self: end;
}

/* ------------------------------------------------------------
   Versandkosten-Auflistung (.preview-cost-*) — geteilt zwischen dem Freigabe-Dialog
   (ApproveDialog) und den Abschluss-Karten (MailingCompletionCards). Zwei Komponenten
   nutzen dieselbe Zeilenoptik, daher global statt scoped.
   ------------------------------------------------------------ */
.preview-cost {
    flex: 0 0 auto;
    border-top: 1px solid var(--color-border);
    padding: 10px 14px;
    background: var(--color-bg-subtle);
    font-size: 11.5px;
    max-height: 40%;
    overflow-y: auto;
}
.preview-cost-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 6px;
}
.preview-cost-title {
    font-weight: 600;
    color: var(--color-text-primary);
}
.preview-cost-stand {
    color: var(--color-text-muted);
    font-size: 10.5px;
}
.preview-cost-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 2px 0;
    color: var(--color-text-secondary);
}
.preview-cost-label {
    flex: 1;
    min-width: 0;
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.preview-cost-label .ds-badge {
    font-size: 9.5px;
}
.preview-cost-muted { color: var(--color-text-muted); }
.preview-cost-count {
    font-variant-numeric: tabular-nums;
    text-align: right;
    min-width: 48px;
}
.preview-cost-sum {
    font-variant-numeric: tabular-nums;
    text-align: right;
    min-width: 72px;
}
.preview-cost-total {
    margin-top: 4px;
    padding-top: 6px;
    border-top: 1px solid var(--color-border);
    font-weight: 600;
    color: var(--color-text-primary);
}
.preview-cost-note {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    margin-top: 6px;
    color: var(--color-warning, var(--color-text-secondary));
    font-size: 10.5px;
}
.preview-cost-gross {
    font-weight: 600;
    color: var(--color-text-primary);
}

/* ─── PdfViewer (pdf.js-Wrapper, Components/Shared/PdfViewer.razor) ─── */
/* Bewusst ohne Rahmen/Radius: der Viewer ist neutral und füllt seinen Container —
   Card-Chrome macht, wer ihn einbettet (z. B. .elc-pdf-wrap). */
.os-pdfviewer {
    display: flex;
    flex-direction: column;
    min-height: 0;
    background: var(--color-bg-subtle);
    overflow: hidden;
}
/* Umbruch statt Überlauf: in schmalen Panes (Split-View) rutschen die Zoom-Bedienelemente in
   eine zweite Zeile, statt aus der Toolbar zu laufen. */
.os-pdf-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    padding: 6px 8px;
    border-bottom: 1px solid var(--color-border);
    background: var(--color-bg-subtle);
    flex: 0 0 auto;
}
.os-pdf-search-input {
    flex: 1;
    min-width: 120px;
    height: 28px;
    font-size: 12.5px;
}
.os-pdf-match-count {
    font-size: 11.5px;
    color: var(--color-text-muted);
    white-space: nowrap;
    padding: 0 4px;
}
/* Schiebt die Zoom-Gruppe nach rechts bzw. in die nächste Zeile. */
.os-pdf-toolbar-sep {
    flex: 1 0 0;
    min-width: 0;
}
.os-pdf-page-count {
    font-size: 11.5px;
    color: var(--color-text-muted);
    white-space: nowrap;
    padding: 0 4px;
}
.os-pdf-zoom-select {
    width: auto;
    min-width: 104px;
    height: 28px;
    font-size: 12px;
}
.os-pdf-body {
    display: flex;
    flex: 1;
    /* Sicherheitsnetz statt min-height:0: der Seiten-Container darin ist absolut positioniert
       und trägt daher nichts zur Höhe bei. Wo die Höhenkette des Einbetters unbestimmt ist
       (z. B. einspaltige Umbrüche mit height:auto), würde der Viewer sonst auf 0 kollabieren. */
    min-height: 240px;
}
/* Breite muss zu THUMB_SCALE in js/pdf-viewer.js passen: 0.18 ergibt bei A4 (595 pt) ein 107 px
   breites Thumbnail. Mit den vorherigen 84 px lief es aus der Spalte heraus — sichtbar als
   horizontaler Scrollbalken und als Thumbnail, das den Rahmen der Leiste überschrieb. */
.os-pdf-thumbs {
    flex: 0 0 124px;
    overflow-y: auto;
    padding: 8px 6px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    border-right: 1px solid var(--color-border);
    background: var(--color-bg-subtle);
}
.os-pdf-thumb {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding: 0;
    border: 1px solid transparent;
    border-radius: 4px;
    background: none;
    cursor: pointer;
}
.os-pdf-thumb:hover { border-color: var(--color-border); }
.os-pdf-thumb canvas {
    display: block;
    /* Deckel für Seitenformate, die breiter sind als A4 (Querformat, Letter): das Thumbnail wird
       dann proportional heruntergerechnet, statt die Leiste erneut zu sprengen. */
    max-width: 100%;
    height: auto;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
    background: #fff; /* bewusst weiß: PDF-Seite selbst ist immer weißes Papier, themenunabhängig */
}
.os-pdf-thumb-label {
    font-size: 10px;
    color: var(--color-text-muted);
}
/* Nur Positionierungs-Kontext: gescrollt wird im .os-pdf-container darin, weil pdf.js' PDFViewer
   einen absolut positionierten Container zwingend braucht (misst darüber den Viewport). */
.os-pdf-pages {
    position: relative;
    flex: 1;
    min-width: 0;
}
.os-pdf-container {
    position: absolute;
    inset: 0;
    overflow: auto;
}
.os-pdf-status {
    font-size: 12px;
    color: var(--color-text-muted);
    padding: 12px;
    text-align: center;
}
.os-pdf-status-error { color: var(--color-danger-text, var(--color-danger)); }

/* ── Anpassungen an pdf.js' mitgeliefertem web/pdf_viewer.css ──
   Seiten-Layout (Größe, Skalierung, Text-/Annotation-Layer) kommt komplett von dort; hier nur
   Optik im Design-System. */
.os-pdf-container .pdfViewer {
    --page-margin: 0 auto 12px;
    --page-border: none;
    padding: 12px 0;
}
/* Unsere globale .page-Regel (Seiten-Layout, weiter oben) greift sonst auch auf die von pdf.js
   erzeugten div.page und schießt max-width/padding hinein — pdf.js setzt beides selbst nicht. */
.os-pdf-container .pdfViewer .page {
    max-width: none;
    padding: 0;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
}
/* Treffer-Optik der pdf.js-Suche; Variablen sind der dafür vorgesehene Hebel. */
.os-pdf-container .textLayer .highlight {
    --highlight-bg-color: rgba(250, 204, 21, 0.55);
    --highlight-selected-bg-color: rgba(249, 115, 22, 0.75);
}
.os-pdf-container .textLayer ::selection { background: rgba(37, 99, 235, 0.35); }

/* ============================================================
   Reconnect-Overlay (#components-reconnect-modal, Markup in App.razor)
   Sichtbarkeit und Zustandstexte hängen ausschließlich an den Klassen, die Blazor auf dem
   Element setzt: components-reconnect-show/-retrying/-paused/-failed/-resume-failed/-rejected
   (und -hide für „wieder verbunden").
   ============================================================ */
.os-reconnect {
    position: fixed;
    inset: 0;
    z-index: 10000; /* über allem, inkl. Drawer (900) und Toasts (1100) */
    display: none;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: rgba(15, 23, 42, 0.45);
    backdrop-filter: blur(2px);
}
html[data-theme="dark"] .os-reconnect { background: rgba(0, 0, 0, 0.62); }

/* Die 0.4s Verzögerung der Einblendung schluckt kurze Aussetzer: ein Reconnect, der schneller
   durch ist, bleibt unsichtbar — genau dafür hat auch Blazors eingebaute Anzeige eine. */
.os-reconnect.components-reconnect-show,
.os-reconnect.components-reconnect-retrying,
.os-reconnect.components-reconnect-paused,
.os-reconnect.components-reconnect-failed,
.os-reconnect.components-reconnect-resume-failed,
.os-reconnect.components-reconnect-rejected {
    display: flex;
    animation: os-rc-overlay-in 0.3s ease-out 0.4s both;
}
@keyframes os-rc-overlay-in { from { opacity: 0; } to { opacity: 1; } }

.os-reconnect-card {
    width: min(420px, 100%);
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-lg), 0 24px 48px -12px rgba(15, 23, 42, 0.28);
    overflow: hidden;
    animation: os-rc-card-in 0.5s cubic-bezier(0.05, 0.89, 0.25, 1.02) 0.4s both;
}
@keyframes os-rc-card-in {
    from { transform: translateY(14px) scale(0.97); }
    to   { transform: none; }
}

/* Absperrband oben und unten. Das Streifenmuster liegt auf einem breiteren ::before und wird
   um genau eine Musterperiode (24px / cos45° = 33.94px) verschoben — dadurch nahtlos. */
.os-reconnect-hazard {
    position: relative;
    height: 10px;
    overflow: hidden;
}
.os-reconnect-hazard::before {
    content: '';
    position: absolute;
    inset: 0 -40px 0 0;
    background: repeating-linear-gradient(45deg,
        var(--color-accent) 0 12px,
        var(--color-primary) 12px 24px);
    animation: os-rc-hazard 1.1s linear infinite;
}
@keyframes os-rc-hazard { to { transform: translateX(-33.94px); } }

.os-reconnect-body {
    padding: 20px 24px 24px;
    text-align: center;
}
.os-reconnect-badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px;
    border-radius: var(--radius-pill);
    background: var(--color-primary-soft);
    color: var(--color-primary);
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.16em;
    text-transform: uppercase;
}
.os-reconnect-badge svg {
    width: 10px;
    height: 10px;
    fill: var(--color-accent);
}
.os-reconnect-title {
    margin: 6px 0 8px;
    font-size: 16px;
    font-weight: 600;
    color: var(--color-text-primary);
}
.os-reconnect-status {
    margin: 0;
    font-size: 13px;
    line-height: 1.55;
    color: var(--color-text-secondary);
}
.os-reconnect-status b { font-variant-numeric: tabular-nums; }
.os-reconnect-meta {
    margin: 0;
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--color-text-muted);
}
.os-reconnect-actions {
    display: flex;
    justify-content: center;
    gap: 8px;
}
/* Abstände nur, wo die Zeile bzw. die Buttons im jeweiligen Zustand auch Inhalt haben —
   sonst bliebe darunter Luft ohne sichtbares Element. */
.os-reconnect.components-reconnect-show .os-reconnect-meta { margin-top: 10px; }
.os-reconnect.components-reconnect-paused .os-reconnect-actions,
.os-reconnect.components-reconnect-failed .os-reconnect-actions,
.os-reconnect.components-reconnect-resume-failed .os-reconnect-actions,
.os-reconnect.components-reconnect-rejected .os-reconnect-actions { margin-top: 16px; }

/* Zustands-abhängige Textblöcke und Buttons. „retrying" kommt bei Blazor zusätzlich zu „show",
   deshalb der :not() beim Verbinden-Text. */
.os-reconnect [data-rc-state] { display: none; }
.os-reconnect.components-reconnect-show:not(.components-reconnect-retrying) [data-rc-state~="connecting"],
.os-reconnect.components-reconnect-retrying [data-rc-state~="retrying"],
.os-reconnect.components-reconnect-paused [data-rc-state~="paused"],
.os-reconnect.components-reconnect-failed [data-rc-state~="failed"],
.os-reconnect.components-reconnect-resume-failed [data-rc-state~="resume-failed"],
.os-reconnect.components-reconnect-rejected [data-rc-state~="rejected"] {
    display: inline;
}
/* Buttons brauchen das Flex-Layout aus .ds-btn zurück. */
.os-reconnect.components-reconnect-paused button[data-rc-state~="paused"],
.os-reconnect.components-reconnect-failed button[data-rc-state~="failed"],
.os-reconnect.components-reconnect-resume-failed button[data-rc-state~="resume-failed"],
.os-reconnect.components-reconnect-rejected button[data-rc-state~="rejected"] {
    display: inline-flex;
}

/* ── Bagger-Szene ────────────────────────────────────────── */
.os-reconnect-scene {
    display: block;
    width: 100%;
    max-width: 230px;
    height: auto;
    margin: 4px auto 2px;
}
.os-rc-ground     { stroke: var(--color-border-strong); }
.os-rc-body       { fill: var(--color-accent); }
.os-rc-boom       { stroke: var(--color-accent); }
.os-rc-metal      { fill: var(--color-text-secondary); }
.os-rc-wheel      { fill: var(--color-surface); stroke: var(--color-text-secondary); }
.os-rc-glass      { fill: var(--color-surface); }
.os-rc-dirt       { fill: var(--color-text-faint); }
.os-rc-beacon     { fill: var(--color-warning); }
.os-rc-beacon-halo { stroke: var(--color-warning); }

/* transform-origin in viewBox-Einheiten: transform-box ist bei SVG-Elementen ohnehin view-box. */
.os-rc-machine    { animation: os-rc-bob 2.4s ease-in-out infinite; }
.os-rc-arm        { transform-origin: 100px 66px; animation: os-rc-dig 2.4s ease-in-out infinite; }
.os-rc-beacon     { animation: os-rc-blink 1.1s steps(1, end) infinite; }
.os-rc-beacon-halo { transform-origin: 53.5px 52px; animation: os-rc-halo 1.1s ease-out infinite; }
.os-rc-clod       { animation: os-rc-clod 2.4s ease-in-out infinite; }
.os-rc-clod-2     { animation-delay: 0.1s; }
.os-rc-clod-3     { animation-delay: 0.2s; }
.os-rc-dust       { animation: os-rc-dust 2.4s ease-out infinite; }
.os-rc-dust-1     { transform-origin: 164px 98px; }
.os-rc-dust-2     { transform-origin: 174px 100px; animation-delay: 0.18s; }
.os-rc-smoke      { transform-origin: 88.5px 49px; animation: os-rc-smoke 2.4s linear infinite; }
.os-rc-smoke-2    { animation-delay: 0.9s; }
/* Grundzustand ohne Animation: nur die ruhende Maschine, nichts fliegt. */
.os-rc-beacon-halo, .os-rc-clod, .os-rc-dust, .os-rc-smoke { opacity: 0; }

@keyframes os-rc-dig {
    0%   { transform: rotate(0deg); }    /* Schaufel im Boden */
    30%  { transform: rotate(-20deg); }
    46%  { transform: rotate(-26deg); }
    54%  { transform: rotate(-22deg); }  /* Kipp-Ruck */
    70%  { transform: rotate(-25deg); }
    100% { transform: rotate(0deg); }
}
@keyframes os-rc-bob {
    0%, 100% { transform: translateY(0); }
    10%      { transform: translateY(1.2px); }
    46%      { transform: translateY(-0.6px); }
}
@keyframes os-rc-clod {
    0%, 44%   { opacity: 0; transform: translate(0, 0); }
    50%       { opacity: 1; transform: translate(1px, 2px); }
    74%       { opacity: 1; transform: translate(10px, 20px); }
    82%, 100% { opacity: 0; transform: translate(12px, 23px); }
}
@keyframes os-rc-dust {
    0%, 70% { opacity: 0;    transform: scale(0.4); }
    78%     { opacity: 0.5;  transform: scale(0.9) translateY(-2px); }
    100%    { opacity: 0;    transform: scale(1.6) translateY(-10px); }
}
@keyframes os-rc-halo {
    0%   { opacity: 0.5; transform: scale(0.6); }
    100% { opacity: 0;   transform: scale(2.2); }
}
@keyframes os-rc-blink {
    0%, 49%   { opacity: 1; }
    50%, 100% { opacity: 0.3; }
}
@keyframes os-rc-smoke {
    0%   { opacity: 0;   transform: translate(0, 0) scale(0.6); }
    12%  { opacity: 0.4; }
    45%  { opacity: 0;   transform: translate(-7px, -16px) scale(1.4); }
    100% { opacity: 0;   transform: translate(-7px, -16px) scale(1.4); }
}

/* Steht die Verbindung nicht mehr in Aussicht, steht auch die Baustelle still — nur die
   Warnleuchte blinkt weiter, dann in Rot. */
.os-reconnect.components-reconnect-paused .os-rc-anim,
.os-reconnect.components-reconnect-failed .os-rc-anim,
.os-reconnect.components-reconnect-resume-failed .os-rc-anim,
.os-reconnect.components-reconnect-rejected .os-rc-anim,
.os-reconnect.components-reconnect-paused .os-reconnect-hazard::before,
.os-reconnect.components-reconnect-failed .os-reconnect-hazard::before,
.os-reconnect.components-reconnect-resume-failed .os-reconnect-hazard::before,
.os-reconnect.components-reconnect-rejected .os-reconnect-hazard::before {
    animation: none;
}
.os-reconnect.components-reconnect-failed .os-rc-beacon,
.os-reconnect.components-reconnect-resume-failed .os-rc-beacon,
.os-reconnect.components-reconnect-rejected .os-rc-beacon {
    fill: var(--color-danger);
    animation: os-rc-blink 1.1s steps(1, end) infinite;
}

@media (prefers-reduced-motion: reduce) {
    .os-reconnect, .os-reconnect *, .os-reconnect-hazard::before { animation: none !important; }
}

/* ApexCharts-Tooltip: die Lib rendert eigenes Hell-Styling — im Dark-Theme auf Tokens ziehen. */
html[data-theme="dark"] .apexcharts-tooltip,
html[data-theme="dark"] .apexcharts-tooltip-title,
html[data-theme="dark"] .apexcharts-menu {
    background: var(--color-surface-hover);
    color: var(--color-text-primary);
    border-color: var(--color-border);
}
