/* RPB · Stilvorlage – ein einziges Stylesheet.
   Zusammengelegt aus tokens.css und app.css: zwei Dateien im <head> kosteten zwei blockierende
   Netzumläufe vor dem ersten Anstrich. Reihenfolge ist Pflicht – die Tokens stehen oben, alle
   Regeln darunter greifen darauf zu. */

/* ═══ Design-Tokens ═════════════════════════════════════════════════ */

/* RPB · Design-Tokens
   Farbwelt aus den Entwürfen: dunkelblauer Kopf, blaue und grüne Kennzeichnungen, rote Warnbereiche.
   Alle Kontraste mindestens 4,5:1 auf ihrem Hintergrund. Systemschriften, nichts extern geladen. */
:root {
  color-scheme: light;

  --font-sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;

  --color-primary: #1b3a5c;
  --color-primary-strong: #12283f;
  /* Grund der Topbar: heller als --color-primary, aber dunkel genug, dass weißer Text 7,75:1 und die
     Fokusfarbe 4,64:1 erreichen – beides über den 4,5:1, die der Entwicklungsplan pauschal verlangt.
     Gemessen am 29.09.2026. Wer hier aufhellt, muss beides neu rechnen. */
  --color-primary-light: #2a5580;
  --color-primary-contrast: #ffffff;
  --color-accent: #1d4ed8;
  --color-accent-strong: #1e40af;
  --color-accent-soft: #dbeafe;
  --color-success: #166534;
  --color-success-soft: #dcfce7;
  --color-warning: #92400e;
  --color-warning-soft: #fef3c7;
  --color-danger: #991b1b;
  --color-danger-strong: #7f1d1d;
  --color-danger-soft: #fee2e2;
  --color-info: #1e40af;
  --color-info-soft: #dbeafe;

  --color-bg: #f3f5f8;
  --color-surface: #ffffff;
  --color-surface-muted: #f8fafc;
  /* Hover einer Tabellenzeile: eigener, heller Blauton. `--color-surface-muted` ist seit den Zeilenstreifen die
     Farbe jeder zweiten Zeile, ein Hover darin wäre auf gestreiften Zeilen unsichtbar; heller als die Auswahl
     (`--color-accent-soft`), damit „überfahren“ und „ausgewählt“ nicht verwechselt werden. */
  --color-row-hover: #eaf1fb;
  --color-text: #1f2937;
  --color-text-muted: #4b5563;
  --color-border: #d1d5db;
  --color-border-strong: #6b7280;
  --color-focus: #b45309;
  /* Fokus auf dunklen Flächen. --color-focus erreicht auf --color-primary nur 2,32:1 und auf
     --color-primary-light sogar nur 1,54:1 – beides unter den 3:1 aus WCAG 1.4.11. Dieser Ton erreicht
     dort 6,96:1 bzw. 4,64:1. Eine einzige Fokusfarbe für helle und dunkle Flächen geht nicht auf; der
     Geltungsbereich ist deshalb eng geschnitten, siehe unten bei :focus-visible.
     Gerechnet in tests/unit/FocusContrast.test.ts. */
  --color-focus-on-dark: #fbbf24;

  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;
  --space-7: 3rem;

  --radius-sm: 0.375rem;
  --radius: 0.625rem;
  --radius-lg: 1rem;
  --radius-pill: 999px;

  --shadow-sm: 0 1px 2px rgb(15 23 42 / 0.06);
  --shadow: 0 4px 16px rgb(15 23 42 / 0.08);
  --shadow-lg: 0 16px 48px rgb(15 23 42 / 0.22);
  --backdrop: rgb(15 23 42 / 0.55);

  --container: 72rem;
  --container-narrow: 40rem;
  --touch-target: 44px;
  --transition: 150ms ease;
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --transition: 0ms;
  }
}

/* ═══ Regeln ════════════════════════════════════════════════════════ */
/* RPB · Grundstile (Mobile First ab 320 px) */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  font-family: var(--font-sans);
  font-size: 100%;
  line-height: 1.5;
  -webkit-text-size-adjust: 100%;
  background: var(--color-bg);
  color: var(--color-text);
}

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

img,
svg {
  max-width: 100%;
  height: auto;
}

a {
  color: var(--color-accent);
  text-underline-offset: 0.15em;
}

a:hover {
  color: var(--color-accent-strong);
}

:focus-visible {
  outline: 3px solid var(--color-focus);
  /* Der Abstand hält den Rahmen von gefüllten Flächen fern: auf `.btn--primary` (Akzentblau) stieße
     er sonst direkt an die Füllung und käme dort auf 1,33:1, auf `.btn--danger` auf 1,65:1 – unter den
     3:1 aus WCAG 1.4.11 (AA). Mit Abstand liegt er im Seitenhintergrund und erreicht 5,0:1.
     Offen bleibt die Kopfzeile: dort liegt dieselbe Farbe innen wie außen auf Dunkelblau und erreicht
     nur 2,32:1 – das löst kein Abstand, sondern nur eine eigene Fokusfarbe (Phase 4b). */
  outline-offset: 2px;
}

/* Die beiden dunklen Flächen tragen die dunkle Fokusfarbe.

   GELTUNGSBEREICH, nicht wegkürzen: Innerhalb dieser Flächen liegen helle Bereiche – heute keiner mehr,
   seit das Benutzermenü der Topbar gewichen ist, aber jeder künftige (Klappfach, Karte, Meldung) braucht
   die Rücknahme. Ein `.site-header :focus-visible` ohne sie erzeugte auf weißem Grund 1,67:1, wo sonst
   5,02:1 stehen – und ein reiner Token-Test bliebe dabei grün. Der Fall ist in
   tests/unit/FocusContrast.test.ts festgehalten. */
.site-header :focus-visible,
.topbar :focus-visible,
.skip-link:focus-visible {
  outline-color: var(--color-focus-on-dark);
}

/* Rücknahme für helle Flächen innerhalb der dunklen. Die Auswahl steht hier bewusst auch ohne heutigen
   Treffer: Sie ist die Regel, nicht die Ausnahme. */
.site-header .card :focus-visible,
.topbar .card :focus-visible {
  outline-color: var(--color-focus);
}

[hidden] {
  display: none !important;
}

/* Nur für Screenreader sichtbar (z. B. Beschriftungen ohne Platz im Layout). */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  background: var(--color-surface-muted);
  padding: 0.1em 0.35em;
  border-radius: var(--radius-sm);
}

/* ─── Layout ─────────────────────────────────────────────────────────── */

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--space-4);
}

.site-main {
  flex: 1;
  padding-block: var(--space-5) var(--space-7);
}

.site-main:focus {
  outline: none;
}

.skip-link {
  position: absolute;
  left: var(--space-4);
  top: -100px;
  z-index: 100;
  padding: var(--space-2) var(--space-4);
  background: var(--color-surface);
  color: var(--color-text);
  /* Rahmen bleibt bei der HELLEN Fokusfarbe, obwohl die Sprungmarke im sichtbaren Zustand auf dem dunklen
     Kopf liegt: Sie ist eine weiße Kachel (11,63:1 gegen den Kopf, also ohnehin gut sichtbar), und der
     Rahmen grenzt innen an dieses Weiß – dort erreicht --color-focus 5,02:1, die dunkle Fokusfarbe nur
     1,67:1. Die naheliegende Angleichung an --color-focus-on-dark wäre hier eine Verschlechterung. */
  border: 2px solid var(--color-focus);
  border-radius: var(--radius-sm);
  text-decoration: none;
  font-weight: 600;
}

.skip-link:focus {
  top: var(--space-4);
}

/* ─── Kopfbereich ────────────────────────────────────────────────────── */

.site-header {
  background: var(--color-primary);
  color: var(--color-primary-contrast);
  box-shadow: var(--shadow-sm);
}

.site-header__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3) var(--space-5);
  padding-block: var(--space-4);
}

.site-header__brand {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  color: inherit;
  text-decoration: none;
  min-height: var(--touch-target);
  justify-content: center;
}

.site-header__brand:hover {
  color: inherit;
}

.site-header__title {
  font-size: 1.125rem;
  font-weight: 700;
  letter-spacing: 0.01em;
}

.site-header__subtitle {
  font-size: 0.875rem;
  opacity: 0.9;
}

.main-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
}

.main-nav__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin: 0;
  padding: 0;
  list-style: none;
}

.main-nav__list a {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  padding-inline: var(--space-3);
  color: inherit;
  text-decoration: none;
  border-radius: 0;
  border-bottom: 3px solid transparent;
}

.main-nav__list a:hover,
.main-nav__list a[aria-current="page"] {
  border-bottom-color: var(--color-primary-contrast);
}

/* ─── Topbar: Benutzerbereich über der Hauptnavigation ──────────────────
   Ersetzt das frühere Aufklappmenü (Commit 874f9c5). Ein Menü, das beim Überfahren öffnet, lässt sich ohne
   JavaScript nicht schließen, wie WCAG 1.4.13 es verlangt – und `::details-content { content-visibility:
   hidden }` machte die reine CSS-Lösung ohnehin wirkungslos. Eine sichtbare Leiste braucht die Interaktion
   gar nicht: zwei Verweise, immer sichtbar, immer erreichbar, ohne eine Zeile Skript. */
.topbar {
  background: var(--color-primary-light);
  color: var(--color-primary-contrast);
  font-size: 0.8125rem;
}

.topbar__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2) var(--space-4);
}

.topbar__user {
  margin: 0;
  font-weight: 700;
}

.topbar__separator {
  opacity: 0.6;
}

.topmenu__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.topmenu__list form {
  margin: 0;
}

/* Verweis und Schaltfläche sehen gleich aus – für die Bedienung ist „Abmelden“ ein Vorgang, kein Ziel,
   deshalb bleibt es ein Formular mit CSRF-Feld und wird nur optisch angeglichen. */
.topmenu__list a,
.topmenu__logout {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  padding-inline: var(--space-1);
  font: inherit;
  color: inherit;
  text-decoration: none;
  background: none;
  border: 0;
  cursor: pointer;
}

/* Ohne Unterstreichung im Ruhezustand unterscheiden sich die Verweise optisch nicht vom Benutzernamen
   daneben – gleiche Farbe, gleiche Zeile. Beim Überfahren und bei Tastaturfokus erscheint sie deshalb,
   damit erkennbar bleibt, was anklickbar ist. */
.topmenu__list a:hover,
.topmenu__logout:hover,
.topmenu__list a:focus-visible,
.topmenu__logout:focus-visible,
.topmenu__list a[aria-current="page"] {
  color: inherit;
  text-decoration: underline;
}

/* ─── Fußzeile ───────────────────────────────────────────────────────── */

.site-footer {
  border-top: 1px solid var(--color-border);
  background: var(--color-surface);
  font-size: 0.875rem;
}

.site-footer__inner {
  /* Festes Raster statt Flex-Umbruch: Zeile 1 Copyright | Pflichtseiten, Zeile 2 Support | Version.
     Schmal untereinander, ab 40em zwei Spalten – die Version steht dann rechtsbündig unter den Links. */
  display: grid;
  grid-template-columns: 1fr;
  align-items: center;
  gap: var(--space-2) var(--space-5);
  padding-block: var(--space-4);
}

@media (min-width: 40em) {
  .site-footer__inner {
    grid-template-columns: 1fr auto;
  }

  .site-footer__nav,
  .site-footer__version {
    justify-self: end;
    text-align: end;
  }
}

.site-footer__inner p {
  margin: 0;
}

.site-footer__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

.site-footer__links a {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
}


/* ─── Karten und Text ────────────────────────────────────────────────── */

.card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--space-5);
}

.card--narrow {
  max-width: var(--container-narrow);
  margin-inline: auto;
}

.card__title {
  margin: 0 0 var(--space-3);
  font-size: 1.5rem;
  line-height: 1.25;
}

/* ─── Rückverweis der Unterseiten (element back-link) ───────────────────
   Steht als erstes Element in der Karte einer Unterseite. Zielgröße über --touch-target; der Fokusrahmen kommt
   aus der allgemeinen :focus-visible-Regel. Der Pfeil ist Schmuck, der Text trägt den Sinn. */
.back-nav {
  margin: 0 0 var(--space-3);
}

.back-nav__link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-target);
  font-weight: 600;
}

.back-nav__arrow {
  font-weight: 400;
}

.card__subtitle {
  margin: var(--space-5) 0 var(--space-3);
  font-size: 1.125rem;
}

.lead {
  margin-top: 0;
  font-size: 1.0625rem;
}

.muted {
  color: var(--color-text-muted);
}

.small {
  font-size: 0.875rem;
}

/* ─── Schlüssel-Wert-Liste ───────────────────────────────────────────── */

.kv {
  margin: var(--space-5) 0;
  padding: 0;
  border-top: 1px solid var(--color-border);
}

.kv__row {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-1) var(--space-4);
  padding-block: var(--space-3);
  border-bottom: 1px solid var(--color-border);
}

.kv dt {
  font-weight: 600;
  color: var(--color-text-muted);
}

.kv dd {
  margin: 0;
}

@media (min-width: 40em) {
  .kv__row {
    grid-template-columns: 14rem 1fr;
  }
}

/* ─── Verlauf (Detailansicht Dokumente) ─────────────────────────────── */

/* Ohne Aufzählungspunkte: Text, Zeitpunkt (gedämpft) und IP stehen als Zeile nebeneinander, schmal umbrechend. */
.history-list {
  list-style: none;
  margin: 0 0 var(--space-5);
  padding: 0;
  border-top: 1px solid var(--color-border);
}

.history-list__item {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  padding-block: var(--space-2);
  border-bottom: 1px solid var(--color-border);
}

.history-list__time {
  color: var(--color-text-muted);
}

/* Schaltflächen am Fuß einer Karte (z. B. ZIP und Löschen in der Detailansicht) nebeneinander, schmal umbrechend.
   `align-items: center` richtet begleitenden Text (Datenschutzhinweis neben dem Export im Protokoll) an der
   Mitte der Schaltfläche aus; bei gleich hohen Schaltflächen ändert sich dadurch nichts. */
.card__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-5);
}

/* Steht die Aktionszeile nicht am Fuß, sondern über weiterem Inhalt (Export über der Protokolltabelle),
   braucht sie auch nach unten Luft – sonst saß der Datenschutzhinweis 0 px über dem Suchfeld. Bewusst
   `--space-4` und nicht `--space-5` wie oben: der Export gehört zur Tabelle darunter, nicht zu den
   Hinweisen darüber, und steht deshalb näher an ihr. Am Fuß einer Karte (Detailansicht) greift die
   Regel nicht. */
.card__actions:not(:last-child) {
  margin-bottom: var(--space-4);
}

/* ─── Kennzeichnungen ────────────────────────────────────────────────── */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  padding: 0.2em 0.7em;
  border-radius: var(--radius-pill);
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1.4;
  background: var(--color-surface-muted);
  color: var(--color-text);
  border: 1px solid var(--color-border);
}

.badge--info {
  background: var(--color-info-soft);
  color: var(--color-info);
  border-color: transparent;
}

.badge--success {
  background: var(--color-success-soft);
  color: var(--color-success);
  border-color: transparent;
}

.badge--warning {
  background: var(--color-warning-soft);
  color: var(--color-warning);
  border-color: transparent;
}

.badge--danger {
  background: var(--color-danger-soft);
  color: var(--color-danger);
  border-color: transparent;
}

/* ─── Schaltflächen ──────────────────────────────────────────────────── */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--touch-target);
  padding: var(--space-2) var(--space-4);
  border: 1px solid transparent;
  border-radius: var(--radius);
  font: inherit;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--transition), color var(--transition), border-color var(--transition);
}

.btn--primary {
  background: var(--color-accent);
  color: var(--color-primary-contrast);
}

.btn--primary:hover {
  background: var(--color-accent-strong);
  color: var(--color-primary-contrast);
}

.btn--ghost {
  background: transparent;
  color: inherit;
  border-color: currentColor;
}

/* Zweitrangige Schaltfläche. Die Klasse wurde an drei Stellen benutzt (upload/confirm.ejs,
   log/views/index.ejs, settings/views/index.ejs), war aber im gesamten Stylesheet nie definiert: Die
   Elemente erbten nur `.btn` (durchsichtiger Rahmen) und von `a` die Akzentfarbe, und `.btn` nahm ihnen
   zugleich die Unterstreichung. Übrig blieb fett-blauer Text ohne Fläche neben einer gefüllten
   Schaltfläche – unterscheidbar allein an der Farbe (2,19:1 zum Fließtext, WCAG 1.4.1). */
.btn--secondary {
  background: var(--color-surface-muted);
  color: var(--color-text);
  border-color: var(--color-border-strong);
}

.btn--secondary:hover {
  background: var(--color-surface);
  color: var(--color-text);
  border-color: var(--color-text);
}

.btn--danger {
  background: var(--color-danger);
  color: var(--color-primary-contrast);
}

.btn--danger:hover {
  background: var(--color-danger-strong);
  color: var(--color-primary-contrast);
}

/* 0.6 ergab 4,04:1. WCAG 1.4.3 nimmt inaktive Bedienelemente zwar ausdrücklich aus, aber der gesperrte
   Export-Verweis im Protokoll bleibt lesbar bedienbar (mit `aria-disabled`, nicht `disabled`) – 0.75
   bringt 6,47:1 und macht die Diskussion überflüssig. Gilt für ALLE gesperrten Schaltflächen. */
.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.75;
  cursor: not-allowed;
}

/* ─── Fehlerseite ────────────────────────────────────────────────────── */

.card--error {
  border-top: 4px solid var(--color-danger);
}

.error-status {
  margin: 0;
  font-size: 3rem;
  font-weight: 700;
  line-height: 1;
  color: var(--color-border-strong);
}

/* ─── Formulare ──────────────────────────────────────────────────────── */

.form {
  display: grid;
  gap: var(--space-4);
  margin-top: var(--space-4);
}

.field {
  display: grid;
  gap: var(--space-1);
}

.field__label {
  font-weight: 600;
}

.field__input {
  min-height: var(--touch-target);
  padding: var(--space-2) var(--space-3);
  font: inherit;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
}

/* Auswahlfelder zeigen den Zeigefinger. Bewusst auf Elementebene statt an `.field__input`: Die
   Auswahlfelder tragen unterschiedliche Klassen – in den Vorlagen `field__input`, in `TableView.js`
   einmal zusätzlich `tv-pagination__select` und beim Spaltenfilter gar keine. Eine Klassenregel hätte
   den Filter übersehen. Gesperrte Felder folgen der Regel der Schaltflächen. */
select:not(:disabled) {
  cursor: pointer;
}

select:disabled {
  cursor: not-allowed;
}

.field__input:focus-visible {
  border-color: var(--color-accent);
}

/* Schreibgeschützt (`readonly`, nur Anzeige – Option `readonly` des Elements `field`): grau hinterlegt
   und gestrichelt umrandet, damit es nicht wie ein Eingabefeld wirkt; die Form trägt das mit, nicht nur die Farbe
   (WCAG 1.4.1). Der Text bleibt gut lesbar (`--color-text-muted` auf `--color-bg` rund 7:1), und anders als bei
   `disabled` bleibt das Feld fokussier- und vorlesbar. */
.field__input[readonly] {
  border-style: dashed;
  border-color: var(--color-border-strong);
  background: var(--color-bg);
  color: var(--color-text-muted);
  cursor: default;
}

.field__hint {
  margin: 0;
  font-size: 0.875rem;
  color: var(--color-text-muted);
}

.field__error {
  margin: 0;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--color-danger);
}

.field__input[aria-invalid="true"] {
  border-color: var(--color-danger);
  box-shadow: 0 0 0 1px var(--color-danger);
}

/* ─── Schalter (Checkbox als Switch) ─────────────────────────────────── */

.field--switch {
  position: relative;
}

.switch__input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  opacity: 0;
  pointer-events: none;
}

.switch__label {
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-height: var(--touch-target);
  font-weight: 600;
  cursor: pointer;
}

.switch__track {
  position: relative;
  flex: none;
  width: 2.75rem;
  height: 1.5rem;
  border-radius: var(--radius-pill);
  background: var(--color-border-strong);
  transition: background-color var(--transition);
}

.switch__track::after {
  content: "";
  position: absolute;
  top: 0.125rem;
  left: 0.125rem;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 50%;
  background: var(--color-surface);
  box-shadow: var(--shadow-sm);
  transition: transform var(--transition);
}

.switch__input:checked + .switch__label .switch__track {
  background: var(--color-success);
}

.switch__input:checked + .switch__label .switch__track::after {
  transform: translateX(1.25rem);
}

.switch__input:focus-visible + .switch__label .switch__track {
  outline: 3px solid var(--color-focus);
  outline-offset: 2px;
}

.switch__input:disabled + .switch__label {
  opacity: 0.6;
  cursor: not-allowed;
}

.secret {
  font-family: var(--font-mono);
  font-size: 1.0625rem;
  letter-spacing: 0.08em;
  user-select: all;
  overflow-wrap: anywhere;
}

.form__actions {
  margin: 0;
}

/* ─── Meldungen ──────────────────────────────────────────────────────── */

.alert {
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius);
  border: 1px solid var(--color-border);
  background: var(--color-surface-muted);
  margin-block: var(--space-3);
}

.alert__text {
  margin: 0;
  font-weight: 600;
}

.alert--success {
  background: var(--color-success-soft);
  color: var(--color-success);
  border-color: transparent;
}

.alert--error {
  background: var(--color-danger-soft);
  color: var(--color-danger);
  border-color: transparent;
}

.alert--info {
  background: var(--color-info-soft);
  color: var(--color-info);
  border-color: transparent;
}

.alert--warning {
  background: var(--color-warning-soft);
  color: var(--color-warning);
  border-color: transparent;
}

/* Zweite Zeile eines Hinweises (Erläuterung unter dem fett gesetzten Titel). */
.alert__detail {
  margin: var(--space-1) 0 0;
}

/* Aufzählung unter dem Text eines Hinweises (`alert.ejs` mit `items`). */
.alert__list {
  margin: var(--space-2) 0 0;
  padding-inline-start: var(--space-5);
}

/* ─── Werkzeugleiste und Reiter ──────────────────────────────────────── */

.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-3);
  margin-block: var(--space-4) var(--space-5);
}

.toolbar__search {
  flex: 1 1 14rem;
  min-width: 0;
}

.toolbar__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-inline-start: auto;
}

.tabs__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0 0 var(--space-5);
  padding: 0;
  border-bottom: 1px solid var(--color-border);
}

.tabs__list [role="tab"] {
  appearance: none;
  margin-bottom: -1px;
  padding: var(--space-2) var(--space-3);
  min-height: var(--touch-target);
  border: 0;
  border-bottom: 3px solid transparent;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  background: transparent;
  color: var(--color-text-muted);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  transition: color var(--transition), border-color var(--transition);
}

.tabs__list [role="tab"]:hover {
  color: var(--color-text);
}

.tabs__list [role="tab"][aria-selected="true"] {
  color: var(--color-text);
  border-bottom-color: var(--color-accent);
}

.tabs__list [role="tab"]:focus-visible {
  outline: 3px solid var(--color-focus);
  outline-offset: -3px;
}

/*
 * Bereichsnavigation als echte Links statt ARIA-Reiter (Protokoll, Plan 3b Task 2): eigene Regeln, weil die
 * obigen `[role="tab"]`-Selektoren an dieses Attribut gebunden sind und einen einfachen `<a>` ohne Rolle nicht
 * träfen – ohne diesen Block blieben die Links ungepolstert, ohne Mindest-Tippfläche und ohne sichtbaren
 * aktiven Zustand (Fixrunde 1, Vera I2/Wanda A2).
 */
.tabs__list a {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  margin-bottom: -1px;
  padding: var(--space-2) var(--space-3);
  border-bottom: 3px solid transparent;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  color: var(--color-text-muted);
  text-decoration: none;
  transition: color var(--transition), border-color var(--transition);
}

.tabs__list a:hover {
  color: var(--color-text);
}

/* Zustand nie nur über Farbe (WCAG 1.4.1): Der aktive Reiter trägt zusätzlich Fettschrift und einen
   3 px starken Unterstrich (`border-bottom`, im Ruhezustand durchsichtig). Die Unterstreichung des Textes
   ist am 30.09.2026 entfallen – sie stand direkt über dem Rahmen und wirkte wie eine Dopplung. Die beiden
   verbleibenden Merkmale genügen 1.4.1; wer sie entfernt, muss ein anderes an ihre Stelle setzen. */
.tabs__list a[aria-current="page"] {
  color: var(--color-text);
  font-weight: 700;
  border-bottom-color: var(--color-accent);
}

.tabs__list a:focus-visible {
  outline: 3px solid var(--color-focus);
  outline-offset: -3px;
}

/* ─── Einstellungen: Untermenü (settings/views/index.ejs, pages/settings.js) ─────────────────────────
   Schmal: Menü als umbrechende Reihe über dem Inhalt; ab 48em links neben dem Inhalt. Ohne JavaScript sind die
   Einträge Sprungverweise und alle Abschnitte stehen untereinander. */
.settings__layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-4) var(--space-6);
}

.settings__nav {
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--color-border);
}

.settings__nav-groups,
.settings__nav-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.settings__nav-groups > li + li {
  margin-top: var(--space-3);
}

/* Innenabstand statt Außenabstand: Ränder des ersten Kindes fielen sonst mit denen von `li` und `nav` zusammen.
   Oben mehr Luft (zur Seitenüberschrift bzw. zur Gruppe davor), unten wenig – die Überschrift gehört sichtbar
   zu den Einträgen darunter, nicht zu denen darüber. */
.settings__nav-group {
  display: block;
  padding-block: var(--space-3) var(--space-1);
  padding-inline: var(--space-3);
  font-size: 0.875rem;
  font-weight: 700;
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.settings__nav-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}

.settings__nav-list a {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-target);
  padding: var(--space-2) var(--space-3);
  border-inline-start: 3px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--color-text-muted);
  text-decoration: none;
}

.settings__nav-list a:hover {
  background: var(--color-surface-muted);
  color: var(--color-text);
}

/* Zustand nie nur über Farbe (WCAG 1.4.1): der aktive Eintrag trägt zusätzlich Fettschrift und einen
   3 px breiten Balken; `aria-current` steht im Markup, nicht nur im Stil. */
.settings__nav-list a[aria-current] {
  background: var(--color-surface-muted);
  border-inline-start-color: var(--color-accent);
  color: var(--color-text);
  font-weight: 700;
}

.settings__nav-list a:focus-visible {
  outline: 3px solid var(--color-focus);
  outline-offset: -3px;
}

/* Abschnitt mit Fehlern (vom Skript ergänzt): Text statt nur Farbe */
.settings__nav-flag {
  padding: 0 var(--space-2);
  border: 1px solid var(--color-danger);
  border-radius: var(--radius-sm);
  background: var(--color-danger-soft);
  color: var(--color-danger);
  font-size: 0.875rem;
  font-weight: 700;
}

.settings__content {
  min-width: 0;
  max-width: var(--container-narrow);
}

/* Seit dem Untermenü stehen die Felder in Abschnitten und nicht mehr direkt im Formular – dessen Abstand
   (`.form`, `gap` 16 px) reichte nicht bis hierher, die Felder lagen mit 0 px übereinander. Derselbe Abstand
   im Abschnitt; ausgeblendete Abschnitte bleiben trotz `display: grid` unsichtbar (`[hidden]` mit `!important`). */
.settings__section {
  display: grid;
  gap: var(--space-4);
  margin-bottom: var(--space-6);
}

/* Kein eigener unterer Rand mehr: den Abstand zum ersten Feld gibt `gap` – mit Rand stünde die Überschrift
   weiter von ihren Feldern ab (28 px) als die Felder voneinander (16 px). */
.settings__heading {
  margin: 0;
  font-size: 1.125rem;
}

.settings__subheading {
  margin: var(--space-5) 0 var(--space-2);
  font-size: 1rem;
}

.settings__subheading--minor {
  margin-top: var(--space-4);
  font-size: 0.9375rem;
}

/* Prüfliste: Status-Kennzeichen in fester Spalte, Text bündig daneben – auch bei mehrzeiligem Text. */
.checklist {
  display: grid;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.checklist__item {
  display: grid;
  grid-template-columns: 7rem 1fr;
  align-items: baseline;
  gap: var(--space-3);
}

.checklist__item .badge {
  justify-self: start;
}

.settings__heading:focus-visible {
  outline: 3px solid var(--color-focus);
  outline-offset: 2px;
}

@media (min-width: 48em) {
  .settings__layout {
    grid-template-columns: 15rem minmax(0, 1fr);
    align-items: start;
  }

  .settings__nav {
    padding-bottom: 0;
    padding-inline-end: var(--space-3);
    border-bottom: 0;
    border-inline-end: 1px solid var(--color-border);
  }

  .settings__nav-list {
    flex-direction: column;
    flex-wrap: nowrap;
  }

  .settings__nav-list a {
    display: flex;
  }
}

/* ─── Dateitabelle der Dokument-Detailansicht (documents/views/detail.ejs) ────────────────────────────
   Schlicht, Farben der Seite. Auf schmalen Bildschirmen scrollt nur der Rahmen (fokussierbar, beschriftet),
   nie die Seite; lange Dateinamen und Prüfsummen brechen um. */
.file-table__scroll {
  overflow-x: auto;
  margin-bottom: var(--space-5);
  border-top: 1px solid var(--color-border);
}

.file-table__scroll:focus-visible {
  outline: 3px solid var(--color-focus);
  outline-offset: 2px;
}

.file-table {
  width: 100%;
  min-width: 32rem;
  border-collapse: collapse;
}

.file-table th,
.file-table td {
  padding: var(--space-3);
  border-bottom: 1px solid var(--color-border);
  text-align: start;
  vertical-align: top;
}

.file-table thead th {
  border-bottom-color: var(--color-border-strong);
  background: var(--color-bg);
  color: var(--color-text-muted);
  font-weight: 600;
}

/* Dieselben Zeilenstreifen wie in den Übersichtstabellen (`.tv-row:nth-child(even)`). */
.file-table tbody tr:nth-child(even) {
  background: var(--color-surface-muted);
}

.file-table__name {
  overflow-wrap: anywhere;
  font-weight: 400;
}

.file-table .file-table__num {
  text-align: end;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.file-table__hash {
  font-family: var(--font-mono);
  font-size: 0.875rem;
  overflow-wrap: anywhere;
  user-select: all;
}

/* ─── Projektkarten ──────────────────────────────────────────────────── */

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
  gap: var(--space-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Karten und Kartenraster, die im Hauptbereich aufeinander folgen, brauchen Abstand (z. B. Startseite) */
.site-main > .card + .card-grid,
.site-main > .card-grid + .card,
.site-main > .card + .home-grid,
.site-main > .card + .card {
  margin-top: var(--space-5);
}

/* Startseite: Infobereich zwei Drittel, Projekt-Anmeldung ein Drittel; auf schmalen Bildschirmen untereinander */
.home-grid {
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: var(--space-4);
  align-items: start;
}

@media (max-width: 48rem) {
  .home-grid {
    grid-template-columns: 1fr;
  }

  .home-grid__side {
    order: -1;
  }
}

.project-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.project-card__head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2) var(--space-3);
}

.project-card__title {
  margin: 0;
  font-size: 1.125rem;
  line-height: 1.3;
  overflow-wrap: anywhere;
}

.project-card__rows {
  margin: 0;
  font-size: 0.9375rem;
}

.project-card__rows .kv__row {
  grid-template-columns: 1fr;
  padding-block: var(--space-2);
}

/*
 * Passwort-Zeile der Projektkarte: Geheimtext, darunter „Passwort anzeigen", darunter „Passwort neu
 * erzeugen". `align-items: start` hält die Knöpfe auf ihrer Eigenbreite – ohne das zöge der
 * Rasterplatz sie über die ganze Kartenbreite, und eine Schaltfläche, die wie ein Balken aussieht,
 * liest sich als Hauptaktion der Karte.
 */
.project-card__password {
  display: flex;
  flex-direction: column;
  align-items: start;
  gap: var(--space-2);
}

.project-card__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: auto;
  padding-top: var(--space-2);
}

/* ─── Upload: Projekt-Banner, Dateifeld, Bestätigung ─────────────────── */

/* „Angemeldet bei Projekt: …“ mit Abmelden-Schaltfläche über dem Formular; schmal untereinander */
.project-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
  max-width: var(--container-narrow);
  margin: 0 auto var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-surface-muted);
}

.project-banner__text {
  margin: 0;
}

.project-banner__logout {
  margin: 0;
}

.field__input--file {
  padding: var(--space-2);
}

.confirmation__reference {
  font-size: 1.25rem;
  letter-spacing: 0.04em;
}

.confirmation__remark {
  white-space: pre-line;
}

.file-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.file-list__item {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-1) var(--space-3);
  padding-block: var(--space-1);
  border-bottom: 1px solid var(--color-border);
}

.file-list__item:last-child {
  border-bottom: 0;
}

.file-list__name {
  overflow-wrap: anywhere;
}

/* ─── Ablagefläche (Upload, Plan 2b) ─────────────────────────────────
   Ohne JavaScript: nur Beschriftung, natives Dateifeld, Hinweis, Fehlerzeile.
   `lib/Uploader.js` setzt `dropzone--enhanced` (Rahmen, Text, Schaltfläche, Liste) und `dropzone--active` beim Ziehen. */

.dropzone__area {
  display: grid;
  gap: var(--space-2);
}

.dropzone--enhanced .dropzone__area {
  justify-items: center;
  padding: var(--space-5) var(--space-4);
  border: 2px dashed var(--color-border-strong);
  border-radius: var(--radius);
  background: var(--color-surface-muted);
  text-align: center;
  cursor: pointer;
  transition: border-color var(--transition), background-color var(--transition);
}

/* Das Dateifeld ist nur visuell versteckt und bleibt fokussierbar – sein Fokus wird an der Fläche sichtbar
   (Rahmenfarbe überall, der Fokusring über `:has()`; die Schaltfläche behält ihren eigenen Ring). */
.dropzone--enhanced .dropzone__area:focus-within {
  border-color: var(--color-accent);
}

.dropzone--enhanced .dropzone__area:has(> .field__input--file:focus-visible) {
  outline: 3px solid var(--color-focus);
  outline-offset: 2px;
}

.dropzone--active .dropzone__area {
  border-color: var(--color-accent);
  background: var(--color-accent-soft);
}

.dropzone__text {
  margin: 0;
  font-weight: 600;
}

.dropzone__button {
  cursor: pointer;
}

.dropzone__security {
  /* Eine Zeilenhöhe bleibt immer frei, auch solange hier nichts steht: Erschiene die Zeile erst beim
     ersten Fokus, rutschte die Absendeschaltfläche genau im Moment des Mausdrucks nach unten und der
     Klick ginge verloren (Punkt 59). `prepareAltcha()` nimmt dafür das `hidden` schon beim Start weg. */
  min-height: 1.5em;
  margin: var(--space-2) 0 0;
  font-size: 0.875rem;
  color: var(--color-text-muted);
}

.dropzone__summary {
  margin: 0;
  font-weight: 600;
}

/* Zeilen der Upload-Liste: Kennzeichen · Name · Größe · Entfernen, darunter Fortschritt und Status, zuletzt der Fehlertext. */
.file-list--upload .file-list__item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  grid-template-areas:
    "type name size remove"
    "progress progress progress status"
    "error error error error";
  align-items: center;
  gap: var(--space-1) var(--space-3);
  padding-block: var(--space-2);
}

.file-list__type {
  grid-area: type;
  font-size: 0.75rem;
  letter-spacing: 0.04em;
}

.file-list--upload .file-list__name {
  grid-area: name;
  min-width: 0;
}

.file-list--upload .file-list__size {
  grid-area: size;
  white-space: nowrap;
}

.file-list__remove {
  grid-area: remove;
  padding-inline: var(--space-3);
}

/* Track in `--color-border-strong` (Kontrast zum Hintergrund ≥ 3:1, WCAG 1.4.11), Wert in `--color-accent`. */
.file-list__progress {
  grid-area: progress;
  width: 100%;
  height: 0.5rem;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--color-border-strong);
  overflow: hidden;
  appearance: none;
}

.file-list__progress::-webkit-progress-bar {
  background: var(--color-border-strong);
  border-radius: var(--radius-pill);
}

/* Innenkontur wie bei `.storage-bar__progress`: Füllung (--color-accent) gegen Spur
   (--color-border-strong) erreicht nur 1,39:1 – die Grenze zwischen „übertragen" und „offen" ist für
   Nutzer mit Sehschwäche sonst nicht erkennbar (WCAG 1.4.11). Bei der Speicherleiste war das längst
   gelöst, beim Upload-Balken fehlte die Zeile. */
.file-list__progress::-webkit-progress-value {
  background: var(--color-accent);
  border-radius: var(--radius-pill);
  box-shadow: inset 0 0 0 1px var(--color-surface);
  transition: width var(--transition);
}

.file-list__progress::-moz-progress-bar {
  background: var(--color-accent);
  border-radius: var(--radius-pill);
  box-shadow: inset 0 0 0 1px var(--color-surface);
}

.file-list__status {
  grid-area: status;
  font-size: 0.875rem;
  white-space: nowrap;
}

.file-list__error {
  grid-area: error;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--color-danger);
}

.file-list__item--error {
  padding-left: var(--space-3);
  border-left: 4px solid var(--color-danger);
}

@media (max-width: 30rem) {
  .file-list--upload .file-list__item {
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas:
      "type name"
      "size remove"
      "progress progress"
      "status status"
      "error error";
  }
}

/* ─── Dialog ─────────────────────────────────────────────────────────── */

.dialog {
  width: min(32rem, calc(100% - 2 * var(--space-4)));
  max-width: 32rem;
  max-height: calc(100dvh - 2 * var(--space-4));
  overflow-y: auto;
  margin: auto;
  padding: var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  color: var(--color-text);
  box-shadow: var(--shadow-lg);
}

.dialog::backdrop {
  background: var(--backdrop);
}

.dialog__form {
  display: grid;
  gap: var(--space-4);
  margin: 0;
}

.dialog__title {
  margin: 0;
  font-size: 1.25rem;
  line-height: 1.3;
}

.dialog__text {
  margin: 0;
}

.dialog__list {
  margin: 0;
  padding-inline-start: var(--space-5);
  display: grid;
  gap: var(--space-1);
  max-height: 12rem;
  overflow-y: auto;
}

.dialog__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-3);
  margin: 0;
}

/* Abschnitt „Aktionen“ im Benutzer-Dialog: Überschrift und Schaltflächenreihe. Die Linie trennt sie sichtbar
   von den Feldern darüber – oben wird eingegeben und gespeichert, hier wird sofort gehandelt. Die Reihe
   umbricht auf schmalen Geräten, die Höhe je Knopf kommt aus `.btn` (--touch-target). */
.dialog__section {
  display: grid;
  gap: var(--space-3);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border);
}

.dialog__subtitle {
  margin: 0;
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.3;
}

.dialog__section-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

/* ─── Hinweise (Toast) ───────────────────────────────────────────────── */

.toast-region {
  position: fixed;
  inset-inline-end: var(--space-4);
  inset-block-end: var(--space-4);
  z-index: 200;
  display: grid;
  gap: var(--space-2);
  width: min(24rem, calc(100% - 2 * var(--space-4)));
  pointer-events: none;
}

/* Kein `cursor: pointer` auf dem Hinweis selbst: Seit Plan 4b schließt ihn ausschließlich die
   Schaltfläche. Ein Zeiger auf der ganzen Fläche sagte sonst eine Funktion zu, die es nicht gibt –
   dieselbe Klasse Fehler wie beim früheren Benutzermenü. */
.toast {
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius);
  border: 1px solid var(--color-border);
  border-inline-start: 4px solid var(--color-info);
  background: var(--color-surface);
  color: var(--color-text);
  box-shadow: var(--shadow);
  font-weight: 600;
  animation: toast-in var(--transition) both;
}

.toast__text {
  flex: 1;
  margin: 0;
}

.toast__close {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--touch-target);
  height: var(--touch-target);
  margin: calc(-1 * var(--space-2)) calc(-1 * var(--space-2)) 0 0;
  font: inherit;
  font-size: 1.25rem;
  line-height: 1;
  color: var(--color-text-muted);
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.toast__close:hover {
  color: var(--color-text);
  background: var(--color-surface-muted);
}

.toast--info {
  border-inline-start-color: var(--color-info);
  background: var(--color-info-soft);
  color: var(--color-info);
}

.toast--success {
  border-inline-start-color: var(--color-success);
  background: var(--color-success-soft);
  color: var(--color-success);
}

.toast--warning {
  border-inline-start-color: var(--color-warning);
  background: var(--color-warning-soft);
  color: var(--color-warning);
}

.toast--error {
  border-inline-start-color: var(--color-danger);
  background: var(--color-danger-soft);
  color: var(--color-danger);
}

@keyframes toast-in {
  from {
    opacity: 0;
    transform: translateY(0.5rem);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

/* ─── TableView ──────────────────────────────────────────────────────── */
/* Baustein `lib/TableView.js`: Werkzeugleiste · Bulk-Leiste · Scrollbereich mit Tabelle · Seitennavigation.
   Alle Farben und Abstände aus tokens.css; Symbole (Sortierpfeile, Reihenfolge, Ausrichtung) rein per CSS. */

.tv {
  display: grid;
  /* `minmax(0, 1fr)` statt der stillen Vorgabe `auto`: eine `auto`-Spur wächst auf die Mindestbreite
     ihres breitesten Kindes – bei 390 px war das die Werkzeugleiste (zwei Datumsfelder, eine Auswahl mit
     langem Eintrag), die Spur wurde 414 px breit statt 308, und die ganze Seite scrollte waagerecht.
     So bleibt die Spur bei der Breite der Karte; was breiter ist, scrollt in `.tv-scroll`, wo es hingehört. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-3);
}

.tv-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

.tv-search {
  flex: 1 1 14rem;
  min-width: 0;
}

.tv-search__input {
  width: 100%;
}

.tv-filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  min-width: 0;
  max-width: 100%;
}

/* Ein Filter darf schmaler werden als sein Inhalt: eine Auswahlliste ist sonst so breit wie ihr längster
   Eintrag („Sonderprüfung" in der Dateitypen-Verwaltung: 414 px) und schiebt die Leiste aus der Karte. */
.tv-filter {
  min-width: 0;
  max-width: 100%;
}

.tv-filter__input {
  min-width: 10rem;
  max-width: 100%;
}

/* Zeitraumfilter (`filterType: 'date'`): zwei Datumsfelder von/bis nebeneinander – und untereinander,
   wenn die Zeile dafür nicht reicht (ab etwa 400 px Kartenbreite). Ohne Umbruch waren beide zusammen
   415 px breit und das schmalste, was die Leiste werden konnte. Der Abstand zwischen den beiden Paaren
   ist größer als der zwischen Beschriftung und Feld (`.tv-filter__date`), damit „Bis" sichtbar zum
   zweiten Feld gehört und nicht zum ersten. */
.tv-filter--date {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
}

.tv-filter__date {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

.tv-filter__date-label {
  /* Gleiche Mindestbreite für „Von" und „Bis": stehen die beiden Paare untereinander (schmale Karte),
     beginnen beide Datumsfelder an derselben Kante statt um die Breite eines Buchstabens versetzt. */
  min-width: 2em;
  font-size: 0.875rem;
  color: var(--color-text-muted);
}

.tv-filter__input--date {
  min-width: 9rem;
}

/* Spaltenkonfiguration: <details> als Aufklappmenü rechts in der Werkzeugleiste */
/* Rahmen um Scrollbereich und Zahnrad (`TableView#build()`): Das Zahnrad liegt oben rechts über der Kopfzeile,
   aber außerhalb des Scrollbereichs – sein Menü wird nicht abgeschnitten (`.tv-scroll` hat `overflow-x: auto`),
   und es bleibt beim waagerechten Scrollen stehen. Vorher stand es in einer eigenen Zeile der Werkzeugleiste,
   die oft nur aus ihm bestand. */
.tv-frame {
  position: relative;
  min-width: 0;
}

.tv-columns {
  position: absolute;
  top: calc(1px + var(--space-1));
  inset-inline-end: calc(1px + var(--space-1));
  z-index: 2;
}

/* Platz für das Zahnrad in der Kopfzeile, nur in Tabellen mit Spaltenmenü: mindestens so hoch wie der Knopf
   samt Innenabstand – auch wenn keine sortierbare Spalte sichtbar ist, deren Knopf die Zeile sonst so hoch
   macht –, und rechts frei, damit es keine Überschrift verdeckt. */
.tv--columns-in-head .tv-table thead .tv-cell {
  height: calc(var(--touch-target) + 2 * var(--space-1));
}

.tv--columns-in-head .tv-table thead .tv-cell:last-child {
  padding-inline-end: calc(var(--touch-target) + var(--space-3));
}

/* Nur das Symbol: quadratisch und so groß wie jede andere Schaltfläche (`--touch-target`, 44 px – über der
   Zielgröße aus WCAG 2.5.8). `position: relative` verankert den Tooltip. */
.tv-columns__summary {
  position: relative;
  min-width: var(--touch-target);
  padding-inline: var(--space-2);
  list-style: none;
}

.tv-columns__summary::-webkit-details-marker {
  display: none;
}

/* Das Zahnrad steht in einem `aria-hidden`-Element und nicht mehr an der Zusammenfassung selbst: erzeugter
   Inhalt zählt sonst zum Namen des Knopfs, und ein Screenreader läse „Zahnrad" mit. */
.tv-columns__icon::before {
  content: "\2699";
  font-size: 1.25em;
  line-height: 1;
}

/* Tooltip des Spaltenknopfs (WCAG 1.4.13): erscheint bei Zeiger **und** Tastaturfokus, bleibt, solange einer von
   beiden da ist, lässt sich mit Escape ausblenden (`TableView#onTooltipEscape`) und bleibt stehen, wenn man mit
   dem Zeiger auf ihn fährt – er hängt im Knopf selbst, und `::before` überbrückt die Lücke darüber. Unter dem
   Knopf und rechtsbündig, weil der Knopf am rechten Rand der Leiste steht. Der Rand bleibt im Kontrastmodus
   sichtbar, wenn das System Hintergrund und Schrift ersetzt. */
.tv-tooltip {
  position: absolute;
  inset-inline-end: 0;
  top: calc(100% + var(--space-2));
  z-index: 31;
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--color-text);
  border-radius: var(--radius);
  background: var(--color-text);
  color: var(--color-surface);
  font-size: 0.875rem;
  font-weight: 400;
  line-height: 1.4;
  white-space: nowrap;
  visibility: hidden;
}

.tv-tooltip::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: 100%;
  height: calc(var(--space-2) + 1px);
}

.tv-columns__summary:hover .tv-tooltip,
.tv-columns__summary:focus-visible .tv-tooltip {
  visibility: visible;
}

/* Nach den beiden Regeln oben, gleiche Spezifität: offenes Menü und Escape gewinnen. */
.tv-columns[open] .tv-tooltip,
.tv-columns__summary[data-tooltip-dismissed] .tv-tooltip {
  visibility: hidden;
}

.tv-columns__panel {
  position: absolute;
  inset-inline-end: 0;
  top: calc(100% + var(--space-1));
  z-index: 30;
  display: grid;
  gap: var(--space-2);
  /* Breite aus dem Inhalt: der längste Spaltenname („Ansprechpartner Stadt“) soll in einer Zeile
     stehen. Erst an der Obergrenze – damit das Menü im Blickfeld bleibt – greift der Umbruch. */
  width: max-content;
  max-width: min(44rem, calc(100vw - 2 * var(--space-4)));
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-surface);
  box-shadow: var(--shadow);
}

.tv-columns__list {
  display: grid;
  gap: var(--space-1);
}

.tv-columns__item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  min-height: var(--touch-target);
  margin: 0;
  padding: var(--space-1) var(--space-2);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
}

.tv-columns__item:hover {
  background: var(--color-surface-muted);
}

.tv-columns__item--dragging {
  opacity: 0.5;
}

.tv-columns__item--over {
  border-color: var(--color-accent);
  background: var(--color-accent-soft);
}

.tv-columns__handle {
  flex: none;
  width: 0.75rem;
  height: 1.25rem;
  color: var(--color-border-strong);
  background-image: radial-gradient(currentColor 1.5px, transparent 1.6px);
  background-size: 0.375rem 0.4rem;
  cursor: grab;
}

.tv-columns__item:active .tv-columns__handle {
  cursor: grabbing;
}

.tv-columns__toggle {
  display: inline-flex;
  /* `auto` statt einer festen Basis: nur so geht der Spaltenname in die Inhaltsbreite des Menüs ein. */
  flex: 1 1 auto;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  cursor: pointer;
}

.tv-columns__label {
  overflow-wrap: anywhere;
}

.tv-columns__order,
.tv-columns__align {
  display: inline-flex;
  gap: var(--space-1);
}

.tv-columns__reset {
  justify-self: start;
}

/* Ausrichtung: drei Radiobuttons als Symbol (drei Linien, mittlere Linie links/mittig/rechts) */
.tv-columns__radio {
  position: relative;
  display: inline-grid;
  place-items: center;
  width: var(--touch-target);
  height: var(--touch-target);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  cursor: pointer;
}

.tv-columns__radio input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.tv-columns__radio:has(:checked) {
  border-color: var(--color-primary);
  background: var(--color-primary);
  color: var(--color-primary-contrast);
}

/* Ohne :has() bleibt der Radiobutton sichtbar, damit Auswahl und Fokus erkennbar sind. */
@supports not selector(:has(a)) {
  .tv-columns__radio input {
    position: static;
    width: auto;
    height: auto;
    opacity: 1;
  }
}

.tv-columns__radio:has(:focus-visible) {
  outline: 3px solid var(--color-focus);
  outline-offset: 2px;
}

.tv-columns__glyph {
  width: 1rem;
  height: 0.75rem;
  background-image:
    linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-size: 100% 2px, 60% 2px, 100% 2px;
  background-position: 0 0, 0 5px, 0 10px;
}

.tv-columns__radio[data-align="center"] .tv-columns__glyph {
  background-position: 0 0, 50% 5px, 0 10px;
}

.tv-columns__radio[data-align="right"] .tv-columns__glyph {
  background-position: 0 0, 100% 5px, 0 10px;
}

/* Symbolschaltflächen (Reihenfolge) */
/* `--color-border` erreicht auf Weiß nur 1,47:1; `--color-border-strong` 4,83:1. Die Schaltflächen sind
   zwar an Ziffer bzw. Pfeilzeichen erkennbar (14,68:1), aber die Kontur ist Teil der Bedienelement-Grenze
   (WCAG 1.4.11) – der stärkere Ton kostet nichts. Gilt auch für .tv-page weiter unten. */
.tv-iconbtn {
  display: inline-grid;
  place-items: center;
  width: var(--touch-target);
  height: var(--touch-target);
  padding: 0;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: inherit;
  font: inherit;
  cursor: pointer;
  transition: background-color var(--transition);
}

.tv-iconbtn:hover:not(:disabled) {
  background: var(--color-surface-muted);
}

.tv-iconbtn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.tv-iconbtn--col-up::before {
  content: "\2191";
}

.tv-iconbtn--col-down::before {
  content: "\2193";
}

/* Bulk-Leiste: erscheint, sobald etwas gewählt ist */
.tv-bulk {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-accent);
  border-radius: var(--radius);
  background: var(--color-accent-soft);
  animation: tv-in var(--transition) both;
}

.tv-bulk__count {
  font-weight: 600;
  color: var(--color-accent-strong);
}

.tv-bulk__hint {
  font-size: 0.875rem;
  color: var(--color-text-muted);
}

.tv-bulk__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-inline-start: auto;
}

@keyframes tv-in {
  from {
    opacity: 0;
    transform: translateY(-0.25rem);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

/* Scrollbereich (tabindex="0", waagerecht scrollbar) und Tabelle */
.tv-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-surface);
}

.tv-scroll:focus-visible {
  outline: 3px solid var(--color-focus);
  outline-offset: 2px;
}

.tv-scroll[aria-busy="true"] {
  opacity: 0.7;
}

.tv-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9375rem;
}

.tv-cell {
  padding: var(--space-2) var(--space-3);
  text-align: left;
  vertical-align: middle;
  border-bottom: 1px solid var(--color-border);
}

/* Kopf: Seitenfarbe und eine kräftige Linie darunter – deutlich dunkler als die gestreiften Zeilen, damit Kopf
   und Zeilen nicht verschwimmen. */
.tv-table thead .tv-cell {
  padding-block: var(--space-1);
  border-bottom-color: var(--color-border-strong);
  background: var(--color-bg);
  font-weight: 600;
  white-space: nowrap;
}

.tv-table tbody .tv-row:last-child .tv-cell {
  border-bottom: 0;
}

.tv-cell--center {
  text-align: center;
}

.tv-cell--right {
  text-align: right;
}

.tv-cell--select {
  width: 2.75rem;
}

/* Die Aktionen-Spalte bleibt rechts stehen, wenn eine breite Tabelle waagerecht scrollt – auf schmalen
   Bildschirmen wären „Öffnen“, „Eintrag anzeigen“ & Co. sonst erst nach dem Scrollen erreichbar. Deckend, damit
   darunter durchlaufender Inhalt nicht durchscheint: im Kopf mit dessen Hintergrund, in den Zeilen mit dem der
   Zeile (`inherit`, damit Hover, Auswahl und „überfällig“ mitgehen). Deshalb trägt `.tv-row` die Kartenfarbe
   jetzt ausdrücklich – vorher war sie durchsichtig, und `inherit` hätte nichts geerbt. */
.tv-cell--actions {
  position: sticky;
  inset-inline-end: 0;
  z-index: 1;
  white-space: nowrap;
}

.tv-table tbody .tv-cell--actions {
  background: inherit;
}

.tv-row {
  background: var(--color-surface);
}

/* Zeilenstreifen: jede zweite Zeile leicht getönt, damit man die Zeilen einer breiten Tabelle besser auseinander-
   hält. Steht vor Hover und Auswahl; die Auswahl trägt deshalb `.tv-row.tv-row--selected` (sonst gewänne der
   Streifen mit seiner höheren Spezifität), „überfällig“ steht weiter unten und gewinnt über die Reihenfolge. */
.tv-row:nth-child(even) {
  background: var(--color-surface-muted);
}

.tv-row:hover {
  background: var(--color-row-hover);
}

.tv-row.tv-row--selected,
.tv-row.tv-row--selected:hover {
  background: var(--color-accent-soft);
}

.tv-empty,
.tv-loading {
  padding-block: var(--space-6);
  text-align: center;
  color: var(--color-text-muted);
}

.tv-error {
  padding-block: var(--space-6);
  text-align: center;
  color: var(--color-danger);
}

/* Sortierschaltfläche im <th>; Pfeil folgt aria-sort auf dem <th> */
.tv-sort {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: var(--touch-target);
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

.tv-sort:hover {
  color: var(--color-accent-strong);
}

/* 0.5 ergab rechnerisch exakt 3,00:1 auf dem Kopfzeilengrau – genau auf der Grenze von WCAG 1.4.11,
   jede Rundung kippt es. 0.65 bringt 4,72:1. */
.tv-sort__icon::after {
  content: "\2195";
  opacity: 0.65;
}

th[aria-sort="ascending"] .tv-sort__icon::after {
  content: "\2191";
  opacity: 1;
}

th[aria-sort="descending"] .tv-sort__icon::after {
  content: "\2193";
  opacity: 1;
}

/* Checkbox ohne native Darstellung (nur so greifen Pseudo-Elemente): Kästchen 20 px, Klickfläche 44 px über ::before
   (Muster .tv-columns__radio) – ein Klick auf die Fläche trifft die Checkbox selbst. ::after zeichnet Haken bzw. Strich. */
.tv-check {
  appearance: none;
  position: relative;
  display: inline-grid;
  place-items: center;
  width: 1.25rem;
  height: 1.25rem;
  margin: 0;
  border: 2px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  vertical-align: middle;
  cursor: pointer;
  transition: background-color var(--transition), border-color var(--transition);
}

.tv-check::before {
  content: "";
  position: absolute;
  inset: -12px;
}

.tv-check::after {
  content: "";
  width: 0.375rem;
  height: 0.75rem;
  border: solid var(--color-primary-contrast);
  border-width: 0 2px 2px 0;
  transform: translateY(-1px) rotate(45deg);
  opacity: 0;
}

.tv-check:checked,
.tv-check:indeterminate {
  border-color: var(--color-accent);
  background: var(--color-accent);
}

.tv-check:checked::after {
  opacity: 1;
}

.tv-check:indeterminate::after {
  width: 0.625rem;
  height: 0;
  border-width: 0 0 2px;
  transform: none;
  opacity: 1;
}

/* Seitennavigation */
.tv-pagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
}

.tv-pagination__info {
  margin: 0;
  font-size: 0.9375rem;
  color: var(--color-text-muted);
}

.tv-pagination__pages {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1);
  margin-inline: auto;
}

.tv-page {
  min-width: var(--touch-target);
  min-height: var(--touch-target);
  padding: 0 var(--space-2);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: inherit;
  font: inherit;
  cursor: pointer;
  transition: background-color var(--transition), color var(--transition);
}

/* Die aktuelle Seite ist hier ausgenommen: Sie trägt weiße Schrift auf --color-primary, und diese
   Hover-Regel setzte früher nur den Hintergrund um. Die weiße Schrift blieb stehen – weiß auf
   --color-surface-muted sind 1,05:1, die Zahl war beim Überfahren schlicht nicht mehr lesbar.
   Die aktuelle Seite bekommt darunter ihren eigenen Hover-Zustand. */
.tv-page:hover:not(:disabled):not([aria-current="page"]) {
  background: var(--color-surface-muted);
}

.tv-page[aria-current="page"] {
  border-color: var(--color-primary);
  background: var(--color-primary);
  color: var(--color-primary-contrast);
  font-weight: 600;
}

/* Eigener Hover für die aktuelle Seite: dunkler statt heller, damit die weiße Schrift bleibt, was sie
   ist – 14,99:1 auf --color-primary-strong. Die Schaltfläche reagiert damit sichtbar, ohne den
   Farbwechsel des Textes zu erzwingen. */
.tv-page[aria-current="page"]:hover:not(:disabled) {
  border-color: var(--color-primary-strong);
  background: var(--color-primary-strong);
}

.tv-page:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.tv-pagination__gap {
  padding-inline: var(--space-1);
  color: var(--color-text-muted);
}

.tv-pagination__size {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.tv-pagination__select {
  min-width: 5rem;
}

/* ─── Reiter Benutzer: Feldgruppen, Projektkacheln, Schalter in Tabellenzellen ── */

.fieldset {
  display: grid;
  gap: var(--space-3);
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}

.fieldset__legend {
  padding: 0;
  margin-bottom: var(--space-1);
  font-weight: 600;
}

.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.tile {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--touch-target);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius);
  background: var(--color-surface);
  cursor: pointer;
}

.tile:has(.tile__input:checked) {
  border-color: var(--color-accent);
  background: var(--color-accent-soft);
}

.tile:has(.tile__input:focus-visible) {
  outline: 3px solid var(--color-focus);
  outline-offset: 2px;
}

.tile:has(.tile__input:disabled) {
  opacity: 0.6;
  cursor: not-allowed;
}

.tile__input {
  flex: none;
  width: 1.25rem;
  height: 1.25rem;
  margin: 0;
}

.tile__text {
  display: grid;
  min-width: 0;
}

.tile__name {
  font-weight: 600;
}

.tile__code {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  color: var(--color-text-muted);
}

/* Schalter ohne sichtbaren Text in einer Tabellenzelle (Beschriftung nur für Screenreader). */
.tv-switch {
  position: relative;
  display: inline-flex;
  vertical-align: middle;
}

.tv-switch .switch__label {
  gap: 0;
}

.badges {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}

@media (prefers-reduced-motion: reduce) {
  .switch__track,
  .switch__track::after,
  .tabs__list [role="tab"],
  .tabs__list a,
  .dropzone--enhanced .dropzone__area,
  .file-list__progress::-webkit-progress-value {
    transition: none;
  }

  .toast,
  .tv-bulk {
    animation: none;
  }

  .tv-check,
  .tv-iconbtn,
  .tv-page {
    transition: none;
  }
}

/* ─── Dokumente: Kennzahlen, Speicherbalken, erledigte Zeilen ────────── */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
  gap: var(--space-4);
  margin: var(--space-5) 0;
}

.stat-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-surface-muted);
}

/* `<dt>`/`<dd>` innerhalb `.stat-card` (Plan §6.3: Kennzahlen als Beschreibungsliste, WCAG 1.3.1) tragen
   sonst Browser-Vorgaben (`dd` u. a. `margin-left`) – hier ohne Bedeutung, also zurückgesetzt. */
.stat-card__label,
.stat-card__value {
  margin: 0;
}

.stat-card__label {
  font-size: 0.875rem;
  color: var(--color-text-muted);
}

.stat-card__value {
  font-size: 1.75rem;
  font-weight: 700;
  line-height: 1.2;
}

.storage-bar {
  margin: var(--space-5) 0;
}

/* Track in `--color-border-strong` (Kontrast zum Hintergrund ≥ 3:1, WCAG 1.4.11), Wert in `--color-accent`
   (Muster `.file-list__progress`); `--warn`/`--critical` färben den Wert um, der Text daneben bleibt die
   eigentliche Aussage (Farbe allein reicht nicht, WCAG 1.4.1). */
.storage-bar__progress {
  width: 100%;
  height: 0.625rem;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--color-border-strong);
  overflow: hidden;
  appearance: none;
  margin-bottom: var(--space-2);
}

.storage-bar__progress::-webkit-progress-bar {
  background: var(--color-border-strong);
  border-radius: var(--radius-pill);
}

/* Innenkontur in `--color-surface` trennt den Füllstand hart vom Rest der Leiste (WCAG 1.4.11: Füllung vs.
   Leiste allein unterschreitet 3:1) – zusätzlich zur Farbe, unabhängig vom Kontraststand der Töne selbst. */
.storage-bar__progress::-webkit-progress-value {
  background: var(--color-accent);
  border-radius: var(--radius-pill);
  box-shadow: inset 0 0 0 1px var(--color-surface);
  transition: width var(--transition);
}

.storage-bar__progress::-moz-progress-bar {
  background: var(--color-accent);
  border-radius: var(--radius-pill);
  box-shadow: inset 0 0 0 1px var(--color-surface);
}

.storage-bar__progress--warn::-webkit-progress-value {
  background: var(--color-warning);
}

.storage-bar__progress--warn::-moz-progress-bar {
  background: var(--color-warning);
}

.storage-bar__progress--critical::-webkit-progress-value {
  background: var(--color-danger);
}

.storage-bar__progress--critical::-moz-progress-bar {
  background: var(--color-danger);
}

.storage-bar__text {
  margin: 0;
  color: var(--color-text-muted);
  font-size: 0.9375rem;
}

/* Erledigter, liegen gebliebener Vorgang (`documents.staleBadge` daneben trägt den Text – nie Farbe allein). */
.tv-row.is-stale {
  background: var(--color-warning-soft);
  box-shadow: inset 3px 0 0 var(--color-warning);
}

.tv-row.is-stale:hover {
  background: var(--color-warning-soft);
}

/* Ausgewählt und stale zugleich: beide Aussagen bleiben sichtbar statt dass die spezifischere .is-stale-Regel
   die Auswahl unter den Tisch fallen lässt (WCAG 1.4.11 – Auswahl braucht ihre eigene, klar erkennbare Kontur). */
.tv-row.is-stale.tv-row--selected,
.tv-row.is-stale.tv-row--selected:hover {
  background: var(--color-accent-soft);
  box-shadow: inset 3px 0 0 var(--color-warning), inset 0 0 0 2px var(--color-accent);
}

/* Anonymisierte Protokollzeile (Aufbewahrungsfrist abgelaufen) – `log.anonymized` ersetzt die betroffenen
   Zellen bereits textlich; kursiv zusätzlich zur gedämpften Farbe, damit die Kennzeichnung nie an Farbe allein hängt (WCAG 1.4.1). */
.tv-row.is-anonymized {
  color: var(--color-text-muted);
  font-style: italic;
}

.tv-row.is-anonymized:hover {
  color: var(--color-text-muted);
}

/* Abgeschalteter Dateityp (`filetypes.inactive` daneben trägt den Text – nie Farbe allein, WCAG 1.4.1). */
.tv-row.is-inactive {
  color: var(--color-text-muted);
  font-style: italic;
}

.tv-row.is-inactive:hover {
  color: var(--color-text-muted);
}

/* Zeilenaktionen nebeneinander statt über Text-Leerzeichen getrennt – umbricht bei schmalen Spalten. */
.tv-actions {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

/* Rohdaten-Block (z. B. Überwachungsdaten auf /admin/system) */
.code {
  font-family: var(--font-mono);
  font-size: 0.875rem;
  background: var(--color-surface-muted);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: var(--space-3);
  overflow-x: auto;
  white-space: pre;
}

/* ─── Erzwungene Farben (Windows-Kontrastmodus, hoher Kontrast) ──────────
   EN 301 549 §11.7 verlangt, Nutzervoreinstellungen zu achten. In diesem Modus ersetzt das System
   Hintergrund- und Textfarben durch eigene; `opacity`, `box-shadow` und Hintergrundbilder verschwinden
   dagegen oder bleiben wirkungslos. Zustände, die allein über eine Hintergrundfarbe codiert sind,
   verlieren damit ihre Aussage.

   Gemessen am 30.09.2026 über `Emulation.setEmulatedMedia` (DevTools-Protokoll, Chrome 152) – nicht an
   einem echten Windows-Gerät. Die Systemfarben selbst können dort anders ausfallen; dass die Regeln
   greifen und die Zustände unterscheidbar bleiben, ist geprüft. Der Durchgang am echten Gerät steht
   weiterhin unter „Was dieser Plan nicht leisten kann“. */
@media (forced-colors: active) {
  /* Ausrichtung im Spalten-Panel: Der native Radiobutton liegt mit `opacity: 0` über der Fläche, den
     gewählten Zustand trug allein `:has(:checked) { background }`. Gemessen: gewählt wird `Canvas`
     (weiß), nicht gewählt durchsichtig – auf weißem Grund sehen beide gleich aus. Deshalb wird der
     Radiobutton wieder sichtbar, wie im `@supports not selector(:has(a))`-Zweig. */
  .tv-columns__radio input {
    position: static;
    width: auto;
    height: auto;
    opacity: 1;
  }

  /* Die Ablagefläche beim Ziehen war nur an ihrer Hintergrundfarbe erkennbar. */
  .dropzone--active .dropzone__area {
    border-style: solid;
    border-width: 3px;
  }

  /* Fortschrittsbalken: `appearance: none` nimmt die Systemdarstellung, und die Innenkontur aus
     `box-shadow` fällt hier weg. Ein Rahmen bleibt. */
  .file-list__progress,
  .storage-bar__progress {
    border: 1px solid CanvasText;
  }
}

/* ─── Betreiberblock und Versionshinweis ─────────────────────────────── */

/* Logo eigene Zeile, linksbündig mit Überschrift und Text; die Kontaktzeilen nutzen `.kv` wie der Statusblock. */
.vendor__logo {
  display: block;
  width: 11.25rem;
  max-width: 100%;
  height: auto;
  margin-block: var(--space-4) 0;
}

.vendor__address {
  font-style: normal;
}

/* Abstand zum Logo wie zwischen Absatz und Liste; `.kv` setzt sonst oben `--space-5`. */
.vendor__kv {
  margin-block-start: var(--space-4);
}

.site-footer__version {
  color: var(--color-text-muted);
  font-size: 0.8125rem;
}

/* ─── Hilfe im Portal (/admin/hilfe) ─────────────────────────────────── */

/* Seitenleiste mit Suche und Kapiteln links, Inhalt rechts; schmal untereinander (Suche zuerst).
   `minmax(0, …)` auf beiden Spalten: keine darf breiter werden als zugeteilt – vorher schob das Suchfeld die
   Seitenleiste über den Rand der Karte. */
.help {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-5) var(--space-6);
  align-items: start;
}

@media (min-width: 60em) {
  .help {
    grid-template-columns: minmax(0, 16rem) minmax(0, 1fr);
  }

  /* Oberkante der Seitenleiste auf Höhe des Karteninhalts (Innenabstand der Karte), nicht auf Höhe ihres Rahmens. */
  .help__sidebar {
    position: sticky;
    top: var(--space-4);
    padding-top: var(--space-5);
  }
}

.help__sidebar {
  display: grid;
  gap: var(--space-5);
  min-width: 0;
}

.help__search {
  margin: 0;
}

.help__search-row {
  display: flex;
  gap: var(--space-2);
}

.help__search-row .field__input {
  flex: 1 1 auto;
  width: 100%;
  min-width: 0;
}

.help__search-row .btn {
  flex: none;
}

.help__sidebar-title,
.help__toc-title {
  margin: 0 0 var(--space-2);
  font-size: 1rem;
}

.help__chapter-list,
.help__overview {
  display: grid;
  gap: var(--space-1);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Kapitel-Links: Text bündig mit „Kapitel“ und dem Suchfeld; die Fläche (Hover, aktuelles Kapitel) ragt dafür
   um den Innenabstand nach links in den Rand – nach rechts bleibt sie in der Spalte. */
.help__chapter-list a {
  display: flex;
  align-items: center;
  min-height: var(--touch-target);
  margin-inline-start: calc(-1 * var(--space-3));
  padding-inline: var(--space-3);
  border-radius: var(--radius-sm);
}

.help__chapter-list a:hover {
  background: var(--color-bg);
}

.help__chapter-list a[aria-current="page"] {
  background: var(--color-bg);
  font-weight: 600;
  text-decoration: none;
  box-shadow: inset 3px 0 0 var(--color-primary);
}

.help__overview {
  gap: var(--space-2);
  margin-top: var(--space-4);
}

.help__overview a {
  display: flex;
  align-items: center;
  min-height: var(--touch-target);
  padding-inline: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}

.help__content {
  min-width: 0;
}

/* Eine gemeinsame Höchstbreite für alle Blöcke der Karte – Einleitung, Inhaltsverzeichnis, Text und Fuß enden an
   derselben rechten Kante; 75 Zeichen halten die Zeilen lesbar. */
.help__content > * {
  max-width: 75ch;
}

.help__toc {
  margin: var(--space-4) 0 var(--space-5);
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-bg);
}

.help__toc-list {
  margin: 0;
  padding-inline-start: var(--space-5);
}

.help__toc-list li + li {
  margin-top: var(--space-1);
}

/* Text des Kapitels: Überschriften mit Abstand nach oben, Sprungziele nicht unter dem Rand. */

.help__body h2,
.help__body h3 {
  scroll-margin-top: var(--space-4);
}

.help__body h2 {
  margin: var(--space-6) 0 var(--space-3);
  font-size: 1.25rem;
}

.help__body h3 {
  margin: var(--space-5) 0 var(--space-2);
  font-size: 1.0625rem;
}

.help__body ul,
.help__body ol {
  padding-inline-start: var(--space-5);
}

.help__body li + li {
  margin-top: var(--space-1);
}

.help__note {
  margin: var(--space-4) 0;
  padding: var(--space-3) var(--space-4);
  border-inline-start: 4px solid var(--color-primary);
  background: var(--color-bg);
}

.help__note p {
  margin: 0;
}

.help__table {
  max-width: 100%;
  margin: var(--space-4) 0;
  overflow-x: auto;
}

.help__table:focus-visible {
  outline: 3px solid var(--color-focus);
  outline-offset: 2px;
}

.help__table table {
  width: 100%;
  border-collapse: collapse;
}

.help__table th,
.help__table td {
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--color-border);
  text-align: start;
  vertical-align: top;
}

.help__table th {
  background: var(--color-bg);
  font-weight: 600;
}

.help__footer {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
  margin-top: var(--space-6);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border);
}

.help__hits {
  display: grid;
  gap: var(--space-4);
  margin: var(--space-4) 0 0;
  padding: 0;
  list-style: none;
}

.help__hit {
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--color-border);
}

.help__hit-link {
  font-weight: 600;
}

.help__hit-chapter {
  display: block;
}

.help__hit-snippet {
  margin: var(--space-2) 0 0;
}

.help__hit-snippet mark {
  padding-inline: 0.125em;
  background: var(--color-warning-soft);
  color: inherit;
}

.help__contact {
  margin-top: var(--space-6);
}

/* Drucken: nur der Kapiteltext – Menüs, Seitenleiste, Inhaltsverzeichnis und Fuß entfallen. */
@media print {
  .topbar,
  .site-header nav,
  .site-footer,
  .help__sidebar,
  .help__toc,
  .help__footer {
    display: none;
  }

  .help {
    display: block;
  }

  .help__content {
    border: 0;
    box-shadow: none;
  }

  .help__table {
    overflow: visible;
  }
}

/* ─── Zwei-Faktor-Anmeldung (Profil) ─────────────────────────────────── */

.two-factor__subtitle {
  margin: var(--space-5) 0 var(--space-2);
  font-size: 1rem;
}

.two-factor__steps {
  display: grid;
  gap: var(--space-3);
  padding-inline-start: var(--space-5);
}

/* Weißer Rand um den QR-Code, damit ihn Kameras auch auf dunklem Hintergrund erkennen. */
.two-factor__qr {
  display: block;
  margin-top: var(--space-2);
  padding: var(--space-2);
  background: #fff;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}

.two-factor__secret {
  word-spacing: 0.25em;
  overflow-wrap: anywhere;
}

/* Codes in zwei Spalten, gut lesbar und abschreibbar; beim Drucken bleibt die Liste erhalten. */
.two-factor__codes {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
  gap: var(--space-2) var(--space-4);
  margin: var(--space-3) 0;
  padding-inline-start: var(--space-5);
  font-size: 1.0625rem;
}

.two-factor__choices {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-3);
}

/* Auswahl des Wegs: Knopf und Beschriftung auf einer Linie, ausreichend große Klickfläche. */
.field--radio {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-target);
}

.field--radio input {
  width: 1.25rem;
  height: 1.25rem;
  margin: 0;
  accent-color: var(--color-primary);
}

/* ─── Formatierte Texte (Pflichtseiten, Infotext) ────────────────────── */

/* Ausgabe von `RichText` auf Pflichtseiten, Anmeldeseite, Vorschau und Fassungen. */
.richtext h2,
.richtext h3,
.richtext h4 {
  margin: var(--space-5) 0 var(--space-2);
  font-size: 1.125rem;
}

.richtext h3 {
  font-size: 1.0625rem;
}

.richtext h4 {
  font-size: 1rem;
}

.richtext > :first-child {
  margin-top: 0;
}

.richtext ul,
.richtext ol {
  padding-inline-start: var(--space-5);
}

.legal__stand {
  margin-top: var(--space-5);
}

/* Eingabefeld in den Einstellungen: Werkzeugleiste direkt über dem Feld, Aktionen in einer Zeile darunter. */
.richtext-field {
  display: grid;
  gap: var(--space-2);
  margin-bottom: var(--space-5);
}

.richtext-field__toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  /* Bleibt beim Scrollen oben stehen, solange sein Feld sichtbar ist (sticky innerhalb von `.richtext-field`);
     deckend, damit der Text darunter nicht durchscheint. */
  position: sticky;
  top: 0;
  z-index: 2;
  padding-block: var(--space-2);
  background: var(--color-surface);
}

.richtext-field__tool {
  min-height: 2.75rem;
  padding: 0 var(--space-3);
  font: inherit;
  font-size: 0.875rem;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  cursor: pointer;
}

.richtext-field__tool:hover {
  background: var(--color-bg);
}

.richtext-field__tool:focus-visible {
  outline: 3px solid var(--color-focus);
  outline-offset: 2px;
}

/* Gedrückter Zustand (fett, Liste, HTML-Code …): Rahmen und Fläche, nie nur Farbe. */
.richtext-field__tool[aria-pressed="true"] {
  border-width: 2px;
  border-color: var(--color-text);
  background: var(--color-bg);
  font-weight: 600;
}

.richtext-field__tool[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Knopfgruppen der Leiste; „HTML-Code“ rückt nach rechts, solange Platz ist. */
.richtext-field__group {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin-right: var(--space-2);
}

.richtext-field__group--end {
  margin-left: auto;
  margin-right: 0;
}

/* Bearbeitungsfläche (WYSIWYG): sieht aus wie das Eingabefeld, Inhalt wie auf der Seite (`.richtext`). */
.richtext-field__editor {
  /* Höhe: Vorgabe aus den Zeilen des Textfelds bzw. die gemerkte Höhe (`pages/settings-richtext.js`); unten rechts
     in der Höhe ziehbar wie das Textfeld. */
  min-height: 10rem;
  overflow-y: auto;
  resize: vertical;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  line-height: 1.5;
}

.richtext-field__editor:focus-visible {
  outline: 3px solid var(--color-focus);
  outline-offset: 2px;
}

.richtext-field__editor[aria-invalid="true"] {
  border-color: var(--color-danger);
  border-width: 2px;
}

.richtext-field__editor[hidden],
.richtext-field__input[hidden] {
  display: none;
}

/* Linkadresse: eine Zeile mit Eingabe und Knöpfen, auf schmalen Bildschirmen umbrechend. */
.richtext-field__linkform {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-bg);
}

.richtext-field__linkform[hidden] {
  display: none;
}

.richtext-field__linkrow {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.richtext-field__linkrow .field__input {
  flex: 1 1 16rem;
  min-width: 0;
}

.richtext-field__linkform .field__hint,
.richtext-field__linkform .field__error,
.richtext-field__linkform .field__label {
  margin: 0;
}

/* Überschreitung des Zeichenlimits: Farbe plus Fettdruck (nie nur Farbe); --color-danger auf Weiß ≈ 8:1. */
.richtext-field__count--over {
  color: var(--color-danger);
  font-weight: 600;
}

.richtext-field__input {
  width: 100%;
  min-height: 10rem;
  resize: vertical;
  /* HTML-Code: dicktengleich, damit Auszeichnung und Einrückung lesbar bleiben. */
  font-family: var(--font-mono);
  font-size: 0.875rem;
  line-height: 1.5;
}

.richtext-field__count,
.richtext-field .field__hint,
.richtext-field .field__error {
  margin: 0;
}

.richtext-field__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
}

.richtext-field__link {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
}

.richtext-field__checklist {
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}

.richtext-field__checklist-title {
  margin: 0 0 var(--space-2);
  font-weight: 600;
}

.richtext-field__preview {
  padding: var(--space-4);
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-bg);
}

.richtext-field__preview-title {
  margin: 0 0 var(--space-3);
}

/* Frühere Fassungen: jede Fassung aufklappbar, Inhalt wie auf der Seite. */
.versions {
  display: grid;
  gap: var(--space-2);
  margin: var(--space-4) 0 0;
  padding: 0;
  list-style: none;
}

.versions__item {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}

.versions__summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  min-height: var(--touch-target);
  padding: var(--space-2) var(--space-4);
  cursor: pointer;
}

.versions__body {
  padding: 0 var(--space-4) var(--space-4);
}

.versions__restore {
  margin-top: var(--space-4);
}

/* Systemstatus › Installation und Betrieb: Status und Angabe nebeneinander, bei wenig Platz untereinander. */
.ops-check {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-2);
}

.ops-history {
  display: grid;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Systemstatus › Änderungsverzeichnis: aufklappbar, Inhalt im Stil der Hilfe. */
.changelog {
  margin-bottom: var(--space-5);
}

.changelog > summary {
  min-height: var(--touch-target);
  display: flex;
  align-items: center;
  cursor: pointer;
  font-weight: 600;
}

.changelog__body {
  padding-top: var(--space-3);
}
