/* Design-Token liegen in tokens.css und werden davor geladen. */

/* Skip-Link (WCAG 2.4.1): liegt als erstes Element im Body, sitzt aber über dem
   Viewport und kommt nur bei Tastaturfokus herunter. Ziel ist #main.
   Er landet auf dem Navy-Kopfband, deshalb trägt er die eine regelkonforme
   cyane Fläche der App — mit Navy-Schrift, weil Weiß darauf 2,1:1 wäre. */
.skip-link {
  position: absolute; left: var(--space-2); top: calc(-1 * var(--space-16)); z-index: 100;
  padding: var(--space-2) var(--space-4);
  font-family: var(--font-display); font-size: var(--text-sm); font-weight: 600;
  color: var(--color-primary); background: var(--color-accent);
  border-radius: var(--radius-control); text-decoration: none;
  transition: top var(--motion-fast) var(--ease-out);
}
.skip-link:focus { top: var(--space-2); }

/* Schriftfamilien nach CI: DM Sans (humanistisch) traegt den Fliesstext,
   Space Grotesk (technisch) die Ueberschriften, Zahlen und Labels.
   Die Dateien liegen self-hosted, siehe fonts.css. */
body { font-family: var(--font-body); margin: 0; color: var(--color-text); background: var(--color-bg); line-height: var(--leading-normal); }
h1, h2, h3, h4 { font-family: var(--font-display); }
.nav-brand, .unit-index, .flag-severity, .admin-section-label { font-family: var(--font-display); }

/* Grund der Arbeitsseiten: ein 8px-Punktfeld statt einer leeren grauen Fläche.
   Es macht den Grund als Fläche kenntlich, wodurch die weißen Inhaltsflächen
   darauf liegen statt in ihm zu verschwinden — und es ist die einzige
   Dekoration der App. Kein Verlauf, kein Bild, ein Raster. */
.app-page {
  min-height: 100vh;
  background-color: var(--color-bg);
  background-image: radial-gradient(var(--texture-dot) 1px, transparent 1px);
  background-size: var(--texture-step) var(--texture-step);
}
.app-main { max-width: var(--container-app); padding: var(--space-4) var(--space-8) var(--space-8); }

/* Kopf: geschlossenes Navy-Band. Es trägt die Marke, gibt der Seite eine
   Oberkante und ist die Fläche, auf der APV Blue regelkonform Text sein darf
   (7,3:1 auf #002060) — dort sitzt der Aktiv-Zustand der Navigation. */
/* Die Bandhöhe kommt aus Plakette (32px) + Polster und braucht kein min-height
   — das würde sich aufs Polster addieren und den Kopf auf 80px treiben.
   56px mobil, 64px ab Tablet. Jeder Pixel hier kostet den Lernschirm Falz:
   die Tutor-Überschrift muss bei 1440×900 unter ~300px vom Oberrand bleiben. */
/* An `.app-nav` gebunden, nicht am blossen `nav`: die Regel meint das Kopfband und
   sonst nichts. Als Elementregel traf sie jedes künftige <nav> — die Reiterleiste des
   Reviewers stand prompt als navyfarbener Balken mit dunkler Schrift darin. */
.app-nav {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--space-3) var(--space-6);
  margin-bottom: var(--space-4); padding: var(--space-3) var(--space-8);
  background: var(--color-header-bg); border-bottom: 0;
}
@media (min-width: 768px) {
  .app-nav { padding: var(--space-4) var(--space-8); }
}
/* Die Bildmarke auf einer weißen Plakette, nicht direkt auf dem Band: das „A"
   der Marke ist #192e47 und würde auf Navy verschwinden, und der CI-Guide will
   das Logo ohnehin auf weißem Grund. So bleibt es unverändert wie geliefert —
   inklusive Hundekopf und cyaner Schale — und die Plakette gibt dem Kopf
   gleichzeitig ein Formmotiv (gerundetes Quadrat), das Zähler und Badges
   wieder aufnehmen.
   background-image statt <img>: test_nav_consistency vergleicht den Anker in
   nav.html literal, dort darf kein Markup dazukommen. Und background statt
   mask-image, weil eine Maske nur den Alphakanal nutzt — die Marke fiele zu
   einer einfarbigen Silhouette zusammen. */
.nav-brand {
  display: inline-flex; align-items: center; gap: var(--space-3);
  font-weight: 700; color: var(--color-on-header); text-decoration: none;
  border-radius: var(--radius-badge);
}
.nav-brand::before {
  content: ""; flex: none;
  width: 2rem; height: 2rem;
  border-radius: var(--radius-badge);
  background: var(--color-surface) url('/static/brand/apv-mark.svg') center / 1.4rem auto no-repeat;
}
a.nav-brand:hover { text-decoration: underline; }
.nav-links { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3) var(--space-4); }
.nav-links a { color: var(--color-on-header); text-decoration: none; font-weight: 500; padding: 0.25rem 0; border-bottom: 2px solid transparent; }
.nav-links a:hover { text-decoration: underline; }
.nav-links a.active { color: var(--color-accent-on-primary); font-weight: 700; border-bottom-color: var(--color-accent); text-decoration: none; }
.nav-user { color: var(--color-accent-on-primary); font-size: var(--text-sm); }
/* Auf dem Navy-Band dreht sich der Fokusring: Navy wäre dort unsichtbar,
   der Akzent trägt 7,3:1. Gilt für alles im Kopf, auch den Sprachumschalter. */
.app-nav :where(a, button):focus-visible { outline-color: var(--focus-color-on-primary); }
a.nav-brand:focus-visible { outline: var(--focus-width) solid var(--focus-color-on-primary); outline-offset: 3px; }
.lang-switch { display: inline-flex; }
/* line-height: 0 gegen den Baseline-Spalt, den Inline-SVG sonst unter sich lässt. */
.lang-flag { display: inline-flex; padding: 0; margin: 0; background: none; border: 1px solid var(--color-border-strong); border-radius: var(--radius-badge); line-height: 0; cursor: pointer; overflow: hidden; }
.lang-flag:hover { border-color: var(--color-accent); box-shadow: 0 0 0 2px var(--color-accent-tint); }
.lang-flag:focus-visible { outline: var(--focus-width) solid var(--focus-color); outline-offset: var(--focus-offset); }
/* Umschalter-Kopf für die zwei Seiten ohne nav.html (Landing, Login). */
.page-topbar { display: flex; justify-content: flex-end; max-width: var(--container-narrow); margin: 0 auto; padding: var(--space-4) var(--space-6) 0; }
.lang-corner { position: absolute; top: 0.9rem; right: 0.9rem; }
/* Zentrieren gilt fuer jeden <main>; Breite und Polster setzt die Seite. */
main { margin: 0 auto; }
.public-main { max-width: var(--container-narrow); padding: var(--space-8) var(--space-6) var(--space-12); }
/* Ohne eigenen Rahmen: der Lerntext soll im Panel wohnen, nicht darin
   eingerahmt sitzen. Die Kante kommt vom umgebenden .learning-text-panel. */
.content-frame { width: 100%; min-height: 320px; border: 0; border-radius: var(--radius-control); background: var(--color-surface); }
/* Tabellen ohne Gitter: `td, th { border: 1px solid }` war der Excel-Effekt.
   Jetzt trennt nur eine Haarlinie die Zeilen, der Kopf ist ein Label statt
   einer grauen Fläche, und Zahlen stehen in Tabellenziffern untereinander.
   Die Fläche ist weiß und gerundet, damit eine Tabelle auch dort eine eigene
   Fläche ist, wo sie ohne Karte direkt auf dem Punktraster steht (Review-Queue,
   Evidenz-Register). Innerhalb einer Karte fällt sie mit ihr zusammen und
   bleibt unsichtbar. `border-collapse: separate` ist dafür Pflicht — bei
   `collapse` greift die Rundung nicht. */
table {
  border-collapse: separate; border-spacing: 0; width: 100%;
  background: var(--color-surface);
  border-radius: var(--radius-card);
}
/* Die Rundung sitzt an den Eckzellen, nicht an einem `overflow: hidden` der Tabelle.
   Das war zwar kürzer, hat aber jedes absolut positionierte Kind an der
   Tabellenkante abgeschnitten — konkret das „Weitere"-Menü der Review-Queue, von dem
   nur die obersten Pixel innerhalb der Zeile sichtbar blieben. */
thead tr:first-child th:first-child, tbody tr:first-child td:first-child { border-start-start-radius: var(--radius-card); }
thead tr:first-child th:last-child, tbody tr:first-child td:last-child { border-start-end-radius: var(--radius-card); }
tbody tr:last-child td:first-child { border-end-start-radius: var(--radius-card); }
tbody tr:last-child td:last-child { border-end-end-radius: var(--radius-card); }
td, th { border: 0; border-bottom: 1px solid var(--color-border); padding: var(--space-3) var(--space-4); text-align: left; }
th {
  background: transparent; color: var(--color-text-muted);
  font-family: var(--font-display); font-size: var(--label-size); font-weight: var(--label-weight);
  letter-spacing: var(--label-tracking); text-transform: uppercase;
  border-bottom: 2px solid var(--color-primary);
}
td { height: var(--row-h); font-variant-numeric: tabular-nums; }
td:first-child { font-weight: 500; }
tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: var(--color-bg); }
/* Dringlichkeit als Regel, nicht als Fläche: eine rosa Zeile schreit über die
   ganze Breite und färbt dabei auch Daten ein, die nichts damit zu tun haben
   (Datum, Aktionsknopf). Die 3px-Kante am Zeilenanfang sagt dasselbe an der
   Stelle, an der das Auge die Zeile betritt. Das Statuswort trägt die Aussage
   ohnehin doppelt. */
tr.overdue { background: transparent; }
tr.overdue td:first-child { box-shadow: inset 3px 0 0 var(--color-error); }

/* Sortierbare Spaltenköpfe. Der Link erbt die Kopf-Typografie vollständig, sonst
   stünde eine sortierbare Spalte als Fremdkörper zwischen den anderen. Die aktive
   Spalte wird dunkler statt bunt — die Richtung trägt der Pfeil, und der ist als
   `aria-hidden` markiert, weil `aria-sort` am <th> dasselbe schon sagt. */
.sort-link {
  color: inherit; font: inherit; letter-spacing: inherit; text-decoration: none;
  display: inline-flex; align-items: center; gap: var(--space-1); white-space: nowrap;
}
.sort-link:hover { color: var(--color-text); text-decoration: underline; text-decoration-color: var(--color-link-underline); }
.sort-link.is-sorted { color: var(--color-text); }
.sort-mark { font-size: 0.9em; }

/* Pager unter der Tabelle: Gesamtzahl links, Blättern rechts. Die Zahl steht auch
   dann da, wenn es nur eine Seite gibt — sie beantwortet „ist das alles?", und
   genau diese Antwort fehlte, solange drei Listen stumm abschnitten. */
.pager { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-2) var(--space-4); margin-top: var(--space-3); }
.pager p { margin: 0; }
.pager-count, .pager-position { color: var(--color-text-muted); font-size: var(--text-sm); font-variant-numeric: tabular-nums; }
.pager-links { display: flex; align-items: center; gap: var(--space-3); }

/* Karten sind weiße Inseln auf dem Raster — ohne 1px-Umrandung. Der Rahmen war
   Teil der Default-Panel-Signatur; die Kante entsteht jetzt aus dem Kontrast
   zur Textur. Der transparente Rahmen bleibt stehen, damit Modifier wie
   .danger-zone und .review-gate weiterhin eine Kante setzen können. */
.card {
  border: 1px solid transparent; border-radius: var(--radius-card);
  padding: var(--card-pad); margin-bottom: var(--space-6);
  background: var(--color-surface); box-shadow: var(--shadow-card);
}
/* Sektionsüberschriften im Label-Stil — die identitätstragendste Regel des CI.
   Sie ordnet die Fläche ein, ohne mit dem Seitentitel zu konkurrieren.
   Zwei Ausnahmen weiter unten: das Tutor-Panel und die Landing-Karten tragen
   echte Überschriften, weil dort der Text der Inhalt ist. */
.card > h2 {
  font-size: var(--label-size); font-weight: var(--label-weight);
  letter-spacing: var(--label-tracking); text-transform: uppercase;
  color: var(--color-text-muted); margin: 0 0 var(--space-4);
}
.hint { color: var(--color-text-muted); font-size: 0.95rem; }
/* Leerzustand als Fläche, nicht als grauer Satz im Nichts. Sechs Wörter in
   grauer Schrift lassen eine leere Liste wie einen Fehler aussehen; der
   gestrichelte Rahmen sagt „hier ist ein Platz, er ist nur gerade leer".
   Kein Kursiv: es gibt keinen italic-Schnitt, und synthetisch geneigte
   DM Sans sieht schlecht aus. */
.empty {
  display: block; margin: 0;
  padding: var(--space-6);
  color: var(--color-text-muted); text-align: center;
  border: 1px dashed var(--color-border-strong); border-radius: var(--radius-control);
}
.ok { color: var(--color-success-ink); }
.error { color: var(--color-error-ink); }
.warn { color: var(--color-warning-ink); background: var(--color-warning-bg); border: 1px solid var(--color-warning-border); border-radius: var(--radius-control); padding: 0.75rem 1rem; }
/* Zwei Register für H1: Display (40–72px) nur für Marke und Eintritt, hier die
   Arbeitsseiten-Größe. 48px würden auf dem Lernschirm den Tutor unter die Falz
   schieben. balance verhindert die einzelne Restsilbe in Zeile zwei. */
.app-main h1 { font-size: var(--text-3xl); letter-spacing: -0.02em; line-height: var(--leading-tight); }
h1 { margin-top: 0; text-wrap: balance; }
h2, h3 { text-wrap: balance; }
.hint, .landing-lead { text-wrap: pretty; }
/* Der Lead unter dem Seitentitel ist Prosa und läuft auf dem Satzspiegel, nicht
   über die volle Containerbreite — 1100px sind bei 16px rund 140 Zeichen. */
.app-main h1 + .hint { max-width: var(--measure); }
h2 { font-size: 1.15rem; margin: 0 0 0.75rem; }

/* Affordanz, Fokus, Bewegung
   -------------------------------------------------------------------------
   Mit der APV-Palette tragen Links Navy — dieselbe Farbe wie der Fliesstext.
   Vorher war es ein mittleres Blau, das allein durch die Farbe „anklickbar"
   gesagt hat. Diese Aussage uebernimmt jetzt der Akzent als Unterstreichung:
   APV Blue darf keine Textfarbe sein, als 2px-Linie unter dem Text ist es
   dagegen genau richtig. */
a {
  color: var(--color-link);
  text-decoration-color: var(--color-link-underline);
  text-decoration-thickness: 2px;
  text-underline-offset: 2px;
}
a:hover { color: var(--color-link-hover); }
/* Navigation traegt keine Dauer-Unterstreichung (sie hat den Aktiv-Zustand),
   aber der Hover muss als Akzent lesbar sein und nicht als Textfarbe. */
.nav-links a:hover, a.nav-brand:hover, .preview-back:hover {
  text-decoration-color: var(--color-link-underline);
}
/* Und die Textfarbe muss dabei stehen bleiben. `a:hover` oben setzt sie auf
   --color-link-hover (#001440) — auf dem Navy-Band des Headers sind das
   gemessene 1,17:1, der Text verschwindet also beim Zeigen darauf. Der Hover
   gehoert hier der Unterstreichung, nicht der Farbe. */
.nav-links a:hover, a.nav-brand:hover { color: var(--color-on-header); }
.nav-links a.active:hover { color: var(--color-accent-on-primary); }

/* Ein Fokusmuster fuer alles statt drei Einzelselektoren. :where() hat
   Spezifitaet 0 — bestehende Sonderregeln gewinnen weiterhin. */
:where(a, button, summary, input, select, textarea, [tabindex]):focus-visible {
  outline: var(--focus-width) solid var(--focus-color);
  outline-offset: var(--focus-offset);
}

/* Die Motion-Token schalten sich in tokens.css selbst ab. Dieser Block fasst
   zusaetzlich alles, was nicht ueber die Token laeuft — fremde Keyframes,
   scroll-behavior, scrollIntoView-Animationen. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
}

/* Forms */
/* Formularelemente erben die App-Schrift — sonst rendern Browser textarea/input in Monospace. */
button, input, select, textarea, .button { font-family: inherit; }
button, input[type="submit"] { padding: 0.55rem 1rem; border-radius: var(--radius-control); border: 1px solid var(--color-field-border); background: var(--color-surface); cursor: pointer; font-size: 0.95rem; }
button:hover, input[type="submit"]:hover { background: var(--color-bg); }
.stack-form { display: flex; flex-direction: column; gap: 0.75rem; }
/* Ein Knopf ist so breit wie sein Label. In einer Spaltenform streckt
   align-items: stretch ihn sonst über die ganze Formularbreite — 500px
   „Suchen" liest als Fläche, nicht als Aktion. Die Login-Karte ist bewusst
   ausgenommen: dort ist der Knopf der einzige Weg und darf die Karte füllen. */
.stack-form > .btn, .stack-form > button, .stack-form > input[type="submit"] { align-self: start; }
.stack-form label.field, .field { display: flex; flex-direction: column; gap: 0.35rem; font-size: 0.9rem; }
.stack-form input, .stack-form select, .stack-form textarea,
.filter-form input, .filter-form select { padding: 0.55rem 0.75rem; border: 1px solid var(--color-field-border); border-radius: var(--radius-control); font-size: 1rem; }
.filter-form { display: grid; gap: 0.75rem; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); align-items: end; }
.inline-form { display: inline-block; margin-right: 0.35rem; }
.actions { white-space: nowrap; }
/* Zählwerte in der Review-Queue: "6 Flags · 6 blockierend" darf nicht mitten
   im Label brechen, sonst steht "blockierend" allein auf Zeile zwei. */
.findings-open-btn, .flag-open-btn { white-space: nowrap; }
.checkbox-label, .radio-label { display: flex; align-items: center; gap: 0.5rem; margin: 0.35rem 0; }
fieldset { border: 1px solid var(--color-border); border-radius: var(--radius-control); padding: 0.75rem 1rem; margin-bottom: 0.75rem; }
legend { font-weight: 600; padding: 0 0.25rem; }
.batch-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.75rem; margin-bottom: 0.75rem; }
.appeal-details { margin-top: 1rem; }

/* Training layout + tutor (tutor-first) */
.training-layout { display: grid; gap: 1rem; grid-template-columns: 1fr; }
@media (min-width: 900px) {
  /* Eine Spalte, immer: der Lerntext klappt unter dem Tutor auf, wo er auch
     zugeklappt sitzt. Ein Nebeneinander würde beiden Bereichen Breite nehmen. */
  .training-layout.tutor-first { grid-template-columns: minmax(0, 1fr); align-items: start; }
}
.progress-summary { font-weight: 600; color: var(--color-primary); margin: 0.25rem 0 0.75rem; }
.unit-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.35rem; }
.unit-list-item a, .unit-list-item .unit-row { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.75rem; text-decoration: none; color: inherit; padding: 0.5rem 0.65rem; border-radius: var(--radius-control); border: 1px solid transparent; }
.unit-list-item.is-current a, .unit-list-item.is-current .unit-row { background: var(--color-accent-tint); border-color: var(--color-accent-tint-border); }
.unit-list-item.is-done a { opacity: 0.85; }
/* Gesperrt: sichtbar, lesbar, nicht anklickbar. Gedaempft statt ausgeblendet — der
   Lernende soll die Form der ganzen Schulung sehen, nur eben noch nicht betreten. */
.unit-list-item.is-locked .unit-row { color: var(--color-text-muted); cursor: default; }
.unit-index { display: inline-flex; align-items: center; justify-content: center; width: 1.6rem; height: 1.6rem; border-radius: var(--radius-badge); background: var(--color-bg); font-weight: 700; font-size: 0.85rem; }
/* nowrap ist hier Pflicht: bricht ein Inline-Element mit Rahmen um, reißen
   Hintergrund und Rahmen in zwei Hälften auseinander. */
.badge { display: inline-flex; align-items: center; gap: 0.45em; white-space: nowrap; font-size: 0.75rem; font-weight: 600; padding: 0.2rem 0.55rem; border-radius: var(--radius-badge); }
.badge-open { background: var(--color-warning-bg); color: var(--color-warning-ink); border: 1px solid var(--color-warning-border); }
.badge-done { background: var(--color-success-bg); color: var(--color-success-ink); border: 1px solid var(--color-success-border); }
.badge-locked { background: var(--color-bg); color: var(--color-text-muted); border: 1px solid var(--color-border); }

/* Status badges (localized assignment/skill state). */
.status-badge { display: inline-flex; align-items: center; gap: 0.45em; font-size: 0.75rem; font-weight: 600; padding: 0.2rem 0.6rem; border-radius: var(--radius-badge); white-space: nowrap; border: 1px solid transparent; }

/* Die Kompetenzen eines Kurses in seiner Katalogzeile. Kein status-badge: das steht
   fuer einen Zustand, hier geht es um Inhalt. */
.skill-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 0.3rem; }
.skill-chip { font-size: 0.75rem; padding: 0.15rem 0.5rem; border-radius: var(--radius-badge); background: var(--color-bg); border: 1px solid var(--color-border); color: var(--color-text-muted); }
/* Die Kursart in den Lernerlisten. Derselbe Chip wie eine Kompetenz — es ist eine
   Eigenschaft des Inhalts und kein Zustand —, nur etwas fester gesetzt und mit
   Abstand zum Titel, hinter dem er steht. Beide Formen sehen gleich aus: der
   Unterschied steht im Wort, und eine Ampelfarbe wuerde eine Wertung behaupten,
   die es hier nicht gibt. */
.course-kind-chip { display: inline-block; margin-left: 0.4rem; font-weight: 600; color: var(--color-text); }

/* Der Weg zurueck zur Confluence-Seite. `baseline` statt `middle`, damit das Symbol
   auf der Schriftlinie des Textes sitzt, hinter dem es steht — in einer Tabellenzelle
   faellt ein um zwei Pixel verrutschtes Icon sofort auf. Die Flaeche ist bewusst
   groesser als das Zeichen: 14px sind als Klickziel zu wenig. */
.confluence-link { display: inline-flex; align-items: center; padding: 0.2rem; margin-left: 0.25rem; vertical-align: baseline; color: var(--color-text-muted); }
.confluence-link:hover, .confluence-link:focus-visible { color: var(--color-link); }

/* Ein 6px-Punkt vor jedem Badge — die Icon-Strategie für Zustände, ganz ohne
   Assets: eine Form, die pro Rolle nur die Farbe wechselt. Er trägt keine
   eigene Information (das Wort daneben sagt alles) und ist deshalb dekorativ;
   currentColor garantiert, dass er dieselbe geprüfte Tinte hat wie der Text
   und nicht die durchgefallene Guide-Sättigung. */
.status-badge::before, .badge::before {
  content: ""; flex: none;
  width: 6px; height: 6px; border-radius: var(--radius-round);
  background: currentColor;
}
.status-open { background: var(--color-bg); color: var(--color-text-muted); border-color: var(--color-field-border); }
.status-in_progress { background: var(--color-info-bg); color: var(--color-info-ink); border-color: var(--color-info-border); }
.status-overdue { background: var(--color-error-bg); color: var(--color-error-ink); border-color: var(--color-error-border); }
.status-completed { background: var(--color-success-bg); color: var(--color-success-ink); border-color: var(--color-success-border); }
.status-update { background: var(--color-warning-bg); color: var(--color-warning-ink); border-color: var(--color-warning-border); }
/* Inhaltsstatus im Admin-Katalog (publiziert / Draft in Review / retired). */
.status-draft { background: var(--color-warning-bg); color: var(--color-warning-ink); border-color: var(--color-warning-border); }
.status-retired { background: var(--color-bg); color: var(--color-text-muted); border-color: var(--color-border); }
/* Der Kenntnisnahme-Text im Arbeitsplatz: Lesemass wie im Lerntext, damit der
   Reviewer beurteilt, was der Lernende sieht — und nicht eine Textwueste. */
.review-ack-body { max-width: 64ch; }
.review-ack-body p { margin: 0 0 0.75rem; }

/* --- Pflichtschulung ------------------------------------------------------ */

/* Gliederung links, Dokument rechts, Tutor unten rechts — die konventionellste Form,
   die es gibt, und genau die ist hier die Vorgabe. Auf schmalen Schirmen faellt es auf
   eine Spalte zurueck, Gliederung zuerst.

   Das Grid sitzt auf einem Rahmen um beide und nicht auf `.training-layout`: die
   Gliederung (`nav.unit-stepper`) ist dessen Geschwister, nicht sein Kind. Ein Grid auf
   dem Layout hatte nur den Inhalt zu verteilen und quetschte ihn in die schmale Spalte,
   waehrend die Gliederung darunter voll ausbreitete. */
.compliance-columns { display: grid; gap: var(--space-6); align-items: start; }
.compliance-columns > .unit-stepper { order: -1; }
.compliance-columns > .training-layout { order: 0; }
/* Innen bleibt es einspaltig: das Tutor-Panel haengt per JS an `<body>`, hier steht nur
   noch der Inhalt. */
.training-layout.compliance-mode { display: block; }
@media (min-width: 900px) {
  /* Zwei Spalten nur, wenn die Gliederung auch da ist. Die Zuordnung im Grid ist
     ortsbasiert, nicht inhaltsbasiert: faellt `nav.unit-stepper` weg — nach
     Abschluss oder bei einer Kenntnisnahme — rutscht `.training-layout` als erstes
     Kind in die 16rem-Spalte, und der Rest der Seite bleibt leer. Gemeldet als
     „Abschlussseite rendert in schmaler linker Spalte".

     `:has()` macht die Regel inhaltsbedingt. `:where()` haelt die Spezifitaet bei
     null und ist zugleich die Absicherung: wo `:has()` fehlt, greift die Regel gar
     nicht und die Seite bleibt einspaltig ueber die volle Breite — schlechter als
     zwei Spalten, aber nie die 16rem-Falle. Dasselbe Muster wie bei den
     Feedback-Sternen weiter unten. */
  .compliance-columns:where(:has(> .unit-stepper)) {
    grid-template-columns: minmax(0, 16rem) minmax(0, 1fr);
  }
  /* Die Gliederung bleibt stehen, waehrend das Kapitel scrollt: in einer
     Pflichtschulung ist „wie viel kommt noch" eine Dauerfrage, keine einmalige. */
  .compliance-columns > .unit-stepper { position: sticky; top: var(--space-4); }
}

/* Der Abschnitt steht ohne Rahmen in der Seite. Das Lesemass gilt trotzdem — 64ch
   sind der Grund, warum der Text lesbar bleibt, und eine volle Spaltenbreite waeren
   bei 1200px rund 140 Zeichen pro Zeile. */
.learning-text-inline { padding: var(--space-6); }
.learning-text-inline > section { max-width: var(--measure); }

/* --- Kapitel-Check ("Kurz nachgedacht") ----------------------------------- */

.unit-check { margin-top: var(--space-6); }
.unit-check h2 { margin-top: 0; }
.check-item { margin-top: var(--space-6); }
.check-item:first-of-type { margin-top: var(--space-4); }

/* Frage und Optionen sind nicht markierbar. Der Tutor sitzt einen Klick daneben, und
   die ganze Frage samt Antworten war mit einem Zug hineinkopiert — dann denkt das
   Modell, nicht der Lernende. Abschreiben von Hand bleibt moeglich und ist genau die
   Reibung, die reichen soll (Felix, 07.09.2026).

   Das ist **Reibung, kein Schutz**: der Text steht im Seitenquelltext. Wer ihn dort
   holt, hat mehr Aufwand als mit Abschreiben — mehr will diese Regel nicht leisten.
   Die Frage bewusst mit: bliebe sie markierbar, waere die halbe Abkuerzung offen, denn
   der Tutor beantwortet sie auch ohne die Optionen.

   Der Abschlusstest braucht das nicht — dort ist der Tutor nicht gerendert
   (`tutor_visible` schliesst `is_mastery_mode` aus). */
.check-prompt,
.check-option {
  -webkit-user-select: none;
  user-select: none;
}
.check-prompt { margin: 0 0 var(--space-3); font-weight: 600; }
.check-number {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.6rem; height: 1.6rem; margin-right: var(--space-2);
  border-radius: var(--radius-badge);
  background: var(--color-bg);
  font-size: var(--text-sm); font-weight: 700;
}
.check-options { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }

/* Jede Option ist ein Link — ohne JavaScript ist das der ganze Mechanismus. Sie sieht
   aus wie eine Schaltflaeche, weil sie sich so verhaelt. */
.check-option {
  display: block;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-control);
  background: var(--color-surface);
  color: inherit;
  text-decoration: none;
  transition: border-color var(--motion-fast) var(--ease-out), background var(--motion-fast) var(--ease-out);
}
.check-option:hover { border-color: var(--color-accent); background: var(--color-bg); }
/* Beantwortet: die Optionen sind Ergebnis, keine Auswahl mehr. `aria-disabled` allein
   haelt keinen Browser auf, deshalb faengt das Skript den Klick zusaetzlich ab. */
.check-item.is-answered .check-option { pointer-events: none; }
.check-option.is-correct {
  border-color: var(--color-success-border);
  background: var(--color-success-bg);
  color: var(--color-success-ink);
  font-weight: 600;
}
.check-option.is-wrong {
  border-color: var(--color-error-border);
  background: var(--color-error-bg);
  color: var(--color-error-ink);
}
.check-verdict {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--space-3);
  margin: var(--space-3) 0 0;
}
/* Urteil und „Erklär mir das" erscheinen erst mit der Antwort. Das Template setzt
   dafuer `hidden`, aber die Regel darueber setzt `display: flex` — Autor-CSS schlaegt
   `[hidden] { display: none }` aus dem Browser-Stylesheet, und der Knopf stand sichtbar
   da, bevor jemand nachgedacht hatte. Ein Klick verriet dann die Loesung. Gemeldet am
   03.09.2026; dieselbe Falle steht bei `.review-image-details` schon kommentiert. */
.check-verdict[hidden] { display: none; }
.check-verdict-text { font-weight: 600; }

/* „Hier fehlt noch etwas." Erscheint erst beim Versuch zu bestaetigen, und dann genau
   an der Stelle — der Knopf bleibt drueckbar und fuehrt hierher, statt tot zu sein und
   den Lernenden suchen zu lassen. Derselbe `[hidden]`-Waechter wie oben: `display`
   gesetzt heisst `[hidden]` ueberschrieben. */
.check-missing {
  display: block;
  margin: var(--space-3) 0 0;
  padding: var(--space-2) var(--space-3);
  border-left: 3px solid var(--color-error-border);
  background: var(--color-error-bg);
  color: var(--color-error-ink);
  font-weight: 600;
}
.check-missing[hidden] { display: none; }
.check-item.is-missing,
details.is-missing {
  outline: 2px solid var(--color-error-border);
  outline-offset: var(--space-2);
  border-radius: var(--radius-control);
}

/* Der Weg nach vorn, unter dem Check. `flex-end` statt `center`, damit die Knoepfe auf
   einer Linie stehen, auch wenn ein Kind einmal hoeher wird als das andere: `center`
   zentriert „Zurueck" auf die Mitte des *ganzen* Formulars, und solange die
   Erklaerzeile darin stand, sass er dadurch sichtbar zu hoch. */
.unit-advance-row {
  display: flex; align-items: flex-end; flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-6);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border);
}

/* --- Der Tutor als Nebenfenster ------------------------------------------- */

/* In der Pflichtschulung fuehrt das Dokument. Der Tutor steht unten rechts wie auf
   jeder Seite mit Hilfe-Chat: erreichbar, aber nicht im Weg. Er haengt per Skript an
   `<body>` — in `main > *` waere `position: fixed` wirkungslos. */
.tutor-panel.is-docked {
  position: fixed;
  right: var(--space-4);
  bottom: var(--space-4);
  width: min(22rem, calc(100vw - 2 * var(--space-4)));
  max-height: min(70vh, 34rem);
  z-index: 30;
  box-shadow: var(--shadow-overlay);
}
/* Eingeklappt bleibt nur die Kopfzeile stehen — die ist die Leiste.

   Jede dieser Regeln traegt `:not(.is-expanded)`. Grund: „eingeklappt" und „Vollbild"
   sind derselbe Knoten mit zwei Klassen, und beide gleichzeitig ergibt ein Panel ueber
   den ganzen Bildschirm, dessen Inhalt `display: none` ist — ein leerer Kasten, mit dem
   sich nichts machen laesst. Das ist genau einmal passiert. Das CSS soll nicht davon
   abhaengen, dass ein Skript rechtzeitig aufraeumt. */
.tutor-panel.is-docked.is-collapsed:not(.is-expanded) { max-height: none; }
.tutor-panel.is-docked.is-collapsed:not(.is-expanded) > *:not(.tutor-panel-header) { display: none; }
/* Eingeklappt bleibt die Titelzeile, nicht der ganze Kopf: mit Hinweistext waere die
   Leiste 150px hoch und damit eine Karte, die im Weg steht — kein Nebenfenster. */
.tutor-panel.is-docked.is-collapsed:not(.is-expanded) .tutor-panel-header > *:not(.tutor-header-row) { display: none; }
/* Und ohne die Kartenpolsterung: eine Leiste, keine Karte.

   Ein Wert auf allen vier Seiten, und die Kopfzeile gibt ihre eigene Polsterung ab.
   Vorher lagen beide uebereinander und nur oben: `--space-2` vom Panel plus 0.85rem
   vom Header ergaben 21,6px ueber dem Knopf, waehrend darunter die 8px des Panels
   allein standen (der Header-Boden ist auf 0). Der Knopf sass damit sichtbar zu tief
   in einer Leiste, die an den Seiten 32px verschenkte. Jetzt 12px rundum. */
.tutor-panel.is-docked.is-collapsed:not(.is-expanded) { padding: var(--space-3); }
.tutor-panel.is-docked.is-collapsed:not(.is-expanded) .tutor-panel-header {
  border-bottom: 0;
  padding: 0;
}
.tutor-panel.is-docked .tutor-panel-header { cursor: pointer; }
.tutor-panel.is-docked.is-collapsed:not(.is-expanded) .tutor-panel-header { margin-bottom: 0; }
/* Der Punkt an der Leiste: der Tutor hat etwas gesagt, waehrend niemand hinsah. */
.tutor-panel.is-docked.is-collapsed:not(.is-expanded).has-news .tutor-header-row h2::after {
  content: "";
  display: inline-block;
  width: 0.5rem;
  height: 0.5rem;
  margin-left: var(--space-2);
  border-radius: var(--radius-round);
  background: var(--color-accent);
  vertical-align: middle;
}
/* Ausgeklappt gilt wieder die Vollbild-Regel weiter oben — und nur die eine Eigenschaft,
   die dort fehlt. `.is-expanded` setzt `top`/`left`/`right`/`height` selbst, aber kein
   `width`; ohne diese Zeile bliebe die angedockte Breite von 22rem stehen. Alles
   Weitere gehoert hier NICHT hin: zwei Klassen schlagen die eine in `.is-expanded`, und
   ein `right: auto` an dieser Stelle machte aus dem Vollbild einen 199px-Streifen am
   linken Rand. */
.tutor-panel.is-docked.is-expanded {
  width: auto;
}

/* Fortschritt als Balken. `appearance: none` ist noetig, sonst zeichnet jeder Browser
   seinen eigenen Fortschrittsbalken und keiner davon kennt unsere Farben. */
.unit-progress-bar {
  appearance: none; -webkit-appearance: none;
  display: block; width: 100%; max-width: 22rem; height: 6px;
  margin: 0 0 var(--space-2);
  border: 0; border-radius: var(--radius-badge);
  background: var(--color-border);
  color: var(--color-accent);
}
.unit-progress-bar::-webkit-progress-bar { background: var(--color-border); border-radius: var(--radius-badge); }
.unit-progress-bar::-webkit-progress-value { background: var(--color-accent); border-radius: var(--radius-badge); }
.unit-progress-bar::-moz-progress-bar { background: var(--color-accent); border-radius: var(--radius-badge); }


/* Der Hinweis am Kurskopf. Er benennt eine Regel und keinen Fehler, deshalb die
   neutrale Info-Rolle und nicht die Warnfarbe. */
.compliance-badge {
  display: inline-flex; align-items: center; gap: 0.45em;
  margin: 0.35rem 0 0; font-size: 0.8rem; font-weight: 600;
  padding: 0.25rem 0.6rem; border-radius: var(--radius-badge);
  background: var(--color-info-bg); color: var(--color-info-ink);
  border: 1px solid var(--color-info-border);
}

/* Der Weg nach vorn. Die Spalte bleibt, obwohl unbestaetigt nur noch der Knopf darin
   steht: bestaetigt zeigt dasselbe Element zwei Dinge nebeneinander (`.is-done`). */
.unit-confirm { display: flex; flex-direction: column; align-items: flex-start; gap: 0.4rem; margin: 0; }
.unit-confirm.is-done { flex-direction: row; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.75rem; }
.unit-confirm-done { font-size: 0.85rem; font-weight: 600; color: var(--color-success-ink); }

/* Die Abschlusserklaerung im Test-Dialog. Abgesetzt, damit sie nicht als weitere
   Frage gelesen wird — sie ist die Unterschrift unter das Ganze. */
.final-declaration {
  margin: 0.75rem 0 0; padding: 0.75rem 0.85rem;
  border: 1px solid var(--color-info-border); border-radius: var(--radius-control);
  background: var(--color-info-bg);
}
.final-declaration legend { font-size: 0.8rem; font-weight: 700; padding: 0 0.35rem; color: var(--color-info-ink); }

.unit-nav-controls { margin-top: 0.85rem; padding-top: 0.75rem; border-top: 1px solid var(--color-border); }
.unit-nav-links { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.tutor-panel h2 { margin: 0 0 0.35rem; font-size: 1.15rem; }
/* Jedes Tutor-Panel ist eine Spalte, in der genau ein Teil scrollt: die
   Nachrichtenliste. Kopfzeile, Status, Eingabe und Fussleiste behalten ihre Groesse
   (sie tragen alle `flex-shrink: 0`), die Liste nimmt den Rest und scrollt.

   Das stand bisher nur in `.tutor-main` — und die Klasse vergibt das Template im
   Pflichtmodus bewusst nicht. Damit war das angedockte Panel ein normaler Block, das
   `flex: 1 1 auto` / `min-height: 0` / `overflow-y: auto` der Liste wirkungslos, und
   der Verlauf lief unten aus dem Panel heraus: gemessen endete das Eingabefeld bei
   3426px, waehrend das Panel bei 910px schloss und der Bildschirm bei 950px. Genau so
   war es gemeldet („die zweite Antwort ueberlappt das Eingabefeld und wird vom unteren
   Bildschirmrand abgeschnitten").

   Es gehoert hierher und nicht in einen der drei Zustaende: „der Chat bleibt im
   Fenster" ist eine Eigenschaft des Panels, nicht von angedockt/offen/Vollbild. Kein
   `!important` — die Leisten-Polsterung weiter oben muss weiter gewinnen. */
.tutor-panel {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  /* Die Polsterung tragen die Kinder (Kopfzeile, Liste, Eingabe) — sonst liegt sie
     doppelt und frisst genau die Hoehe, um die es hier geht. */
  padding: 0;
}
.tutor-main {
  /* Fallback ohne JS; das Skript streckt die Höhe exakt bis zum Fold. Nicht sticky. */
  height: min(62vh, 560px);
  min-height: 20rem;
  align-self: start;
}
.tutor-panel-header {
  flex-shrink: 0;
  padding: 0.85rem 1rem 0.5rem;
  border-bottom: 1px solid var(--color-border);
}
/* Überschrift und Ausklappknopf teilen sich eine Zeile: der Knopf kostet dadurch
   keine Höhe, und Höhe ist im Header das knappste Gut — er ist `flex-shrink: 0`,
   alles darin geht direkt von #tutor-messages ab. */
.tutor-header-row { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
.tutor-header-row h2 { margin: 0; }
.tutor-header-actions { flex: none; display: inline-flex; align-items: center; gap: 0.35rem; }
.tutor-expand {
  flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 2rem; height: 2rem; padding: 0;
  background: none; color: var(--color-text-muted);
  border: 1px solid var(--color-border-strong); border-radius: var(--radius-badge);
  cursor: pointer;
}
.tutor-expand:hover { border-color: var(--color-accent); color: var(--color-text); }
/* Vollbild an/aus. `aria-pressed`, weil der Knopf einen Modus schaltet und nichts
   aufklappt — das tut der Knopf daneben. */
.tutor-expand .icon-collapse,
.tutor-expand[aria-pressed="true"] .icon-expand { display: none; }
.tutor-expand[aria-pressed="true"] .icon-collapse { display: inline; }

/* Der Chat-Knopf: Sprechblase zu, Chevron offen. */
.tutor-dock-toggle .icon-chat { display: none; }
.tutor-panel.is-collapsed:not(.is-expanded) .tutor-dock-toggle .icon-chat { display: inline; }
.tutor-panel.is-collapsed:not(.is-expanded) .tutor-dock-toggle .icon-minimize { display: none; }

/* Wer welchen Knopf sieht, haengt am Zustand und gehoert damit ins Stylesheet:
   - nicht angedockt (Wissensinhalt): kein eingeklappter Zustand, also kein Chat-Knopf
   - Leiste: nur die Sprechblase. Der Sprung von 56px auf Vollbild ist damit gar nicht
     mehr erreichbar; der Riegel im Skript ist der Guertel dahinter
   - Vollbild: nur „Vollbild beenden" — zwei Ausstiege waeren einer zu viel */
.tutor-panel:not(.is-docked) .tutor-dock-toggle { display: none; }
.tutor-panel.is-docked.is-collapsed:not(.is-expanded) #tutor-expand { display: none; }
.tutor-panel.is-expanded .tutor-dock-toggle { display: none; }

/* Ausgeklappt: das Panel verlässt den Fluss und nimmt den Bildschirm.

   Die Höhe steht explizit da und wird nicht aus `top`/`bottom` abgeleitet: mit
   `height: auto` gewinnt das `min-height: 20rem` von oben, und das Panel bleibt
   320px hoch. `min-height: 0` allein reichte auch nicht — explizit ist hier
   schlicht das, was man beim Lesen erwartet.

   Das Skript hängt das Panel dafür an <body>. Grund: `main > *` trägt die
   Reveal-Animation und ist damit ein eigener Stacking-Context — ein z-index am
   Panel wirkt darin nur gegen seine Geschwister, nicht gegen den Scrim und nicht
   gegen den Unit-Stepper, der im Markup danach kommt. */
/* Gilt fuer beide: der Wissensinhalt-Tutor traegt immer `tutor-main`, das
   angedockte Fenster der Pflichtschulung nicht — der Vollbild-Modus ist derselbe. */
.tutor-panel.is-expanded {
  position: fixed;
  top: var(--space-4); left: var(--space-4); right: var(--space-4);
  height: calc(100vh - 2 * var(--space-4));
  max-height: none;
  z-index: 40;
  box-shadow: var(--shadow-overlay);
}
.tutor-scrim { position: fixed; inset: 0; z-index: 39; background: var(--scrim); }
.tutor-disclaimer { font-size: 0.85rem; border-left: 3px solid var(--color-primary); padding-left: 0.75rem; margin: 0.35rem 0 0; }
/* Der Hinweis steht direkt über dem Tutor-Panel und geht damit 1:1 von dessen Höhe
   ab (fitToFold misst die Oberkante). Ohne eigene Regel trug er die vollen
   Browser-Default-Margins eines <p> — einzeilig und eng ist er genauso sichtbar. */
.machine-translation-notice {
  margin: 0 0 var(--space-2);
  border-left: 3px solid var(--color-accent);
  padding-left: 0.75rem;
  font-size: 0.85rem;
}
.tutor-messages {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 0.75rem 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  scroll-behavior: smooth;
  /* Volle Panel-Breite für Höhe und Ruhe, aber ein lesbarer Satzspiegel —
     über ~80 Zeichen Zeilenlänge wird Fließtext schlechter lesbar, nicht besser. */
  width: 100%;
  max-width: 78ch;
  margin-inline: auto;
  /* Ohne das rechnet `width: 100%` die Polsterung obendrauf: 352px Panel + 2×16px
     ergaben 384px, und `overflow: hidden` am Panel schnitt die letzten Zeichen samt
     rechtem Blasenrand ab — gemeldet am 07.09.2026 als „orientiert sich am rechten
     Rand des Browserfensters". Genau das tat es, 16px jenseits der Fensterkante.
     Die Regel steht lokal, weil es im Projekt keinen globalen `border-box`-Reset
     gibt; ihn nachzuziehen rechnet jede Breite der App neu und ist eine eigene
     Entscheidung, kein Nebeneffekt dieses Berichts. Im Vollbild und in `.tutor-main`
     fiel der Defekt nie auf: dort greift `max-width: 78ch` vorher. */
  box-sizing: border-box;
}
.tutor-msg {
  padding: 0.6rem 0.85rem;
  border-radius: var(--radius-control);
  font-size: 1rem;
  line-height: var(--leading-relaxed);
  box-sizing: border-box;
  /* Ein Fachbegriff oder Pfad ohne Leerzeichen ist in einer 22rem-Blase breiter als
     die Blase. Ohne Umbruch schneidet ihn dieselbe Kante ab — ein Scrollbalken
     erscheint nicht, `overflow` steht auf `hidden`. */
  min-width: 0;
  overflow-wrap: anywhere;
}
/* Die Tutor-Antwort ist der eigentliche Lerninhalt — sie bekommt die ganze Spalte. */
.tutor-msg-user { max-width: 95%; }
.tutor-msg p { margin: 0 0 0.45rem; }
.tutor-msg p:last-child { margin-bottom: 0; }
.tutor-msg ul, .tutor-msg ol { margin: 0.25rem 0 0.45rem; padding-left: 1.2rem; }
.tutor-msg li { margin: 0.15rem 0; }
.tutor-msg-user {
  background: var(--color-accent-tint);
  align-self: flex-end;
  white-space: pre-wrap;
}
.tutor-msg-assistant {
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  align-self: stretch;
}
.tutor-msg-error { background: var(--color-error-bg); color: var(--color-error-ink); align-self: stretch; }

/* Abbildungen in der Tutor-Antwort. Das Panel ist schmal, deshalb eine gedeckelte
   Vorschau statt der vollen Breite — der Klick oeffnet sie gross. */
.tutor-figure { margin: 0.5rem 0; }
/* Der Rahmen legt sich ums Bild (`fit-content`), statt es in eine feste Breite zu
   zwingen: hochkant-Screenshots stuenden sonst in einem breiten leeren Kasten.
   Und kein `cover` — ein zugeschnittener Screenshot ist als Vorschau wertlos, der
   Lernende soll erkennen, welches Bild ihn erwartet. */
.tutor-figure-btn { display: block; padding: 0; border: 1px solid var(--color-border); background: none; border-radius: var(--radius-control); cursor: zoom-in; width: fit-content; max-width: 100%; overflow: hidden; }
.tutor-figure-btn:hover { border-color: var(--color-accent); }
.tutor-figure-btn img { display: block; width: auto; height: auto; max-width: 100%; max-height: 18rem; }
.tutor-figure figcaption { font-size: var(--text-sm); color: var(--color-text-muted); margin-top: 0.3rem; }
.tutor-image-modal { border: none; border-radius: var(--radius-card); padding: 0; max-width: min(1100px, calc(100% - 2rem)); box-shadow: var(--shadow-overlay); background: var(--color-surface); }
.tutor-image-modal::backdrop { background: var(--scrim); }
.tutor-image-modal-inner { position: relative; padding: 1.25rem; }
.tutor-image-modal-inner img { display: block; max-width: 100%; max-height: 78vh; height: auto; margin-inline: auto; border-radius: var(--radius-control); }
.tutor-image-modal-inner .hint { margin: 0.6rem 0 0; text-align: center; }
.tutor-image-modal .dialog-close-btn { position: absolute; top: 0.5rem; right: 0.5rem; z-index: 1; }

/* Wartezustand
   -------------------------------------------------------------------------
   Die Antwort braucht 3–7 Sekunden. Drei pulsende Punkte stehen an genau der
   Stelle, an der sie erscheinen wird — das ist die ganze Botschaft.
   Ausdrücklich keine Sparkles, kein Gradient-Avatar-Ring, kein „KI denkt
   nach"-Hirn: die Identität des Tutors ist die Akzentfarbe und der Label-Stil,
   nicht ein Klischee. */
.tutor-msg-pending {
  display: inline-flex; align-items: center; gap: 0.4rem;
  align-self: flex-start;
  background: var(--color-bg); border: 1px solid var(--color-border);
  padding: 0.75rem 0.9rem;
}
.tutor-msg-pending span {
  width: 6px; height: 6px; border-radius: var(--radius-round);
  background: var(--color-accent); opacity: 0.4;
  animation: apv-tutor-dot 1.1s var(--ease-out) infinite;
}
.tutor-msg-pending span:nth-child(2) { animation-delay: 0.16s; }
.tutor-msg-pending span:nth-child(3) { animation-delay: 0.32s; }
@keyframes apv-tutor-dot {
  0%, 100% { opacity: 0.4; }
  40% { opacity: 1; }
}
/* Der Statustext liegt in einer eigenen Live-Region (siehe Template). Leer
   nimmt er keine Höhe — aber er bleibt im Renderbaum: ein `display: none`
   erst beim Füllen sichtbar zu machen, hindert manche Screenreader daran,
   die Änderung überhaupt anzukündigen. Genau darum geht es hier. */
.tutor-status {
  flex-shrink: 0; margin: 0; padding: 0 1rem 0.5rem;
  font-family: var(--font-display); font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.tutor-status:empty { padding: 0; height: 0; overflow: hidden; }
.tutor-form {
  flex-shrink: 0;
  display: flex;
  gap: 0.5rem;
  align-items: flex-end;
  padding: 0.65rem 1rem 0.85rem;
  border-top: 1px solid var(--color-border);
  background: var(--color-surface);
}
/* Übungsfrage im Chat. Echte Radio-Buttons statt Freitext: das Prüfungsformat ist
   Teil der Übung, und nur so ist die Auswertung deterministisch. */
.tutor-msg-practice {
  align-self: stretch;
  display: flex; flex-direction: column; gap: 0.4rem;
  padding: 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-control);
  background: var(--color-bg);
}
.tutor-msg-practice.is-correct { border-color: var(--color-success, var(--color-accent)); }
.tutor-msg-practice.is-wrong { border-color: var(--color-error-ink); }
.practice-prompt { margin: 0 0 0.25rem; font-weight: 600; }
.practice-option { display: flex; gap: 0.5rem; align-items: flex-start; margin: 0; cursor: pointer; }
.practice-option input { margin-top: 0.2rem; flex-shrink: 0; }
.practice-verdict { margin: 0.35rem 0 0; font-family: var(--font-display); }
/* Der Weg nach vorn, im Panel. Ohne ihn endet das Panel per fitToFold an der Falz
   und der „Weiter"-Knopf im Unit-Stepper liegt darunter — sichtbar erst nach dem
   Scrollen, was Lernende als Sackgasse erlebt haben.

   Eine Leiste statt zweier Zeilen: Übungsfrage links, Weiter/Abschlusstest rechts.
   Und er ist jetzt primär gestylt. Das war er nicht, solange „Senden" der
   Primärknopf des Panels war — dieser Knopf ist ein Icon geworden, und damit ist
   der Slot frei. Ein Abschlusstest als kleinster Knopf der Skala in der Ecke sah
   nicht nach dem Schritt aus, der er ist. */
.tutor-footer {
  flex-shrink: 0;
  display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
  padding: 0.65rem 1rem 0.85rem;
  border-top: 1px solid var(--color-border);
}
.tutor-practice-actions, .tutor-advance { display: inline-flex; margin: 0; }
.tutor-input-field { flex: 1; margin: 0; }
.tutor-form textarea {
  resize: none;
  min-height: 2.6rem;
  max-height: 6rem;
  margin: 0;
  width: 100%;
}
.tutor-form .btn { flex-shrink: 0; }
.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;
}

/* Abschlusstest modal — replaces tutor, no chatting during audit check */
.mastery-dialog {
  border: none;
  border-radius: var(--radius-card);
  padding: 0;
  max-width: min(720px, calc(100vw - 2rem));
  width: 100%;
  max-height: calc(100vh - 2rem);
  overflow: auto;
  box-shadow: var(--shadow-overlay);
}
.mastery-dialog::backdrop {
  background: var(--scrim);
}
.mastery-dialog-inner {
  margin: 0;
  padding: 1.25rem 1.35rem 1.5rem;
  border: none;
  box-shadow: none;
}
.mastery-dialog-header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.75rem 1rem;
  margin-bottom: 1rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--color-border);
}
.mastery-dialog-header h2 { margin: 0; flex: 1 1 12rem; }
.mastery-dialog-header .hint { flex: 1 1 100%; margin: 0; }
.mastery-dialog-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.5rem;
}
.training-layout.mastery-active {
  grid-template-columns: 1fr;
}
.learning-text-panel { margin-bottom: 1rem; }
.learning-text-panel summary { cursor: pointer; font-weight: 600; }
.learning-text-panel .content-frame { margin-top: 0.75rem; min-height: 240px; }

/* Autoren-Vorschau (/admin/preview) — Rückweg und Freigabe-Aktionen am Inhalt */
.preview-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
  margin-bottom: 1.25rem;
  padding: 0.75rem 1rem;
  background: var(--color-bg);
  border: 1px solid var(--color-field-border);
  border-radius: var(--radius-control);
}
.preview-back { color: var(--color-primary); font-weight: 600; text-decoration: none; white-space: nowrap; }
.preview-back:hover { text-decoration: underline; }

.preview-meta { flex: 1 1 14rem; margin: 0; color: var(--color-text-muted); font-size: 0.95rem; }
.preview-result-list { margin: 0.5rem 0 1rem; padding-left: 1.1rem; }
.preview-result-list li { margin: 0.25rem 0; }

/* Reviewer-Arbeitsplatz */
.review-head { margin-bottom: 1rem; }
.review-head h1 { margin: 0.35rem 0 0.25rem; }
.review-gate { border-left: 4px solid var(--color-primary); }
.review-gate-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin: 0.75rem 0 0.5rem; }
.review-gate-actions .inline-form { margin: 0; }

/* Nachweisform: zwei Wahlmoeglichkeiten, deren Hilfetext mitgelesen werden muss —
   daran haengen Kapitelsperre, Leseprotokoll und zwei Zeilen auf der Bescheinigung.
   Deshalb untereinander statt nebeneinander: die Erklaerung steht unter ihrer
   eigenen Wahl und nicht in einer zweiten Spalte daneben. */
.choice-list { list-style: none; margin: 0.75rem 0; padding: 0; }
.choice-list > li + li { margin-top: 0.75rem; }
.choice-list label { display: flex; align-items: baseline; gap: 0.5rem; cursor: pointer; }
/* Die Erklaerung auf die Textkante des Labels einruecken, damit sie zur Beschriftung
   gehoert und nicht zum Kasten darueber. */
.choice-list .hint { margin: 0.2rem 0 0 1.6rem; }
/* Steht die Wahl schon fest, zeigt die Liste sie nur noch an. Bewusst gedaempft statt
   ausgeblendet: der Reviewer soll sehen, was der Kurs ist — und dass die andere Form
   existiert, ohne sie anklicken zu koennen. Der Zeiger bleibt neutral, sonst
   verspricht das Label eine Handlung, die der Browser ohnehin verweigert. */
.choice-list.is-locked { opacity: 0.72; }
.choice-list.is-locked label { cursor: default; }
.review-unit { margin-bottom: 1.25rem; }
.review-unit-head h3 { margin: 0 0 0.35rem; }
.review-preview summary, .review-add-question summary { cursor: pointer; font-weight: 600; padding: 0.35rem 0; }
.review-preview { margin: var(--space-4) 0; }
.review-preview .content-frame { margin-top: 0.75rem; min-height: 320px; width: 100%; }
/* Die Abschnitte einer Einheit tragen jetzt Überschriften statt Aufklapp-Kästen:
   Lerntext, Bilder, Fragen stehen offen da und sind auf einen Blick zu überfliegen. */
.review-section-heading { margin: var(--space-6) 0 var(--space-2); }
.review-section-heading:first-child { margin-top: 0; }
.unit-form-actions { margin-top: var(--space-6); }

/* Reiter je Einheit
   -------------------------------------------------------------------------
   Drei Einheiten untereinander waren mehrere Bildschirmhöhen Scrollweg, bevor der
   Reviewer wusste, was überhaupt zur Prüfung steht. Die Panels bleiben alle im
   Markup — ohne JS ist die Leiste `hidden` und die Seite verhält sich wie vorher. */
.unit-tabs {
  display: flex; flex-wrap: wrap; gap: var(--space-1);
  margin: 0 0 var(--space-4);
  border-bottom: 2px solid var(--color-border);
}
.unit-tab {
  display: inline-flex; align-items: center; gap: var(--space-2);
  max-width: 22rem;
  padding: var(--space-2) var(--space-4);
  margin-bottom: -2px;
  border: 0; border-bottom: 2px solid transparent;
  border-start-start-radius: var(--radius-control);
  border-start-end-radius: var(--radius-control);
  background: none; cursor: pointer;
  font-family: var(--font-display); font-size: var(--text-sm); font-weight: 600;
  color: var(--color-text-muted);
}
.unit-tab:hover { background: var(--color-hover-bg); color: var(--color-text); }
.unit-tab.is-active { color: var(--color-text); border-bottom-color: var(--color-primary); }
.unit-tab-index { font-variant-numeric: tabular-nums; color: var(--color-text-muted); }
.unit-tab.is-active .unit-tab-index { color: var(--color-primary); }
.unit-tab-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Ein Befund in Einheit 3 darf nicht unsichtbar werden, nur weil Reiter 1 offen ist. */
.unit-tab-findings {
  padding: 0 var(--space-2); border-radius: var(--radius-badge);
  background: var(--color-error-bg); color: var(--color-error-ink);
  font-size: var(--text-xs); font-variant-numeric: tabular-nums;
}
.unit-tab-dirty {
  width: 0.5rem; height: 0.5rem; border-radius: var(--radius-round);
  background: var(--color-accent);
}
/* Steht direkt unter dem Reiterband, wo der Blick beim Wechseln ohnehin ist. Kein
   „gespeichert!“ nach jedem Wechsel - das wäre Lärm. Es bleibt das Warten (gedeckt)
   und der Fehlschlag (rot); der Erfolg lässt die Zeile wieder verschwinden. */
.autosave-note {
  margin: calc(var(--space-4) * -1) 0 var(--space-4);
  font-size: var(--text-sm); color: var(--color-text-muted);
}
.autosave-note.is-error { color: var(--color-error-ink); }

/* „Nicht gespeichert" — der Ausgang, den es vorher nicht gab */
.unsaved-dialog {
  border: none; border-radius: var(--radius-card); padding: 0;
  max-width: var(--container-form); width: calc(100% - 2rem);
  background: var(--color-surface); color: var(--color-text);
  box-shadow: var(--shadow-overlay);
}
.unsaved-dialog::backdrop { background: var(--scrim); }
.unsaved-dialog-inner { padding: var(--space-6); }
.unsaved-dialog-inner h2 { margin: 0 0 var(--space-2); font-size: var(--text-lg); }
.unsaved-dialog-inner p { margin: 0; }
.unsaved-dialog-actions {
  display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-6);
}
.review-question { border: 1px solid var(--color-border); border-radius: var(--radius-control); padding: 0.85rem 1rem; margin: 0.75rem 0; }
.review-question.has-finding { border-color: var(--color-error-ink); background: var(--color-error-bg); }
.review-finding { margin: 0 0 0.5rem; font-size: 0.95rem; }
.review-question-form .field { margin-bottom: 0.5rem; }
.review-options { margin: 0.5rem 0; }
.review-option { display: flex; align-items: center; gap: 0.6rem; margin: 0.3rem 0; }
.review-option input[type="text"] { flex: 1; min-width: 0; }
.review-question-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
.findings-list { margin: 0; padding: 1rem 1.25rem 1rem 2.4rem; max-height: 50vh; overflow-y: auto; }
.findings-list li { margin: 0.4rem 0; }

/* Verify-flags modal (Review-Queue) */
.verify-flags-dialog { border: none; border-radius: var(--radius-card); padding: 0; max-width: 720px; width: calc(100% - 2rem); box-shadow: var(--shadow-overlay); }
.verify-flags-dialog::backdrop { background: var(--scrim); }
.verify-flags-dialog-inner { margin: 0; padding: 0; }
.verify-flags-dialog-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; padding: 1.25rem 1.25rem 0.5rem; border-bottom: 1px solid var(--color-border); }
.verify-flags-dialog-header h2 { margin: 0; font-size: 1.15rem; }
.dialog-close-btn { padding: 0.35rem 0.65rem; min-width: 2rem; }
.verify-flags-dialog-inner > .hint { padding: 0 1.25rem; margin: 0.5rem 0 0; }
.verify-flags-list { padding: 1rem 1.25rem; max-height: 50vh; overflow-y: auto; display: flex; flex-direction: column; gap: 0.75rem; }
.verify-flags-dialog-footer { padding: 0.75rem 1.25rem 1.25rem; border-top: 1px solid var(--color-border); display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.75rem; }
.verify-flag-card { border: 1px solid var(--color-border); border-radius: var(--radius-control); padding: 0.85rem 1rem; background: var(--color-bg); }
.verify-flag-card-head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin-bottom: 0.5rem; }
.flag-severity { font-size: 0.75rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; padding: 0.2rem 0.5rem; border-radius: var(--radius-badge); }
.flag-severity-high { background: var(--color-error-bg); color: var(--color-error-ink); border: 1px solid var(--color-error-border); }
.flag-severity-medium { background: var(--color-warning-bg); color: var(--color-warning-ink); border: 1px solid var(--color-warning-border); }
.flag-severity-low { background: var(--color-bg); color: var(--color-text-muted); border: 1px solid var(--color-border); }
/* Die Karte bietet zwei Auswege an, und sie sind nicht gleichrangig: korrigieren steht
   oben, begruenden darunter. Die Zeile dazwischen sagt, dass es eine Alternative ist und
   keine zweite Pflicht — ohne sie lesen sich zwei Formulare wie ein Formular mit zwei
   Teilen, von dem man beide ausfuellen muss. */
/* Der Weg ins Original steht offen in der Karte, nicht im zugeklappten Ausschnitt:
   wer pruefen soll, ob eine Aussage gedeckt ist, braucht ihn am haeufigsten. */
.verify-flag-source { margin: 0.5rem 0 0; }

/* Der Gegenweg: nicht zur Quelle, sondern an die Stelle im Entwurf. Steht direkt
   darunter, weil beides dieselbe Frage bedient — „wo kann ich das nachsehen?" —, und
   traegt denselben Hinweistext-Rahmen. */
.verify-flag-jump { margin: 0.25rem 0 0.75rem; }
.verify-flag-jump a { color: var(--color-primary); font-weight: 500; }

.verify-flag-or {
  margin: 0.9rem 0 0.4rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--color-border);
  font-weight: 600;
}
.verify-flag-message { margin: 0; font-size: 0.95rem; }
.verify-flag-claim,
.verify-flag-context { margin-top: 0.65rem; font-size: 0.9rem; }
.verify-flag-claim summary,
.verify-flag-context summary { cursor: pointer; color: var(--color-primary); font-weight: 500; }
.verify-flag-claim p,
.verify-flag-context p { margin: 0.35rem 0 0; color: var(--color-text-muted); white-space: pre-wrap; }
.verify-flag-preview { margin: 0.75rem 0 0; font-size: 0.9rem; }
.verify-flag-preview a { color: var(--color-primary); font-weight: 500; }

/* Beanstandungen als Checkliste ------------------------------------------------
   Zwanzig aufgeklappte Karten untereinander sind kein Stapel, sondern eine Wand:
   der Reviewer scrollt durch Formulare, ohne je zu sehen, wie viele noch kommen.
   Zugeklappt ist jede eine Zeile — Kasten, Schwere, erster Satz —, und die Liste
   passt auf den Schirm.

   Der Abstand dazwischen ist keine Zierde: ohne ihn stossen die Rahmen aneinander
   und zwanzig Zeilen lesen sich als ein einziger Block mit Trennstrichen. */
/* `minmax(0, 1fr)` und nicht einfach `1fr`: eine Grid-Spalte ist `min-width: auto`, und
   das heisst „mindestens so breit wie der Inhalt". Der Inhalt ist hier eine Zeile mit
   `white-space: nowrap` — also so breit wie der laengste Beanstandungstext. Die Karte
   waere dann breiter als die Seite, und die Kuerzung, fuer die `nowrap` ueberhaupt da
   ist, faende nie statt. */
.verify-flag-list { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-3); margin-top: var(--space-3); }
.verify-flag-progress { margin: 0.75rem 0 0; font-weight: 600; }

details.verify-flag-card { padding: 0; }
/* Erledigtes tritt zurueck: heller Grund, gruener Rahmen. Es bleibt lesbar, zieht
   aber den Blick nicht mehr auf sich — gesucht wird das, was noch offen ist. */
details.verify-flag-card.is-resolved { background: var(--color-surface); border-color: var(--color-success-border); }

.verify-flag-summary {
  display: flex; align-items: flex-start; gap: 0.6rem;
  padding: 0.7rem 0.9rem;
  cursor: pointer; list-style: none; user-select: none;
}
.verify-flag-summary::-webkit-details-marker { display: none; }
/* Der Pfeil steht am Ende, nicht vorn: vorn sitzt der Kasten, und zwei Zeichen
   nebeneinander sagen nicht mehr als eines. Dasselbe Zeichen wie im Aktionsmenue. */
.verify-flag-summary::after {
  content: '\25BE'; margin-inline-start: auto; padding-inline-start: 0.5rem;
  font-size: 0.9em; line-height: 1.5; color: var(--color-text-muted);
}
details.verify-flag-card[open] > .verify-flag-summary::after { content: '\25B4'; }
.verify-flag-summary:hover { background: var(--color-hover-bg); }
.verify-flag-summary .flag-severity { flex-shrink: 0; }

.verify-flag-box {
  flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.05rem; height: 1.05rem; margin-top: 0.15rem;
  border: 2px solid var(--color-border-strong); border-radius: var(--radius-badge);
  background: var(--color-surface);
}
.verify-flag-card.is-resolved .verify-flag-box {
  border-color: var(--color-success-border); background: var(--color-success-bg);
}
/* Der Haken selbst: zwei Kanten eines gedrehten Rechtecks. Kein Asset und keine
   Icon-Schrift — ein Netzabruf je Seitenaufruf fuer ein einziges Zeichen. */
.verify-flag-card.is-resolved .verify-flag-box::after {
  content: ''; width: 0.28rem; height: 0.55rem; margin-top: -0.14rem;
  border: solid var(--color-success-ink); border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

.verify-flag-line { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Aufgeklappt darf die Zeile umbrechen. Zugeklappt zaehlt die Uebersicht, offen der
   Wortlaut — und den abzuschneiden, waehrend darunter Platz ist, waere absurd. */
details.verify-flag-card[open] > .verify-flag-summary .verify-flag-line { white-space: normal; overflow: visible; }
.verify-flag-card.is-resolved .verify-flag-line { color: var(--color-text-muted); }

.verify-flag-body { padding: 0 0.9rem 0.9rem; }
.verify-flag-by { margin: 0 0 0.5rem; }

/* Danger zone + retire modal */
.admin-cols { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); align-items: start; }
.admin-section-label { color: var(--color-text-muted); font-size: 0.8rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; margin: 2rem 0 0.5rem; }
.danger-zone { border-color: var(--color-error-border); background: var(--color-error-bg); margin-top: 1.5rem; }
.danger-zone h2 { color: var(--color-error-ink); }
.danger-zone-warning { background: var(--color-error-bg); border: 1px solid var(--color-error-border); border-radius: var(--radius-control); padding: 0.85rem 1rem; color: var(--color-error-ink); margin: 0 0 1rem; }
.danger-zone-warning p { margin: 0.35rem 0 0; }
.btn-danger { background: var(--color-error-ink); color: var(--color-surface); }
.btn-danger:hover { background: var(--color-error-ink); }
.btn-danger:disabled { background: var(--color-error-border); cursor: not-allowed; }
.retire-modal { border: none; border-radius: var(--radius-card); padding: 0; max-width: 720px; width: calc(100% - 2rem); box-shadow: var(--shadow-overlay); }
.retire-modal::backdrop { background: var(--scrim); }
.retire-modal-inner { padding: 0; }
.retire-modal-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; padding: 1.25rem 1.25rem 0.75rem; border-bottom: 1px solid var(--color-border); }
.retire-modal-header h2 { margin: 0; font-size: 1.15rem; }
.retire-step { padding: 1rem 1.25rem 1.25rem; }
.retire-select-list { max-height: 45vh; overflow: auto; margin: 0.75rem 0; border: 1px solid var(--color-border); border-radius: var(--radius-control); }
.retire-select-list table { margin: 0; }
.retire-modal-footer { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 0.75rem; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--color-border); }
.retire-final-warning { margin-bottom: 1rem; }
.retire-confirm-list { margin: 0 0 1rem; padding-left: 1.25rem; max-height: 30vh; overflow-y: auto; }
.import-hint { margin-bottom: 1rem; }
.import-result-banner { font-size: 1rem; }
.import-running-notice { margin: 0 0 1rem; }

/* Buttons */
.btn { display: inline-block; padding: 0.65rem 1.25rem; border-radius: var(--radius-control); font-weight: 600; text-decoration: none; border: none; cursor: pointer; font-size: 1rem; }
.btn-primary { background: var(--color-primary); color: var(--color-surface); }
.btn-primary:hover { background: var(--color-primary-hover); }
.btn-secondary { background: var(--color-bg); color: var(--color-text); border: 1px solid var(--color-field-border); }
.btn-secondary:hover { background: var(--color-hover-bg); }
.btn-slack { background: var(--color-slack); color: var(--color-surface); width: 100%; text-align: center; box-sizing: border-box; }
.btn-slack:hover { background: var(--color-slack-hover); }
.btn-lg { padding: 0.85rem 1.75rem; font-size: 1.05rem; }
.btn-sm { padding: 0.35rem 0.85rem; font-size: 0.85rem; }
/* Icon statt Beschriftung. Quadratisch und mindestens --control-min gross, damit
   er auch ohne Wort eine ausreichende Trefferflaeche hat; die Beschriftung steckt
   in aria-label und title. */
.btn-icon {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: var(--control-min); min-height: var(--control-min);
  padding: 0.5rem; line-height: 0;
}
a.btn { text-align: center; }
/* Ein Knopf traegt seine Textfarbe auf einer farbigen Flaeche. Ist er ein <a>,
   ersetzt `a:hover` sie durch --color-link-hover — auf dem Slack-Violett sind
   das gemessene 1,13:1, die Beschriftung ist beim Zeigen darauf weg. Der Hover
   dunkelt die Flaeche, der Text bleibt. */
a.btn-primary:hover { color: var(--color-surface); }
a.btn-secondary:hover { color: var(--color-text); }
a.btn-slack:hover { color: var(--color-surface); }
a.btn-danger:hover { color: var(--color-surface); }

/* Aktionsgruppe: die wahrscheinlichste nächste Aktion steht als Knopf da,
   alles Weitere liegt im Kontextmenü — vorhanden, auffindbar, aber nicht im Weg. */
/* nowrap: Primäraktion und Umschalter gehören optisch zusammen und sollen in einer
   Tabellenzelle nicht übereinander rutschen — die Spalte darf dafür breiter werden. */
.action-group { display: inline-flex; flex-wrap: nowrap; align-items: center; gap: 0.5rem; }
.action-menu { position: relative; display: inline-block; }
/* Ohne diese Regel liegt das offene Menü hinter der nächsten Karte. Grund ist die
   Reveal-Animation ganz unten: `main > *` animiert `opacity` mit `both`, und damit ist
   jede Karte ein eigener Stacking-Context. Das `z-index: 30` der Menüliste wirkt dann
   nur noch *innerhalb* ihrer Karte — die folgende Karte malt darüber, egal wie hoch
   der Wert ist. Also muss die Karte selbst steigen, und nur solange das Menü offen
   ist: eine dauerhaft gehobene Karte würde ihre Nachbarn ohne Anlass überlagern. */
.card:has(.action-menu[open]) { position: relative; z-index: 5; }
.action-menu > summary { list-style: none; display: inline-flex; align-items: center; gap: 0.4rem; user-select: none; }
.action-menu > summary::-webkit-details-marker { display: none; }
.action-menu > summary::after { content: '\25BE'; font-size: 0.8em; line-height: 1; }
.action-menu[open] > summary { background: var(--color-hover-bg); }
.action-menu-list {
  position: absolute;
  right: 0;
  top: calc(100% + 0.35rem);
  z-index: 30;
  min-width: 15rem;
  padding: 0.35rem;
  background: var(--color-surface);
  border: 1px solid var(--color-field-border);
  border-radius: var(--radius-control);
  box-shadow: var(--shadow-overlay);
  text-align: left;
  white-space: normal;
}
.action-menu-list form { margin: 0; display: block; }
/* Ein Eingabefeld im Kontextmenue — bisher trugen die Menues nur Knoepfe. Das Feld
   bekommt dasselbe Polster wie ein Menueeintrag, damit die Spalte fluchtet, und eine
   Breite, die dem Menue folgt statt es aufzureissen. */
.action-menu-form { padding: 0.35rem 0.65rem 0; }
.action-menu-form input[type="text"] {
  width: 100%;
  box-sizing: border-box;
  font-size: 0.85rem;
  padding: 0.3rem 0.4rem;
}
.action-menu-form .action-menu-item { padding-left: 0; padding-right: 0; }

.action-menu-item {
  display: block;
  width: 100%;
  box-sizing: border-box;
  text-align: left;
  padding: 0.5rem 0.65rem;
  border: none;
  /* Eine Stufe kleiner als das Menü selbst: Innenradius = Außenradius minus
     Polster, sonst laufen die Kanten nicht parallel. */
  border-radius: var(--radius-badge);
  background: none;
  font: inherit;
  font-size: 0.95rem;
  color: var(--color-text);
  text-decoration: none;
  cursor: pointer;
}
.action-menu-item:hover,
.action-menu-item:focus-visible { background: var(--color-accent-tint); }
.action-menu-item.is-danger { color: var(--color-error-ink); }
.action-menu-item.is-danger:hover,
.action-menu-item.is-danger:focus-visible { background: var(--color-error-bg); }
.action-menu-note { margin: 0 0.65rem 0.35rem; color: var(--color-text-muted); font-size: 0.8rem; line-height: 1.35; }
.action-menu-sep { border: 0; border-top: 1px solid var(--color-border); margin: 0.3rem 0; }
/* Eintritt (Landing + Login) — hier trägt die Navy-Fläche
   -------------------------------------------------------------------------
   Die beiden anonymen Seiten sind der einzige Ort, an dem die Marke ganz
   auftreten darf: volle Navy-Seite, dasselbe Punktraster wie die Arbeitsseiten
   (dort in Cyan, weil Grau auf Navy unsichtbar wäre), und das Logo auf weißer
   Plakette. Auf den Arbeitsseiten bleibt der Grund hell — der Lerntext-iframe
   ist immer weiß und würde aus einer dunklen Seite herausleuchten. */
.landing-page, .auth-page {
  min-height: 100vh;
  background-color: var(--color-primary);
  background-image: radial-gradient(var(--texture-dot-on-primary) 1px, transparent 1px);
  background-size: var(--texture-step) var(--texture-step);
  color: var(--color-on-primary);
}
.landing { max-width: var(--container-narrow); padding: var(--space-12) var(--space-6) var(--space-16); }
.landing-hero { margin-bottom: var(--space-12); }
/* Das vollständige Lockup, nicht nur die Bildmarke: hier steht kein
   Navigationskopf daneben, der den Namen schon nennt. Auf Weiß, wie der
   CI-Guide es verlangt — und wie im Kopf als gerundete Plakette. */
.landing-hero::before {
  content: ""; display: block;
  width: 9.5rem; height: 7rem;
  margin-bottom: var(--space-6);
  border-radius: var(--radius-card);
  background: var(--color-surface) url('/static/brand/apv-logo.svg') center / 7rem auto no-repeat;
}
.landing-hero h1 { font-size: var(--text-display); line-height: var(--leading-tight); letter-spacing: -0.02em; margin: var(--space-2) 0 var(--space-4); }
.landing-lead { font-size: var(--text-lg); color: var(--color-accent-on-primary); max-width: var(--measure); margin: 0 0 var(--space-8); }
.landing-page .auth-kicker { color: var(--color-accent-on-primary); letter-spacing: var(--label-tracking); }
/* Der einzige gefüllte cyane Knopf der App — und er trägt ein Navy-Label,
   weil Weiß darauf 2,1:1 wäre. Genau die Stelle, an der der Guide Cyan als
   Fläche zulässt: groß, einmal pro Viewport, mit geprüfter Schriftfarbe. */
.landing-page .btn-primary { background: var(--color-accent); color: var(--color-primary); }
.landing-page .btn-primary:hover { background: var(--color-accent-hover); }
/* Hier dreht der Knopf die Farben (Navy auf Cyan), deshalb eine eigene Zeile:
   sonst zieht `a.btn-primary:hover` oben Weiss auf Cyan darueber. */
.landing-page a.btn-primary:hover { color: var(--color-primary); }
.landing-grid { display: grid; gap: var(--space-4); grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
/* Die drei Nutzenkarten sind weiße Inseln — dort ist der Text der Inhalt,
   also tragen sie echte Überschriften statt Mikro-Labels. */
.landing-grid .card { color: var(--color-text); }
.landing-grid .card > h2 {
  font-size: var(--text-lg); font-weight: 600; text-transform: none;
  letter-spacing: normal; color: var(--color-text); margin: 0 0 var(--space-2);
}

/* Auth / Login */
.auth-page { display: flex; align-items: center; justify-content: center; padding: var(--space-6); }
.auth-card { position: relative; width: 100%; max-width: var(--container-form); background: var(--color-surface); color: var(--color-text); border: 0; border-radius: var(--radius-card); padding: var(--space-8); box-shadow: var(--shadow-overlay); }
.auth-header { text-align: center; margin-bottom: 1.5rem; }
/* Die Bildmarke über dem Login — hier ohne Plakette, weil die Karte schon weiß
   ist. Nur die Marke und nicht das Lockup: „AlphaPet" steht im Kicker
   darunter schon, ein zweiter Schriftzug wäre eine Wiederholung. */
.auth-header::before {
  content: ""; display: block;
  width: 2.75rem; height: 2.75rem; margin: 0 auto var(--space-4);
  background: url('/static/brand/apv-mark.svg') center / contain no-repeat;
}
.auth-header h1 { margin: 0.25rem 0 0.5rem; font-size: 1.75rem; }
.auth-kicker { text-transform: uppercase; letter-spacing: var(--label-tracking); font-size: var(--label-size); color: var(--color-text-muted); margin: 0; font-weight: var(--label-weight); }
.auth-section { margin-top: 1.25rem; }
.auth-section h2 { font-size: 1rem; margin: 0 0 0.5rem; }
.auth-section-divider { border-top: 1px solid var(--color-border); padding-top: 1.25rem; }
.auth-form { display: flex; flex-direction: column; gap: 0.75rem; }
.field { display: flex; flex-direction: column; gap: 0.35rem; font-size: 0.9rem; }
.field input { padding: 0.6rem 0.75rem; border: 1px solid var(--color-field-border); border-radius: var(--radius-control); font-size: 1rem; }
.field input:focus { outline: var(--focus-width) solid var(--focus-color); border-color: var(--color-primary); }
.auth-footer { margin-top: 1.5rem; text-align: center; font-size: 0.9rem; }
.auth-footer a { color: var(--color-primary); }
.alert { border-radius: var(--radius-control); padding: 0.85rem 1rem; margin-bottom: 1rem; }
.alert-error { background: var(--color-error-bg); border: 1px solid var(--color-error-border); color: var(--color-error-ink); }
.alert-error p { margin: 0.35rem 0 0; font-size: 0.95rem; }

/* Datenschutzhinweis: Kenntnisnahme am Login + der Hinweis selbst.
   `align-items: flex-start` statt des zentrierten Standards aus .checkbox-label —
   der Satz bricht auf schmalen Schirmen um, die Box soll dann an der ersten
   Zeile stehen und nicht in der Mitte des Blocks schweben. */
.notice-ack { align-items: flex-start; gap: 0.5rem; margin: 0.25rem 0 0.5rem; font-size: 0.9rem; line-height: var(--leading-normal); }
.notice-ack input { margin-top: 0.15rem; flex: none; }
/* Ein Knopf, der als Link liest: er öffnet den Dialog, navigiert also nicht —
   ein <a href> wäre hier die falsche Semantik. */
.notice-link-inline { background: none; border: 0; padding: 0; font: inherit; color: var(--color-link); text-decoration: underline; text-decoration-color: var(--color-link-underline); text-decoration-thickness: 2px; text-underline-offset: 2px; cursor: pointer; }
.notice-link-inline:hover { color: var(--color-link-hover); }
.notice-modal { border: none; border-radius: var(--radius-card); padding: 0; max-width: 640px; width: calc(100% - 2rem); box-shadow: var(--shadow-overlay); color: var(--color-text); }
.notice-modal::backdrop { background: var(--scrim); }
.notice-modal-inner { display: flex; flex-direction: column; max-height: 85vh; }
/* Der Text ist länger als bei den übrigen Dialogen, deshalb scrollt er innen
   und die Fußzeile mit dem Schließen-Knopf bleibt sichtbar. */
.notice-modal-body { overflow-y: auto; padding: 1.25rem 1.25rem 0.5rem; }
.notice-modal-body h2 { margin: 0 0 0.5rem; font-size: 1.15rem; }
.notice-modal-body h3 { margin: 1.1rem 0 0.35rem; font-size: 0.95rem; }
.notice-modal-body p { margin: 0 0 0.5rem; font-size: 0.9rem; line-height: var(--leading-normal); }
.notice-modal-body ul { margin: 0 0 0.5rem; padding-left: 1.15rem; font-size: 0.9rem; line-height: var(--leading-normal); }
.notice-modal-body li { margin-bottom: 0.25rem; }
.notice-modal-footer { display: flex; justify-content: flex-end; padding: 0.75rem 1.25rem 1.25rem; border-top: 1px solid var(--color-border); }
/* Fassung ohne JavaScript: derselbe Text, nur ohne Überlagerung. */
.notice-fallback { border-top: 1px solid var(--color-border); padding-top: 1.25rem; }

/* Success Mirror + checkpoint formats */
.success-mirror-cols { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }
.success-mirror-cols h3 { margin: 0 0 0.5rem; font-size: 0.95rem; }
.success-mirror-cols ul { margin: 0; padding-left: 1.1rem; }

/* Einheiten-Titel und „Wie läuft das ab?"-Disclosure */
.unit-title {
  flex: 1;
  min-width: 10rem;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.how-it-works { margin: 0 0 1rem; }
.how-it-works summary { cursor: pointer; font-weight: 500; color: var(--color-text-muted); }
.how-it-works-list { margin: 0.75rem 0 0; padding-left: 1.25rem; }

/* Fokuszone des Lernschirms: aktuelle Einheit zuerst, Stepper nachgeordnet */
.training-head h1 { margin-bottom: 0.25rem; }
.unit-focus-head { margin: 0.25rem 0 1rem; }
.unit-progress-meta { color: var(--color-text-muted); font-size: 0.9rem; font-weight: 600; margin: 0 0 0.35rem; }
.current-unit-heading { font-size: 1.4rem; line-height: 1.3; margin: 0; }
.unit-stepper { margin: var(--space-6) 0 var(--space-4); }
.unit-stepper-title { font-size: 1rem; color: var(--color-text-muted); margin: 0 0 0.75rem; }

/* ==========================================================================
   Mikro-Interaktionen
   --------------------------------------------------------------------------
   Ein Muster für alles Bedienbare statt Einzelfällen: 120 ms auf Farbe, 1 px
   Versatz beim Druck. Bewusst NICHT auf transform/box-shadow im Hover — das
   erzeugt Layout-Flimmern in Tabellenzeilen. Alles hängt an den Motion-Token,
   die sich unter prefers-reduced-motion selbst auf 1 ms setzen.
   ========================================================================== */
.btn, button, input[type="submit"], .action-menu-item, .unit-list-item a,
.lang-flag, .nav-links a, summary, tbody tr {
  transition:
    background-color var(--motion-fast) var(--ease-out),
    border-color var(--motion-fast) var(--ease-out),
    color var(--motion-fast) var(--ease-out);
}
.btn:active, button:active, input[type="submit"]:active { transform: translateY(1px); }

/* Wartescreen der Übersetzung
   --------------------------------------------------------------------------
   Der Lauf dauert etwa eine Minute. Vorher stand hier eine Karte ohne jedes
   Lebenszeichen, die sich alle vier Sekunden komplett neu lud — beides Gründe,
   wegzunavigieren. Jetzt: ein Zeichen, dass etwas läuft, und ein Satz, der sagt
   was.

   Der Puls skaliert, anders als der Seiteneintritt weiter unten. Das ist kein
   Widerspruch: dort verbietet sich `transform`, weil fitToFold beim Laden misst
   — hier hat der Container eine feste Größe, die Marke skaliert in ihm, und
   nichts ausserhalb bewegt sich mit. */
.translating-card { text-align: center; max-width: var(--measure); margin-inline: auto; }
.translating-lead { font-size: var(--text-lg); margin: 0 0 var(--space-2); }
.translating-mark {
  width: 5rem; height: 5rem;
  margin: var(--space-2) auto var(--space-4);
  display: flex; align-items: center; justify-content: center;
}
.translating-mark svg {
  width: 100%; height: 100%;
  animation: apv-mark-pulse 1.8s var(--ease-out) infinite;
}
@keyframes apv-mark-pulse {
  0%, 100% { opacity: 0.45; transform: scale(0.88); }
  50% { opacity: 1; transform: scale(1); }
}
/* Steht die Animation still (prefers-reduced-motion), soll die Marke wenigstens
   vollständig da sein statt auf halber Deckkraft einzufrieren. Die Aussage trägt
   dann ohnehin der Statustext. */
@media (prefers-reduced-motion: reduce) {
  .translating-mark svg { opacity: 1; transform: none; }
}
.translating-status {
  min-height: 1.5rem; margin: 0 0 var(--space-4);
  font-family: var(--font-display); color: var(--color-text-muted);
}
.translating-card .unit-nav-links { justify-content: center; }

/* Dasselbe Lebenszeichen im Admin, nur waagerecht und kleiner: dort steht es neben
   der Läufe-Tabelle, nicht allein auf einer Wartekarte. Puls und
   reduced-motion-Verhalten teilt es sich mit `.translating-mark` — es ist derselbe
   Zustand, nur aus der anderen Rolle gesehen. */
.ingest-live {
  display: flex; align-items: center; gap: var(--space-3);
  margin: 0 0 var(--space-4);
}
.ingest-mark { width: 2.5rem; height: 2.5rem; flex-shrink: 0; }
.ingest-mark svg {
  width: 100%; height: 100%;
  animation: apv-mark-pulse 1.8s var(--ease-out) infinite;
}
@media (prefers-reduced-motion: reduce) {
  .ingest-mark svg { opacity: 1; transform: none; }
}
.ingest-status {
  min-height: 1.5rem; margin: 0;
  font-family: var(--font-display); color: var(--color-text);
}
.ingest-live .hint { margin: 0; }

/* Seitenauswahl: die Gliederung ist die Information. Ein Space sieht ohne
   Einrückung wie hundert gleichrangige Titel aus. */
.tree-toolbar {
  display: flex; align-items: baseline; justify-content: space-between;
  flex-wrap: wrap; gap: var(--space-3);
  padding-bottom: var(--space-3); margin-bottom: var(--space-3);
  border-bottom: 1px solid var(--color-border);
}
.tree-toggle-all { display: inline-flex; align-items: center; gap: 0.5rem; font-weight: 600; cursor: pointer; }
.page-tree { list-style: none; margin: 0; padding: 0; }
.page-tree-row {
  display: flex; align-items: center; flex-wrap: wrap; gap: 0.5rem 0.75rem;
  padding: 0.35rem 0.5rem;
  border-radius: var(--radius-control);
}
/* Die Einrückung setzt das Template je Zeile: `margin-inline-start: calc(var(--space-4)
   * <tiefe>)`. Eine Klasse je Ebene wäre eine willkürliche Obergrenze, und ein
   Baum ist beliebig tief. */
.page-tree-row:hover { background: var(--color-bg); }
/* Die Wurzelzeile ist bewusst nicht vorgehakt — eine Überblicksseite ist ein Behälter
   und kein Kurs. Fettdruck allein sagt das niemandem; die Marke daneben hat einen
   Titel, und der Satz über dem Baum erklärt es. */
.page-tree-row.is-root { font-weight: 600; }
.page-tree-row.is-root .page-tree-title::after {
  content: attr(data-root-note);
  margin-left: var(--space-2);
  padding: 0.05rem 0.4rem;
  border-radius: var(--radius-badge);
  background: var(--color-bg);
  color: var(--color-text-muted);
  font-size: 0.7rem;
  font-weight: 400;
  vertical-align: middle;
}

/* Der Bericht je Seite in der Läufe-Tabelle. Zugeklappt bleibt die Zeile so schmal
   wie vorher — die Zahlen sind die Übersicht, die Liste ist die Auskunft. */
.run-pages { margin-top: var(--space-2); }
.run-pages > summary { cursor: pointer; font-size: 0.8rem; color: var(--color-text-muted); }
.run-pages ul { margin: var(--space-2) 0 0; padding-left: 0; list-style: none; }
/* Die Einheiten eines Entwurfs in der Review-Queue — dasselbe Mittel wie bei den
   Importlaeufen darueber, und bewusst eine eigene Klasse: `.unit-list` teilt sich die
   Queue mit der Lerneransicht, eine Aenderung dort traefe den Kurs selbst. */
.unit-summary { margin: 0; }
.unit-summary > summary { cursor: pointer; font-size: 0.8rem; color: var(--color-text-muted); }
.unit-summary ul { margin: var(--space-2) 0 0; padding-left: 0; list-style: none; }
.unit-summary li { font-size: 0.85rem; overflow-wrap: anywhere; }
.unit-summary li + li { margin-top: 0.2rem; }
.run-page {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  padding: 0.15rem 0;
  font-size: 0.8rem;
}
.run-page-outcome {
  flex: 0 0 auto;
  min-width: 6.5rem;
  font-variant: small-caps;
  letter-spacing: 0.02em;
}
.run-page-created .run-page-outcome { color: var(--color-success-ink); }
.run-page-updated .run-page-outcome { color: var(--color-accent); }
.run-page-skipped .run-page-outcome { color: var(--color-text-muted); }
.run-page-failed .run-page-outcome { color: var(--color-error-ink); }
.run-page-title { font-weight: 600; overflow-wrap: anywhere; }
/* Der Grund gehört unter seine Seite, nicht neben die nächste: eine eigene Zeile,
   eingerückt auf die Breite der Ausgangs-Spalte. Nebeneinander sah er im Umbruch aus,
   als gehörte er zur Zeile darunter. */
.run-page-detail {
  flex: 1 0 100%;
  margin-left: calc(6.5rem + var(--space-2));
  overflow-wrap: anywhere;
}
.page-tree-label { display: inline-flex; align-items: center; gap: 0.5rem; cursor: pointer; min-width: 0; }
.page-tree-title { overflow-wrap: anywhere; }
.btn-subtree { padding: 0.1rem 0.5rem; font-size: 0.75rem; }
.page-tree-state { font-size: 0.75rem; color: var(--color-text-muted); }
.page-tree-state.is-done { color: var(--color-success-ink); }
.tree-actions {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3);
  margin-top: var(--space-4);
}

/* ==========================================================================
   Seiteneintritt
   --------------------------------------------------------------------------
   240 ms Einblenden mit 40 ms Versatz je Block, einmalig, nichts
   scroll-getriggert.

   AUSSCHLIESSLICH opacity, kein translateY — und das ist kein Geschmack:
   learner_training.html misst beim Laden per getBoundingClientRect().top, wie
   viel Platz bis zur Falz bleibt, und schreibt daraus die Panelhöhe des
   Tutors. Ein Transform während dieser Messung verschiebt die gemessene
   Position und verfälscht die Höhe. Opacity berührt die Geometrie nicht.
   ========================================================================== */
@keyframes apv-reveal { from { opacity: 0; } to { opacity: 1; } }
main > * { animation: apv-reveal var(--motion-base) var(--ease-out) both; }
main > *:nth-child(2) { animation-delay: var(--reveal-step); }
main > *:nth-child(3) { animation-delay: calc(var(--reveal-step) * 2); }
main > *:nth-child(4) { animation-delay: calc(var(--reveal-step) * 3); }
main > *:nth-child(n + 5) { animation-delay: calc(var(--reveal-step) * 4); }

/* ==========================================================================
   Breakpoints
   --------------------------------------------------------------------------
   Vorher gab es genau eine Media Query im Projekt, und die war faktisch ein
   No-op. Die Stufen folgen dem Guide: 576 / 768 / 1024 / 1200.
   ========================================================================== */
@media (max-width: 575px) {
  nav { padding: var(--space-3) var(--space-4); }
  .app-main { padding: var(--space-4) var(--space-4) var(--space-8); }
  .public-main { padding: var(--space-6) var(--space-4) var(--space-8); }
  .landing { padding: var(--space-8) var(--space-4) var(--space-12); }
  .page-topbar { padding: var(--space-3) var(--space-4) 0; }
  .auth-card { padding: var(--space-6); }
  /* Auf 390px kostet die Plakette zu viel Breite für den Schriftzug daneben. */
  .nav-brand::before { width: 1.75rem; height: 1.75rem; background-size: 1.25rem auto; }
  .landing-hero::before { width: 7rem; height: 5.25rem; background-size: 5.25rem auto; }
  /* Zellpolster zusammenziehen, sonst bleibt für den Inhalt nichts übrig. */
  td, th { padding: var(--space-2) var(--space-3); }
}

/* Ergebnisbereich nach dem Abschlusstest (UX-010). Vorher war das Ergebnis ein
   grüner Nebensatz in der Randspalte, während der leere Tutor-Kasten die Seite
   dominierte — der Durchfall-Fall war prominenter gestaltet als der Erfolg.
   Deshalb: erstes Element, eigene Fläche, linker Akzentbalken statt Vollfarbe,
   damit der Bereich trägt, ohne zu schreien. */
.result-panel {
  border-left: 4px solid var(--color-border);
  margin-bottom: var(--space-6);
}
/* Kein eigener Fokusring: die globale :focus-visible-Regel deckt [tabindex] mit ab. */
.result-passed { border-left-color: var(--color-success); background: var(--color-success-bg); }
.result-failed { border-left-color: var(--color-warning); background: var(--color-warning-bg); }
.result-headline { font-size: var(--text-2xl); line-height: var(--leading-tight); margin: 0 0 var(--space-2); }
.result-passed .result-headline { color: var(--color-success-ink); }
.result-failed .result-headline { color: var(--color-warning-ink); }
.result-score { font-size: var(--text-lg); margin: 0 0 var(--space-1); }
.result-meta { color: var(--color-text-muted); font-size: var(--text-sm); margin: 0 0 var(--space-4); }
.result-gaps { margin-bottom: var(--space-4); }
.result-gaps h3 { font-size: var(--text-md); margin: 0 0 var(--space-2); }
.result-actions { display: flex; flex-wrap: wrap; gap: var(--space-3); margin: 0; }

/* Trefferflächen: 44px ist WCAG 2.5.5 (AAA) und nur dort erzwungen, wo mit dem
   Finger gezielt wird. Auf dem Desktop würde es die Listenseiten um rund ein
   Drittel höher machen, und die heutigen 34px erfüllen 2.5.8 (AA, 24px)
   bereits. Bewusster Trade-off. */
@media (pointer: coarse), (max-width: 767px) {
  .btn-sm, .nav-links a, .action-menu > summary, .action-menu-item, .lang-flag,
  .tutor-expand {
    min-height: var(--control-min);
    display: inline-flex;
    align-items: center;
  }
  .tutor-expand { min-width: var(--control-min); justify-content: center; }
  .action-menu-item { display: flex; }
  .learning-text-panel summary, .how-it-works summary { min-height: var(--control-min); display: flex; align-items: center; }
}

/* Bild-Auswahl im Reviewer-Arbeitsplatz. Die Bilder stehen offen da statt hinter
   einem Aufklapper: wer entscheiden soll, wo eine Abbildung hingehört und was auf ihr
   zu sehen ist, muss sie zuerst sehen. */
.review-image {
  display: grid; gap: 0.5rem;
  border: 1px solid var(--color-border); border-radius: var(--radius-control);
  padding: var(--space-3); margin: 0 0 var(--space-3);
}
/* `justify-self`, nicht `justify-items` am Raster: sonst schrumpfen auch die
   Eingabefelder auf ihre Inhaltsbreite, und im Alt-Text sind dann acht Zeichen zu
   sehen — ausgerechnet in dem Feld, in dem ein ganzer Satz stehen soll. */
.review-image-btn {
  justify-self: start; line-height: 0; padding: 0; border: 0; background: none;
  cursor: zoom-in; border-radius: var(--radius-control); max-width: 100%;
}
/* Klein und ruhig. 340px waren gut gemeint — bei sechs Bildern in *jedem* Reiter
   war das aber der Grund, warum niemand bis zu den Fragen scrollte. Lesbar wird der
   Screenshot im Modal, einen Klick entfernt; hier reicht das Wiedererkennen. */
.review-image-thumb {
  max-width: 100%; max-height: 8rem; width: auto; height: auto;
  border: 1px solid var(--color-border); border-radius: var(--radius-control);
}
.review-image-btn:hover .review-image-thumb { border-color: var(--color-accent); }
/* Die Beschreibung erscheint erst mit dem Haken. `[hidden]` braucht das explizite
   `display: none`, weil die Regel darüber `display: grid` setzt und gewinnen würde. */
.review-image-details { display: grid; gap: 0.5rem; }
.review-image-details[hidden] { display: none; }
.review-image-caption-note { margin: 0; }
/* Steht direkt am betroffenen Bild. Die Sammelmeldung oben nennt die Dateinamen,
   aber bei sechs Bildern in drei Einheiten sucht man sonst trotzdem. */
.review-image-alt-missing { margin: 0; font-size: var(--text-sm); }
.review-derived-from { margin: 0 0 0.4rem; font-style: italic; }

/* Abbildung im Lerntext. Das Markup kommt aus render_unit_html(); hier steht nur,
   wie es aussieht. Der Rahmen setzt eigene Styles (content_document), diese Regeln
   gelten für den Review-Bereich ausserhalb. */
.unit-figure { margin: var(--space-3) 0; }
.unit-figure img { max-width: 100%; height: auto; border-radius: var(--radius-control); }
.unit-figure figcaption { font-size: var(--text-sm); color: var(--color-text-muted); margin-top: 0.35rem; }

/* Kursbewertung am Ende eines Moduls (REQ-DL-8). Der Dialog traegt `open` fest
   im Markup — ohne JS ist er ein sichtbares Formular unter dem Ergebnis, mit JS
   wird daraus ein Modal. Beide Zustaende muessen also tragen. */
.feedback-dialog {
  border: none;
  border-radius: var(--radius-card);
  padding: 0;
  max-width: min(560px, calc(100vw - 2rem));
  width: 100%;
  max-height: calc(100vh - 2rem);
  overflow: auto;
  box-shadow: var(--shadow-overlay);
}
.feedback-dialog::backdrop { background: var(--scrim-strong); }
/* Als Inline-Formular (kein showModal) soll er sich einreihen, nicht schweben.
   `position: static` ist dabei die entscheidende Zeile und nicht Kosmetik: das
   UA-Stylesheet setzt ein offenes <dialog> auf `position: absolute`. Ohne JS lag
   der Kasten damit ueber dem Lerninhalt, und das Content-iframe darunter fing
   jeden Klick auf die Sterne ab — sichtbar, aber unbedienbar. */
.feedback-dialog:not(:modal) {
  position: static;
  box-shadow: none;
  margin: 0 0 var(--space-6);
  max-width: none;
}
.feedback-dialog-inner {
  margin: 0;
  padding: var(--space-6) var(--space-6) var(--space-4);
  border: none;
  box-shadow: none;
}
/* Titel und Schliessen-Knopf als zwei Spalten statt als Text mit reserviertem
   Rand: der Knopf ist breiter als jede feste Reserve (min-width 2rem plus
   Polsterung), und die lange Frage lief mit `padding-right` unter ihn. Im Raster
   koennen sie sich nicht mehr ueberlappen, egal wie lang der Titel wird. */
.feedback-dialog-header {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: start;
  column-gap: var(--space-4);
  margin-bottom: var(--space-4);
}
.feedback-dialog-header h2 { grid-column: 1; font-size: var(--text-xl); margin: 0; }
/* Das Ergebnis noch einmal, weil showModal() die Ansage des Panels dahinter
   verschluckt — aber nur fuer den Screenreader (`visually-hidden` im Template,
   referenziert ueber aria-describedby). Im Bild braucht es die Zeile nicht
   mehr: der Dialog geht erst nach zwei Sekunden auf, das Ergebnis stand also
   vorher gross auf der Seite. Der Dialog stellt genau eine Frage. */
.feedback-dismiss { grid-column: 2; grid-row: 1 / -1; margin: 0; }

.feedback-stars {
  border: 0;
  padding: 0;
  margin: 0 0 var(--space-6);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}
/* Die Legende ist der zugaengliche Name der Radio-Gruppe und bleibt im DOM,
   steht aber nicht mehr im Bild (`visually-hidden` im Template): die
   Dialogueberschrift stellt die Frage bereits, ein zweites "Deine Bewertung"
   darunter war Doppelung. */
.feedback-star {
  /* Positioniert, damit das absolut gesetzte Radio darin bleibt. Ohne das
     bezieht es sich auf den Dialog und alle fuenf stapeln sich in dessen Ecke —
     unsichtbar zwar, aber der Treffer liegt dann nicht mehr am Stern. */
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  cursor: pointer;
  border-radius: var(--radius-control);
}
/* Das Radio bleibt bedienbar und fokussierbar, nur unsichtbar — die Skala soll
   mit Pfeiltasten laufen wie jede andere Radio-Gruppe. */
.feedback-star input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
  margin: 0;
}
/* Fuellung in Stufen wachsender Spezifitaet — die Reihenfolge IST die Logik:
   leer < gewaehlt < Vorschau unter dem Cursor. Solange die Maus auf der Leiste
   liegt, soll der Cursor gegen die gespeicherte Auswahl gewinnen; genau dafuer
   stehen die :has()-Regeln unten in :where(). :where() wiegt null — ohne das
   waeren sie 0,6,1 und keine Hover-Regel kaeme je dagegen an.

   Die Zustandsregeln kippen nur die drei Laufvariablen aus tokens.css, alle
   Paint-Deklarationen stehen genau einmal. */
.feedback-star-mark {
  font-size: var(--star-size);
  line-height: 1;
  background-image: var(--star-face);
  color: var(--star-ink);
  filter: var(--star-lift);
  transition:
    filter var(--motion-fast) var(--ease-out),
    transform var(--motion-fast) var(--ease-out);
}
/* Die Glyphe wird zur Maske fuer den Verlauf. Bewusst hinter @supports: ohne
   background-clip waere `color: transparent` kein flacher Stern, sondern gar
   keiner. */
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .feedback-star-mark {
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
}
.feedback-star input:focus-visible ~ .feedback-star-mark {
  outline: var(--focus-width) solid var(--focus-color);
  outline-offset: var(--focus-offset);
  border-radius: var(--radius-control);
}
/* Gewaehlt — ohne :has(), damit die Auswahl auch ohne :has()-Unterstuetzung am
   Stern sichtbar bleibt. */
.feedback-star input:checked ~ .feedback-star-mark {
  --star-face: var(--star-filled-face);
  --star-ink: var(--star-filled-ink);
  --star-lift: var(--star-glow);
}
/* Und alle darunter mit. Eigene Regel aus demselben Grund: faellt :has() aus,
   faellt nur diese weg. */
.feedback-stars:where(:has(input[value="2"]:checked)) .feedback-star:nth-of-type(-n+2) .feedback-star-mark,
.feedback-stars:where(:has(input[value="3"]:checked)) .feedback-star:nth-of-type(-n+3) .feedback-star-mark,
.feedback-stars:where(:has(input[value="4"]:checked)) .feedback-star:nth-of-type(-n+4) .feedback-star-mark,
.feedback-stars:where(:has(input[value="5"]:checked)) .feedback-star:nth-of-type(-n+5) .feedback-star-mark {
  --star-face: var(--star-filled-face);
  --star-ink: var(--star-filled-ink);
  --star-lift: var(--star-glow);
}
/* Vorschau, Schritt 1: liegt der Cursor irgendwo auf der Leiste, zaehlt nur
   noch er — alles zurueck auf leer. */
.feedback-stars:hover .feedback-star .feedback-star-mark {
  --star-face: var(--star-empty-face);
  --star-ink: var(--star-empty-ink);
  --star-lift: var(--star-bevel);
}
/* Schritt 2: der Stern unter dem Cursor ... */
.feedback-stars:hover .feedback-star:hover .feedback-star-mark {
  --star-face: var(--star-filled-face);
  --star-ink: var(--star-filled-ink);
  --star-lift: var(--star-glow);
  transform: scale(1.08);
}
/* ... und alle links davon. `:has(~ .feedback-star:hover)` ist der
   Vorgaenger-Selektor, den CSS nicht direkt kennt: "hat einen spaeteren
   Geschwister-Stern, der gerade gehovert wird". Vorher leuchtete immer nur
   genau einer, und eine Skala, bei der Zeigen auf die 4 nicht vier Sterne
   fuellt, liest sich nicht wie eine Skala. */
.feedback-stars:hover .feedback-star:has(~ .feedback-star:hover) .feedback-star-mark {
  --star-face: var(--star-filled-face);
  --star-ink: var(--star-filled-ink);
  --star-lift: var(--star-glow);
}
/* Steht die Bewertung, ist die Leiste ein Ergebnis und kein Bedienelement mehr.
   `pointer-events: none` ist dabei die Regel, die die Vorschau abschaltet —
   ohne Hover greift keine der Regeln darueber. Die Radios sind zusaetzlich
   `disabled`, damit auch die Tastatur nichts mehr aendert. */
.feedback-stars.is-locked { pointer-events: none; }

.feedback-comment { margin-bottom: var(--space-4); }
/* Stufe 1: nur Frage, Sterne und der Datenschutzsatz. Freitext, Absenden und
   die Sekundaertaste kommen erst, wenn die Sterne gesetzt (und gespeichert)
   sind — zuerst die eine Frage, die jeder in zwei Sekunden beantwortet.
   Die Klasse setzt das Skript; ohne JavaScript steht alles von Anfang an da. */
.feedback-dialog.is-stars-only .feedback-comment,
.feedback-dialog.is-stars-only .feedback-actions,
.feedback-dialog.is-stars-only .feedback-later { display: none; }
.feedback-comment label {
  display: block;
  margin-bottom: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
/* Ohne `border-box` schlaegt `width: 100%` Rahmen und Polsterung obendrauf —
   das Feld ragte rechts aus dem Dialog heraus. */
.feedback-comment textarea { width: 100%; box-sizing: border-box; }
.feedback-actions { display: flex; flex-wrap: wrap; gap: var(--space-3); margin: 0; }
.feedback-error { color: var(--color-error-ink); margin: var(--space-3) 0 0; }
/* Der Dialog kommt nicht sofort — die ersten zwei Sekunden gehoeren dem
   Ergebnis (die Zeit haelt das Skript im Template). Damit der Wechsel danach
   nicht schneidet, blenden Scrim und Kasten ein. An `:modal` gebunden: der
   Fall ohne JavaScript ist ein Formular im Fluss und darf davon nichts
   abbekommen. Die Dauer haengt an den Motion-Token, `prefers-reduced-motion`
   nimmt sie damit mit. */
.feedback-dialog:modal { animation: feedback-rise var(--motion-base) var(--ease-out) both; }
.feedback-dialog:modal::backdrop { animation: feedback-scrim var(--motion-slow) var(--ease-out) both; }
@keyframes feedback-rise {
  from { opacity: 0; transform: translateY(var(--reveal-distance)); }
  to { opacity: 1; transform: none; }
}
@keyframes feedback-scrim {
  from { opacity: 0; }
  to { opacity: 1; }
}
.feedback-later { margin: 0; }
.result-feedback-cta { margin: var(--space-4) 0 0; }
.feedback-spread { color: var(--color-text-muted); font-size: var(--text-sm); }
.feedback-spread-item { display: inline-block; margin-right: var(--space-3); white-space: nowrap; }
.feedback-comment-list { list-style: none; padding: 0; margin: 0; }
.feedback-comment-list li {
  padding: var(--space-3) 0;
  border-top: 1px solid var(--color-border);
}
.feedback-comment-list li:first-child { border-top: 0; }
.feedback-comment-text { margin: 0 0 var(--space-1); white-space: pre-wrap; }
