/* ==========================================================================
   Reset + Basis
   ========================================================================== */

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

html, body { height: 100%; }

body {
  font-family: var(--font-sans);
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--foreground);
  background: var(--background);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow: hidden;
}

button, input, textarea, select { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }
svg { display: block; flex: none; }
[hidden] { display: none !important; }

/* Fokus wie in shadcn: focus-visible:ring-[3px] ring-ring/50 — ein weicher
   Hof, keine harte Linie. Der Unterschied ist nicht nur Geschmack: eine
   deckende 2-px-Linie in --ring sieht aus wie der Rahmen eines Eingabefelds,
   und in Listen mit Umbenennen-Feld (all-chats.html) war der fokussierte
   Eintrag vom Bearbeitungsmodus nicht mehr zu unterscheiden.

   Als outline und nicht als box-shadow wie bei .input: ein Schatten wird von
   jedem overflow: hidden weiter oben abgeschnitten, eine Outline nie. Die
   Variable steht hier, damit Sonderfälle denselben Ring setzen können, ohne
   ihn abzuschreiben — .chats-row etwa, wo der Ring um die ganze Zeile läuft
   und nicht um den Verweis darin. */
:root { --focus-ring: 3px solid color-mix(in oklab, var(--ring) 50%, transparent); }

:focus-visible {
  outline: var(--focus-ring);
  outline-offset: 1px;
}

::selection { background: color-mix(in oklab, var(--foreground) 18%, transparent); }

/* Scrollbars im shadcn-Look */
* { scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--border);
  border: 3px solid transparent;
  border-radius: 999px;
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--muted-foreground); background-clip: content-box; }

/* ==========================================================================
   Button
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  /* 40 px hoch, 16 px seitlich: Bei 14 px Schrift bleiben oben und unten je
     10 px — Luft ringsum im selben Maß. Mit 36 px und 18 px seitlich wirkte
     der runde Knopf breit gezogen und oben und unten knapp. */
  height: 2.5rem;
  padding: 0 1rem;
  /* Ganz rund, wie auf der Website — erster Schritt zu einer ruhigeren,
     aufgeräumteren Oberfläche (2026-10-16). Icon-Knöpfe werden damit Kreise. */
  border-radius: 999px;
  border: 1px solid transparent;
  font-size: 0.875rem;
  font-weight: 500;
  white-space: nowrap;
  user-select: none;
  text-decoration: none; /* .btn läuft gelegentlich auch auf <a> statt <button> */
  transition: background-color .15s, border-color .15s, color .15s, opacity .15s;
}
.btn:disabled { opacity: .5; pointer-events: none; }
.btn svg { width: 1rem; height: 1rem; }

.btn-primary { background: var(--primary); color: var(--primary-foreground); box-shadow: var(--shadow-xs); }
/* Hell aufgehellt zu 75 %, dunkel zu 90 %: Seit --primary hell echtes
   Schwarz ist, ergaben 90 % dort nur Helligkeit 0.1 — auf Schwarz sieht man
   das kaum. 75 % ergeben 0.25, etwa #262626. Dunkel (Weißgrau auf Schwarz)
   reichen die 90 % weiterhin. */
.btn-primary { --primary-hover: 75%; }
.dark .btn-primary { --primary-hover: 90%; }
.btn-primary:hover { background: color-mix(in oklab, var(--primary) var(--primary-hover), var(--background)); }

/* Drei Stufen der durchscheinenden Tönung für .btn-outline und .btn-ghost.
   Dunkel stärker: Weiß zu 6 % auf Schwarz ergibt kaum einen Ton, der Hover
   war dort zu schwach — hell bleibt es bei den Werten der Website. */
.btn { --tint-1: 6%; --tint-2: 10%; --tint-3: 14%; }
.dark .btn { --tint-1: 10%; --tint-2: 14%; --tint-3: 18%; }

/* Ohne Rahmen, mit leichter durchscheinender Fläche in --foreground — wie
   auf der Website. Eine weiße Fläche mit Rahmen las sich neben dem primären
   Knopf wie ein zweiter Hauptknopf; ganz ohne Fläche wäre er kein Knopf mehr.
   Durchscheinend statt deckend, damit er auf jedem Papier (Karte, Dialog,
   Seitenleiste) gleich stark tönt. */
.btn-outline {
  background: color-mix(in oklab, var(--foreground) var(--tint-1), transparent);
  /* Ausdrücklich, nicht geerbt: Auf einem <button> käme die Farbe von oben,
     auf einem <a> aber malt der Browser sein eigenes Linkblau — und .btn
     nimmt nur die Unterstreichung weg, nicht die Farbe. Betrifft die Knöpfe
     auf home.html, settings.html und den Verweis unter einer abgeschnittenen
     Antwort. .btn-danger steht weiter unten und übersteuert das weiterhin. */
  color: var(--foreground);
}
.btn-outline:hover { background: color-mix(in oklab, var(--foreground) var(--tint-2), transparent); }
.btn-outline:active { background: color-mix(in oklab, var(--foreground) var(--tint-3), transparent); }

/* Hover durchscheinend in --foreground statt deckend in --accent: tönt den
   Grund, auf dem der Knopf steht, statt ihn mit einem festen Grau zu
   überdecken. */
.btn-ghost { color: var(--foreground); }
.btn-ghost:hover { background: color-mix(in oklab, var(--foreground) var(--tint-1), transparent); color: var(--foreground); }
.btn-ghost:active { background: color-mix(in oklab, var(--foreground) var(--tint-2), transparent); }

/* In Ruhe schwarz wie jeder andere Knopf, erst unter dem Zeiger rot: Eine
   Liste voller roter Papierkörbe schrie, bevor jemand etwas wollte. Die
   Warnung gehört an den Moment, in dem man zugreift. */
.btn-danger { color: var(--foreground); }
.btn-danger:hover,
.btn-danger:focus-visible { background: color-mix(in oklab, var(--destructive) 12%, transparent); color: var(--destructive); }

/* Gefuellt statt nur eingefaerbt: fuer den Knopf, der das Loeschen wirklich
   ausfuehrt. .btn-danger ist die leise Fassung fuer Papierkoerbe in Listen. */
.btn-destructive {
  background: var(--destructive); color: var(--destructive-foreground);
  box-shadow: var(--shadow-xs);
}
.btn-destructive:hover { background: color-mix(in oklab, var(--destructive) 88%, black); }

.btn-icon { width: 2.5rem; padding: 0; flex: none; }
.btn-sm { height: 2.25rem; padding: 0 0.875rem; font-size: 0.8125rem; }
/* Klein und quadratisch zugleich: Breite folgt der kleinen Höhe, sonst
   wäre der runde Knopf ein Oval. */
.btn-icon.btn-sm { width: 2.25rem; }
.btn-block { width: 100%; }
.btn-start { justify-content: flex-start; }

/* --------------------------------------------------------------------------
   Knöpfe in einer eingefärbten Zeile

   Betrifft den Drei-Punkte-Knopf (js/row-menu.js), der erst erscheint, wenn
   die Zeile unter dem Zeiger liegt: in der Seitenleiste (.chat-item-actions),
   auf all-chats.html in den Zeilen (.chats-row-actions) und an den
   Ordner-Abschnitten (.chats-group-acts), und in der Ablage
   (.saved-item-acts, .saved-folder-acts). Umbenennen, Löschen und der Rest
   stehen im Menü dahinter — rot ist dort der Eintrag, nicht der Knopf.

   Für sie gilt .btn-ghost nicht, und der Grund ist rechnerisch: die Zeile
   selbst ist beim Überfahren --accent, und der Hover des Ghost-Knopfes tönt
   sie nur um 6 % (dunkel 10 %) nach. Ein Knopf, der kaum anders aussieht als sein Grund, meldet
   nichts. Er geht deshalb den umgekehrten Weg zurück auf das Papier, auf dem
   die Zeile liegt.

   Dazu ein haarfeiner Rand, und der ist der eigentliche Unterschied: hell
   liegen --accent (0.97) und --background (1.0) drei Hundertstel auseinander,
   das sieht auf einem 24-px-Knopf niemand. Der Rand zeichnet die Fläche nach,
   statt sie nur aufzuhellen. box-shadow und nicht border: ein Rahmen machte
   den Knopf 2 px größer und schöbe die Zeile auseinander.

   Welches Papier gemeint ist, sagt die Liste selbst über --row-surface — in
   der Seitenleiste ist das --sidebar und nicht --background, sonst säße dort
   im dunklen Design ein schwarzes Loch in der helleren Leiste.

   Solange sein Menü offen ist, sieht der Knopf aus wie unter dem Zeiger. */
.chat-item-actions .btn:hover,
.chats-row-actions .btn:hover,
.chats-group-acts .btn:hover,
.saved-item-acts .btn:hover,
.saved-folder-acts .btn:hover,
.chat-item-actions .btn[aria-expanded="true"],
.chats-row-actions .btn[aria-expanded="true"],
.chats-group-acts .btn[aria-expanded="true"],
.saved-item-acts .btn[aria-expanded="true"],
.saved-folder-acts .btn[aria-expanded="true"] {
  background: var(--row-surface, var(--background));
  color: var(--foreground);
  box-shadow: 0 0 0 1px var(--row-surface-border, var(--border));
}

/* Und er bleibt stehen, wenn der Zeiger die Zeile verlässt — auf dem Weg ins
   Menü tut er das fast immer, und ein Auslöser, der unter seinem eigenen
   Menü verschwindet, lässt offen, wozu das Menü gehört. */
.chat-item-actions:has(> [aria-expanded="true"]),
.chats-row-actions:has(> [aria-expanded="true"]),
.chats-group-acts:has(> [aria-expanded="true"]),
.saved-item-acts:has(> [aria-expanded="true"]),
.saved-folder-acts:has(> [aria-expanded="true"]) { opacity: 1; pointer-events: auto; }

kbd {
  display: inline-block;
  padding: 0.0625rem 0.3125rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--muted);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  line-height: 1.35;
  color: var(--muted-foreground);
}

/* ==========================================================================
   Karte

   Weiß mit 1 px Rand, ohne Schatten, --radius-xl — wie die Kacheln auf
   assistants.html (.lib-card). Hier steht, wie eine Karte aussieht; an den
   einzelnen Klassen weiter unten steht nur noch, wie sie innen aufgebaut
   ist. Keine Karten sind die Farbfelder (.detail-band, .page-band, die
   Wolkenflächen). Eine Ausnahme: die Marketingkarte (.lib-hero) ist
   hellgrau ohne Rand, siehe unten. Siehe DESIGN.md, „Karten und Flächen".

   Grund ist --background und nicht --card: dunkel ist --card grau
   (gray-900), und die Karten sollen dort genauso aussehen wie .lib-card
   und .lib-area — Seitengrund mit Rand. Weil Karte und Seite denselben Grund
   haben, behalten --muted, --secondary und --accent darin ihre normalen
   Töne. Bis 2026-10-16 waren die Karten kurz grau ohne Rand und rückten
   diese Töne eine Stufe dunkler.
   ========================================================================== */

:is(.settings-card, .lib-card, .lib-area, .lib-hero, .stat-card, .stats-panel,
    .home-plan, .home-promo, .chats-empty, .placeholder,
    .support-card, .suggestion, .auth-card, .plan-nudge) {
  background: var(--background);
  color: var(--card-foreground);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  box-shadow: none;
  --card: var(--background);
  --logo-bg: var(--background);
}

/* Die eine Ausnahme: die Marketingkarte (.lib-hero) — die erste Karte auf
   Startseite und Übersichten, mit der Wolkenfläche darin. Hellgrau ohne
   Rand, auf Wunsch (2026-10-16): Sie ist Werbung, kein Inhalt, und setzt
   sich so von den weißen Karten darunter ab. Auf ihrem Grau rücken
   --muted, --secondary und --accent eine Stufe dunkler, sonst verschwänden
   der Nebenknopf und Schienen darin. */
.lib-hero {
  background: var(--surface);
  border: 0;
  --card: var(--surface);
  --muted: var(--surface-muted);
  --secondary: var(--surface-muted);
  --accent: var(--surface-accent);
  --logo-bg: var(--surface);
}

/* Die Karte, die selbst ein Knopf ist, wird unter dem Zeiger getönt.
   Die Kachel (.lib-card) hat ihren eigenen Hover, siehe dort. */
.suggestion:hover { background: var(--accent); }

/* Ein Kästchen für ein Zeichen auf der Karte (Leerzustand, Hinweis): weiß
   mit Rand wie die Karte selbst — ohne Rand verschwände es auf ihrem Weiß. */
:is(.placeholder-icon, .chats-empty-icon, .home-promo-icon) {
  border: 1px solid var(--border);
  background: var(--background);
  --logo-bg: var(--background);
}

/* ==========================================================================
   Input / Textarea / Range
   ========================================================================== */

.input {
  width: 100%;
  height: 2.5rem;   /* so hoch wie .btn, damit beide nebeneinander bündig stehen */
  padding: 0 1rem;  /* wie im Knopf: an der Rundung braucht der Text Luft */
  border: 1px solid var(--input);
  border-radius: 999px;   /* rund wie die Knöpfe; mehrzeilig siehe .textarea */
  background: var(--background);
  box-shadow: var(--shadow-xs);
  font-size: 0.875rem;
  transition: border-color .15s, box-shadow .15s;
}
.input::placeholder { color: var(--muted-foreground); }
.input:focus {
  outline: none;
  border-color: var(--ring);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--ring) 40%, transparent);
}
.input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; }

/* Mehrzeilig keine Pille — ein hohes Oval schnitte die Ecken des Textes ab.
   Stattdessen der Radius einer Karte. */
.textarea {
  height: auto; min-height: 4.5rem; padding: 0.625rem 1rem;
  border-radius: var(--radius-xl);
  resize: vertical; line-height: 1.6;
}

.range {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 1.25rem; background: transparent; cursor: pointer;
}
.range::-webkit-slider-runnable-track { height: 0.375rem; border-radius: 999px; background: var(--secondary); }
.range::-moz-range-track { height: 0.375rem; border-radius: 999px; background: var(--secondary); }
.range::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 1rem; height: 1rem; margin-top: -0.3125rem;
  border-radius: 999px; background: var(--background);
  border: 2px solid var(--primary); box-shadow: var(--shadow-xs);
}
.range::-moz-range-thumb {
  width: 1rem; height: 1rem; border-radius: 999px;
  background: var(--background); border: 2px solid var(--primary);
}
/* Modell ignoriert den Wert — Regler ausgegraut und gesperrt, wie ein
   Werkzeugschalter, den das Modell nicht kann. */
.range.is-muted { opacity: 0.5; }
.range:disabled { cursor: not-allowed; }

.label { display: block; font-size: 0.875rem; font-weight: 500; line-height: 1; margin-bottom: 0.5rem; }
.label.is-muted { opacity: 0.5; }

/* Feldname mit einer Handlung rechts daneben — „Mit KI erstellen" über jedem
   System-Prompt- und Skill-Feld. Der Knopf ist 24 px hoch (WCAG 2.5.8) und steht in der
   Zeile des Namens statt darunter: er gehört zum Feld, nicht zum Hinweis, und
   unter einem langen Feld läge er außer Sicht.
   Passt die Zeile nicht (zwei Aktionen auf einem 320-px-Telefon), bricht sie
   um, und die Aktionen stehen rechtsbündig darunter — statt das Fenster, in
   dem sie stehen, zur Seite rollen zu lassen. */
.label-row {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 0.25rem 0.5rem;
  min-height: 1.5rem; margin-bottom: 0.375rem;
}
.label-row .label { margin-bottom: 0; }
.label-action {
  height: 1.5rem; padding: 0 0.5rem; gap: 0.375rem;
  font-size: 0.75rem; color: var(--muted-foreground); flex: none;
}
.label-action:hover { color: var(--foreground); }
/* Mehrere Aktionen an einem Feld (Laden, Mit KI erstellen) — die Zeile hält
   sie zusammen am rechten Ende, statt sie auf die Breite zu verteilen. Reicht
   auch die eigene Zeile nicht, bricht sie selbst noch einmal um. */
.label-actions {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 0.25rem;
  flex: 0 1 auto; min-width: 0; margin-left: auto;
}
.label-action svg { width: 0.875rem; height: 0.875rem; }
.hint { font-size: 0.75rem; color: var(--muted-foreground); margin-top: 0.4375rem; line-height: 1.5; }
.hint-warn { color: var(--warning-foreground); }
.hint code {
  font-family: var(--font-mono); font-size: 0.6875rem;
  background: var(--muted); padding: 0.0625rem 0.25rem; border-radius: var(--radius-sm);
}
.field + .field { margin-top: 1.25rem; }
/* Monatsbudget in den App-Einstellungen. Der Balken faerbt sich mit,
   sobald es knapp wird — die Zahl allein liest sonst niemand. */
.budget { display: flex; flex-direction: column; gap: 0.375rem; }
.budget-bar {
  height: 0.5rem; border-radius: 999px; background: var(--muted);
  overflow: hidden;
}
.budget-bar span {
  display: block; height: 100%; width: 0;
  border-radius: 999px; background: var(--primary);
  transition: width 0.3s ease, background-color 0.3s ease;
}
.budget[data-level="warn"] .budget-bar span { background: var(--warning, #d97706); }
.budget[data-level="over"] .budget-bar span { background: var(--destructive); }
.budget[data-level="over"] #budgetText { color: var(--destructive); }

/* Belegter Speicherplatz, ebenfalls in den App-Einstellungen. Anders als beim
   Budget gibt es keine Obergrenze, an der sich der Balken messen liesse — er
   zeigt deshalb nicht "wie voll", sondern das Verhaeltnis der drei Arten von
   Dateien zueinander. Die Palette ist einfarbig, also trennt sie die
   Helligkeit; die Punkte in der Liste darunter tragen dieselbe. */
.storage { display: flex; flex-direction: column; gap: 0.375rem; }
.storage-total {
  font-size: 1.375rem; font-weight: 600; letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums; line-height: 1.2;
}
.storage-bar {
  display: flex; height: 0.5rem; border-radius: 999px;
  background: var(--muted); overflow: hidden;
}
/* min-width, damit eine einzelne kleine Datei neben 200 MB Bildern nicht auf
   null Pixel zusammenfaellt und die Kategorie leer aussieht. */
.storage-bar span {
  display: block; height: 100%; min-width: 3px;
  transition: width 0.3s ease;
}
.storage-bar span + span { box-shadow: -1px 0 0 var(--card); }
.storage-dot {
  display: inline-block; width: 0.5rem; height: 0.5rem;
  border-radius: 999px; margin-right: 0.5rem;
}
.storage-bar [data-slice="attachments"],
.storage-dot[data-slice="attachments"] { background: var(--primary); }
.storage-bar [data-slice="images"],
.storage-dot[data-slice="images"] { background: color-mix(in oklab, var(--primary) 55%, var(--muted)); }
.storage-bar [data-slice="knowledge"],
.storage-dot[data-slice="knowledge"] { background: color-mix(in oklab, var(--primary) 28%, var(--muted)); }
.value-badge {
  font-family: var(--font-mono); font-weight: 400; font-size: 0.75rem;
  color: var(--muted-foreground); margin-left: 0.25rem;
}

.segmented {
  display: inline-flex; gap: 0.25rem; padding: 0.25rem;
  /* Halbe Höhe einer Reihe (1.875 + 2 × 0.25 rem): einreihig eine Pille wie
     die Knöpfe darin, umgebrochen (.segmented-wrap) ein gerundetes Rechteck
     statt eines Ovals. */
  background: var(--muted); border-radius: 1.1875rem;
}
.segmented button {
  height: 1.875rem; padding: 0 0.875rem; border-radius: 999px;
  font-size: 0.8125rem; font-weight: 500; color: var(--muted-foreground);
  transition: background-color .15s, color .15s;
}
.segmented button[aria-checked="true"] {
  background: var(--background); color: var(--foreground); box-shadow: var(--shadow-xs);
}
/* Für Reihen, die auf schmalen Dialogen umbrechen müssen (Denkaufwand: 6 Stufen). */
.segmented-wrap { display: flex; flex-wrap: wrap; }
.segmented-wrap button { padding: 0 0.625rem; font-size: 0.75rem; }

/* Eingerückte Unterzeile zu einer Checkbox — gehört sichtbar zu ihr. */
.field-sub {
  margin: 0.625rem 0 0 1.625rem;
  padding-left: 0.75rem;
  border-left: 2px solid var(--border);
}
.field-sub[hidden] { display: none; }
.label-sm { font-size: 0.75rem; margin-bottom: 0.375rem; color: var(--muted-foreground); }
.check-row-spaced { margin-top: 1rem; }

.badge {
  display: inline-flex; align-items: center; gap: 0.25rem;
  height: 1.375rem; padding: 0 0.5rem;
  border: 1px solid var(--border); border-radius: 999px;
  font-size: 0.6875rem; font-weight: 600; letter-spacing: .02em;
  color: var(--muted-foreground);
}

/* Indikatoren: hier wartet etwas Neues (siehe DESIGN.md, „Indikatoren").
   Die Zahl steht am Eintrag, der dorthin führt — gefüllt in --primary, das
   einzige Gefüllte in einer Liste aus ruhigen Einträgen. Der Punkt steht
   dort, wo für eine Zahl kein Platz ist: am Avatar und am Menüknopf. */
.count-badge {
  display: inline-grid; place-items: center; flex: none;
  min-width: 1.125rem; height: 1.125rem; padding: 0 0.3125rem;
  margin-left: auto; border-radius: 999px;
  background: var(--primary); color: var(--primary-foreground);
  font-size: 0.6875rem; font-weight: 600; line-height: 1;
  font-variant-numeric: tabular-nums;
}
.menu-item .count-badge { align-self: center; }

/* Der Ring in der Farbe des Untergrunds trennt den Punkt von dem, worauf er
   sitzt — ohne ihn verschwämme ein Punkt in --primary auf dem Avatar, der
   dieselbe Farbe trägt. */
.has-dot::after,
.dot-before::before {
  content: ""; width: 0.5rem; height: 0.5rem; border-radius: 999px;
  background: var(--primary);
}
/* Auf einem Element: oben rechts in der Ecke. */
.has-dot { position: relative; --dot-ring: var(--background); }
.has-dot::after {
  position: absolute; top: -0.125rem; right: -0.125rem;
  box-shadow: 0 0 0 2px var(--dot-ring); pointer-events: none;
}
.avatar.has-dot { --dot-ring: var(--sidebar); }
/* Vor einem Text, etwa einer Tabellenzeile, die noch niemand geöffnet hat. */
.dot-before::before {
  display: inline-block; vertical-align: middle; margin-right: 0.375rem;
}

/* ==========================================================================
   Layout
   ========================================================================== */

.layout {
  display: flex; height: 100dvh; overflow: hidden;

  /* Breite der Seitenleiste. Sie steht als Eigenschaft am .layout, weil
     js/nav.js sie beim Ziehen am Rand dort setzt — so hängt jede Regel, die
     sie braucht, an einer einzigen Stelle. Die 17rem sind die Voreinstellung,
     die vorher fest an .sidebar stand. */
  --sidebar-w: 17rem;

  /* Eingeklappt: gerade breit genug für ein zentriertes Icon (2rem Knopf
     plus zweimal 0.375rem Innenabstand der Liste). Derselbe Wert steht als
     SIDEBAR_RAIL in js/nav.js — dort rechnet das Ziehen damit. */
  --sidebar-rail-w: 3.25rem;
}

.sidebar {
  position: relative;
  display: flex; flex-direction: column; flex: none;
  width: var(--sidebar-w);
  background: var(--sidebar);
  color: var(--sidebar-foreground);
  border-right: 1px solid var(--sidebar-border);
}

.sidebar-head {
  display: flex; align-items: center; justify-content: space-between;
  height: 3.5rem; padding: 0 0.75rem 0 1rem; flex: none;
}
.brand {
  display: flex; align-items: center; gap: 0.5rem;
  font-weight: 600;
  color: inherit; text-decoration: none;
}
.brand-mark { display: grid; place-items: center; --logo-bg: var(--sidebar); }
.brand-mark svg { width: 1.25rem; height: 1.25rem; }
/* Etwas kleiner als das Logo daneben (1.25rem): der Schriftzug soll die
   Marke begleiten und nicht ueberragen. */
.brand-name { font-size: 0.875rem; }
.sidebar-close { display: none; }

/* ==========================================================================
   Breite und Rail — Ziehen am Rand, Einklappen über den Knopf (js/nav.js)

   Zwei Bedienungen an derselben Kante: Ziehen ändert die Breite, ein Klick
   ohne Ziehen klappt ein und wieder aus. Derselbe Zustand hängt am Knopf in
   der Kopfzeile der Leiste.

   Beides gilt erst ab 1025 px, und zwar für alles in diesem Abschnitt: unter
   1024 px ist die Leiste kein Streifen neben dem Inhalt, sondern eine
   Schublade darüber (siehe „Responsive" weiter unten). Eine Schublade auf
   3.25rem einzuklappen ergäbe keinen Sinn — dort schließt man sie ganz.
   Deshalb steht die Mediaanfrage um den ganzen Block und nicht an jeder
   einzelnen Regel: so kann keine Rail-Regel versehentlich auf das Handy
   durchschlagen.
   ========================================================================== */

/* Ohne die Mediaanfrage unsichtbar — sonst stünde auf dem Handy ein
   unbeschrifteter Streifen am Rand der Schublade. */
.sidebar-resizer, .sidebar-collapse { display: none; }

@media (min-width: 1025px) {

  /* Der Griff liegt auf der Trennlinie, halb in der Leiste und halb daneben.
     Er ist absolut positioniert und nicht Teil der Spalte, damit die Breite
     der Leiste weiterhin allein --sidebar-w ist. */
  .sidebar-resizer {
    display: block;
    position: absolute; z-index: 20;
    top: 0; bottom: 0; right: -0.25rem; width: 0.5rem;
    cursor: col-resize; touch-action: none;
  }
  .sidebar-resizer::before {
    content: ''; position: absolute; top: 0; bottom: 0; left: 50%;
    width: 2px; margin-left: -1px; border-radius: 999px;
    background: transparent; transition: background .15s;
  }
  .sidebar-resizer:hover::before,
  .sidebar-resizer:focus-visible::before,
  .layout.sidebar-resizing .sidebar-resizer::before { background: var(--ring); }
  .sidebar-resizer:focus-visible { outline: none; }

  /* Während des Ziehens bleibt der Zeiger überall der Doppelpfeil, und
     markiert werden darf nichts — sonst zieht man den halben Chat blau. */
  .layout.sidebar-resizing { cursor: col-resize; user-select: none; }

  /* Der Knopf ist Beiwerk, keine Aktion der Seite: er steht in der
     Sekundärfarbe und mit dünnerem Strich als die Einträge daneben. Unter
     dem Zeiger nimmt er die volle Farbe an — dann tut er ja etwas. */
  .sidebar-collapse { display: inline-flex; color: var(--muted-foreground); }
  .sidebar-collapse svg { stroke-width: 1.5; }
  .sidebar-collapse:hover, .sidebar-collapse:focus-visible { color: var(--foreground); }

  /* ---------- Eingeklappt: nur noch Icons ---------- */

  /* Die Breite steht hier an der Leiste und nicht in --sidebar-w: die
     Eigenschaft setzt js/nav.js beim Ziehen als Inline-Stil ans .layout,
     und ein Inline-Stil schlägt jede Regel im Stylesheet. Die gezogene
     Breite bleibt dort also stehen und gilt wieder, sobald man ausklappt —
     genau so soll es sein. */
  .sidebar-rail .sidebar { width: var(--sidebar-rail-w); }

  /* Was in 3.25rem nicht mehr hineinpasst und in einer Icon-Spalte auch
     nichts zu suchen hat. Die Verläufe sind der eigentliche Punkt: eine
     Liste von Titeln ohne Titel wäre eine Reihe leerer Zeilen. */
  .sidebar-rail .brand-name,
  .sidebar-rail .nav-item-text,
  .sidebar-rail .nav-caret,
  .sidebar-rail .recent,
  .sidebar-rail .plan-nudge,
  .sidebar-rail .account-text,
  .sidebar-rail .account-caret { display: none; }

  /* Das Logo bleibt stehen — die Leiste soll auch als Streifen die App sein
     und nicht eine Reihe anonymer Icons. Es teilt sich den Platz mit dem
     Ausklapp-Knopf: beide liegen in derselben Rasterzelle, zu sehen ist das
     Logo, und sobald der Zeiger (oder der Tastaturfokus) in die Kopfzeile
     kommt, tritt der Knopf an seine Stelle. Nebeneinander gingen sie in
     3.25rem nicht, und ein Logo, das nichts tut, waere den Platz nicht
     wert. Wer die Leiste nicht anfasst, sieht die Marke; wer sie anfasst,
     sieht, was der Klick tut. */
  /* justify-content muss mit: aufgeklappt steht dort space-between (Marke
     links, Knopf rechts), und das schiebt die eine Spalte dieses Rasters an
     den linken Rand statt in die Mitte. */
  .sidebar-rail .sidebar-head {
    display: grid; place-items: center; justify-content: center; padding: 0;
  }
  .sidebar-rail .brand,
  .sidebar-rail .sidebar-collapse { grid-area: 1 / 1; transition: opacity .15s; }
  /* Das Logo liegt unter dem Knopf — Klicks gehoeren dem Knopf, sonst
     landete man am Rand der Kopfzeile im Chat statt beim Ausklappen. */
  .sidebar-rail .brand { pointer-events: none; }
  .sidebar-rail .sidebar-collapse { opacity: 0; }
  .sidebar-rail .sidebar-head:hover .brand { opacity: 0; }
  .sidebar-rail .sidebar-head:hover .sidebar-collapse { opacity: 1; }

  /* Für die Tastatur zählt :focus-visible und nicht :focus-within: nach einem
     Klick mit der Maus behält der Knopf den Fokus, und mit :focus-within
     stünde das Icon danach so lange, bis man irgendwo anders hinklickt —
     das Logo käme nie zurück. Wer mit Tab dorthin kommt, soll den Knopf
     dagegen sehen.

     Eigene Regel, nicht angehängt: ein Browser ohne :has() verwirft die
     ganze Regel, in der es vorkommt — die Vertauschung beim Überfahren
     darf davon nicht mit betroffen sein. */
  .sidebar-rail .sidebar-head:has(:focus-visible) .brand { opacity: 0; }
  .sidebar-rail .sidebar-head:has(:focus-visible) .sidebar-collapse { opacity: 1; }

  /* Die Library klappt auch als Streifen auf — nur nicht nach unten in eine
     Spalte, in der die Namen nicht stünden, sondern daneben über den Inhalt.
     Ein Menü wie das Kontomenü am Fuß: derselbe Popover-Untergrund, dieselbe
     Kante, dieselbe Schattierung. Die Beschriftungen kommen darin zurück —
     sieben eingerückte Icons ohne Namen wären keine Library.

     Der Umweg über .nav-group.open ist Absicht: dessen Regel weiter unten
     ist die spezifischere, und ein schlichtes „.sidebar-rail .nav-sub"
     verlöre gegen sie. */
  .sidebar-rail .nav-group { position: relative; }
  .sidebar-rail .nav-group.open > .nav-sub {
    position: absolute; z-index: 30;
    /* Der Abstand ist so bemessen, dass das Menü rechts der Trennlinie
       beginnt: die Gruppe endet 0.375rem vor der Kante der Leiste. */
    top: 0; left: calc(100% + 0.75rem);
    width: 13rem;
    margin: 0; padding: 0.25rem;
    border: 1px solid var(--border); border-radius: var(--radius-lg);
    background: var(--popover); color: var(--popover-foreground);
    box-shadow: var(--shadow-lg);
    transform: none;
    animation: dialog-in .14s cubic-bezier(.16, 1, .3, 1);
  }
  /* Im Menü sind es wieder gewöhnliche Einträge: mit Namen, in voller Höhe
     und ohne den Versatz, mit dem sie sonst an der Führungslinie stehen. */
  .sidebar-rail .nav-sub .nav-item-text { display: block; }
  .sidebar-rail .nav-sub .nav-sub-item { padding: 0 0.625rem; transform: none; }


  /* „Recent Chats" war der Abschnitt, der den Platz bis zum Fuß ausgefüllt
     hat. Ohne ihn muss die Bereichsliste das tun, sonst rutscht die
     Konto-Zeile von unten direkt unter die Icons.

     overflow: visible, weil das Library-Menü in dieser Liste liegt — ihre
     Scrollfläche schnitte es sonst an der Kante der Leiste ab. Eingeklappt
     stehen hier höchstens sieben Icons; zu scrollen gibt es nichts. */
  .sidebar-rail .sidebar-nav { flex: 1 1 auto; padding: 0.5rem 0.375rem; overflow: visible; }
  .sidebar-rail .nav-item,
  .sidebar-rail .account { justify-content: center; padding: 0; }
  .sidebar-rail .sidebar-foot { padding: 0.375rem; }

  /* Das Kontomenü ist sonst so breit wie der Fuß der Leiste — hier also
     3.25rem. Es klappt stattdessen nach rechts über den Inhalt auf. */
  .sidebar-rail .account-menu { right: auto; left: 0.375rem; width: 15rem; }

  /* Der Knopf in der Kopfzeile zeigt, was der Klick tut: zuklappen, solange
     die Leiste offen ist — aufklappen, solange sie es nicht ist. Zwei Icons
     im Knopf, eines davon verborgen; genauso hält es .theme-sun/.theme-moon. */
  .sidebar-collapse-in, .sidebar-collapse-out { display: flex; }
  .sidebar-collapse-out { display: none; }
  .sidebar-rail .sidebar-collapse-in { display: none; }
  .sidebar-rail .sidebar-collapse-out { display: flex; }
}

/* ==========================================================================
   Navigation in der Seitenleiste (js/nav.js)

   Die Bereiche stehen fest oben, scrollen soll nur „Recent Chats". Deshalb
   trägt .recent den weit größeren flex-shrink (1000 gegen 1): Platz wird zuerst
   dort abgezogen, bis dessen Mindesthöhe erreicht ist. Erst danach schrumpft
   die Bereichsliste und bekommt eine eigene Scrollfläche — auf normalen
   Fenstern passiert das nie.
   ========================================================================== */

.sidebar-nav {
  flex: 0 1 auto; min-height: 0; overflow-y: auto;
  padding: 0.5rem;                     /* SidebarGroup: p-2 */
  display: flex; flex-direction: column; gap: 0.125rem;
}

/* Seitenleisten-Knopf. Gebaut nach shadcn SidebarMenuButton (rounded-md,
   nur mit 2.25rem statt h-8: die Ziele sollen groesser sein), gezeichnet wie ein Eintrag der Aufklappmenüs, die aus
   derselben Leiste aufgehen (.menu-item: Kontomenü, Bereichsmenü): 13 px
   Schrift auf 500, Icon in Textfarbe mit dem gewöhnlichen Strich
   von 2, 0.625rem Abstand und Innenrand. Vorher stand die Leiste auf 14 px
   mit Icons in Textfarbe und Strich 2.25 — öffnete man darin das Kontomenü,
   standen zwei Sorten Einträge übereinander, die dasselbe tun.

   Die 500 gelten für alle drei Zustände — ruhend, überfahren, aktiv. Eine
   Zeile, die beim Aktivwerden fetter wird, springt in der Breite, und in
   einer Leiste voller kurzer Wörter sieht man das Springen. Hervorgehoben
   wird der aktive Eintrag über die Accent-Fläche; das Icon trägt in
   allen Zuständen die Textfarbe, im Menü (.menu-item) ebenso.
   Die Fläche ist --accent, dieselbe wie in jedem Menü. */
.nav-item {
  display: flex; align-items: center; gap: 0.5rem;
  width: 100%; height: 2.25rem; padding: 0 0.625rem; flex: none;
  border-radius: var(--radius-md);
  color: var(--sidebar-foreground); text-decoration: none;
  font-size: 0.8125rem; font-weight: 500; text-align: left;
  transition: background-color .15s, color .15s;
}
.nav-item:hover { background: var(--accent); color: var(--accent-foreground); }
.nav-item.active { background: var(--accent); color: var(--accent-foreground); }

/* Icon in der Farbe der Schrift (currentColor) — ruhend, überfahren, aktiv. */
.nav-item svg { width: 1rem; height: 1rem; color: currentColor; }
.nav-item-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.nav-caret { display: flex; transition: transform .18s ease; }
.nav-group.open > .nav-toggle .nav-caret { transform: rotate(90deg); }

/* Untermenü — shadcn SidebarMenuSub:
     mx-3.5 · px-2.5 · py-0.5 · gap-1 · border-l · translate-x-px
   Die Führungslinie ist dort ein echtes border-left, kein Pseudoelement,
   und sie steht zwei Pixel links der Icon-Mitte des Gruppenknopfs, nicht
   darauf. Bei shadcn sind das mx-3.5 (0.875rem); hier hat der Knopf
   0.625rem Innenrand statt 0.5rem, die Icon-Mitte liegt also bei 18 px und
   die Linie bei 1rem. Das Pixel hin und im Knopf wieder zurück
   (translate-x-px / -translate-x-px) rückt die Einträge von der Linie ab,
   ohne die Linie selbst zu verschieben. */
.nav-sub {
  display: none;
  flex-direction: column; gap: 0.125rem;
  margin: 0 1rem; padding: 0.125rem 0.625rem;
  border-left: 1px solid var(--sidebar-border);
  transform: translateX(1px);
}
.nav-group.open > .nav-sub { display: flex; }
/* shadcn SidebarMenuSubButton — sonst wie der Hauptknopf, auch in der
   Icon-Farbe, aber eine Stufe niedriger (2.125rem statt 2.25rem). */
.nav-sub-item { height: 2.125rem; transform: translateX(-1px); }

/* ---------- Recent Chats — der einzige scrollende Abschnitt ----------

   Eigene SidebarGroup mit p-2 (oben hier, unten und seitlich an .chat-list
   und .recent-head). Getrennt wird nach oben mit shadcns SidebarSeparator:
   mx-2 w-auto bg-sidebar-border, also eine um 0.5rem eingerückte 1-px-Linie
   statt eines randlos durchgezogenen border-top. Ein Rahmen lässt sich nicht
   einrücken, deshalb ::before. */
.recent {
  position: relative;
  flex: 1 1000 auto; min-height: 6.5rem;
  display: flex; flex-direction: column;
  padding-top: 0.5rem;
}
.recent::before {
  content: ""; position: absolute; left: 0.5rem; right: 0.5rem; top: 0;
  height: 1px; background: var(--sidebar-border);
}

/* SidebarGroupLabel: h-8 · text-xs · font-medium ·
   text-sidebar-foreground/70 — keine Versalien, keine Sperrung. Die
   1.125rem links sind 0.5rem der Gruppe plus die 0.625rem Innenrand eines
   .nav-item: Überschrift, Verlaufstitel und die Icons der Navigation
   darüber stehen auf einer Kante. Die 0.75rem rechts sind das right-3 der
   SidebarGroupAction daneben. */
.recent-head {
  flex: none; display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
  height: 2rem; padding: 0 0.75rem 0 1.125rem;
}
.recent-title {
  font-size: 0.75rem; font-weight: 500;
  color: color-mix(in oklab, var(--sidebar-foreground) 70%, transparent);
}
.recent-head-actions { display: flex; align-items: center; gap: 0.25rem; flex: none; }
/* shadcns SidebarGroupAction ist w-5 im Quadrat mit einem size-4-Icon. Genau
   das stand hier und war das Problem: 16 px Zeichnung in 20 px Knopf lassen
   2 px Rand, und der eingefaerbte Hover klebt am Strich, statt eine Flaeche
   zu sein. Dazu kam die Groesse selbst — 16 px neben einer 12-px-Ueberschrift
   zieht den Blick auf die Knoepfe statt auf „Recent Chats".
   Also 24 px Knopf mit 14-px-Icon: 5 px Luft ringsum statt 2, und der Knopf
   erreicht nebenbei die 24 px, die WCAG 2.5.8 als Zielgroesse verlangt —
   bisher trug diese Groesse eine unsichtbare Flaeche nach. Ausgerichtet
   bleibt es:
   0.75rem Rand plus 5 px Luft ergeben dieselben 17 px bis zum Icon wie
   0.5rem + 0.25rem + 5 px in der Liste darunter.

   Farbe: ruhend --muted-foreground (die Icons der Navigation darüber,
   .nav-item svg, tragen dagegen die Textfarbe), unter dem Zeiger der
   durchscheinende Hover von .btn-ghost wie überall. */
.recent-head .btn { width: 1.5rem; height: 1.5rem; color: var(--muted-foreground); }
.recent-head .btn svg { width: 0.875rem; height: 0.875rem; }
.recent-search { flex: none; padding: 0.25rem 0.5rem 0.375rem; --search-pad-x: 0.5rem; }
.recent-search .search-input { height: 2rem; }

/* --search-pad-x kommt von Varianten wie .recent-search/.command-search und
   bleibt hier bewusst ungesetzt: eine eigene 0rem-Deklaration würde je nach
   Position im Stylesheet die spätere Variante überschreiben (beide Selektoren
   haben dieselbe Spezifität) — der var()-Fallback ist ordnungsunabhängig. */
.search { position: relative; }
.search-icon {
  position: absolute; left: calc(var(--search-pad-x, 0rem) + 0.625rem); top: 50%; transform: translateY(-50%);
  color: var(--muted-foreground); pointer-events: none;
}
/* size-4 wie jedes andere Icon der App — 0.875rem war die einzige Ausnahme. */
.search-icon svg { width: 1rem; height: 1rem; }
.search-input { padding-left: 2rem; }

.chat-list {
  flex: 1; min-height: 0; overflow-y: auto;
  padding: 0 0.5rem 0.5rem; display: flex; flex-direction: column; gap: 0.125rem;
}
.chat-list-empty { padding: 1.5rem 0.75rem; text-align: center; font-size: 0.8125rem; color: var(--muted-foreground); }

.chat-item {
  display: flex; align-items: center; gap: 0.5rem;
  /* flex: none wie bei .nav-item — .chat-list ist eine Flex-Spalte, und ohne
     das geben die Einträge nach, sobald mehr Verläufe da sind als Platz:
     18 Chats standen dann 28 px hoch, 3 Assistenten-Chats 36 px. Nachgeben
     soll hier nichts, scrollen schon (overflow-y an .chat-list). */
  flex: none;
  /* 0.625rem links wie .nav-item — der Titel steht unter den Icons der
     Navigation und unter „Recent Chats". */
  width: 100%; padding: 0 0.25rem 0 0.625rem; height: 2rem;
  border-radius: var(--radius-md); color: var(--sidebar-foreground);
  transition: background-color .15s, color .15s;
}
.chat-item:hover { background: var(--accent); color: var(--accent-foreground); }
.chat-item.active {
  background: var(--accent); color: var(--accent-foreground);
  font-weight: 500;
}
/* 13 px wie die Navigation darüber (.nav-item) und wie jede dichte Liste. */
.chat-item-title {
  flex: 1; min-width: 0; text-align: left; font-size: 0.8125rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* --row-surface: das Papier, auf dem diese Zeile liegt — hier die Leiste und
   nicht die Seite. Gelesen wird es vom gemeinsamen Hover der Listenknöpfe
   oben im Abschnitt „Knöpfe in einer eingefärbten Zeile". */
.chat-item-actions {
  display: flex; gap: 0.25rem; opacity: 0; transition: opacity .15s; flex: none;
  --row-surface: var(--sidebar);
  --row-surface-border: var(--sidebar-border);
}
.chat-item:hover .chat-item-actions,
.chat-item:focus-within .chat-item-actions { opacity: 1; }
/* SidebarMenuAction, aber mit demselben Zuschlag wie in .recent-head: 24 px
   Knopf, 14-px-Icon, ruhend in --muted-foreground wie dort. right-1 kommt
   aus dem padding-right von .chat-item. */
.chat-item-actions .btn { width: 1.5rem; height: 1.5rem; color: var(--muted-foreground); }
.chat-item-actions .btn svg { width: 0.875rem; height: 0.875rem; }

.chat-item-title-input {
  flex: 1; min-width: 0; height: 1.75rem; padding: 0 0.375rem;
  border: 1px solid var(--ring); border-radius: var(--radius-sm);
  background: var(--background); font-size: 0.8125rem;
}
.chat-item-title-input:focus { outline: none; }

/* SidebarFooter: p-2 · gap-2. Die Trennung nach oben ist wieder ein
   SidebarSeparator (mx-2), nicht ein randloses border-top — .sidebar-foot
   traegt position: relative auch in css/auth.css (wegen des Kontomenues);
   hier steht es noch einmal, damit diese Regel fuer sich allein haelt. */
.sidebar-foot {
  position: relative;
  flex: none; padding: 0.5rem;
  display: grid; gap: 0.5rem;
}
.sidebar-foot::before {
  content: ""; position: absolute; left: 0.5rem; right: 0.5rem; top: 0;
  height: 1px; background: var(--sidebar-border);
}
.sidebar-foot .btn { font-weight: 400; font-size: 0.8125rem; height: 2.125rem; }

/* Der Plan über dem Konto — nur, wenn es etwas zu tun gibt (Plan.nudge()).
   Vorbild ist die Karte im SidebarFooter der shadcn-Blöcke: Card ohne
   Schatten, p-3, Titel text-sm/600, Beschreibung text-xs muted. Siehe
   DESIGN.md, „Plan und Upgrade". */
.plan-nudge {
  flex: none; margin: 0 0.5rem 0.5rem; padding: 0.875rem;
  display: grid; gap: 0.5rem;
}
.plan-nudge-head { display: flex; align-items: center; gap: 0.375rem; }
.plan-nudge-icon { display: inline-flex; color: var(--foreground); }
.plan-nudge-icon svg { width: 1rem; height: 1rem; }
.plan-nudge[data-level="warn"] .plan-nudge-icon { color: var(--warning); }
.plan-nudge[data-level="over"] .plan-nudge-icon { color: var(--destructive); }
.plan-nudge-title { font-size: 0.8125rem; font-weight: 600; }
.plan-nudge-text { margin: 0; font-size: 0.75rem; line-height: 1.45; color: var(--muted-foreground); }
.theme-sun { display: none; }
.dark .theme-sun { display: block; }
.dark .theme-moon { display: none; }

.scrim {
  position: fixed; inset: 0; z-index: 30;
  background: oklch(0 0 0 / 0.5);
  animation: fade-in .15s ease-out;
}

.main { display: flex; flex-direction: column; flex: 1; min-width: 0; background: var(--background); }

/* ==========================================================================
   Topbar
   ========================================================================== */

/* Ohne Linie zum Inhalt (2026-10-16): Die Kopfzeile liegt auf demselben
   Weiß wie die Seite, und der Strich war der letzte Rand, der die Fläche
   quer durchschnitt. Getrennt wird durch den Abstand darunter. */
.topbar {
  display: flex; align-items: center; gap: 0.5rem;
  height: 3.5rem; flex: none;
  padding: 0 0.75rem;
  background: color-mix(in oklab, var(--background) 80%, transparent);
  backdrop-filter: blur(8px);
}
.topbar-spacer { flex: 1; }
.only-mobile { display: none; }

/* Das Logo in der Kopfzeile — nur auf schmalen Fenstern, wo die
   Seitenleiste als Schublade zu ist und ihres damit nicht zu sehen ist.
   Eingesetzt von js/nav.js gleich hinter dem Menueknopf. */
.brand-top { flex: none; }
.brand-top .brand-mark { --logo-bg: var(--background); }

.topbar-title {
  min-width: 0; max-width: min(60vw, 26rem);
  padding-left: 0.25rem;
  font-size: 0.875rem; font-weight: 500;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Die Knoepfe rechts in der Kopfzeile: Icon UND Wort. Vorher waren es reine
   Icon-Knoepfe, und was sie tun, stand nur im Tooltip — der erscheint erst,
   wenn man schon geraten hat.

   Etwas kleiner als ein normaler .btn — so groß wie .btn-sm (2.25rem statt
   2.5rem, 13px statt 14px):
   drei benannte Knoepfe in voller Groesse wuerden die Kopfzeile beherrschen,
   und der Chatname daneben ist wichtiger als sie.

   Auf schmalen Fenstern tritt das Wort zurueck — siehe den Responsive-Block
   weiter unten. Es verschwindet dort nicht, es wird nur abgedeckt: der Name
   des Knopfes kommt aus seinem Text, und mit display:none haette er keinen
   mehr. */
.topbar-btn {
  position: relative;   /* Anker fuer den abgedeckten Text auf schmal */
  flex: none;
  height: 2.25rem; padding: 0 0.875rem; gap: 0.375rem;
  font-size: 0.8125rem;
}
.topbar-btn svg { width: 0.9375rem; height: 0.9375rem; }

/* Auswahl zwischen den beiden Chatseiten — steht oben links, gleich hinter
   dem Menüknopf. Der Knopf nennt den aktuellen Modus, das Menü darunter
   beschreibt beide in je einem Satz; gebaut wird es von js/nav.js aus
   denselben Bausteinen wie das Kontomenü (.menu, .menu-item).

   position: relative statt der Klasse .menu-anchor — sonst zählte der
   „Klick daneben"-Wächter des Plus-Menüs im Eingabefeld einen Klick hier
   oben als Klick in seinem eigenen Menü und ließe es offen stehen. */
.topbar-switch { position: relative; display: inline-flex; flex: none; }

.topbar-switch-trigger {
  display: inline-flex; align-items: center; gap: 0.375rem;
  height: 2rem; padding: 0 0.5rem 0 0.625rem;
  border-radius: 999px;
  background: var(--muted); color: var(--foreground);
  font-size: 0.8125rem; font-weight: 500; white-space: nowrap;
  transition: background-color .15s;
}
.topbar-switch-trigger:hover { background: var(--accent); }
.topbar-switch-trigger[aria-expanded="true"] { background: var(--accent); }
.topbar-switch-trigger svg { width: 0.875rem; height: 0.875rem; }
.topbar-switch-caret svg { width: 0.8125rem; height: 0.8125rem; opacity: .7; }

/* .menu-below steht bei .menu weiter unten im Stylesheet — dort, wo es das
   `bottom` des Grundstils auch wirklich überschreiben kann. */

/* Schmale Fenster: nur Icon und Pfeil. Der Chatname daneben braucht den Platz
   dringender als ein Wort, das das Icon schon sagt — im Menü steht es ohnehin.

   Das Menü hängt hier nicht am Knopf, sondern an der Kopfzeile, mit 0.75rem
   Rand wie deren Inhalt. Am Knopf begann es bei x = 87 und war 18.5rem
   breit: Auf jedem Telefon unter 390 px stand es rechts über den Rand. */
@media (max-width: 40rem) {
  .topbar-switch-text { display: none; }
  .topbar-switch-trigger { padding: 0 0.375rem 0 0.5rem; }
  .topbar { position: relative; }
  .topbar-switch { position: static; }
  .topbar-switch .menu-below {
    top: calc(100% + 0.25rem); left: 0.75rem;
    width: min(18.5rem, calc(100% - 1.5rem)); min-width: 0;
  }
}

/* ==========================================================================
   Seiten-Hülle — Bereiche außerhalb des Chats (image.html, Library-Seiten)

   Bislang steht dort nur der Platzhalter: Rahmen, Titel, ein Satz zum Zweck
   des Bereichs. Die Inhalte kommen später in .page-inner.
   ========================================================================== */

/* scrollbar-gutter reserviert den Platz der Scrollleiste dauerhaft. Ohne das
   springt der Inhalt um die Breite der Leiste, sobald man zwischen einem
   langen und einem kurzen Bereich wechselt — in den Einstellungen etwa von
   „Allgemein" (scrollt) zu „Über Meteor-AI" (scrollt nicht). */
.page { flex: 1; min-height: 0; overflow-y: auto; scrollbar-gutter: stable; }
.page-inner { max-width: 64rem; margin: 0 auto; padding: 2rem 1.5rem 4rem; }

.placeholder {
  display: grid; justify-items: center; gap: 0.375rem;
  padding: 3.5rem 2rem; text-align: center;
}
.placeholder-icon {
  display: grid; place-items: center; width: 2.75rem; height: 2.75rem;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs); margin-bottom: 0.5rem;
}
.placeholder-icon svg { width: 1.5rem; height: 1.5rem; }
.placeholder h2 { font-size: 1.125rem; font-weight: 600; }
.placeholder p { color: var(--muted-foreground); max-width: 34rem; }
.placeholder-note {
  margin-top: 0.75rem; font-size: 0.75rem;
  padding: 0.25rem 0.625rem; border-radius: 999px;
  border: 1px solid var(--border); background: var(--background);
}

/* ==========================================================================
   Home (home.html) — Begrüßung, Konto, Einstieg in den Chat

   Aufgebaut wie die Übersichten der Library (2026-10-16): keine Kopfzeile,
   ein Seitenkopf mit der Anrede als Titel und dem Datum darunter, rechts das
   Konto als Avatar; darunter die Marketingfläche (.lib-hero) mit Wolken, hier
   zum Chat, und die Knöpfe hinein. Name und Adresse stehen im Tooltip des
   Avatars und ausgeschrieben am Fuß der Seitenleiste — die Karte mit der
   Kontozeile, die hier vorher stand, wiederholte nur, was dort schon steht.
   ========================================================================== */

.home { display: flex; flex-direction: column; gap: 1.5rem; }

/* Den Abstand nach unten gibt .home (gap), nicht der Seitenkopf. */
.home-head { margin-bottom: 0; }

.home-role { flex: none; color: var(--foreground); }
.home-role svg { width: 0.75rem; height: 0.75rem; color: var(--muted-foreground); }

/* Der Avatar ist der Weg in die Kontoeinstellungen — so groß wie die Knöpfe
   neben dem Titel auf den Übersichten (32 px). .avatar kommt aus auth.css. */
.home-account {
  display: inline-flex; flex: none; border-radius: 999px;
  text-decoration: none;   /* ein <a>: ohne das wären die Initialen unterstrichen */
  transition: opacity .15s;
}
.home-account:hover { opacity: .85; }
.home-account:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }

/* Die Knöpfe in den Chat stehen mittig unter dem Text der Marketingfläche. */
.home-hero-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.5rem; margin-top: 1.25rem; }

/* Drei Kennzahlen statt der vier in Statistics — mit dem Vierer-Raster von
   dort bliebe rechts eine leere Spalte stehen. Zwei Klassen tief, weil
   .stats-cards weiter unten im Stylesheet steht und bei gleicher
   Spezifität gewänne; die zweite Regel holt dafür den Umbruch nach, den
   .stats-cards dort mitbringt. */
.home .home-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 900px) {
  .home .home-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Der Hinweis steht über den Karten, nicht daneben — ohne Abstand klebte er
   an der ersten Zeile. Sichtbar wird er nur, wenn etwas fehlgeschlagen ist. */
.home .stats-panel-body > .stats-error { margin-bottom: 1rem; }

/* Der Plan über den drei Kennzahlen: oben Name groß wie eine Kennzahl und
   rechts der Weg weiter, darunter die Zeilen aus Plan.facts() — Kontingent,
   Guthaben, Enterprise. Ein Kasten statt einer vierten Karte: der Plan ist
   keine Zahl unter anderen. Unter 640 px rutschen die Knöpfe unter den
   Namen. Siehe DESIGN.md, „Plan und Upgrade". */
.home-plan {
  display: grid; gap: 0.75rem;
  padding: 1.25rem 1.5rem; margin-bottom: 1.25rem;
}
.home-plan-head { display: flex; align-items: center; gap: 1rem; }
.home-plan-name {
  flex: 1; min-width: 0;
  display: flex; align-items: center; gap: 0.5rem;
  font-size: 1.125rem; font-weight: 600; letter-spacing: -0.01em;
}
.home-plan-name svg { width: 1.125rem; height: 1.125rem; }
.home-plan-text { margin: 0; font-size: 0.8125rem; color: var(--muted-foreground); }
.home-plan[data-level="over"] .home-plan-text { color: var(--destructive); }
.home-plan-actions { flex: none; display: flex; flex-wrap: wrap; gap: 0.5rem; }
@media (max-width: 640px) {
  .home-plan-head { flex-direction: column; align-items: stretch; }
  .home-plan-actions .btn { flex: 1 1 auto; }
}

/* Die Zeilen unter dem Plan (Plan.factsHtml) — auf der Startseite und unter
   Abo & Zahlung dieselben. Links die Bezeichnung, rechts Betrag, Balken und
   ein Satz, wozu das gut ist. Das Guthaben bekommt eine eigene Zeile statt
   eines Anhängsels hinter dem Kontingent: wer nicht sieht, dass es da ist
   und wann es einspringt, lädt auch nicht auf. */
.plan-facts { display: grid; gap: 0; margin: 0; }
.plan-fact {
  display: grid; grid-template-columns: 8rem minmax(0, 1fr); gap: 0.25rem 1rem;
  padding: 0.625rem 0; border-top: 1px solid var(--border);
}
.plan-fact:first-child { border-top: 0; padding-top: 0; }
.plan-fact:last-child { padding-bottom: 0; }
.plan-fact dt { font-size: 0.8125rem; font-weight: 500; color: var(--muted-foreground); padding-top: 0.0625rem; }
.plan-fact dd { margin: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 0.375rem 0.5rem; }
.plan-fact-value { font-size: 0.9375rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.plan-fact .budget { flex: 0 0 100%; }
.plan-fact .budget-bar { max-width: 28rem; }
.plan-fact-hint { flex: 1 0 100%; font-size: 0.75rem; color: var(--muted-foreground); }
.plan-fact[data-level="over"] .plan-fact-value { color: var(--destructive); }
@media (max-width: 640px) {
  .plan-fact { grid-template-columns: minmax(0, 1fr); }
}

/* Statusschild für den Weg zu Enterprise (Plan.enterpriseState) und einen
   vorgemerkten Wechsel (Plan.switchState). Offen in
   --warning wie alles, was auf jemanden wartet; freigegeben und vorgemerkt
   gefüllt in --primary, weil dann etwas geht; abgelehnt in --destructive. */
.plan-state {
  display: inline-flex; align-items: center;
  padding: 0.125rem 0.5rem; border-radius: 999px;
  font-size: 0.75rem; font-weight: 500; line-height: 1.25rem; white-space: nowrap;
  color: var(--muted-foreground); background: var(--secondary);
}
.plan-state[data-state="requested"],
.plan-state[data-state="canceled"] {
  color: var(--warning-foreground);
  background: color-mix(in oklab, var(--warning) 22%, transparent);
}
.plan-state[data-state="approved"],
.plan-state[data-state="scheduled"] { color: var(--primary-foreground); background: var(--primary); }
.plan-state[data-state="declined"] {
  color: var(--destructive);
  background: color-mix(in oklab, var(--destructive) 12%, transparent);
}

/* Die drei Schritte zu Enterprise unter Abo & Zahlung: anfragen, Freigabe,
   Wechsel. Erledigte mit Haken, der aktuelle umrandet, kommende leise; eine
   Ablehnung färbt den zweiten Schritt rot. So steht da, wo man ist und was
   noch kommt — der Satz allein sagte das nicht. */
.plan-steps { list-style: none; margin: 0.25rem 0 0; padding: 0; display: grid; gap: 0.625rem; }
.plan-step { display: grid; grid-template-columns: 1.5rem minmax(0, 1fr); gap: 0 0.75rem; align-items: start; }
.plan-step-mark {
  width: 1.5rem; height: 1.5rem; border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 0.75rem; font-weight: 600;
  border: 1px solid var(--border); color: var(--muted-foreground); background: var(--background);
}
.plan-step-mark svg { width: 0.875rem; height: 0.875rem; }
.plan-step-title { font-size: 0.875rem; font-weight: 500; color: var(--muted-foreground); }
.plan-step-text { grid-column: 2; font-size: 0.75rem; color: var(--muted-foreground); }
.plan-step[data-state="done"] .plan-step-mark { background: var(--primary); border-color: var(--primary); color: var(--primary-foreground); }
.plan-step[data-state="done"] .plan-step-title,
.plan-step[data-state="current"] .plan-step-title { color: var(--foreground); }
.plan-step[data-state="current"] .plan-step-mark { border: 2px solid var(--primary); color: var(--foreground); }
.plan-step[data-state="failed"] .plan-step-mark {
  border-color: var(--destructive); color: var(--destructive);
  background: color-mix(in oklab, var(--destructive) 12%, var(--background));
}
.plan-step[data-state="failed"] .plan-step-title { color: var(--destructive); }

.settings-card-title .plan-state { margin-left: 0.5rem; vertical-align: 0.125rem; }

/* Die drei Hilfe-Kacheln borgen sich .lib-card aus der Library-Uebersicht —
   bis auf den Zeilenschnitt. Dort steht in einer Kachel, was jemand als
   Beschreibung eingetippt hat, und die drei Zeilen halten die Reihe gleich
   hoch, egal wie ausschweifend das ausfiel. Hier stehen drei feste Saetze,
   und der Schnitt wuerde bei mittleren Fensterbreiten (drei Spalten, aber
   schmale Karten) mitten im Satz abschneiden. Gleich hoch bleiben die drei
   auch ohne ihn — dafuer sorgt das Raster. */
.home-help .lib-card-desc {
  display: block; overflow: visible; -webkit-line-clamp: none;
}

/* Hinweis auf Artificial Analysis — der einzige Block der Seite, der nach
   draußen zeigt. Eine Karte wie die übrigen (Abschnitt „Karte"); anders
   macht ihn das Kästchen mit dem Zeichen und die Liste darin. */
.home-promo {
  display: flex; gap: 1rem; padding: 2rem;
}
.home-promo-icon {
  display: grid; place-items: center; flex: none;
  width: 2.5rem; height: 2.5rem; border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs); color: var(--muted-foreground);
}
.home-promo-icon svg { width: 1.25rem; height: 1.25rem; }
.home-promo-body { min-width: 0; }
.home-promo-title { font-size: 1rem; font-weight: 600; }
.home-promo-text { margin-top: 0.375rem; color: var(--muted-foreground); max-width: 44rem; }
.home-promo-points {
  list-style: none; display: grid; gap: 0.375rem;
  margin: 0.875rem 0 0; font-size: 0.875rem;
}
.home-promo-points li { display: flex; align-items: flex-start; gap: 0.5rem; }
/* Der Haken sitzt sonst auf der Grundlinie und rutscht bei zwei Zeilen mit
   nach unten — die 0.2rem heben ihn auf die Höhe der ersten Zeile. */
.home-promo-points svg {
  width: 0.875rem; height: 0.875rem; flex: none;
  margin-top: 0.2rem; color: var(--muted-foreground);
}
.home-promo-actions {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 0.5rem 0.875rem; margin-top: 1.125rem;
}
.home-promo-note { font-size: 0.75rem; color: var(--muted-foreground); }

/* ==========================================================================
   Alle Chats (all-chats.html) — vollständige Liste statt der gekürzten
   Recent-Chats-Liste in der Sidebar.
   ========================================================================== */

/* Zwei Zeilen und nicht eine: oben die Bedienung, darunter die Auskunft.

   Nebeneinander bestimmte der Text die Lage des Umschalters, und der Text
   ist in jedem Bereich anders lang — der Umschalter sprang beim Wechseln um
   mehrere hundert Pixel zur Seite und in „Zuletzt", wo der Hinweis am
   laengsten ist, gleich in die zweite Zeile. Ein Bedienelement, das seine
   Stelle wechselt, sobald man es benutzt, muss man jedes Mal neu suchen.
   Untereinander haengt seine Lage nur noch an der Breite der Seite. */
.chats-toolbar {
  display: flex; flex-direction: column; gap: 0.5rem;
  margin-bottom: 1.25rem;
}
.chats-controls {
  display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
  flex-wrap: wrap;
}

/* Zaehler und Hinweis stehen als Block untereinander unter der Bedienung —
   direkt ueber der Liste, von der sie handeln.

   Die Hoehe ist reserviert und nicht gewachsen: der Hinweis faellt bei den
   Bildern weg, solange die 500 nicht erreicht sind, und ohne die reservierte
   Zeile ruecken Liste und Raster beim Umschalten um 20 px hoch und wieder
   herunter. 41 px sind Zaehler (21) + Abstand (2) + Hinweis (18). */
.chats-meta {
  display: flex; flex-direction: column; gap: 0.125rem; min-width: 0;
  min-height: 2.5625rem;
}
.chats-count { font-size: 0.875rem; color: var(--muted-foreground); }
.chats-limit { font-size: 0.75rem; color: var(--muted-foreground); }
.chats-search { max-width: 20rem; flex: 1 1 16rem; --search-pad-x: 0rem; }

.chats-list { display: flex; flex-direction: column; list-style: none; margin: 0; padding: 0; }

.chats-row {
  position: relative; display: flex; align-items: center; gap: 0.75rem;
  padding: 0.4375rem 0.625rem;
  border-radius: var(--radius-lg);
  transition: background-color .15s;
}
.chats-row:hover, .chats-row:focus-within { background: var(--accent); }
/* Der Ring gehört um die ganze Zeile, nicht um den Verweis darin: der deckt
   zwar per ::after die ganze Fläche ab, seine eigene Box ist aber nur die
   Textzeile — eine Outline darum säße mitten in der Zeile. */
.chats-row:has(.chats-row-main:focus-visible) { outline: var(--focus-ring); outline-offset: 1px; }
.chats-row-main:focus-visible { outline: none; }

/* Trennlinie als eigenes Element statt als border-bottom: ein Rahmen würde
   den abgerundeten Ecken der Zeile folgen und an den Enden nach oben biegen.
   Absolut positioniert läuft die Linie schnurgerade durch. */
.chats-row + .chats-row::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 1px;
  background: var(--border); opacity: 0.65;   /* nur eine Andeutung, kein Strich */
  z-index: 1;   /* sonst deckt die ::after-Flaeche des Verweises sie zu */
}
/* Dunkel ist --border schon oklch(1 0 0 / 10%) — noch einmal auf 0.65
   gedimmt blieben 6,5 % Weiss uebrig, und die Linie war praktisch weg. */
.dark .chats-row + .chats-row::before { opacity: 1; }

.chats-row-icon {
  flex: none; display: grid; place-items: center;
  width: 1.5rem; height: 1.5rem;
}
/* Wie im Seitenleisten-Knopf: Icon und Beschriftung tragen dieselbe Farbe,
   und die Strichbreite bleibt die der uebrigen App. Hier stand frueher
   stroke-width: 1.75 — eine Gegenrechnung zu den alten 2.5 aus js/icons.js.
   Seit die global auf 2 stehen, waeren diese Icons die einzigen duennen. */
.chats-row-icon svg { width: 1rem; height: 1rem; }

.chats-row-main {
  flex: 1; min-width: 0; text-align: left; display: block;
  color: inherit; text-decoration: none;
}
/* Geklickt wird die Zeile, nicht die Textzeile: der Verweis ist nur so hoch
   wie sein Titel (21 von 38 px), waehrend der Hover die ganze Zeile einfaerbt
   und damit ueberall Klickbarkeit verspricht. Die aufgespannte Flaeche loest
   das Versprechen ein — Icon, Zeitangabe und Polsterung oeffnen mit. */
.chats-row-main::after {
  content: ""; position: absolute; inset: 0; border-radius: var(--radius-lg);
}
.chats-row-title {
  display: block; font-size: 0.875rem; font-weight: 400;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Uhrzeit/Zähler und Aktionen liegen im selben Raster-Feld übereinander —
   beim Hover blendet die Zeit aus und macht Platz für Umbenennen/Löschen,
   ohne dass die Zeile in der Breite springt. */
/* Ohne position: relative — sonst liegt die Spalte ueber der aufgespannten
   Flaeche des Verweises, und ein Klick auf die Zeitangabe ginge ins Leere.
   Gebraucht wird sie hier nicht: die Ueberlagerung macht das Raster, und
   die Knoepfe bringen ihre Positionierung selbst mit. */
.chats-row-side {
  flex: none;
  display: grid; align-items: center; justify-items: end;
  min-width: 1.5rem; min-height: 1.5rem;
}
.chats-row-side > * { grid-area: 1 / 1; }

.chats-row-meta {
  font-size: 0.75rem; color: var(--muted-foreground); white-space: nowrap;
  pointer-events: none;   /* liegt ausgeblendet über den Knöpfen — nie anfassbar */
  transition: opacity .1s;
}
/* z-index, weil die aufgespannte Flaeche des Verweises sonst darueber liegt
   und jeden Klick auf Umbenennen/Loeschen abfaengt. */
.chats-row-actions {
  position: relative; z-index: 1;
  display: flex; gap: 0.25rem; opacity: 0; pointer-events: none; transition: opacity .1s;
}
.chats-row:hover .chats-row-meta,
.chats-row:focus-within .chats-row-meta,
.chats-row:has(.row-menu-btn[aria-expanded="true"]) .chats-row-meta { opacity: 0; }
.chats-row:hover .chats-row-actions,
.chats-row:focus-within .chats-row-actions { opacity: 1; pointer-events: auto; }
/* 24 px Knopf mit 14-px-Icon wie in der Seitenleiste. Hier stand vorher
   w-5 mit size-4 und darum eine unsichtbare Trefferflaeche (::after mit
   inset: -0.25rem -0.125rem), weil 20 px unter den 24 liegen, die WCAG 2.5.8
   verlangt. Die Kruecke faellt mit der Groesse weg: der Knopf ist jetzt
   selbst so gross, wie er sein soll. */
.chats-row-actions .btn { width: 1.5rem; height: 1.5rem; }
.chats-row-actions .btn svg { width: 0.875rem; height: 0.875rem; }

/* Der Hover dieser Knöpfe steht im Abschnitt „Knöpfe in einer eingefärbten
   Zeile" ganz oben — dieselbe Regel wie in der Seitenleiste und in der
   Ablage. Hier liegt die Zeile auf der Seite selbst, --row-surface bleibt
   also auf seinem Vorgabewert --background. */

/* Dasselbe Rezept wie .input, nur flacher: --input im Ruhezustand, --ring
   samt 3-px-Hof erst im Fokus. Vorher stand der Ring-Ton dauerhaft im
   Rahmen und im Fokus kam nichts mehr dazu — das Feld sah immer fokussiert
   aus und meldete den Fokus nie. */
.chats-row-title-input {
  position: relative; z-index: 1;
  flex: 1; min-width: 0; height: 1.75rem; padding: 0 0.5rem;
  border: 1px solid var(--input); border-radius: var(--radius-sm);
  background: var(--background); font-size: 0.875rem;
  transition: border-color .15s, box-shadow .15s;
}
.chats-row-title-input:focus {
  outline: none;
  border-color: var(--ring);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--ring) 40%, transparent);
}

/* Leerer Zustand nach dem Muster von shadcns Empty: Icon im Kästchen, Titel,
   Erklaerung, ein Ausweg. Eine Karte wie jede andere (Abschnitt „Karte"),
   nicht gestrichelt — das läse sich als Ablagefeld, in das man etwas
   hineinziehen soll. */
.chats-empty {
  display: flex; flex-direction: column; align-items: center;
  padding: 3.5rem 2rem; text-align: center;
}
.chats-empty-icon {
  display: grid; place-items: center; width: 2.5rem; height: 2.5rem;
  margin-bottom: 0.875rem; border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs); color: var(--muted-foreground);
}
.chats-empty-icon svg { width: 1.25rem; height: 1.25rem; }
.chats-empty-title { font-size: 0.875rem; font-weight: 500; }
.chats-empty-text {
  margin-top: 0.25rem; font-size: 0.875rem;
  color: var(--muted-foreground); max-width: 26rem;
}
.chats-empty .btn { margin-top: 1.125rem; }

/* Der Umschalter steht zwischen Zähler und Suche und darf nicht mitwachsen —
   die Suche nimmt den freien Platz. */
.chats-view { flex: none; }

/* --------------------------------------------------------------------------
   Die Ablage (all-chats.html, Bereich „Gespeichert")

   Je Ordner ein Abschnitt: Kopfzeile mit Klappgriff, darunter seine
   Verläufe. Ordner gibt es nur auf einer Ebene — alle Abschnitte stehen
   deshalb in einer Flucht, keiner in einem anderen. Eingerückt ist allein
   der Inhalt gegen seine Kopfzeile.

   Die Filterleiste aus Pillen, die hier einmal stand, ist damit gegangen:
   sie zeigte immer nur einen Ordner und erst nach einem Klick.
   -------------------------------------------------------------------------- */

.chats-saved-bar {
  display: flex; align-items: center; gap: 0.5rem;
  margin-bottom: 0.75rem;
}

.chats-groups { display: flex; flex-direction: column; gap: 0.75rem; }

/* Die Kopfzeile trägt den Hintergrund und nicht der Klappgriff darin: sonst
   endete er vor den Knöpfen daneben. Dasselbe Rezept wie .saved-folder-row
   in der überlagerten Ablage. */
.chats-group-head {
  display: flex; align-items: center; gap: 0.25rem;
  height: 2.25rem; padding-right: 0.375rem;
  border-radius: var(--radius-lg);
  transition: background-color .15s;
}
.chats-group-head:hover, .chats-group-head:focus-within { background: var(--accent); }

.chats-group-toggle {
  flex: 1; min-width: 0; height: 100%;
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0 0.375rem 0 0.25rem;
  border-radius: var(--radius-lg);
  font-size: 0.875rem; font-weight: 500; color: inherit; text-align: left;
}
.chats-group-caret, .chats-group-icon { display: grid; place-items: center; flex: none; }
.chats-group-caret svg { width: 1rem; height: 1rem; color: var(--muted-foreground); }
.chats-group-icon svg { width: 1rem; height: 1rem; }
.chats-group-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Die Zahl steht direkt am Namen und nicht am rechten Rand: dort stünde sie
   je nach Länge des Namens woanders und läse sich als eigene Spalte, die
   keine ist. Ziffern tabellarisch, sonst tanzt „3/17" gegen „12". */
.chats-group-n {
  flex: none; font-size: 0.75rem; font-weight: 400; color: var(--muted-foreground);
  font-variant-numeric: tabular-nums;
}

/* Umbenennen und Löschen stehen NEBEN dem Klappgriff und nicht darin: ein
   <button> darf keinen zweiten enthalten. Maße und Hover kommen aus dem
   Abschnitt „Knöpfe in einer eingefärbten Zeile" ganz oben. */
.chats-group-acts {
  display: flex; flex: none; gap: 0.25rem;
  opacity: 0; pointer-events: none; transition: opacity .1s;
}
.chats-group-head:hover .chats-group-acts,
.chats-group-head:focus-within .chats-group-acts { opacity: 1; pointer-events: auto; }
.chats-group-acts .btn { width: 1.5rem; height: 1.5rem; }
.chats-group-acts .btn svg { width: 0.875rem; height: 0.875rem; }

/* Der Inhalt hängt sichtbar an seiner Kopfzeile: eine Linie unter dem
   Klappgriff, die Zeilen dahinter. Die Einrückung von 1rem setzt die Linie
   unter die Mitte des Klappgriff-Zeichens. */
.chats-group-body {
  margin-left: 1rem; padding-left: 0.5rem;
  border-left: 1px solid var(--border);
}
.chats-group-empty {
  padding: 0.375rem 0.625rem;
  font-size: 0.8125rem; color: var(--muted-foreground);
}

/* Das bleibende Kennzeichen einer gespeicherten Zeile. Es sitzt vor dem
   Zeitpunkt und NICHT in .chats-row-actions: die Gruppe blendet sich erst
   beim Überfahren ein, und ein Kennzeichen, das nur unter der Maus sichtbar
   ist, kennzeichnet nichts. */
.chats-row-mark {
  position: relative; z-index: 1;
  display: inline-flex; flex: none; color: var(--primary);
}
.chats-row-mark svg { width: 0.875rem; height: 0.875rem; }

/* Der Ordner am Ende der Meta-Zeile. Nur ein Hinweis, wo die Zeile liegt —
   deshalb klein und ohne eigene Fläche. */
.chats-row-folder {
  display: inline-flex; align-items: center; gap: 0.1875rem;
  margin-left: 0.5rem; opacity: 0.85;
}
.chats-row-folder svg { width: 0.75rem; height: 0.75rem; }

/* Die Ordnerliste im Auswahldialog. Zweimal dasselbe: #folderDialog steht im
   Markup von all-chats.html, .saved-dialog baut js/saved-panel.js sich selbst
   — angesehen werden beide gleich, also stehen sie hier zusammen. */
#folderDialogList .command-item.selected,
.saved-dialog .command-item.selected { background: var(--accent); }
#folderDialogList .command-item svg,
.saved-dialog .command-item svg { width: 0.9375rem; height: 0.9375rem; opacity: 0.7; }
#folderDialog .dialog-body,
.saved-dialog .dialog-body { max-height: 60vh; }
/* Mit Abstand zwischen den Zeilen (0.25rem, wie jede Liste — siehe „Maße
   und Abstände" in DESIGN.md). .command-list stapelt ihre Zeilen sonst auf
   Stoß, und hier fällt das auf: der aktuelle Ordner ist hinterlegt, und
   liegt der Zeiger auf dem Nachbarn, verschmelzen beide zu einer Fläche. */
#folderDialogList,
.saved-dialog .command-list {
  max-height: 18rem; overflow-y: auto; padding: 0;
  display: flex; flex-direction: column; gap: 0.25rem;
}
/* Der Knopf „Neuer Ordner" steht links, die Absage rechts — dialog-foot-right
   schiebt sie auseinander. */
#folderDialog .dialog-foot,
.saved-dialog .dialog-foot { display: flex; align-items: center; justify-content: space-between; }

/* --------------------------------------------------------------------------
   Bilderansicht (all-chats.html) — alle erzeugten Bilder als Raster.

   auto-fill statt fester Spaltenzahl: das Raster füllt, was da ist, und
   verhält sich in der schmalen Hauptspalte neben der Seitenleiste genauso
   wie ohne sie. 11rem ist die kleinste Kachel, bei der die Ansage darunter
   noch mehr als zwei Wörter zeigt.
   -------------------------------------------------------------------------- */
.gallery {
  display: grid; gap: 1rem;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  list-style: none; margin: 0; padding: 0;
}

.gal-tile { display: flex; flex-direction: column; gap: 0.5rem; min-width: 0; }

/* Quadratisch beschnitten, obwohl die Bilder es nicht sind: ein Raster aus
   Originalverhältnissen wäre eine Treppe. Der Beschnitt ist die Vorschau,
   das ganze Bild zeigt der Klick.

   Die Fläche trägt schon vor dem Bild ihre Farbe — so steht das Raster von
   Anfang an und rutscht nicht nach, wenn die signierten Adressen eintreffen. */
.gal-shot {
  display: block; width: 100%; aspect-ratio: 1;
  padding: 0; overflow: hidden;
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--muted);
  cursor: zoom-in;
  transition: border-color .15s, box-shadow .15s;
}
.gal-shot:hover { border-color: var(--ring); box-shadow: var(--shadow-sm); }
.gal-shot:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }

.gal-img {
  display: block; width: 100%; height: 100%;
  object-fit: cover;
  transition: transform .2s;
}
.gal-shot:hover .gal-img { transform: scale(1.03); }

/* Kein Bild da, weil die Adresse noch fehlt oder nicht kam: ein leeres <img>
   zeigt sonst das gebrochene Symbol des Browsers. */
.gal-img[data-path] { visibility: hidden; }

.gal-cap { display: flex; flex-direction: column; gap: 0.125rem; min-width: 0; }
.gal-prompt {
  font-size: 0.8125rem; line-height: 1.35;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gal-meta {
  font-size: 0.75rem; color: var(--muted-foreground);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gal-link { color: inherit; text-decoration: none; }
.gal-link:hover { color: var(--foreground); text-decoration: underline; }

.gallery-more { display: flex; justify-content: center; margin-top: 1.5rem; }

@media (max-width: 480px) {
  /* Zwei Spalten bleiben auch auf dem Telefon: eine einzelne Kachel je Zeile
     wäre kein Überblick mehr, sondern der Verlauf mit anderen Mitteln. */
  .gallery { gap: 0.75rem; grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 640px) {
  /* Auch die Bedienzeile wird zur Spalte: Umschalter oben, Suche darunter. */
  .chats-controls { flex-direction: column; align-items: stretch; }

  /* In der Spalte wirkt flex-basis auf die Hoehe: aus den 16rem der Zeile
     wuerde sonst ein 16rem hohes Suchfeld — mit der Lupe in seiner Mitte,
     weit unter dem Eingabefeld. Deshalb hier zurueck auf Inhaltshoehe. */
  .chats-search { max-width: none; flex: none; }

  /* Die gestreckte Leiste bekommt drei gleich breite Drittel — sonst
     klebten die Beschriftungen links und rechts bliebe leere Flaeche.
     Enger gesetzt als am Schreibtisch: „Gespeichert" ist das laengste der
     drei Woerter und passt bei 320 px sonst nicht neben die anderen. */
  .chats-view { display: flex; }
  .chats-view button { flex: 1; padding: 0 0.5rem; font-size: 0.75rem; }

  /* Auf schmalen Schirmen zaehlt der Titel: die Nachrichtenzahl faellt weg,
     die Zeitangabe bleibt. */
  .chats-row-msgs { display: none; }
}

/* Auf dem Telefon gibt es kein Hover — Umbenennen und Loeschen waeren
   ueberhaupt nicht erreichbar, weil der Tap den Verlauf oeffnet. shadcn
   haelt es in SidebarMenuAction genauso (md:opacity-0): unterhalb der
   Grenze stehen die Knoepfe einfach da. Sie liegen dann neben der Zeit
   statt darueber, sonst waere eins von beidem verdeckt.

   (hover: none) gehoert mit dazu: Ein iPad ist breiter als 640 px und hat
   trotzdem keinen Zeiger. Nur an der Breite festgemacht, blieben die Knoepfe
   dort unsichtbar und — wegen pointer-events: none — auch nicht antippbar. */
@media (max-width: 640px), (hover: none) {
  .chats-row-side {
    display: flex; align-items: center; gap: 0.5rem;
    min-width: 0;
  }
  .chats-row-side > * { grid-area: auto; }
  .chats-row-actions { opacity: 1; pointer-events: auto; }
  .chats-row:hover .chats-row-meta,
  .chats-row:focus-within .chats-row-meta,
  .chats-row:has(.row-menu-btn[aria-expanded="true"]) .chats-row-meta { opacity: 1; }
  /* Der Knopf ist hier keine Ergaenzung mehr, sondern die einzige Art, an
     die Aktionen zu kommen — also gleich in Trefferfeldgroesse. */
  .chats-row-actions .btn { width: 1.75rem; height: 1.75rem; }

  /* Dasselbe an den Ordner-Abschnitten: ohne Hover kaeme man an Umbenennen
     und Loeschen gar nicht heran, weil der Tap den Abschnitt zuklappt. */
  .chats-group-acts { opacity: 1; pointer-events: auto; }
  .chats-group-acts .btn { width: 1.75rem; height: 1.75rem; }
}

/* ==========================================================================
   Statistics (statistics.html)

   Eine Auswertung, kein Dashboard-Framework: die Balken sind <div>s mit
   prozentualer Breite bzw. Höhe, die Tabellen sind echte <table>s. Damit
   bleibt die Seite ohne Build-Schritt und ohne Diagramm-Bibliothek — und
   sie druckt und kopiert sich wie Text.
   ========================================================================== */

.stats { display: flex; flex-direction: column; gap: 1.5rem; }

/* ---------- Werkzeugleiste ---------- */

.stats-toolbar {
  display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap;
}
.stats-toolbar .segmented { flex: none; }
/* Auf dem Telefon reicht die Reihe der Zeitraeume sonst ueber den Rand
   hinaus: im Adminbereich heisst die vierte Schaltflaeche „Vormonat“ statt
   „Alles“, und damit war die Leiste 361 px breit, wo 347 px Platz waren.
   Enger gesetzt passt sie; umbrechen darf sie zur Sicherheit trotzdem. */
@media (max-width: 40rem) {
  .stats-toolbar .segmented { flex-wrap: wrap; max-width: 100%; }
  .stats-toolbar .segmented button { padding: 0 0.625rem; font-size: 0.75rem; }
}
.stats-toolbar-end { display: flex; align-items: center; gap: 0.5rem; margin-left: auto; }
.stats-toolbar-label { font-size: 0.75rem; color: var(--muted-foreground); flex: none; }
.stats-select {
  height: 2rem; padding: 0 0.75rem; max-width: 14rem;
  font-size: 0.8125rem; border-radius: 999px;
  border: 1px solid var(--input); background: var(--background); color: var(--foreground);
}
.stats-select:focus { outline: none; border-color: var(--ring); box-shadow: 0 0 0 3px color-mix(in oklab, var(--ring) 30%, transparent); }

/* ---------- Kennzahlen ---------- */

.stats-cards {
  display: grid; gap: 1rem;
  grid-template-columns: repeat(4, minmax(0, 1fr));
}
.stat-card {
  display: flex; flex-direction: column; gap: 0.25rem;
  padding: 1.125rem 1.25rem;
}
.stat-label {
  font-size: 0.75rem; color: var(--muted-foreground);
  display: flex; align-items: center; gap: 0.375rem;
}
.stat-label svg { width: 0.8125rem; height: 0.8125rem; }
.stat-value {
  font-size: 1.375rem; font-weight: 600; letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums; line-height: 1.2;
}
.stat-sub { font-size: 0.75rem; color: var(--muted-foreground); font-variant-numeric: tabular-nums; }

/* ---------- Abschnitt als Karte ----------
   Seitlich durchgehend 1.25rem: Kopf, Körper, Fußnote und die äußeren
   Tabellenzellen stehen auf derselben Kante. Der Kopf trägt keine Linie mehr —
   die Karte ist ohne Rand, und ein Strich quer darüber wäre der einzige. */

.stats-panel { overflow: hidden; }
.stats-panel-head {
  display: flex; align-items: baseline; gap: 0.625rem; flex-wrap: wrap;
  padding: 1.125rem 1.25rem 0.5rem;
}
.stats-panel-title { font-size: 0.875rem; font-weight: 600; }
.stats-panel-sub { font-size: 0.75rem; color: var(--muted-foreground); }
.stats-panel-end { margin-left: auto; display: flex; align-items: center; gap: 0.5rem; }
.stats-panel-body { padding: 1rem 1.25rem 1.25rem; }
/* Fussnote unter einer Tabelle: was die Zahlen darüber NICHT sagen. Steht
   unter und nicht über der Tabelle — gelesen wird sie erst, wenn die Zahlen
   Fragen aufgeworfen haben. */
.stats-panel-note {
  margin: 0; padding: 0.875rem 1.25rem; border-top: 1px solid var(--border);
  font-size: 0.75rem; color: var(--muted-foreground); line-height: 1.5;
}

/* ---------- Balken pro Tag/Monat ---------- */

/* Der Platz oben gehört dem Badge (js/chart.js): über dem höchsten Balken
   muss die Zahl noch hinpassen. Die Höhe des Diagramms wächst um genau
   diesen Streifen, damit die Balken so hoch bleiben wie vorher. */
.stats-chart {
  position: relative;
  display: flex; align-items: flex-end; gap: 0.1875rem;
  height: 9.25rem; padding-top: 1.75rem;
}
.stats-bar {
  flex: 1 1 0; min-width: 0.125rem; height: 100%;
  display: flex; align-items: flex-end;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.stats-bar:hover, .stats-bar-active { background: var(--accent); }
/* Die Füllung: Höhe in Prozent des teuersten Balkens. Auch ein Tag mit einem
   Bruchteil eines Cents bekommt 2 px, sonst sähe er aus wie ein leerer Tag. */
.stats-bar span {
  display: block; width: 100%; min-height: 2px;
  border-radius: var(--radius-sm);
  background: var(--primary);
}
.stats-bar-empty span { background: var(--border); min-height: 2px; }
/* Die Zahl zum Balken. Sie liegt über dem Diagramm statt darin — sonst
   müsste sie zwischen zwei 3 px breite Säulen passen. Angeklickt bleibt sie
   stehen; gesetzt wird die Stelle in js/chart.js, weil erst dort feststeht,
   wie breit der Text geworden ist. */
.stats-badge {
  position: absolute; top: 0; left: 0; z-index: 1;
  pointer-events: none; white-space: nowrap;
  display: flex; align-items: baseline; gap: 0.375rem;
  padding: 0.1875rem 0.4375rem;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--popover); color: var(--popover-foreground);
  box-shadow: var(--shadow-sm);
  font-size: 0.75rem; font-variant-numeric: tabular-nums;
  opacity: 0; transition: opacity 0.1s ease;
}
.stats-badge strong { font-weight: 600; }
.stats-badge-sub { font-size: 0.6875rem; color: var(--muted-foreground); }
.stats-badge-on { opacity: 1; }
.stats-axis {
  display: flex; justify-content: space-between; gap: 1rem;
  margin-top: 0.5rem; font-size: 0.6875rem; color: var(--muted-foreground);
  font-variant-numeric: tabular-nums;
}

/* ---------- Tabellen ---------- */

.stats-scroll { overflow-x: auto; }
.stats-table {
  width: 100%; border-collapse: collapse;
  font-size: 0.8125rem; font-variant-numeric: tabular-nums;
}
.stats-table th, .stats-table td {
  padding: 0.5rem 0.75rem; text-align: left; vertical-align: middle;
  white-space: nowrap;
}
/* Die äußeren Zellen auf der Kante der Karte (siehe „Abschnitt als Karte"). */
.stats-table :is(th, td):first-child { padding-left: 1.25rem; }
.stats-table :is(th, td):last-child { padding-right: 1.25rem; }
.stats-table th {
  font-weight: 500; font-size: 0.75rem; color: var(--muted-foreground);
  border-bottom: 1px solid var(--border); background: var(--card);
}
.stats-table tbody tr + tr td { border-top: 1px solid var(--border); }
.stats-table tbody tr:hover { background: var(--accent); }
.stats-num { text-align: right !important; }
/* Chat-Titel und Modellname dürfen kürzen — der Rest der Zeile ist wichtiger. */
.stats-clip { max-width: 16rem; overflow: hidden; text-overflow: ellipsis; }
.stats-table a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.stats-table a:hover { color: var(--muted-foreground); }
.stats-muted { color: var(--muted-foreground); }

/* Anteil eines Modells an den Gesamtkosten — Balken hinter der Zahl. */
.stats-share { display: flex; align-items: center; gap: 0.5rem; min-width: 7rem; }
.stats-share-bar {
  flex: 1; height: 0.375rem; border-radius: 999px;
  background: var(--secondary); overflow: hidden;
}
.stats-share-bar span { display: block; height: 100%; background: var(--primary); border-radius: 999px; }
.stats-share-num { flex: none; width: 2.75rem; text-align: right; color: var(--muted-foreground); font-size: 0.75rem; }

/* Werkzeuge einer Anfrage — dieselben Pillen wie über der Antwort im Chat. */
.stats-tools { display: inline-flex; gap: 0.25rem; flex-wrap: wrap; }
.stats-tool {
  display: inline-flex; align-items: center; gap: 0.25rem;
  padding: 0.0625rem 0.375rem; border-radius: 999px;
  font-size: 0.6875rem; color: var(--muted-foreground);
  background: var(--secondary);
}
.stats-tool svg { width: 0.6875rem; height: 0.6875rem; }
.stats-tool-warn { color: var(--warning-foreground); background: color-mix(in oklab, var(--warning) 18%, transparent); }

.stats-foot {
  display: flex; align-items: center; justify-content: center; gap: 0.75rem;
  padding: 0.875rem 1.25rem; border-top: 1px solid var(--border);
  font-size: 0.75rem; color: var(--muted-foreground);
}

.stats-empty {
  padding: 3rem 1rem; text-align: center; color: var(--muted-foreground);
  font-size: 0.875rem;
}
.stats-error {
  display: flex; align-items: flex-start; gap: 0.5rem;
  padding: 0.75rem 0.875rem; border-radius: var(--radius-lg);
  border: 1px solid color-mix(in oklab, var(--destructive) 35%, var(--border));
  color: var(--destructive); font-size: 0.8125rem;
}
.stats-error svg { width: 1rem; height: 1rem; margin-top: 0.125rem; flex: none; }
/* Kein Fehler, aber ein Vorbehalt: die Seite zeigt etwas, das unvollständig
   ist. Gelb statt rot — nichts ist kaputt, es fehlt nur etwas. */
.stats-warn {
  border-color: color-mix(in oklab, var(--warning) 40%, var(--border));
  color: var(--warning-foreground);
  background: color-mix(in oklab, var(--warning) 10%, transparent);
}
.stats-warn svg { color: var(--warning); }

@media (max-width: 900px) {
  .stats-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  .stats-toolbar-end { margin-left: 0; width: 100%; }
  .stats-select { flex: 1; max-width: none; }
  .stats-chart { height: 7.25rem; }
  .stats-clip { max-width: 10rem; }
}

/* ==========================================================================
   Einstellungen (settings.html) — ebenso Info & Support und Superadmin

   Oben die Kategorien als waagerechte Leiste, darunter genau eine Kategorie.
   Bis 2026-10-16 stand das Untermenü links als Spalte; seit die Seiten oben
   ein Farbfeld tragen, steht die Navigation darunter und über dem Inhalt,
   den sie umschaltet. Zu viele Kategorien für die Breite: die Leiste scrollt.
   ========================================================================== */

.settings { display: flex; flex-direction: column; gap: 1.5rem; }

.settings-nav {
  display: flex; gap: 0.25rem;
  overflow-x: auto; scrollbar-width: none;
}
.settings-nav::-webkit-scrollbar { display: none; }
/* Pillen wie Knöpfe und Segmentknöpfe: rund, ruhend ohne Fläche. */
.settings-nav-item {
  display: flex; flex: none; align-items: center; gap: 0.5rem;
  height: 2.25rem; padding: 0 0.875rem;
  border: 0; border-radius: 999px;
  background: none; color: var(--muted-foreground);
  font: inherit; font-size: 0.875rem; text-align: left;
  cursor: pointer; white-space: nowrap;
  transition: background .12s, color .12s;
}
.settings-nav-item svg { width: 1rem; height: 1rem; flex: none; }
.settings-nav-item:hover { background: var(--accent); color: var(--accent-foreground); }
.settings-nav-item[aria-selected="true"] {
  background: var(--secondary); color: var(--foreground); font-weight: 500;
}

.settings-panes { display: grid; gap: 1rem; min-width: 0; }
.settings-pane { display: grid; gap: 1.25rem; animation: fade-in .15s ease-out; }
.settings-pane[hidden] { display: none; }

.settings-pane-head { display: grid; gap: 0.3125rem; }
.settings-pane-title { font-size: 1.125rem; font-weight: 600; }
.settings-pane-desc { font-size: 0.8125rem; color: var(--muted-foreground); line-height: 1.55; }

.settings-card { padding: 1.5rem; }
.settings-card-head { display: grid; gap: 0.25rem; margin-bottom: 1rem; }
.settings-card-title { font-size: 0.875rem; font-weight: 600; }
.settings-card-desc { font-size: 0.75rem; color: var(--muted-foreground); line-height: 1.5; }
/* Karten ohne eigene Überschrift beginnen direkt mit dem ersten Feld. */
.settings-card > .field:first-child { margin-top: 0; }

/* Wer über „#antwortlaenge“ aus dem Chat hierherkommt, landet mitten in einer
   langen Seite voller gleich aussehender Karten. Ein paar Sekunden Rahmen
   zeigen, welche gemeint war; danach ist es wieder eine Karte wie jede
   andere. Ohne Bewegung, das Springen hat schon stattgefunden. */
.settings-card-marked {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--primary) 18%, transparent);
  transition: border-color .2s, box-shadow .2s;
}

/* Nur-Lese-Werte: links wofür, rechts was. */
.settings-rows { display: grid; gap: 0.6875rem; }
.settings-row {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 1rem; font-size: 0.8125rem;
}
.settings-row dt { color: var(--muted-foreground); }
.settings-row dd { font-weight: 500; text-align: right; min-width: 0; overflow-wrap: anywhere; }
.settings-row-strong dd { font-size: 1rem; }

.settings-actions {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem;
  margin-top: 1rem;
}
.settings-actions-end { margin-left: auto; }
.settings-saved { font-size: 0.75rem; color: var(--muted-foreground); }

/* Zeigt die eingestellte Chatbreite maßstäblich im verfügbaren Platz. */
.width-preview {
  height: 2.75rem; margin-top: 0.75rem; padding: 0.375rem;
  border: 1px dashed var(--border); border-radius: var(--radius-md);
  background: var(--muted);
  display: flex; align-items: stretch; justify-content: center;
}
.width-preview span {
  display: block; width: 100%; max-width: var(--preview-width, 100%);
  border-radius: var(--radius-sm);
  background: color-mix(in oklab, var(--primary) 22%, transparent);
  transition: max-width .12s ease-out;
}

/* Zahlenfeld neben dem Regler — schmal, damit es nicht wie ein Textfeld wirkt. */
.slider-row { display: flex; align-items: center; gap: 0.75rem; }
.slider-row .range { flex: 1; min-width: 0; }
.slider-number { width: 6rem; flex: none; text-align: right; }
.slider-number:disabled { opacity: .5; }

@media (max-width: 640px) {
  .settings-actions-end { margin-left: 0; }
  .settings-actions .btn { flex: 1 1 auto; }
}

/* ==========================================================================
   Nachrichten
   ========================================================================== */

.messages {
  flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain;

}
/* --chat-width setzt js/ui.js aus den Einstellungen (Allgemein → Breite des
   Chat-Fensters). Ohne die Variable bleibt es bei den 48rem von früher. */
.messages-inner {
  max-width: var(--chat-width, 48rem); margin: 0 auto;
  padding: 1.5rem 1rem 0.5rem;
  display: flex; flex-direction: column; gap: 1.5rem;
}

.msg { display: flex; flex-direction: column; gap: 0.375rem; animation: msg-in .2s ease-out; }
.msg-user { align-items: flex-end; }

.bubble {
  max-width: 85%;
  padding: 0.625rem 0.875rem;
  border-radius: var(--radius-xl);
  background: var(--secondary);
  color: var(--secondary-foreground);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  line-height: 1.6;
}

.msg-assistant { gap: 0.5rem; }
.msg-head {
  display: flex; align-items: center; gap: 0.5rem;
  font-size: 0.75rem; font-weight: 500; color: var(--muted-foreground);
}
.msg-avatar {
  display: grid; place-items: center;
  width: 1.5rem; height: 1.5rem; flex: none;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--card); color: var(--foreground);
  --logo-bg: var(--card);
}
.msg-avatar svg { width: 0.875rem; height: 0.875rem; }

.msg-actions { display: flex; gap: 0.125rem; opacity: 0; transition: opacity .15s; }
.msg:hover .msg-actions, .msg:focus-within .msg-actions { opacity: 1; }
.msg-actions .btn { height: 1.75rem; width: 1.75rem; color: var(--muted-foreground); }
.msg-actions .btn svg { width: 0.8125rem; height: 0.8125rem; }
/* Unter der Antwort, wie unter der eigenen Nachricht — dort aber links. Um
   den Innenrand des Knopfs nach links geschoben, damit das erste Icon an der
   Textkante steht und nicht 7 px eingerueckt. */
.msg-assistant .msg-actions { margin-left: -0.46875rem; }
.msg-meta { font-size: 0.6875rem; color: var(--muted-foreground); font-variant-numeric: tabular-nums; }

.msg-error {
  border: 1px solid color-mix(in oklab, var(--destructive) 40%, var(--border));
  background: color-mix(in oklab, var(--destructive) 8%, transparent);
  color: var(--destructive);
  border-radius: var(--radius-lg);
  padding: 0.75rem 0.875rem;
  display: flex; gap: 0.625rem; align-items: flex-start;
  font-size: 0.8125rem; line-height: 1.55;
}
.msg-error svg { width: 1rem; height: 1rem; margin-top: 0.125rem; }
/* Ablehnung aus Plan und Guthaben: unter dem Satz der Knopf, der weiterhilft
   (Plan.errorAction()). */
.msg-error-body { display: grid; gap: 0.625rem; justify-items: start; }

/* Hinweis, der die Antwort nicht entwertet, aber Aufmerksamkeit braucht. */
.msg-note {
  border: 1px solid color-mix(in oklab, var(--warning) 45%, var(--border));
  background: color-mix(in oklab, var(--warning) 10%, transparent);
  color: var(--warning-foreground);
  border-radius: var(--radius-lg);
  padding: 0.75rem 0.875rem;
  display: flex; gap: 0.625rem; align-items: flex-start;
  font-size: 0.8125rem; line-height: 1.55;
}
.msg-note svg { width: 1rem; height: 1rem; margin-top: 0.125rem; color: var(--warning); }

/* Abbruch an der Token-Grenze. Aufgebaut wie .msg-note, aber mit Zeilen
   untereinander statt Symbol neben Text: Hier stehen drei Dinge — was los
   ist, was man tun kann, und wie man es dauerhaft abstellt. */
.msg-cut {
  border: 1px solid color-mix(in oklab, var(--warning) 45%, var(--border));
  background: color-mix(in oklab, var(--warning) 10%, transparent);
  border-radius: var(--radius-lg);
  padding: 0.75rem 0.875rem;
  display: flex; flex-direction: column; gap: 0.5rem;
  font-size: 0.8125rem; line-height: 1.55;
}
.msg-cut-head {
  display: flex; gap: 0.625rem; align-items: flex-start;
  color: var(--warning-foreground); font-weight: 600;
}
.msg-cut-head svg {
  width: 1rem; height: 1rem; margin-top: 0.125rem;
  flex: none; color: var(--warning);
}
.msg-cut-text { margin: 0; color: var(--warning-foreground); }
.msg-cut-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.125rem; }
/* Die Anleitung ist Kleingedrucktes: gelesen wird sie nur, wenn die beiden
   Knöpfe darüber nicht schon gereicht haben. */
.msg-cut-how {
  margin: 0; padding-top: 0.5rem;
  border-top: 1px solid color-mix(in oklab, var(--warning) 30%, transparent);
  color: var(--muted-foreground); font-size: 0.75rem; line-height: 1.5;
}
.msg-cut-how b { color: var(--foreground); font-weight: 600; }

/* Kennzeichnung in der Kopfzeile, welche Werkzeuge die Antwort benutzt hat. */
.msg-tags { display: inline-flex; gap: 0.25rem; }
.msg-tag {
  display: inline-flex; align-items: center; gap: 0.25rem;
  height: 1.25rem; padding: 0 0.375rem;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--muted);
  font-size: 0.625rem; font-weight: 500; color: var(--muted-foreground);
}
.msg-tag svg { width: 0.6875rem; height: 0.6875rem; }

/* Gedankengang — standardmäßig zugeklappt, beim Streamen offen. */
.reasoning {
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--muted);
}
.reasoning > summary {
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.5rem 0.75rem; cursor: pointer;
  font-size: 0.75rem; font-weight: 500; color: var(--muted-foreground);
  list-style: none;
}
.reasoning > summary::-webkit-details-marker { display: none; }
.reasoning > summary svg { width: 0.875rem; height: 0.875rem; flex: none; }
.reasoning > summary:hover { color: var(--foreground); }
.reasoning-chevron { margin-left: auto; transition: transform .15s; }
.reasoning[open] .reasoning-chevron { transform: rotate(180deg); }
.reasoning-body {
  padding: 0 0.75rem 0.625rem;
  font-size: 0.8125rem; color: var(--muted-foreground);
  max-height: 22rem; overflow-y: auto; overscroll-behavior: contain;
}
.reasoning-body > :first-child { margin-top: 0; }

/* Quellen, die die Web-Suche gelesen hat. */
.msg-sources { display: flex; flex-direction: column; gap: 0.25rem; }
.msg-sources-head {
  display: flex; align-items: center; gap: 0.375rem;
  font-size: 0.6875rem; font-weight: 500; color: var(--muted-foreground);
}
.msg-sources-head svg { width: 0.75rem; height: 0.75rem; }
.msg-sources-list { display: flex; flex-wrap: wrap; gap: 0.375rem; }
.msg-source {
  display: inline-flex; align-items: baseline; gap: 0.375rem;
  max-width: 22rem; padding: 0.25rem 0.5rem;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--card); font-size: 0.75rem; color: var(--foreground);
  text-decoration: none; transition: background-color .15s;
}
.msg-source:hover { background: var(--accent); }
.msg-source-n { color: var(--muted-foreground); font-size: 0.6875rem; flex: none; }
.msg-source-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.msg-source-host { color: var(--muted-foreground); font-size: 0.6875rem; flex: none; }

/* Tippindikator */
.typing { display: inline-flex; gap: 0.25rem; align-items: center; height: 1.5rem; }
.typing i {
  width: 0.375rem; height: 0.375rem; border-radius: 999px;
  background: var(--muted-foreground); animation: blink 1.2s infinite ease-in-out;
}
.typing i:nth-child(2) { animation-delay: .2s; }
.typing i:nth-child(3) { animation-delay: .4s; }

/* Ein Knopf, der auf Stripe wartet (DESIGN.md, „Warten auf Stripe"):
   gesperrt, aber nicht verblasst — halb durchsichtig sähe er aus wie „geht
   gerade nicht". Die Punkte nehmen die Schriftfarbe des Knopfes. */
.btn.is-busy:disabled { opacity: 1; }
.btn .typing { height: auto; gap: 0.1875rem; }
.btn .typing i { width: 0.3125rem; height: 0.3125rem; background: currentColor; }

.caret::after {
  content: ""; display: inline-block; vertical-align: text-bottom;
  width: 0.5rem; height: 1em; margin-left: 0.125rem;
  background: var(--foreground); animation: blink 1s steps(2) infinite;
}

/* Leerzustand */
/* grid-template-columns: minmax(0, 1fr) sieht überflüssig aus, ist es aber
   nicht: ohne Angabe bemisst sich die Spalte am Inhalt, und ein Kind mit
   width: 100% hat dann keinen festen Bezug — die Breite ergibt sich aus dem
   breitesten Element statt aus dem Platz. Genau daran hingen die Vorschläge
   unten: zwei Kacheln standen untereinander, drei nebeneinander. */
.empty {
  display: grid; place-items: center; grid-template-columns: minmax(0, 1fr);
  min-height: 100%; padding: 2rem 1rem 4rem; text-align: center;
}
/* minmax(0, 1fr) aus demselben Grund wie eine Zeile höher: erst eine
   bestimmte Spalte gibt einem max-width: 100% darin etwas zum Rechnen. Ohne
   sie verhält sich die Prozentangabe wie none, und ein langer Einstieg
   schiebt seine Kachel aus dem Block heraus. */
.empty-inner {
  width: 100%; max-width: 34rem;
  display: grid; grid-template-columns: minmax(0, 1fr);
  gap: 0.5rem; justify-items: center;
}
.empty-icon {
  display: grid; place-items: center; width: 2.75rem; height: 2.75rem;
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--card); box-shadow: var(--shadow-xs); margin-bottom: 0.5rem;
  --logo-bg: var(--card);
}
.empty-icon svg { width: 1.5rem; height: 1.5rem; }
.empty h1 { font-size: 1.375rem; font-weight: 600; letter-spacing: -0.01em; }
.empty p { color: var(--muted-foreground); }
.suggestions { display: grid; gap: 0.75rem; width: 100%; margin-top: 1.25rem; grid-template-columns: 1fr 1fr; }

/* Die Einstiege eines Assistenten oder Flows. Anders als die vier festen
   Vorschläge auf chat.html sind es null bis drei, und ihr Text ist eine
   Zeile statt Überschrift plus Erklärung. Zwei Dinge sollen deshalb
   gleichzeitig gelten: eine Kachel ist nicht breiter als ihr Text, und zwei
   nebeneinander sind gleich breit.

   Das leistet ein Raster aus zwei gleichen Spalten in einem Container, der
   sich auf max-content zusammenzieht: fr-Spalten sind untereinander gleich
   und mindestens so breit wie ihr breitester Inhalt, und weil der Container
   nur so breit wird wie das Raster mindestens braucht, bleibt am Text nichts
   Überflüssiges hängen. Mit Flexbox geht beides zusammen nicht — dort wächst
   eine Kachel entweder über ihren Text hinaus (flex-grow) oder die beiden
   werden unterschiedlich breit (flex-basis: auto).

   Einer allein und der ungerade Letzte laufen über beide Spalten und stehen
   mittig; sonst klebten sie links. */
.suggestions-center {
  display: grid; grid-template-columns: repeat(2, 1fr);
  width: max-content; max-width: 100%;
  margin-inline: auto;
}
.suggestions-center .suggestion { min-width: 0; }
.suggestions-center .suggestion:only-child { grid-column: 1 / -1; }
.suggestions-center .suggestion:last-child:nth-child(odd):not(:only-child) {
  grid-column: 1 / -1; justify-self: center;
}

.suggestion {
  text-align: left; padding: 0.875rem 1.125rem;
  font-size: 0.8125rem; line-height: 1.45;
  transition: background-color .15s;
}
.suggestion b { display: block; font-weight: 500; margin-bottom: 0.125rem; }
.suggestion span { color: var(--muted-foreground); }

/* ==========================================================================
   Markdown
   ========================================================================== */

.md { line-height: 1.65; overflow-wrap: anywhere; }
.md > * + * { margin-top: 0.875rem; }
.md h1, .md h2, .md h3, .md h4 { font-weight: 600; line-height: 1.3; }
.md h1 { font-size: 1.25rem; } .md h2 { font-size: 1.125rem; }
.md h3 { font-size: 1rem; } .md h4 { font-size: 0.9375rem; }
.md > h1, .md > h2, .md > h3 { margin-top: 1.5rem; }
.md ul, .md ol { padding-left: 1.375rem; }
.md li + li { margin-top: 0.25rem; }
.md ul { list-style: disc; } .md ol { list-style: decimal; }
.md li::marker { color: var(--muted-foreground); }
.md a { color: var(--foreground); text-decoration: underline; text-underline-offset: 2px; }
.md a:hover { color: var(--muted-foreground); }
.md strong { font-weight: 600; }
.md blockquote {
  border-left: 2px solid var(--border); padding-left: 0.875rem;
  color: var(--muted-foreground); font-style: italic;
}
.md hr { border: 0; border-top: 1px solid var(--border); }
.md code {
  font-family: var(--font-mono); font-size: 0.8125em;
  background: var(--muted); padding: 0.125rem 0.3125rem;
  border-radius: var(--radius-sm); overflow-wrap: break-word;
}
/* Die Tabelle bleibt eine Tabelle; das Scrollen uebernimmt .md-table um sie
   herum (siehe js/markdown.js). width: 100% haelt sie auf voller Breite,
   solange sie hineinpasst. Damit die Spalten dabei nicht zu Buchstabensaeulen
   zerdrueckt werden, hat jede Zelle eine Mindestbreite: passen sie zusammen
   nicht mehr hinein, waechst die Tabelle darueber hinaus und die Huelle
   schiebt.
   Den Aussenrand und die runden Ecken traegt ebenfalls die Huelle, wie beim
   .code-block: border-radius an einer Tabelle mit border-collapse rundet
   nichts. overflow-x: auto schneidet die Zellen an den Ecken ab, und die
   Zellen verzichten auf ihre aeusseren Linien — sonst laege dort ein
   doppelter Rand. */
.md-table {
  overflow-x: auto;
  border: 1px solid var(--border); border-radius: var(--radius-lg);
}
.md table { width: 100%; border-collapse: collapse; font-size: 0.8125rem; }
.md th, .md td {
  border: 1px solid var(--border); padding: 0.375rem 0.625rem;
  text-align: left; min-width: 5rem;
}
.md tr > :first-child { border-left: 0; }
.md tr > :last-child { border-right: 0; }
.md thead th { border-top: 0; }
.md tbody tr:last-child td { border-bottom: 0; }
.md th { background: var(--muted); font-weight: 600; }

/* „Kopieren" laeuft beim Scrollen mit (sticky) und bleibt oben im Verlauf
   stehen, solange vom Block noch etwas zu sehen ist — bei langem Code musste
   man sonst erst wieder an dessen Anfang zurueck. Der Kopf mit der Sprache
   bleibt, wo er ist; mitlaufen soll nur der Knopf.

   Deshalb steht der Knopf nicht im Kopf, sondern davor (js/markdown.js),
   rechts angefloatet: sticky klebt nur innerhalb des Elternelements, und
   der Float haelt den Knopf unten von selbst im Block — er rutscht nicht
   ueber dessen Rand hinaus. Der Kopf ist dafuer ein gewoehnlicher Block und
   kein Flex-Container: ein Flex-Container wiche dem Float aus, und rechts
   fehlte dann seine Flaeche unter dem Knopf.

   Kein overflow: hidden an der Huelle: das machte sie zum Scrollbehaelter,
   und sticky klebte an ihr statt am Verlauf. Die oberen Ecken rundet der
   Kopf selbst; pre hat keinen Grund, der unten ueber den Rand ragen koennte,
   und scrollt quer in sich.

   Der Knopf traegt --muted als festen Grund: ueber dem Code liegt er sonst
   durchsichtig auf dem Text. Der Hover ist der des Ghost-Knopfs, nur ueber
   diesen Grund gelegt statt an seiner Stelle. */
.code-block {
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--card);
}
.code-head {
  height: calc(2.25rem + 1px); line-height: 2.25rem;
  padding: 0 0.75rem;
  border-radius: calc(var(--radius-lg) - 1px) calc(var(--radius-lg) - 1px) 0 0;
  border-bottom: 1px solid var(--border);
  background: var(--muted);
  font-family: var(--font-mono); font-size: 0.6875rem;
  color: var(--muted-foreground);
}
.code-block .copy-code {
  float: right; position: sticky; top: 0.25rem; z-index: 1;
  margin: 0.25rem 0.25rem 0 0;
  height: 1.75rem; padding: 0 0.5rem; font-size: 0.6875rem; gap: 0.3125rem;
  color: var(--muted-foreground); background: var(--muted);
}
.code-block .copy-code:hover {
  background: linear-gradient(color-mix(in oklab, var(--foreground) var(--tint-1), transparent),
                              color-mix(in oklab, var(--foreground) var(--tint-1), transparent)) var(--muted);
  color: var(--foreground);
}
.code-block .copy-code:active {
  background: linear-gradient(color-mix(in oklab, var(--foreground) var(--tint-2), transparent),
                              color-mix(in oklab, var(--foreground) var(--tint-2), transparent)) var(--muted);
}
.code-block .copy-code svg { width: 0.75rem; height: 0.75rem; }
.code-block pre { margin: 0; padding: 0.875rem; overflow-x: auto; }
.code-block code {
  background: none; padding: 0; font-size: 0.8125rem; line-height: 1.6;
  font-family: var(--font-mono); white-space: pre;
}

/* ==========================================================================
   Composer
   ========================================================================== */

.composer-wrap {
  flex: none;
  padding: 0.25rem 1rem calc(0.75rem + env(safe-area-inset-bottom));
  background: linear-gradient(to bottom, transparent, var(--background) 35%);
}
.composer {
  position: relative;
  max-width: var(--chat-width, 48rem); margin: 0 auto;
  display: flex; flex-direction: column; gap: 0.375rem;
  padding: 0.5rem 0.5rem 0.4375rem;
  border: 1px solid var(--input); border-radius: var(--radius-xl);
  background: var(--background); box-shadow: var(--shadow-sm);
  transition: border-color .15s, box-shadow .15s;
}
.composer:focus-within {
  border-color: var(--ring);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--ring) 30%, transparent);
}
/* Eingabezeile: Text links, Senden oben rechts auf Höhe der ersten Zeile. */
.composer-main { display: flex; align-items: flex-start; gap: 0.5rem; }
.composer-input {
  flex: 1; min-width: 0;
  border: 0; background: none; outline: none; resize: none;
  padding: 0.25rem 0.375rem;
  max-height: 12.5rem; line-height: 1.55; font-size: 0.9375rem;
}
.composer-input::placeholder { color: var(--muted-foreground); }

/* Werkzeugleiste unter dem Eingabefeld */
.composer-bar { display: flex; align-items: center; gap: 0.5rem; }
.composer-tools { display: flex; align-items: center; gap: 0.25rem; min-width: 0; }
.composer-end { display: flex; align-items: center; gap: 0.5rem; margin-left: auto; min-width: 0; }

.composer-tool {
  display: inline-flex; align-items: center; gap: 0.3125rem;
  height: 2rem; padding: 0 0.5rem;
  border-radius: 999px;
  font-size: 0.8125rem; font-weight: 500;
  color: var(--muted-foreground);
  transition: background-color .15s, color .15s;
}
.composer-tool:hover { background: var(--accent); color: var(--foreground); }
.composer-tool svg { width: 1rem; height: 1rem; }
/* Icon-Knöpfe sind quadratisch — sie dürfen nie schrumpfen. Nachgeben soll
   der Modellname (er hat Ellipse), sonst wird aus dem Knopf ein Strich. */
.composer-tool-icon { width: 2rem; padding: 0; justify-content: center; flex: none; }
.composer-tool[aria-expanded="true"] { background: var(--accent); color: var(--foreground); }

/* min-width: 0 ist hier nicht Kosmetik: ohne das ist die Mindestbreite des
   Knopfes seine Inhaltsbreite, und die ist bei white-space: nowrap der ganze
   Name, gekappt erst durch max-width. Er gab dann nichts mehr her, und die
   Reihe schob die Pille daneben („0 Schritte“) auf schmalen Telefonen aus
   dem Eingabefeld heraus. Mit min-width: 0 kuerzt stattdessen der Name. */
.composer-model { min-width: 0; max-width: min(48vw, 16rem); padding-right: 0.375rem; }
.composer-model-icon, .composer-model-chevron { flex: none; }
.composer-model-icon svg { width: 0.875rem; height: 0.875rem; }
.composer-model-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.composer-model-chevron svg { width: 0.8125rem; height: 0.8125rem; opacity: .7; }

/* Aktive Werkzeuge (Web-Suche, Thinking) als Pille neben dem Modell. */
.composer-modes { display: flex; align-items: center; gap: 0.25rem; flex: none; }
.composer-mode {
  display: inline-flex; align-items: center; gap: 0.3125rem; flex: none;
  height: 1.75rem; padding: 0 0.5rem 0 0.4375rem;
  border: 1px solid color-mix(in oklab, var(--primary) 35%, var(--border));
  border-radius: 999px;
  background: color-mix(in oklab, var(--primary) 12%, transparent);
  color: var(--primary);
  font-size: 0.75rem; font-weight: 500; white-space: nowrap;
  transition: background-color .15s;
}
.composer-mode:hover { background: color-mix(in oklab, var(--primary) 20%, transparent); }
.composer-mode svg { width: 0.8125rem; height: 0.8125rem; }
.composer-mode-level { font-size: 0.6875rem; opacity: .75; }
/* Auf schmalen Geräten bleibt nur Icon + Name; die Stufe steht im Tooltip
   und im Plus-Menü. Sonst bliebe für den Modellnamen nichts mehr übrig. */
@media (max-width: 30rem) {
  .composer-mode-level { display: none; }
  .composer-mode { padding: 0 0.4375rem; }
}

/* ---------- Assistenten-Chat ----------
   Auf assistant-chat.html steht an der Stelle des Modells der Assistent, und
   die Werkzeuge daneben sind nur noch Anzeige: sie kommen vom Assistenten und
   werden auf seiner Seite verstellt, nicht hier. Darum dieselben Pillen wie
   im Chat, aber ohne Hover und ohne Klickfläche. */
.composer-mode-static { cursor: default; }
.composer-mode-static:hover { background: color-mix(in oklab, var(--primary) 12%, transparent); }

/* Das Icon des Assistenten im Knopf — in seiner Farbe statt in der des Textes. */
.composer-assistant-icon { color: var(--lib-color, currentColor); }
.composer-assistant-icon svg { width: 0.9375rem; height: 0.9375rem; }

/* Der leere Assistenten-Chat: das Icon des Assistenten statt des Logos.
   Verlauf und weisses Zeichen kommen aus dem Block bei .lib-icon. */
.empty-icon-lib { border: 0; box-shadow: none; }

/* Steht statt der Eingabeleiste da, solange es keinen Assistenten gibt. */
.composer-blocked {
  max-width: var(--chat-width, 48rem); margin: 0 auto;
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.875rem 1rem;
  border: 1px dashed var(--border); border-radius: var(--radius-xl);
  background: var(--card); color: var(--muted-foreground); font-size: 0.8125rem;
}
.composer-blocked svg { width: 1rem; height: 1rem; flex: none; }
.composer-blocked .btn { margin-left: auto; flex: none; }

.composer-price {
  font-size: 0.625rem; font-family: var(--font-sans);
  color: var(--muted-foreground); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.composer-price:empty { display: none; }

/* Der Block rechts in der Werkzeugleiste: Preis, auf assistant-chat.html mit
   dem Modellnamen darüber. Das Modell steht dort nicht links im Knopf — den
   hat der Assistent —, deshalb hier.

   Die min-height hält die Modellzeile auch auf chat.html und image.html frei,
   wo sie fehlt: zusammen mit flex-end sitzt der Preis dadurch auf allen drei
   Seiten an derselben Stelle statt einmal mittig und zweimal darunter. Sie ist
   deshalb genau die Höhe beider Zeilen — gerechnet statt gerundet, sonst
   versetzte die Differenz den Preis um ein Zehntelpixel gegen die Referenz.
   Rund 1.5rem bleiben unter den 2rem der Werkzeugknöpfe daneben: die Leiste
   wird davon nicht höher, alles bleibt auf einer Linie mit dem Knopf links. */
.composer-meta {
  display: flex; flex-direction: column;
  align-items: flex-end; justify-content: flex-end;
  min-height: calc((0.6875rem + 0.625rem) * 1.15);
  min-width: 0; line-height: 1.15;
}
.composer-meta-model {
  max-width: 100%;
  font-size: 0.6875rem; font-weight: 500;
  color: var(--muted-foreground);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.composer-meta-model:empty { display: none; }

.composer-send { border-radius: 999px; width: 2rem; height: 2rem; flex: none; }
.composer-send svg { width: 1rem; height: 1rem; }

/* Anhang, der noch gelesen wird (PDFs brauchen einen Moment). */
.chip-pending { opacity: .7; }
.chip-pending .chip-name { font-style: italic; }

.composer-hint {
  max-width: var(--chat-width, 48rem); margin: 0.5rem auto 0;
  text-align: center; font-size: 0.6875rem; color: var(--muted-foreground);
}

/* Menü hinter dem Plus */
.menu-anchor { position: relative; display: inline-flex; }
.menu {
  position: absolute; bottom: calc(100% + 0.5rem); left: 0; z-index: 20;
  /* min() im min-width: ein festes Minimum schlägt max-width, und 18.5rem
     sind auf einem 320-px-Telefon breiter als das, was max-width zulässt. */
  min-width: min(18.5rem, calc(100vw - 3rem)); max-width: calc(100vw - 3rem); padding: 0.25rem;
  /* Die Höhe begrenzt ChatCore.fitMenu() beim Öffnen auf den Platz über dem
     Knopf; was darüber hinausgeht, rollt hier. */
  overflow-y: auto; overscroll-behavior: contain;
  background: var(--popover); color: var(--popover-foreground);
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  animation: dialog-in .14s cubic-bezier(.16,1,.3,1);
}

/* Nach unten statt nach oben (Modusauswahl in der Kopfzeile).

   `bottom: auto` ist hier kein Beiwerk: stünden top und bottom gleichzeitig,
   spannte sich die Box zwischen beide Kanten und wäre neun Pixel hoch — die
   Einträge hingen darunter im Freien. Und weil beides Klassen mit gleichem
   Gewicht sind, entscheidet die Reihenfolge: diese Regel muss hinter .menu
   stehen, nicht bei den Stilen der Kopfzeile. */
.menu-below {
  top: calc(100% + 0.375rem); bottom: auto;
  /* Zwei gleichrangige Einträge ohne Trennlinie dazwischen: der Abstand ist
     hier die Trennung. Dasselbe Maß nimmt das Kontomenü (siehe
     `.account-menu` in css/auth.css); dicht stehen die Einträge nur noch im
     Plus-Menü. */
  display: grid; gap: 0.25rem;
}

.menu-item {
  display: flex; align-items: flex-start; gap: 0.625rem; width: 100%;
  padding: 0.5rem 0.625rem; border-radius: var(--radius-md);
  text-align: left; transition: background-color .1s;
  /* Ein Eintrag ist mal <button>, mal <a>. Im Kontomenü fiel das nie auf,
     weil die Seitenleiste ihre Verweise ohnehin entfärbt — die Modusauswahl
     in der Kopfzeile steht außerhalb davon und sähe sonst aus wie ein
     Fließtext-Link. */
  color: inherit; text-decoration: none;
}
.menu-item:hover:not(:disabled) { background: var(--accent); color: var(--accent-foreground); }
.menu-item:disabled { opacity: .5; cursor: not-allowed; }
/* Die Seite, auf der man gerade ist — im Kontomenü genauso markiert wie in
   der Navigation darüber. */
.menu-item.active { background: var(--secondary); }
/* Das Icon trägt die Farbe der Schrift (currentColor), in allen Zuständen —
   genau wie in der Navigation der Seitenleiste. */
.menu-item svg { width: 1rem; height: 1rem; margin-top: 0.125rem; color: currentColor; }
.menu-item-text { display: block; flex: 1; min-width: 0; }
.menu-item-text b { display: block; font-size: 0.8125rem; font-weight: 500; }
.menu-item-text small { display: block; font-size: 0.6875rem; color: var(--muted-foreground); margin-top: 0.0625rem; }

/* Gefährliche Einträge — Abmelden, Löschen, Konto löschen: in Ruhe wie jeder
   Eintrag, unter dem Zeiger und im Tastaturfokus rot auf --destructive zu
   12 % — dieselbe Regel wie bei .btn-danger.

   Die verdoppelte Klasse ist Absicht. Der graue Hover oben
   (.menu-item:hover:not(:disabled)) wiegt drei Klassen, der Fokus im
   Kontomenü (.account-menu .menu-item:focus-visible, css/auth.css, lädt
   später) ebenso — ein schlichtes .menu-item-danger:hover verlor gegen
   beide, und Abmelden wurde beim Überfahren grau. Schrift und Icon erben
   die Farbe; b und svg ausdrücklich, weil sie sonst ihre eigene behalten. */
.menu-item.menu-item-danger:hover:not(:disabled),
.menu-item.menu-item-danger:focus-visible:not(:disabled) {
  background: color-mix(in oklab, var(--destructive) 12%, transparent);
  color: var(--destructive);
}
.menu-item.menu-item-danger:hover:not(:disabled) b,
.menu-item.menu-item-danger:focus-visible:not(:disabled) b,
.menu-item.menu-item-danger:hover:not(:disabled) > [data-icon]:first-child svg,
.menu-item.menu-item-danger:focus-visible:not(:disabled) > [data-icon]:first-child svg { color: var(--destructive); }

/* Umschaltbare Einträge (Web-Suche, Thinking): Haken nur, wenn aktiv. */
.menu-item-check { margin-left: auto; align-self: center; color: var(--primary); opacity: 0; flex: none; }
.menu-item[aria-checked="true"] .menu-item-check { opacity: 1; }
.menu-item-check svg { color: inherit; margin-top: 0; }

.menu-sep { height: 1px; margin: 0.25rem 0.25rem; background: var(--border); }

/* Das Zeilenmenü hinter dem Drei-Punkte-Knopf (js/row-menu.js) — eines für
   alle Listen und Tabellen. Es hängt nicht in der Zeile, sondern an <body>,
   und steht deshalb `fixed` statt `absolute`: Listen und Tabellen scrollen
   in eigenen Kästen, und ein Kasten mit overflow schneidet auch senkrecht ab
   — overflow-y zählt neben einem gesetzten overflow-x nicht mehr als
   `visible`. Ein Menü in der Zeile endete an der Zeilenunterkante. Die
   Koordinaten setzt js/row-menu.js aus der Lage des Knopfes.

   z-index 46: über der Seitenleiste als Schublade (40) und der Ablage (45),
   aus denen es aufgeht, unter den Dialogen (50), die ein Eintrag öffnet.

   Schmaler als die 18.5 rem von `.menu`: meist steht nur ein Wort je
   Eintrag da („Umbenennen"). Wo ein Satz darunter steht (Kontotabelle im
   Adminbereich), wächst es bis 22 rem mit. */
.row-menu {
  position: fixed; top: 0; left: 0; bottom: auto; z-index: 46;
  min-width: 11rem; max-width: min(22rem, calc(100vw - 2rem));
  display: grid; gap: 0.125rem;
}
/* [hidden] verliert sonst gegen das display: grid darüber. */
.row-menu[hidden] { display: none; }
/* Der Abstand um die Trennlinie kommt aus dem gap. */
.row-menu .menu-sep { margin-block: 0.125rem; }
.row-menu .menu-item:focus-visible {
  outline: none; background: var(--accent); color: var(--accent-foreground);
}

/* Stufenwahl unter einem aktiven Eintrag — bricht um, damit alle sechs
   Aufwandsstufen ins Menü passen. */
.menu-levels {
  display: flex; flex-wrap: wrap; gap: 0.25rem;
  padding: 0.0625rem 0.625rem 0.4375rem 2.25rem;
}
.menu-level {
  height: 1.5rem; padding: 0 0.5rem;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--background); color: var(--muted-foreground);
  font-size: 0.6875rem; font-weight: 500;
  transition: background-color .12s, color .12s, border-color .12s;
}
.menu-level:hover { background: var(--accent); color: var(--foreground); }
.menu-level[aria-checked="true"] {
  background: var(--primary); border-color: var(--primary); color: var(--primary-foreground);
}

/* Anhänge über dem Eingabefeld */
.attachments { display: flex; flex-wrap: wrap; gap: 0.375rem; padding: 0.125rem 0.25rem 0.125rem; }
.chip {
  display: inline-flex; align-items: center; gap: 0.375rem;
  max-width: 14rem; height: 1.75rem; padding: 0 0.25rem 0 0.5rem;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--muted); font-size: 0.75rem;
}
.chip svg { width: 0.8125rem; height: 0.8125rem; color: var(--muted-foreground); }
.chip-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chip-size { color: var(--muted-foreground); font-size: 0.6875rem; flex: none; }
.chip-thumb { width: 1.25rem; height: 1.25rem; border-radius: var(--radius-sm); object-fit: cover; flex: none; }
.chip-x {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.25rem; height: 1.25rem; border-radius: var(--radius-sm);
  color: var(--muted-foreground); flex: none;
}
.chip-x:hover { background: var(--accent); color: var(--foreground); }
.chip-x svg { width: 0.75rem; height: 0.75rem; }

/* Anhänge in der gesendeten Nachricht */
.msg-attachments { display: flex; flex-wrap: wrap; gap: 0.375rem; justify-content: flex-end; max-width: 85%; }
.msg-image { max-width: min(18rem, 80%); border-radius: var(--radius-lg); border: 1px solid var(--border); }

/* Modellzeile im KI-Dialog */
.model-row {
  display: flex; align-items: center; gap: 0.75rem; width: 100%;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--input); border-radius: var(--radius-md);
  background: var(--background); text-align: left;
  transition: background-color .15s, border-color .15s;
}
.model-row:hover { background: var(--accent); }
.model-row-main { flex: 1; min-width: 0; display: block; }
.model-row-name { display: block; font-size: 0.8125rem; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.model-row-sub { display: block; font-size: 0.6875rem; color: var(--muted-foreground); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.model-row-chevron { color: var(--muted-foreground); }
.model-row-chevron svg { width: 0.875rem; height: 0.875rem; }

/* Checkbox-Zeile in den App-Einstellungen.
   Der Abstand zwischen Kaestchen und Beschriftung sind dieselben 0.5rem wie
   zwischen Icon und Wort in jedem Knopf. Vorher standen hier 0.625rem, und
   bei 16 px Kaestchen neben 13 px Text war das genug, damit das Kaestchen
   einzeln im Raum stand statt zu seiner Beschriftung zu gehoeren. */
.check-row { display: flex; align-items: flex-start; gap: 0.5rem; cursor: pointer; }
.check-row input { margin-top: 0.1875rem; width: 1rem; height: 1rem; accent-color: var(--primary); flex: none; }
.check-text b { display: block; font-size: 0.8125rem; font-weight: 500; }
.check-text small { display: block; font-size: 0.75rem; color: var(--muted-foreground); margin-top: 0.125rem; line-height: 1.5; }

/* ==========================================================================
   Dialog
   ========================================================================== */

/* Die Spalte ist fest so breit wie das Fenster (minmax(0, 1fr)), nicht `auto`.
   Eine auto-Spalte wächst mit dem schmalsten Maß ihres Inhalts, und das ist
   bei Text, der nicht umbrechen darf (Modellnamen mit Ellipse), die ganze
   Zeile: Auf einem 375-px-Telefon wurde die Modellauswahl so 390 px breit
   und stand rechts über den Rand — `width: 100%` und `max-width` der Fläche
   beziehen sich auf die Spalte und halten das nicht auf. */
.dialog-root {
  position: fixed; inset: 0; z-index: 50;
  display: grid; grid-template-columns: minmax(0, 1fr); place-items: center;
  padding: 1rem;
}
.dialog-overlay { position: absolute; inset: 0; background: oklch(0 0 0 / 0.55); animation: fade-in .15s ease-out; }
/* Tippen auf den Hintergrund schliesst — auf iOS aber nur, wenn er klickbar
   aussieht: Ein <div> ohne eigenen Lauscher bekommt dort kein click, und der
   Lauscher am Dokument hoert nichts. cursor: pointer ist das Zeichen, auf das
   Safari achtet. Nur auf iOS und iPadOS: -webkit-touch-callout kennt kein
   anderer Browser. Eine Abfrage nach dem Touchschirm (any-pointer: coarse)
   traf auch Windows-Laptops mit Touchschirm, und dort stand dann ueber jedem
   Dialog-Hintergrund die Hand. */
@supports (-webkit-touch-callout: none) { .dialog-overlay { cursor: pointer; } }
.dialog {
  position: relative;
  width: 100%; max-width: 32rem;
  /* vh zuerst: Wer dvh nicht kennt (iOS vor 15.4, Chrome vor 108), verwirft
     die zweite Zeile und hätte ohne die erste gar keine Grenze. */
  max-height: calc(100vh - 2rem); max-height: calc(100dvh - 2rem);
  display: flex; flex-direction: column;
  background: var(--popover); color: var(--popover-foreground);
  border: 1px solid var(--border); border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
  animation: dialog-in .18s cubic-bezier(.16,1,.3,1);
}
.dialog-tall {
  max-width: 34rem;
  height: min(38rem, calc(100vh - 2rem)); height: min(38rem, calc(100dvh - 2rem));
}

.dialog-head { padding: 1.25rem 1.25rem 0.25rem; position: relative; flex: none; }
/* Rechts Platz für das Kreuz (.dialog-x): es steht absolut und beginnt 2.625rem
   vor der Kante, ein langer Titel liefe sonst darunter. */
.dialog-title { font-size: 1.0625rem; font-weight: 600; padding-right: 1.75rem; }
.dialog-desc { font-size: 0.8125rem; color: var(--muted-foreground); margin-top: 0.25rem; }
.dialog-x { position: absolute; top: 0.875rem; right: 0.875rem; width: 1.75rem; height: 1.75rem; color: var(--muted-foreground); }
.dialog-x svg { width: 0.9375rem; height: 0.9375rem; }
.dialog-body { padding: 1.25rem; overflow-y: auto; flex: 1; min-height: 0; }
.dialog-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
  padding: 1rem 1.25rem; border-top: 1px solid var(--border); flex: none;
}
/* margin-left: auto, damit die Knöpfe auch rechts stehen, wenn links keine
   Nebenhandlung steht — mit nur einem Kind setzt space-between es nach links. */
.dialog-foot-right { display: flex; gap: 0.5rem; margin-left: auto; }

/* Der Fließtext in den Dialogen aus js/dialog.js. Nicht `.hint`: dort ist der
   Text das Kleingedruckte unter einem Feld, hier ist er die ganze Meldung und
   braucht Lesegröße. `pre-line`, damit Zeilenumbrüche in der Meldung stehen
   bleiben — window.alert hat sie auch angezeigt. */
.dialog-message { font-size: 0.875rem; line-height: 1.6; color: var(--muted-foreground); white-space: pre-line; }

/* Command-Palette (Modellauswahl) */
.command { display: flex; flex-direction: column; flex: 1; min-height: 0; }
.command-search { padding: 0.75rem 1.25rem; flex: none; --search-pad-x: 1.25rem; }
.command-list { flex: 1; min-height: 0; overflow-y: auto; padding: 0 0.75rem 0.75rem; }
.command-group-label {
  padding: 0.75rem 0.5rem 0.375rem;
  font-size: 0.6875rem; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted-foreground);
}
.command-item {
  display: flex; align-items: center; gap: 0.75rem; width: 100%;
  padding: 0.4375rem 0.625rem; border-radius: var(--radius-md);
  text-align: left; transition: background-color .1s;
}
.command-item:hover, .command-item.focused { background: var(--accent); color: var(--accent-foreground); }
.command-item-main { flex: 1; min-width: 0; display: block; }
.command-item-name {
  display: block;
  font-size: 0.8125rem; font-weight: 500;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.command-item-sub {
  display: block;
  font-size: 0.6875rem; color: var(--muted-foreground); margin-top: 0.0625rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.command-item-check { color: var(--foreground); opacity: 0; }
.command-item-check svg { width: 0.9375rem; height: 0.9375rem; }
.command-item.selected .command-item-check { opacity: 1; }

/* Im Plan Free gesperrtes Modell: bleibt anklickbar — der Klick öffnet den
   Upgrade-Dialog statt zu wählen —, steht aber zurückgenommen da, mit dem
   Schloss an der Stelle des Hakens. Kein :disabled: sonst wäre der Weg zum
   Upgrade nicht zu erreichen. Siehe DESIGN.md, „Plan und Upgrade". */
.command-item.is-locked .command-item-name,
.command-item.is-locked .command-item-sub { color: var(--muted-foreground); }
.command-item.is-locked .command-item-check { opacity: 1; color: var(--muted-foreground); }
.command-empty { padding: 2rem 1rem; text-align: center; font-size: 0.8125rem; color: var(--muted-foreground); }

/* „System-Prompt mit KI erstellen" (js/prompt-generator.js). So breit wie
   der Dialog eines Flow-Schritts: das Ergebnis ist ein Text von ein paar
   hundert Wörtern und soll ohne Waagerecht-Rollen lesbar sein. Das Ergebnis
   in Mono wie im System-Prompt-Panel — es ist dieselbe Sorte Text. */
.dialog-pgen { max-width: 40rem; }
.pgen-run { display: flex; align-items: center; gap: 0.625rem; margin-top: 0.625rem; }
.pgen-run .hint { margin-top: 0; }
.pgen-out { font-family: var(--font-mono); font-size: 0.8125rem; }
.pgen-status { font-variant-numeric: tabular-nums; }
.pgen-status.is-error { color: var(--destructive); }

/* Solange das Modell schreibt: das Streiflicht (.sweep) über dem Feld, bis
   zum ersten Wort dazu der Tippindikator des Chats (.typing) in seiner
   Mitte, danach in der Statuszeile. Ein Satz allein („Wird erstellt …“)
   sah bei zehn Sekunden Wartezeit aus wie eine hängengebliebene Seite.
   Das Licht ist hier deutlicher und schneller als auf der Bildkachel: es
   liegt über einem weißen Feld, und das Warten ist kürzer. */
.pgen-out-wrap { position: relative; }
.pgen-out-wrap .pgen-out { display: block; }
.pgen-out-wrap .pgen-sweep {
  inset: 1px; border-radius: calc(var(--radius-md) - 1px);
  pointer-events: none; opacity: 0; transition: opacity .15s;
  --sweep-strength: 9%; animation-duration: 1.8s;
}
.pgen-out-wrap.is-busy .pgen-sweep { opacity: 1; }
.pgen-wait {
  position: absolute; inset: 0; pointer-events: none;
  display: flex; align-items: center; justify-content: center; gap: 0.625rem;
  font-size: 0.8125rem; color: var(--muted-foreground);
}
.pgen-wait[hidden] { display: none; }
.pgen-status.is-busy { display: flex; align-items: center; gap: 0.5rem; }
.pgen-status .typing { height: auto; }
.pgen-status .typing i { width: 0.3125rem; height: 0.3125rem; }

/* ==========================================================================
   Toaster
   ========================================================================== */

.toaster {
  position: fixed; z-index: 60; bottom: 1rem; right: 1rem;
  display: flex; flex-direction: column; gap: 0.5rem; pointer-events: none;
}
.toast {
  display: flex; align-items: flex-start; gap: 0.625rem;
  min-width: 16rem; max-width: min(24rem, calc(100vw - 2rem));
  padding: 0.75rem 0.875rem;
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--popover); color: var(--popover-foreground);
  box-shadow: var(--shadow-lg);
  font-size: 0.8125rem; line-height: 1.45;
  animation: toast-in .2s cubic-bezier(.16,1,.3,1);
  pointer-events: auto;
}
.toast.leaving { animation: toast-out .15s ease-in forwards; }
.toast svg { width: 1rem; height: 1rem; margin-top: 0.0625rem; }
.toast-error { color: var(--destructive); border-color: color-mix(in oklab, var(--destructive) 35%, var(--border)); }

/* ==========================================================================
   Animationen
   ========================================================================== */

@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes dialog-in { from { opacity: 0; transform: translateY(8px) scale(.98); } to { opacity: 1; transform: none; } }
@keyframes msg-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@keyframes toast-out { to { opacity: 0; transform: translateY(4px); } }
@keyframes blink { 0%, 60%, 100% { opacity: .25; } 30% { opacity: 1; } }

/* Die Regel für Geräte ohne Zeiger steht am ENDE der Datei — siehe dort. */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
  .messages { scroll-behavior: auto; }
}

/* ==========================================================================
   Responsive
   ========================================================================== */

@media (max-width: 1024px) {
  .sidebar {
    position: fixed; inset: 0 auto 0 0; z-index: 40;
    width: min(17rem, 84vw);
    transform: translateX(-100%);
    transition: transform .22s cubic-bezier(.16,1,.3,1);
    box-shadow: var(--shadow-lg);
  }
  .layout.sidebar-open .sidebar { transform: none; }
  .sidebar-close { display: inline-flex; }
  .only-mobile { display: inline-flex; }
  /* Die Kopfzeilen-Knoepfe verlieren ihr Wort: hier zaehlt der Platz fuer den
     Chatnamen mehr. Abgedeckt, nicht entfernt — der Text ist der Name des
     Knopfes, und ohne ihn saesse dort ein namenloses Icon. */
  .topbar-btn { width: 2.25rem; height: 2.25rem; padding: 0; gap: 0; }
  .topbar-btn-text {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    overflow: hidden; white-space: nowrap; clip-path: inset(50%);
  }
}

@media (min-width: 1025px) {
  .scrim { display: none; }
}

@media (max-width: 640px) {
  .messages-inner { padding: 1rem 0.875rem 0.5rem; gap: 1.25rem; }
  .page-inner { padding: 1.25rem 1rem 2.5rem; }
  .placeholder { padding: 2.5rem 1.25rem; }
  .home-promo { padding: 1.5rem; }
  .composer-wrap { padding: 0.25rem 0.75rem calc(0.625rem + env(safe-area-inset-bottom)); }
  /* Der KI-Hinweis bleibt auch mobil stehen, nur die Tastenkürzel entfallen. */
  .composer-hint-keys { display: none; }
  .composer-model { max-width: 9rem; }
  .composer-meta-model { font-size: 0.625rem; max-width: 9rem; }
  .suggestions { grid-template-columns: 1fr; }
  /* Zwei Kacheln nebeneinander lassen auf einem Telefon von jedem Text nur
     noch Umbrüche übrig — hier also untereinander. justify-items: center
     dazu, damit jede nur so breit wird wie ihr eigener Text: ohne das zöge
     die längste alle anderen auf ihre Breite mit. Gleich breit müssen nur
     die sein, die nebeneinander stehen. */
  .suggestions-center {
    grid-template-columns: minmax(0, max-content);
    justify-items: center;
  }
  .bubble { max-width: 92%; }
  .dialog-foot { flex-direction: column-reverse; align-items: stretch; }
  .dialog-foot-right { flex-direction: column-reverse; margin-left: 0; }
  .dialog-foot .btn { width: 100%; }
  .toaster { left: 1rem; right: 1rem; bottom: 1rem; }
  .toast { max-width: none; }
}

/* Die schmalsten Telefone (320 px). Steht hinter dem 640er-Block, weil es
   dessen Werte ueberschreibt — bei gleicher Spezifitaet gewinnt die spaetere
   Regel, nicht die engere Abfrage. */
@media (max-width: 30rem) {
  /* Der Knopf „Artificial Analysis oeffnen“ bricht nicht um und stand mit
     vier Pixeln in der Polsterung der Flaeche. Weniger Polster, engerer
     Abstand zum Zeichen — das schafft die zwoelf Pixel, die fehlten. */
  .home-promo { padding: 1rem; gap: 0.75rem; }
}

/* ==========================================================================
   Seiten-Panel rechts — System-Prompt neben dem Chat bearbeiten (chat.html)

   Das Panel ist ein Flex-Geschwister von .main, kein Overlay: der Chat wird
   schmaler, statt verdeckt zu werden. Die Breite steht als --panel-w am
   .layout, weil js/ui.js sie beim Ziehen dort setzt — so muss nur eine
   Eigenschaft angefasst werden, egal wie viele Regeln daran hängen.
   ========================================================================== */

.layout { --panel-w: 22rem; }

/* Griff am rechten Fensterrand, auf halber Höhe. Halb eingelassen wie eine
   Kerbe: nur links gerundet, rechts randlos an der Fensterkante. Ist das
   Panel offen, liegt dort das Panel — dann verschwindet der Griff. */
.panel-notch {
  position: fixed; z-index: 30;
  top: 50%; right: 0; transform: translateY(-50%);
  display: grid; place-items: center;
  width: 1.5rem; height: 4.5rem; padding: 0;
  border: 1px solid var(--border); border-right: 0;
  border-radius: var(--radius-lg) 0 0 var(--radius-lg);
  background: var(--background); color: var(--muted-foreground);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  transition: width .15s, background .15s, color .15s;
}
.panel-notch[hidden] { display: none; }
.panel-notch svg { width: 1rem; height: 1rem; }
.panel-notch:hover { width: 1.875rem; background: var(--accent); color: var(--accent-foreground); }
.panel-notch:focus-visible {
  outline: var(--focus-ring); outline-offset: 1px;
  color: var(--foreground);
}

.side-panel {
  display: flex; flex: none;
  width: var(--panel-w); min-width: 0;
  background: var(--background);
  border-left: 1px solid var(--border);
}
/* [hidden] verliert gegen display:flex — deshalb hier ausdrücklich. */
.side-panel[hidden] { display: none; }

/* Ziehgriff am linken Rand. Liegt innerhalb des Panels, damit er nichts
   überlappt; sichtbar wird er erst beim Überfahren oder Ziehen. */
.side-panel-resizer {
  position: relative; flex: none;
  width: 0.5rem; align-self: stretch;
  cursor: col-resize; touch-action: none;
  background: transparent;
}
.side-panel-resizer::before {
  content: ''; position: absolute; top: 0; bottom: 0; left: 50%;
  width: 2px; margin-left: -1px; border-radius: 999px;
  background: transparent; transition: background .15s;
}
.side-panel-resizer:hover::before,
.side-panel-resizer:focus-visible::before,
.layout.panel-resizing .side-panel-resizer::before { background: var(--ring); }
.side-panel-resizer:focus-visible { outline: none; }

/* Während des Ziehens darf nichts markiert werden und der Zeiger bleibt
   überall der Doppelpfeil — sonst flackert er über dem Chattext. */
.layout.panel-resizing { cursor: col-resize; user-select: none; }
.layout.panel-resizing .side-panel-text { pointer-events: none; }

.side-panel-inner { display: flex; flex-direction: column; flex: 1; min-width: 0; }

/* Gleiche Höhe wie .topbar und wie sie ohne Linie darunter. */
.side-panel-head {
  display: flex; align-items: center; gap: 0.5rem;
  height: 3.5rem; flex: none;
  padding: 0 0.5rem 0 0.75rem;
}
.side-panel-icon { display: grid; place-items: center; color: var(--muted-foreground); }
.side-panel-icon svg { width: 1rem; height: 1rem; }
.side-panel-title {
  flex: 1; min-width: 0;
  font-size: 0.875rem; font-weight: 500;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.side-panel-body { flex: 1; min-height: 0; display: flex; padding: 0.75rem; }
.side-panel-text {
  flex: 1; width: 100%; min-width: 0;
  padding: 0.75rem 1rem; resize: none;
  font-family: var(--font-mono);
  font-size: 0.8125rem; line-height: 1.65;
  color: var(--foreground); background: var(--background);
  border: 1px solid var(--input); border-radius: var(--radius-xl);   /* mehrzeilig wie .textarea */
  box-shadow: var(--shadow-xs);
  transition: border-color .15s, box-shadow .15s;
}
.side-panel-text::placeholder { color: var(--muted-foreground); }
.side-panel-text:focus {
  outline: none;
  border-color: var(--ring);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--ring) 40%, transparent);
}

/* Unter dem Feld steht nur noch die Rückmeldung — gespeichert wird von
   selbst, gelöscht wird über den Papierkorb in der Kopfzeile. */
.side-panel-foot { flex: none; padding: 0 0.75rem 0.75rem; }
.side-panel-status {
  min-width: 0;
  font-size: 0.75rem; line-height: 1.4; color: var(--muted-foreground);
}
.side-panel-status.is-dirty { color: var(--warning-foreground); }

/* Der Satz darunter im Assistenten-Chat: was der Assistent außer diesem Text
   noch mitbringt. Leer bleibt er unsichtbar, statt eine Lücke zu lassen. */
.side-panel-note {
  margin-top: 0.375rem;
  font-size: 0.6875rem; line-height: 1.45; color: var(--muted-foreground);
}
.side-panel-note:empty { display: none; }

/* Schmale Fenster: für zwei Spalten ist kein Platz mehr, also legt sich das
   Panel über den Chat — mit fester Breite, der Ziehgriff entfällt. */
@media (max-width: 60rem) {
  .side-panel {
    position: fixed; inset: 0 0 0 auto; z-index: 45;
    width: min(26rem, 100%);
    box-shadow: var(--shadow-lg);
  }
  .side-panel-resizer { display: none; }
}

/* ==========================================================================
   Gespeicherte Verläufe (js/saved-panel.js) — überlagert am rechten Rand

   Anders als .side-panel oben: die Fläche liegt IMMER über dem Inhalt und
   macht ihn nie schmaler. Das System-Prompt-Panel gehört zur Arbeit am Chat
   und darf eine Spalte kosten; dies hier ist Navigation — aufschlagen,
   heraussuchen, weg. Deshalb fixed, mit Scrim dahinter, auf jeder Breite.

   Innen borgt sie sich die Bausteine des anderen Panels (.side-panel-inner,
   -head, -icon, -title, -resizer): es ist dieselbe Kante, dieselbe Kopfhöhe
   und derselbe Ziehgriff, und zwei Fassungen davon liefen auseinander.
   ========================================================================== */

.saved-panel {
  position: fixed; inset: 0 0 0 auto; z-index: 45;
  display: flex;
  width: min(var(--saved-w, 22rem), 100%);
  background: var(--background);
  border-left: 1px solid var(--border);
  box-shadow: var(--shadow-lg);
}
.saved-panel[hidden] { display: none; }

/* Der Scrim liegt zwischen Inhalt und Fläche. .scrim allein hätte z-index 30
   und läge damit unter der Fläche — aber auch unter nichts anderem, was hier
   stört; die 44 sind die eine Zahl, die dazwischen passt. */
.saved-scrim { z-index: 44; }

.saved-search { padding: 0.625rem 0.75rem; flex: none; --search-pad-x: 0.75rem; }

/* Eine Spalte mit festem Abstand statt aneinanderstossender Zeilen: die
   Zeilen sassen vorher Kante an Kante, und wie weit zwei auseinanderlagen,
   entschied ihr Inhalt — eine mit Ordnerzeichen war hoeher als eine ohne.

   Der Abstand ist der von .chat-list in der Seitenleiste: dort steht dieselbe
   Art Liste, und 0.125rem hier gegen 0.25rem dort war ein Unterschied ohne
   Grund. */
.saved-body {
  flex: 1; min-height: 0; overflow-y: auto;
  display: flex; flex-direction: column; gap: 0.25rem;
  padding: 0.375rem 0.5rem 0.75rem;
}
.saved-body > * { flex: none; }

/* Was die Verlaufszeile unten und die Ordnerzeile oben gemeinsam haben.

   Sie standen vorher jede fuer sich, und dabei lief jeder einzelne Wert
   auseinander: Rahmen hier keiner und dort einer (also 2 px Unterschied in
   Breite und Hoehe), rechter Rand 0.375rem gegen 0.125rem, Abstand zum Icon
   0.5rem gegen 0.375rem, Icon 14 px gegen 13 px. Beides sind Listen desselben
   Bauplans, uebereinander in derselben Flaeche — so sahen sie aus wie zwei
   Fassungen davon. */
.saved-item,
.saved-folder-row {
  /* Feste Hoehe, nicht gewachsene: was in der Zeile steht, wechselt von
     Zeile zu Zeile — Ordnerzeichen ja oder nein, drei Knoepfe, die beim
     Ueberfahren erscheinen —, und jedes davon hat die Zeile vorher ein
     Stueck hoeher gemacht. Die 2.25rem sind die Hoehe des hoechsten
     Inhalts (die Knoepfe), damit nichts anstoesst. */
  min-height: 2.25rem;
  /* Der Rahmen steht an beiden, obwohl nur die Ordnerzeile ihn beim Ablegen
     einfaerbt (.drop-over): ohne ihn ist die Verlaufszeile 2 px kleiner als
     die Ordnerzeile und ihr Inhalt sitzt um 1 px versetzt. */
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  transition: background-color .1s;
}

/* Ein Eintrag in der Liste darunter. Wie .chat-item in der Seitenleiste, nur
   mit Icon davor — hier stehen vier Arten nebeneinander, und welche es ist,
   muss man sehen. */
.saved-item {
  display: flex; align-items: center; gap: 0.5rem; width: 100%;
  padding: 0 0.25rem 0 0.5rem;
  text-align: left; color: var(--foreground);
}
.saved-item:hover { background: var(--accent); }
.saved-item.active { background: var(--accent); font-weight: 500; }
.saved-item-icon { display: grid; place-items: center; flex: none; color: var(--muted-foreground); }
.saved-item-icon svg { width: 0.875rem; height: 0.875rem; }

/* Der Titel ist der Knopf, der öffnet — die ganze Zeile kann es nicht mehr
   sein, seit zwei weitere Knöpfe darin stehen. cursor: grab sagt dazu, was
   das Ziehen am Eintrag bewirkt; das Ziel steht oben. */
.saved-item-main {
  flex: 1; min-width: 0; text-align: left; color: inherit;
  font-size: 0.8125rem; line-height: 1.4; font-weight: inherit;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  cursor: grab;
}
.saved-item.dragging { opacity: 0.45; }

/* Liegt der Verlauf in einem Ordner, steht hinter dem Titel ein kleines
   Ordnerzeichen — kleiner und blasser als das Art-Icon davor, denn es ist
   eine Nebenauskunft und kein zweites Merkmal der Zeile. Fehlt der Ordner,
   fehlt das Zeichen ganz: ein graues Ordnersymbol an jeder Zeile ohne
   Ordner waere eine Spalte, die nichts unterscheidet. */
.saved-item-folder {
  display: grid; place-items: center; flex: none;
  color: var(--muted-foreground); opacity: .75;
}
.saved-item-folder svg { width: 0.75rem; height: 0.75rem; }

/* Der Drei-Punkte-Knopf (Ordner, Umbenennen und Löschen stehen im Menü
   dahinter). Er erscheint beim Überfahren — in einer Liste aus dreißig
   Zeilen wären dreißig dauerhaft sichtbare Knöpfe nur Rauschen. Auf dem
   Tastweg bleibt er da, sobald der Fokus in der Zeile steht. Platz nimmt er
   auch unsichtbar ein (opacity, nicht display), damit der Titel beim
   Überfahren nicht kürzer wird.

   Derselbe Knopf wie an der Ordnerzeile oben, deshalb hier zusammen: 24 px
   Knopf mit 14-px-Icon wie in der Seitenleiste (.chat-item-actions) und in
   der Liste auf all-chats.html (.chats-row-actions). */
.saved-item-acts,
.saved-folder-acts {
  display: inline-flex; align-items: center; gap: 0.25rem; flex: none;
  opacity: 0; transition: opacity .1s;
}
.saved-item:hover .saved-item-acts,
.saved-item:focus-within .saved-item-acts,
.saved-folder-row:hover .saved-folder-acts,
.saved-folder-row:focus-within .saved-folder-acts { opacity: 1; }
.saved-item-acts .btn,
.saved-folder-acts .btn { width: 1.5rem; height: 1.5rem; }
.saved-item-acts .btn svg,
.saved-folder-acts .btn svg { width: 0.875rem; height: 0.875rem; }

/* Ihr Hover steht im Abschnitt „Knöpfe in einer eingefärbten Zeile" ganz
   oben — dieselbe Regel wie in der Seitenleiste und auf all-chats.html. Die
   Fläche liegt auf --background, --row-surface bleibt also, wie es ist. */

/* Die Ordner in der oberen Hälfte. Sie nehmen sich, was sie brauchen,
   höchstens aber die halbe Höhe — bei zwei Ordnern soll nicht die Hälfte der
   Fläche leer stehen, und bei zwanzig soll die Liste darunter nicht
   verschwinden. Eigene Rollfläche: die Ordner bleiben stehen, während unten
   der Inhalt wechselt. */
.saved-folders {
  display: flex; flex-direction: column;
  flex: 0 1 auto; min-height: 0; max-height: 50%;
  border-bottom: 1px solid var(--border);
}
.saved-folders[hidden] { display: none; }

/* Die Überschrift trägt den Knopf zum Anlegen. Er gehört zur Ordnerhälfte
   und nicht zwischen die Ordner: dort wäre er eine Zeile, die aussieht wie
   ein Ordner und keiner ist. */
/* Der Rand ist der der Zeilen darunter, nicht sein eigener: links steht die
   Überschrift über dem Ordnersymbol, rechts der Knopf über dem Papierkorb.
   Die Werte sind die Summe aus dem Polster der Rollfläche (.saved-tree, 8 px)
   und dem der Zeile (8 px links, 4 px rechts) — wie .recent-head in der
   Seitenleiste über .chat-list steht. */
.saved-folders-head {
  display: flex; align-items: center; gap: 0.5rem; flex: none;
  padding: 0.125rem 0.75rem 0.25rem 1rem;
}
.saved-folders-title {
  flex: 1; min-width: 0;
  font-size: 0.6875rem; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted-foreground);
}
.saved-folders-head .btn { width: 1.5rem; height: 1.5rem; }
.saved-folders-head .btn svg { width: 0.875rem; height: 0.875rem; }

/* Wie die Liste darunter: eine Spalte mit festem Abstand. Vorher entschied
   der Inhalt jeder Zeile, wie hoch sie wurde — „Alle" ohne Knöpfe stand
   enger als ein Ordner mit ihnen. Derselbe Abstand wie in .saved-body, sonst
   stehen die Ordner oben enger als die Verläufe unten. */
.saved-tree {
  flex: 1; min-height: 0; overflow-y: auto;
  display: flex; flex-direction: column; gap: 0.25rem;
  padding: 0 0.5rem 0.5rem;
}
.saved-tree > * { flex: none; }

.saved-folders-hint {
  padding: 0.375rem 0.5rem 0.25rem;
  font-size: 0.6875rem; line-height: 1.5; color: var(--muted-foreground);
}

/* Eine Ordnerzeile: Name links, die beiden Knöpfe rechts.

   Der Hintergrund beim Überfahren liegt an DIESER Zeile und nicht am
   Namensknopf darin. Vorher lag er dort, und dann endete er an dessen Kante:
   Umbenennen und Löschen standen daneben auf blankem Grund und sahen aus, als
   gehörten sie zur nächsten Zeile.

   Höhe, Rahmen und Radius stehen weiter oben bei .saved-item — es sind
   dieselben. */
.saved-folder-row {
  display: flex; align-items: center; gap: 0.5rem;
  padding-right: 0.25rem;   /* wie an der Verlaufszeile: gleicher Rand rechts */
}
.saved-folder-row:hover { background: var(--accent); }
.saved-folder-row.active { background: var(--accent); }

/* Das Ziel unter dem gezogenen Eintrag. Gestrichelt und nicht gefüllt: die
   gefüllte Fläche heißt in dieser Liste schon „ausgewählt", und beides
   gleichzeitig wäre nicht mehr auseinanderzuhalten. Den Platz für den Rahmen
   hält die Zeile ohnehin frei (border: 1px solid transparent). */
.saved-folder-row.drop-over {
  border-style: dashed; border-color: var(--primary);
  background: color-mix(in oklab, var(--primary) 12%, transparent);
}

/* Der Knopf füllt die Zeile aus, malt aber keinen eigenen Hintergrund mehr —
   den bringt die Zeile mit, damit er auch unter den Knöpfen rechts liegt.
   align-self: stretch, damit die ganze Zeilenhöhe anklickbar bleibt und nicht
   nur der Streifen um den Text.

   Schriftgröße und Abstand zum Icon sind die der Verlaufszeile darunter; nur
   das Gewicht liegt eine Stufe höher, weil ein Ordner eine Überschrift für
   das ist, was unten steht (aktiv dann 600 gegen die 500 des aktiven
   Verlaufs — derselbe Sprung wie dort). */
.saved-folder-head {
  display: flex; align-items: center; gap: 0.5rem; flex: 1; min-width: 0;
  align-self: stretch;
  padding: 0 0.5rem;
  text-align: left;
  border-radius: var(--radius-md);
  color: var(--muted-foreground);
  font-size: 0.8125rem; font-weight: 500; letter-spacing: .01em;
  transition: color .1s;
}
.saved-folder-row:hover .saved-folder-head { color: var(--foreground); }
.saved-folder-row.active .saved-folder-head { color: var(--foreground); font-weight: 600; }

/* Der Drei-Punkte-Knopf des Ordners: Größe und Hover stehen oben bei
   .saved-item-acts — es ist derselbe Knopf.

   „Alle" hat ihn nicht und hält seinen Platz trotzdem frei: 24 px. Sonst
   reicht diese eine Zeile weiter nach rechts als alle anderen, und ihre Zahl
   steht aus der Spalte heraus. */
.saved-folder-acts-none { width: 1.5rem; }

.saved-folder-icon { display: grid; place-items: center; flex: none; width: 0.875rem; }
.saved-folder-icon svg { width: 0.875rem; height: 0.875rem; }
.saved-folder-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.saved-folder-n {
  flex: none; font-weight: 500; font-variant-numeric: tabular-nums;
  color: var(--muted-foreground);
}

/* Der Fuß: die eine Zahl, wie viel vom Deckel schon vergeben ist. Er sitzt
   fest unten, damit er nicht mit der Liste wegscrollt.

   Hier standen vorher noch zwei Dinge mehr — ein Knopf „Diesen Verlauf
   speichern" und, war er schon gespeichert, der Satz „Dieser Verlauf ist
   gespeichert". Beides beantwortete eine Frage über den offenen Verlauf und
   nicht über die Ablage: Speichern tut der Knopf in der Kopfzeile, und der
   Satz sagte nur, dass der Knopf daneben nichts mehr zu tun hat. Damit war
   der Fuß halb Zustandsanzeige des Chats und halb Bilanz der Ablage. Jetzt
   steht dort nur noch die Bilanz. */
.saved-foot {
  flex: none; padding: 0.5rem 0.75rem;
  border-top: 1px solid var(--border);
}
.saved-foot[hidden] { display: none; }
.saved-foot-note {
  text-align: center;
  font-size: 0.6875rem; color: var(--muted-foreground);
  font-variant-numeric: tabular-nums;
}

.saved-note { padding: 1.5rem 0.75rem; text-align: center; font-size: 0.8125rem; color: var(--muted-foreground); }

.saved-empty { padding: 2.5rem 1rem; text-align: center; }
.saved-empty-icon { display: grid; place-items: center; margin: 0 auto 0.75rem; color: var(--muted-foreground); }
.saved-empty-icon svg { width: 1.5rem; height: 1.5rem; }
.saved-empty-title { font-size: 0.875rem; font-weight: 500; }
.saved-empty-text { margin-top: 0.375rem; font-size: 0.75rem; line-height: 1.5; color: var(--muted-foreground); }
.saved-retry { text-align: center; }

/* Auf schmalen Fenstern nimmt die Fläche fast alles ein — der Ziehgriff
   entfällt dort, wie beim anderen Panel auch. */
@media (max-width: 60rem) {
  .saved-panel { width: min(22rem, 100%); }
  .saved-panel .side-panel-resizer { display: none; }
}

/* ==========================================================================
   Image (image.html) — die erzeugten Bilder im Verlauf

   Der Verlauf selbst ist der des Chats: .messages, .messages-inner, .msg,
   .bubble. Neu ist nur, was der Chat nicht kennt — die Kachel mit dem Bild,
   ihre Aktionen und die Vorlagen einer Bearbeitung.
   ========================================================================== */

/* Kein Rahmen: ein erzeugtes Bild ist der Inhalt, nicht ein Beleg, den man
   einfassen muesste. Die Rundung bleibt, damit es zu den Blasen passt. */
.imgcard {
  margin: 0;
  max-width: min(28rem, 100%);
  overflow: hidden;
  border-radius: var(--radius-lg);
  background: var(--muted);
}
.imgcard img { display: block; width: 100%; height: auto; cursor: zoom-in; }

/* Der Platzhalter hat dasselbe Seitenverhaeltnis wie das kommende Bild —
   sonst springt der Verlauf, wenn die Antwort eintrifft.

   Er soll leicht wirken, nicht wie ein Ladebalken: eine helle Flaeche, ein
   langsam darueberziehendes Streiflicht, sonst nichts. Ein Symbol in der
   Mitte machte daraus ein Bedienelement, das es nicht ist — die Kachel ist
   der Platz, den das Bild gleich einnimmt. */
.imgcard-loading {
  position: relative;
  width: min(28rem, 100%);
  display: grid; place-items: center;
  background: color-mix(in oklab, var(--muted) 45%, var(--background));
}
/* Das Streiflicht ist ein Baustein für jedes „ein Modell arbeitet gerade“,
   nicht nur für Bilder — `.sweep` nimmt auch das Fenster „… mit KI
   erstellen“ (js/prompt-generator.js). Wie deutlich es ist, sagt
   --sweep-strength; auf der hellen Bildkachel reichen 4 %, über einem
   weißen Textfeld braucht es mehr. */
.imgcard-sweep, .sweep {
  position: absolute; inset: 0;
  background: linear-gradient(100deg,
    transparent 25%,
    color-mix(in oklab, var(--foreground) var(--sweep-strength, 4%), transparent) 50%,
    transparent 75%);
  background-size: 250% 100%;
  animation: img-sweep 3.2s ease-in-out infinite;
}
.imgcard-hint {
  position: relative;
  font-size: 0.8125rem;
  color: color-mix(in oklab, var(--muted-foreground) 70%, transparent);
}

@keyframes img-sweep {
  from { background-position: 150% 0; }
  to   { background-position: -150% 0; }
}

@media (prefers-reduced-motion: reduce) {
  .imgcard-sweep, .sweep { animation: none; }
}

.imgcard-actions { display: flex; flex-wrap: wrap; gap: 0.25rem; }
.imgcard-actions .btn svg { width: 0.8125rem; height: 0.8125rem; }

/* Hat das Modell mit Worten geantwortet statt mit einem Bild, steht der Text
   ueber der Fehlerzeile — er ist die eigentliche Auskunft. */
.imgcard-said { max-width: min(28rem, 100%); }

/* Vorlagen ueber der eigenen Ansage — rechtsbuendig wie die Blase darunter. */
.imgrefs { display: flex; flex-wrap: wrap; gap: 0.375rem; justify-content: flex-end; }
.imgref-thumb {
  width: 3rem; height: 3rem; object-fit: cover;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  cursor: zoom-in;
}

/* --------------------------------------------------------------------------
   Bild in gross

   Im Verlauf ist ein Bild hoechstens 28rem breit — das reicht, um es
   wiederzuerkennen, nicht um es anzusehen. Ein Klick legt es deshalb ueber
   die Seite: der Rahmen ist das Fenster, das Bild nimmt davon so viel, wie
   sein Seitenverhaeltnis zulaesst.

   Aufbau und Verhalten kommen von `.dialog-root` — nur die Flaeche ist
   dunkler und der Rand schmaler als bei einem Dialog, weil hier nichts
   ausser dem Bild zu lesen ist.
   -------------------------------------------------------------------------- */
.lightbox { padding: 0.75rem; }
.lightbox .dialog-overlay { background: oklch(0 0 0 / 0.82); }

/* Die Breite wird gerechnet, nicht dem Bild ueberlassen. Mit `width: auto`
   bliebe ein `<img>` bei seiner Eigengroesse stehen — `max-width` begrenzt nur
   nach oben, es vergroessert nichts. Auf einem 2560er Schirm nahm ein
   1536 × 1024-Bild so knapp 60 % der Breite ein, obwohl daneben alles frei war.

   Darum: die Breite ist der kleinere der beiden Anschlaege — was das Fenster
   breit hergibt, und was bei voller Hoehe zum Seitenverhaeltnis passt. Die
   Hoehe folgt daraus ueber `aspect-ratio`, das Bild bleibt also unverzerrt und
   die Kiste ist genau so gross wie das Bild darin (wichtig fuer Rundung und
   Schatten). `--ratio` setzt js/image.js aus den echten Bildmassen.

   Die Fensterhoehe steht als dvh da und nicht als `100%`: die Grid-Zeile von
   `.dialog-root` richtet sich nach ihrem Inhalt, ein Prozentwert bezoege sich
   also auf das Bild selbst und begrenzte nichts. `.dialog` rechnet aus dem
   gleichen Grund mit dvh. Abgezogen wird jeweils der Rand auf beiden Seiten. */
.lightbox-img {
  --ratio: 1;
  position: relative;
  width: min(calc(100vw - 1.5rem), calc((100vh - 1.5rem) * var(--ratio)));
  width: min(calc(100vw - 1.5rem), calc((100dvh - 1.5rem) * var(--ratio)));
  height: auto;
  aspect-ratio: var(--ratio);
  object-fit: contain;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  cursor: zoom-out;
  animation: dialog-in .18s cubic-bezier(.16,1,.3,1);
}

/* Der Schliesser liegt auf dem Bild, nicht daneben — bei einem quadratischen
   Bild auf einem breiten Schirm waere daneben weit weg. Eigene Farben, weil
   die Flaeche darunter das Bild ist und nicht der Untergrund. */
.lightbox-x {
  position: absolute; top: 1rem; right: 1rem;
  color: oklch(1 0 0 / 0.85);
  background: oklch(0 0 0 / 0.45);
  backdrop-filter: blur(4px);
}
.lightbox-x:hover { color: oklch(1 0 0); background: oklch(0 0 0 / 0.65); }

/* Zeile in den Bild-Verlaeufen: der Eintrag selbst plus ein Papierkorb.
   Deshalb liegt `selected` jetzt an der Zeile und nicht mehr am Knopf — das
   Haekchen haengt daran und braucht die Regel darunter. */
.command-row { display: flex; align-items: center; gap: 0.25rem; }
.command-row .command-item { flex: 1; min-width: 0; }
.command-row.selected .command-item-check { opacity: 1; }
.command-row-del {
  width: 2rem; height: 2rem; flex: none;
  opacity: 0.5; transition: opacity .12s, background-color .12s;
}
.command-row:hover .command-row-del,
.command-row:focus-within .command-row-del { opacity: 1; }
.command-row-del svg { width: 0.875rem; height: 0.875rem; }

/* Loeschen ist zweistufig: der erste Klick scharf, der zweite fuehrt aus.
   Beim reinen Icon-Knopf ist diese Faerbung das einzige Zeichen dafuer. */
.is-armed {
  opacity: 1;
  background: color-mix(in oklab, var(--destructive) 14%, transparent);
  color: var(--destructive);
}

/* ==========================================================================
   Library — Assistants, Skills und Knowledge Items

   Drei Uebersichten und drei Detailseiten, die sich fast alles teilen: die
   Kachelliste mit Suche und Tag-Filter oben, darunter das Formular aus
   .settings-card, .field und .input. Neu ist hier nur, was es vorher nicht
   gab — Kacheln, Tags, Icon- und Farbwahl.
   ========================================================================== */

/* ---------- Die Palette ----------
   Ein Ton je Farbe, die 500er von Tailwind. Aus dem einen Wert leitet sich
   beides ab: die getoente Flaeche und die Farbe des Icons darauf. Deshalb
   steht der Hex-Wert genau hier und nirgends sonst — auch nicht in
   js/library.js, das nur die Namen kennt.

   Ein einziger Ton fuer hell UND dunkel geht auf, weil die Flaeche per
   color-mix aus der Farbe selbst entsteht: auf Weiss mischt sie sich hell,
   auf Schwarz dunkel, und der Kontrast zum Icon bleibt in beiden Faellen. */
[data-color="slate"]   { --lib-color: #64748b; }
[data-color="red"]     { --lib-color: #ef4444; }
[data-color="orange"]  { --lib-color: #f97316; }
[data-color="amber"]   { --lib-color: #f59e0b; }
[data-color="yellow"]  { --lib-color: #eab308; }
[data-color="lime"]    { --lib-color: #84cc16; }
[data-color="green"]   { --lib-color: #22c55e; }
[data-color="emerald"] { --lib-color: #10b981; }
[data-color="teal"]    { --lib-color: #14b8a6; }
[data-color="cyan"]    { --lib-color: #06b6d4; }
[data-color="sky"]     { --lib-color: #0ea5e9; }
[data-color="blue"]    { --lib-color: #3b82f6; }
[data-color="indigo"]  { --lib-color: #6366f1; }
[data-color="violet"]  { --lib-color: #8b5cf6; }
[data-color="purple"]  { --lib-color: #a855f7; }
[data-color="fuchsia"] { --lib-color: #d946ef; }
[data-color="pink"]    { --lib-color: #ec4899; }
[data-color="rose"]    { --lib-color: #f43f5e; }

/* ---------- Seitenkopf mit Marketingfläche ----------
   Aufbau aller sieben Übersichten (seit 2026-10-16):
   Titel mit rundem Plus daneben, darunter die Suche über die ganze
   Breite, dann eine Karte mit Wolkenfläche und Schaubild, die sagt, was der
   Bereich kann. Wer sucht, will die Liste — dann fällt die Karte weg
   (Library.overview setzt #hero auf hidden). Siehe DESIGN.md,
   „Übersicht mit Marketingfläche". */

/* Die Kopfzeile trägt dort nur den Menüknopf; am Desktop gibt es ihn nicht,
   also auch keine leere Leiste. */
@media (min-width: 1025px) { .topbar-bare { display: none; } }

.lib-page-head { display: flex; align-items: center; gap: 1rem; margin-bottom: 1.25rem; }
/* Knöpfe neben dem Titel: 32 px statt 40. In voller Größe und schwarz
   gefüllt war das Plus lauter als der Titel daneben. „Mit KI erstellen"
   (Skills, System Prompts) steht davor, gleich hoch. */
.lib-page-actions { display: flex; align-items: center; gap: 0.5rem; flex: none; }
.lib-page-actions .btn { height: 2rem; }
.lib-page-actions .btn-icon { width: 2rem; }

/* Einzelseiten (Assistent, Flow, Skill …): Zurück links vor dem Titel, so
   groß wie die Knöpfe rechts; Status, Nebenknopf und Speichern rechts. Der
   Kopf klebt beim Scrollen oben — Speichern stand vorher in der Kopfzeile,
   die nie wegscrollte, und muss auch unter einem langen Formular erreichbar
   bleiben. Eigener Grund, damit der Inhalt nicht durchscheint; der
   Innenabstand oben und unten gibt ihm Luft, der negative Rand oben gleicht
   ihn in Ruhe wieder aus. */
.lib-detail-head {
  position: sticky; top: 0; z-index: 5;
  gap: 0.75rem;
  margin: -0.75rem 0 0.5rem; padding: 0.75rem 0;
  background: var(--background);
}
.lib-detail-head > .btn-icon { flex: none; width: 2rem; height: 2rem; }
/* Der Titel steht im Farbfeld (.detail-band-title), nicht hier: die Knöpfe
   rücken ohne ihn nach rechts. */
.lib-detail-head .lib-page-actions { margin-left: auto; }

/* Das Farbfeld einer Einzelseite ist ihr Erscheinungsbild: der Mesh-Grund
   der Kachel in der Farbe des Eintrags (Assistent, Flow) oder des Bereichs
   (alle übrigen), darin das Zeichen auf einer hellen, durchscheinenden
   Kachel. Bei Assistent und Flow ist es der Knopf zum Icon-Wählen, darunter
   stehen die Farben (.detail-appearance). Neben dem Zeichen der Name des
   Eintrags als Seitentitel. Flacher als das Farbfeld der übrigen Seiten
   (6,5 statt 9 rem): es trägt nur Zeichen und Namen. */
.detail-band {
  display: flex; align-items: center; gap: 1.25rem;
  height: 6.5rem; padding: 0 1.75rem;
  border-radius: var(--radius-xl);
}
/* Weiß wie das Zeichen daneben (color der Mesh-Regel), in der Stufe der
   übrigen Seitenüberschriften (22 px). Der leichte Schatten hält ihn auch
   auf den hellen Stellen des Verlaufs lesbar. Ein langer Name wird gekürzt. */
.detail-band-title {
  min-width: 0;
  font-size: 1.375rem; font-weight: 600; letter-spacing: -0.01em; line-height: 1.3;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  text-shadow: 0 1px 2px rgb(0 0 0 / 0.15);
}
.detail-band .lib-icon {
  width: 3.25rem; height: 3.25rem; border-radius: var(--radius-lg);
  background: color-mix(in oklab, white 22%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in oklab, white 35%, transparent);
  transition: background-color .15s;
}
.detail-band .lib-icon svg { width: 1.5rem; height: 1.5rem; }
.detail-band button.lib-icon { cursor: pointer; }
.detail-band button.lib-icon:hover { background: color-mix(in oklab, white 34%, transparent); }
.detail-band button.lib-icon:focus-visible { outline: 3px solid color-mix(in oklab, white 70%, transparent); outline-offset: 2px; }

/* Farben mittig unter dem Feld, darunter Name der Farbe und „Icon wählen".
   Kein negativer Rand: der Abstand von .lib-detail (1,25 rem) trennt die
   Punkte vom Feld — bis 2026-10-16 zog ein -1,25 rem sie bündig an die
   Unterkante. */
.detail-appearance { display: grid; justify-items: center; gap: 0.375rem; }
.detail-appearance-foot { display: flex; align-items: center; gap: 0.5rem; }
.detail-appearance-foot .hint { margin-top: 0; }   /* sonst sitzt sie tiefer als der Knopf */
@media (max-width: 640px) {
  .detail-band { height: 5.5rem; padding: 0 1.25rem; gap: 1rem; }
  .detail-band-title { font-size: 1.125rem; }
}
/* Die Stufe der übrigen Seitenüberschriften (22 px). 28 px waren zur Probe
   gesetzt und wirkten zu groß. */
.lib-page-title {
  flex: 1; min-width: 0;
  font-size: 1.375rem; font-weight: 600; letter-spacing: -0.01em; line-height: 1.3;
}
/* Titel mit einer Zeile darunter (Startseite: Datum, library.html: was die
   Library ist) — beide in .lib-page-text, damit die Knöpfe rechts auf der
   Höhe des Blocks und nicht nur des Titels stehen. */
.lib-page-text { flex: 1; min-width: 0; }
.lib-page-sub { margin-top: 0.25rem; font-size: 0.8125rem; color: var(--muted-foreground); }

/* So hoch wie jedes Feld (40 px); 44 px waren zu groß. Das Icon rückt nur
   von der Rundung weg. */
.lib-page-search { margin-bottom: 1.5rem; --search-pad-x: 0.375rem; }
.lib-page-search .search-input { padding-left: 2.5rem; }

.lib-hero { display: flex; flex-direction: column; padding: 0.5rem; margin-bottom: 2rem; }
.lib-hero[hidden] { display: none; }
/* Auf den Wolken steht ein Motiv aus js/mocks.js — ein Fenster der App, das
   unten auf der Fläche steht und dort ausläuft, wie auf der Website und im
   Willkommen. Deshalb unten ohne Innenabstand. */
.lib-hero-visual {
  display: flex; align-items: flex-end; justify-content: center;
  height: 16rem; padding: 1.75rem 2rem 0;
  border-radius: var(--radius-lg);
}
.lib-hero-text { padding: 1.25rem 1.5rem 1.5rem; text-align: center; }
.lib-hero-title { font-size: 1.0625rem; font-weight: 600; }
.lib-hero-desc {
  max-width: 36rem; margin: 0.375rem auto 0;
  color: var(--muted-foreground); line-height: 1.55;
}

.lib-list-head { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.875rem; }

/* ---------- Flaches Farbfeld der übrigen Seiten ----------
   Einstellungen, Statistik, Alle Verläufe, Info & Support, Superadmin
   (2026-10-16): unter dem Seitenkopf ein Farbfeld wie die Marketingfläche,
   aber ohne Text darunter und flacher — 9 statt 16 rem. Die Seiten sind
   Werkzeuge, keine Werbung; das Feld gibt ihnen dasselbe Gesicht wie der
   Library, ohne den Inhalt nach unten zu schieben. Darauf der obere Teil
   eines Fensters aus js/mocks.js (data-mock, gefüllt von Mocks selbst).
   Navigation zum Umschalten steht darunter, nicht darüber. */
.page-band {
  display: flex; align-items: flex-end; justify-content: center;
  height: 9rem; padding: 1.25rem 2rem 0; margin-bottom: 1.5rem;
  border-radius: var(--radius-xl);
}
.page-band .mock { max-width: 28rem; }
@media (max-width: 640px) {
  .page-band { height: 7.5rem; padding: 1rem 1rem 0; margin-bottom: 1.25rem; }
}

@media (max-width: 640px) {
  .lib-hero-visual { height: 14rem; padding: 1.25rem 1rem 0; }
  .lib-hero-text { padding: 1rem 1rem 1.25rem; }
}

/* ---------- Kopfleiste der Uebersicht ---------- */

.lib-toolbar {
  display: flex; align-items: center; gap: 0.75rem;
  flex-wrap: wrap; margin-bottom: 0.875rem;
}
.lib-count { font-size: 0.8125rem; color: var(--muted-foreground); margin-right: auto; }
.lib-search { max-width: 20rem; flex: 1 1 14rem; --search-pad-x: 0rem; }

/* Die Tag-Filter. Sie stehen nur da, wenn es Tags gibt — sonst waere es eine
   Leiste mit einem einzigen Knopf „Alle". */
.lib-filters {
  display: flex; flex-wrap: wrap; gap: 0.375rem;
  margin-bottom: 1.25rem;
}
.lib-filters:empty { display: none; }

.lib-filter {
  display: inline-flex; align-items: center; gap: 0.375rem;
  height: 1.75rem; padding: 0 0.625rem;
  border: 1px solid var(--border); border-radius: 999px;
  background: transparent; color: var(--muted-foreground);
  font-size: 0.75rem; cursor: pointer;
  transition: background-color .12s, color .12s, border-color .12s;
}
.lib-filter:hover { background: var(--accent); color: var(--foreground); }
.lib-filter.active {
  background: var(--primary); color: var(--primary-foreground);
  border-color: var(--primary);
}
.lib-filter-n { opacity: 0.6; font-variant-numeric: tabular-nums; }

/* ---------- Die Kacheln ---------- */

.lib-grid {
  display: grid; gap: 1rem;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
}

/* Die ganze Kachel ist der Knopf zum Oeffnen — der Papierkorb liegt darueber
   und faengt seinen Klick selbst ab. Ein <button> im <button> geht nicht,
   deshalb ist die Kachel ein <div> mit role="button" und der Papierkorb ein
   gewoehnlicher Knopf darin. */
/* Grund, Rand und Radius kommen aus dem Abschnitt „Karte"; hier nur Aufbau
   und Hover. */
.lib-card {
  position: relative; display: flex; flex-direction: column; gap: 0.625rem;
  padding: 1.125rem;
  text-align: left; cursor: pointer;
  transition: border-color .12s, box-shadow .12s;
}
.lib-card:hover {
  border-color: color-mix(in oklab, var(--ring) 60%, var(--border));
  box-shadow: var(--shadow-sm);
}
.lib-card:focus-visible { outline: var(--focus-ring); outline-offset: 1px; }

.lib-card-head { display: flex; align-items: flex-start; gap: 0.75rem; min-width: 0; }

/* Kachel als Mesh-Verlauf mit weissem Zeichen: die Farbe des Bereichs als
   Grund, darauf vier weiche Flecken derselben Farbe, im Farbton verschoben
   (oklch, relative Farbe) — so bleibt es EIN Ton in der Datenbank und wirkt
   trotzdem mehrfarbig. Die Regel gilt auch fuer das Icon im leeren
   Assistenten-Chat (.empty-icon-lib) — eine Absicht, ein Block.
   Die erste background-Zeile ist der Rueckfall ohne relative Farben: ohne sie
   bliebe das weisse Zeichen dort unsichtbar. Ebenso das Farbfeld einer
   Einzelseite (.detail-band) — die Kachel im Großen. */
.lib-icon, .empty-icon-lib, .detail-band {
  --mesh: var(--lib-color, var(--muted-foreground));
  background: linear-gradient(135deg,
    color-mix(in oklab, var(--mesh) 70%, white), var(--mesh));
  background:
    radial-gradient(circle at 12% 12%, oklch(from var(--mesh) calc(l + 0.16) calc(c * 0.72) calc(h - 18)) 0, transparent 62%),
    radial-gradient(circle at 92% 8%,  oklch(from var(--mesh) calc(l + 0.13) calc(c * 0.72) calc(h + 22)) 0, transparent 58%),
    radial-gradient(circle at 88% 92%, oklch(from var(--mesh) calc(l + 0.07) calc(c * 0.82) calc(h + 8)) 0, transparent 62%),
    radial-gradient(circle at 8% 90%,  oklch(from var(--mesh) calc(l + 0.15) calc(c * 0.72) calc(h + 34)) 0, transparent 58%),
    oklch(from var(--mesh) calc(l + 0.09) calc(c * 0.82) h);
  color: #fff;
}
.lib-icon {
  flex: none; display: grid; place-items: center;
  width: 2.25rem; height: 2.25rem;
  border-radius: var(--radius-md);
}
.lib-icon svg { width: 1.125rem; height: 1.125rem; }

.lib-icon-lg { width: 2.5rem; height: 2.5rem; border-radius: var(--radius-lg); }
.lib-icon-lg svg { width: 1.25rem; height: 1.25rem; }

/* Die kleine Fassung: Auswahllisten (Assistenten, Flows), Knoten im
   Flow-Diagramm, Zeilen auf library.html, die Leiste einer laufenden HTML App.
   Steht hinter .lib-icon und nicht bei ihrer ersten Verwendung weiter oben —
   dort verlor sie bei gleicher Spezifitaet gegen dessen 2.25rem, und jedes
   „kleine" Icon war so gross wie ein normales. */
.lib-icon-sm { width: 1.75rem; height: 1.75rem; border-radius: var(--radius-sm); }
.lib-icon-sm svg { width: 0.875rem; height: 0.875rem; }

.lib-card-text { min-width: 0; flex: 1; }
.lib-card-title {
  display: block; font-size: 0.9375rem; font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Zwei Zeilen und dann Schluss: die Kacheln sollen in einer Reihe gleich hoch
   bleiben, egal wie ausfuehrlich eine Beschreibung ausfiel. */
.lib-card-desc {
  margin: 0.125rem 0 0; font-size: 0.8125rem; line-height: 1.45;
  color: var(--muted-foreground);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}

.lib-card-tags { display: flex; flex-wrap: wrap; gap: 0.25rem; }

/* Steht immer unten, auch wenn die Beschreibung kurz ist. */
.lib-card-meta {
  margin-top: auto; display: flex; flex-wrap: wrap; gap: 0.5rem;
  font-size: 0.75rem; color: var(--muted-foreground);
}
.lib-card-meta span { display: inline-flex; align-items: center; gap: 0.25rem; }
.lib-card-meta svg { width: 0.8125rem; height: 0.8125rem; }

/* Erscheint erst beim Ueberfahren — auf dem Touchgeraet gibt es kein Hover,
   deshalb steht er dort immer da. */
.lib-card-del {
  position: absolute; top: 0.5rem; right: 0.5rem;
  width: 1.75rem; height: 1.75rem;
  color: var(--muted-foreground);
  opacity: 0; transition: opacity .12s;
}
.lib-card:hover .lib-card-del,
.lib-card:focus-within .lib-card-del { opacity: 1; }
.lib-card-del svg { width: 0.875rem; height: 0.875rem; }
@media (hover: none) { .lib-card-del { opacity: 1; } }

/* Der Titel haelt dem Papierkorb rechts Platz frei — dauerhaft, nicht erst
   beim Ueberfahren: sonst liefe ein langer Name unter den Knopf, und sobald
   der erscheint (beim Hover, auf Touchgeraeten immer), laege er ueber dem
   Text. Wieder frei geraeumt, spraenge die Ueberschrift beim Ueberfahren.
   1.625rem = Knopf (1.75) + Abstand zum Rand (0.5) − Innenabstand der
   Kachel (0.875) + 0.25 Luft. Kacheln ohne Papierkorb (die Hilfe auf
   home.html) brauchen das nicht, daher der Geschwister-Selektor. */
.lib-card-del ~ .lib-card-head .lib-card-title { padding-right: 1.625rem; }

/* Der zweite Knopf an der Kachel — bislang nur bei den HTML Apps: „Starten".
   Anders als der Papierkorb steht er immer da, auch ohne Hover: er ist keine
   Notausfahrt, sondern der Grund, warum man auf die Kachel schaut.

   Der Papierkorb rueckt dann eine Knopfbreite nach links, und der Titel
   haelt beiden Platz frei: 1.625rem wie oben plus die 1.875rem, um die der
   Papierkorb gerueckt ist. Der Selektor fuehrt den oberen weiter — kuerzer
   gewaenne der obere mit seiner hoeheren Spezifitaet. */
.lib-card-has-play .lib-card-del { right: 2.375rem; }
.lib-card-has-play .lib-card-del ~ .lib-card-head .lib-card-title { padding-right: 3.5rem; }

.lib-card-play {
  position: absolute; top: 0.5rem; right: 0.5rem;
  width: 1.75rem; height: 1.75rem;
  color: var(--muted-foreground);
}
.lib-card-play:hover { color: var(--foreground); }
.lib-card-play svg { width: 0.875rem; height: 0.875rem; }

.lib-empty {
  grid-column: 1 / -1;
  padding: 2.5rem 1rem; text-align: center;
  font-size: 0.875rem; color: var(--muted-foreground);
}

/* ---------- Die Uebersicht ueber die Bereiche (library.html) ----------
   Ein Abschnitt je Bereich, untereinander (seit 2026-10-16): eine weiße
   Karte mit Rand, links die Wolkenfläche mit dem Schaubild des Bereichs
   (dieselbe wie auf seiner Übersicht, Library.heroStack), rechts Kopf (Icon,
   Name, Anzahl, Anlegen und „Alle anzeigen"), Erklaerung und die zuletzt
   erstellten Eintraege untereinander. Die Eintraege stehen als Zeilen und
   nicht als Kacheln: drei Kacheln nebeneinander passten in die rechte
   Spalte nicht hin. */

.lib-areas { display: flex; flex-direction: column; gap: 1.25rem; }

/* Eine Karte (Abschnitt „Karte"), links die Wolkenfläche, rechts die Zeilen.
   scroll-margin, damit ein Sprung auf library.html#flows die Karte nicht
   unter den Rand schiebt. */
.lib-area {
  display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
  padding: 0.5rem;
  scroll-margin-top: 1rem;
}
/* Die Wolkenfläche wächst mit dem Text daneben, mindestens 18 rem. */
.lib-area-visual { height: auto; min-height: 18rem; }
.lib-area-main { min-width: 0; padding: 1.25rem 1.25rem 1.25rem 1.75rem; }
@media (max-width: 48rem) {
  .lib-area { grid-template-columns: minmax(0, 1fr); }
  .lib-area-visual { min-height: 13rem; }
  .lib-area-main { padding: 1.25rem 1rem 1rem; }
}

.lib-area-head { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; }
.lib-area-name { flex: 1 1 10rem; min-width: 0; display: flex; flex-direction: column; gap: 0.125rem; }

/* 17 px statt der 14 px einer Kartenueberschrift: sieben Karten stehen
   untereinander, und der Name ist das, woran das Auge beim Scrollen haengen
   bleiben soll. Die Stufe gibt es schon — beim Dialogtitel. */
.lib-area-title { font-size: 1.0625rem; font-weight: 600; line-height: 1.3; }
.lib-area-title a { color: inherit; text-decoration: none; }
.lib-area-title a:hover { text-decoration: underline; text-underline-offset: 0.15em; }

.lib-area-count {
  font-size: 0.75rem; color: var(--muted-foreground);
  font-variant-numeric: tabular-nums;
}

.lib-area-acts { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-left: auto; }

/* Erklaerung und Eintraege untereinander: Neben der Wolkenfläche bleibt der
   rechten Spalte zu wenig Breite für zwei Spalten. Ohne Linie zum Kopf —
   der Abstand trennt genug. Die Spalte ist minmax(0, 1fr), nicht die
   implizite auto-Spalte: die wuchs mit Titel und Beschreibung der Zeilen
   (nowrap) in voller Länge, und der Text ragte rechts aus der Karte. */
.lib-area-body {
  display: grid; grid-template-columns: minmax(0, 1fr); gap: 1.25rem;
  margin-top: 1rem;
}

.lib-area-lead { font-size: 0.875rem; line-height: 1.55; }

.lib-area-facts {
  list-style: none; margin-top: 0.875rem;
  display: flex; flex-direction: column; gap: 0.5rem;
  font-size: 0.8125rem; line-height: 1.5; color: var(--muted-foreground);
}
.lib-area-facts li { display: flex; align-items: flex-start; gap: 0.5rem; }
/* Das Icon sitzt auf der ersten Textzeile, nicht in der Mitte des Absatzes. */
.lib-area-facts svg { flex: none; width: 0.875rem; height: 0.875rem; margin-top: 0.1875rem; }
.lib-area-facts strong { font-weight: 500; color: var(--foreground); margin-right: 0.25rem; }

/* Eingerueckt wie der Inhalt der Zeilen darunter: Label und Icons stehen
   auf einer Kante. */
.lib-area-label {
  margin-bottom: 0.5rem; padding: 0 0.5rem;
  font-size: 0.6875rem; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted-foreground);
}

.lib-area-list { display: flex; flex-direction: column; gap: 0.25rem; }

/* Die ganze Zeile ist der Verweis — sie traegt keine Knoepfe. Feste Hoehe,
   auch ohne Beschreibung: sonst stuenden drei Zeilen je nach Inhalt
   verschieden hoch untereinander. */
.lib-area-row {
  display: flex; align-items: center; gap: 0.625rem;
  height: 3rem; padding: 0 0.5rem;
  border-radius: var(--radius-md);
  color: inherit; text-decoration: none;
  transition: background-color .12s;
}
.lib-area-row:hover { background: var(--accent); }
.lib-area-row:focus-visible { outline: var(--focus-ring); outline-offset: 1px; }

.lib-area-row-text { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.lib-area-row-title,
.lib-area-row-desc { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lib-area-row-title { font-size: 0.8125rem; font-weight: 500; }
.lib-area-row-desc { font-size: 0.75rem; color: var(--muted-foreground); }
.lib-area-row-date {
  flex: none; font-size: 0.75rem; color: var(--muted-foreground);
  font-variant-numeric: tabular-nums;
}

.lib-area-note { padding: 0.5rem; font-size: 0.8125rem; color: var(--muted-foreground); }

/* Leer und Fehler — die drei Teile aus DESIGN.md, nur enger als auf einer
   ganzen Seite: hier steht der Zustand in einer Spalte neben Text. */
.lib-area-empty {
  display: grid; justify-items: center; gap: 0.25rem;
  padding: 1.25rem 1rem; text-align: center;
  border: 1px dashed var(--border); border-radius: var(--radius-md);
}
.lib-area-empty svg { width: 1.25rem; height: 1.25rem; color: var(--muted-foreground); margin-bottom: 0.25rem; }
.lib-area-empty .btn svg { width: 1rem; height: 1rem; margin: 0; color: inherit; }
.lib-area-empty-title { font-size: 0.875rem; font-weight: 500; }
.lib-area-empty-text { font-size: 0.75rem; color: var(--muted-foreground); }
.lib-area-empty .btn { margin-top: 0.5rem; }

/* Die Hilfe-Kacheln auf home.html sind Verweise im Aussehen von .lib-card.
   Ohne das truege die Kachel als <a> den Unterstrich des Browsers: einen
   globalen Reset dafuer gibt es in diesem Stylesheet nicht. */
.lib-home-card { text-decoration: none; }

/* Eine Zeile mehr als in den Bereichen. Dort steht unter der Beschreibung
   noch einiges (Tags, Modell, Groesse), hier ist sie der ganze Inhalt. */
.lib-home-card .lib-card-desc { -webkit-line-clamp: 3; }

/* ---------- Tags ---------- */

.tag {
  display: inline-flex; align-items: center; gap: 0.25rem;
  padding: 0.125rem 0.5rem;
  border-radius: 999px;
  background: var(--secondary); color: var(--secondary-foreground);
  font-size: 0.75rem; line-height: 1.5;
}
.tag-sm { font-size: 0.6875rem; padding: 0.0625rem 0.4375rem; }

.tag-x {
  display: grid; place-items: center;
  width: 0.875rem; height: 0.875rem; margin-right: -0.125rem;
  border: 0; border-radius: 999px; padding: 0;
  background: transparent; color: inherit; opacity: 0.55; cursor: pointer;
}
.tag-x:hover { opacity: 1; }
.tag-x svg { width: 0.6875rem; height: 0.6875rem; }

/* Das Eingabefeld sieht aus wie ein .input, ist aber ein Kasten voller Chips
   mit einem mitwachsenden Feld am Ende. */
.tag-field {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.375rem;
  /* Höhe wie .input. Radius die halbe Höhe: einzeilig eine Pille wie das
     Feld, mit umgebrochenen Chips ein gerundetes Rechteck statt eines Ovals. */
  min-height: 2.5rem; padding: 0.4375rem 0.75rem;
  border: 1px solid var(--input); border-radius: 1.25rem;
  background: var(--background); cursor: text;
}
.tag-field:focus-within { border-color: var(--ring); box-shadow: 0 0 0 3px color-mix(in oklab, var(--ring) 20%, transparent); }
.tag-input {
  flex: 1 1 8rem; min-width: 6rem;
  border: 0; padding: 0.125rem 0; background: transparent;
  font: inherit; font-size: 0.875rem; color: inherit;
}
.tag-input:focus { outline: none; }

/* ---------- Detailseite ---------- */

/* So breit wie der Inhaltsbereich und damit wie das Farbfeld darüber. Bis
   2026-10-16 auf 56 rem begrenzt: neben dem breiteren Farbfeld endete das
   Formular rechts sichtbar früher. */
.lib-detail { display: flex; flex-direction: column; gap: 1.25rem; }

/* Die Abschnitte einer Einzelseite sind gewöhnliche Karten (Abschnitt
   „Karte"). Der Rand trennt sie, der Abstand dazwischen ist deshalb so
   knapp wie auf der Library-Übersicht. */
.lib-detail .settings-card { padding: 1.5rem; }
.lib-detail .settings-card-title { font-size: 1rem; }

/* Seitenkopf (.lib-detail-head): Zurueck, Titel, Zustand, Speichern. Der
   Zustand steht neben dem Knopf und nicht darin — ein Knopf, der seine
   Beschriftung wechselt, laesst die Leiste springen. */
.lib-status { font-size: 0.75rem; color: var(--muted-foreground); white-space: nowrap; }
.lib-status.is-dirty { color: var(--warning-foreground); }
/* Schmale Fenster: der Status faellt weg, und der Nebenknopf („Chat starten“,
   „Starten“) gibt seine Beschriftung her und bleibt als Zeichen stehen.
   Ohne das frisst die Knopfreihe die ganze Kopfzeile: auf einem 375-px-Telefon
   blieben vom Titel der Seite vier Pixel, auf einem 320-px-Telefon stand
   „Speichern“ halb ausserhalb des Bildschirms. */
@media (max-width: 40rem) {
  .lib-status { display: none; }
  .lib-page-actions .btn-outline span:not([data-icon]) { display: none; }
  .lib-page-actions .btn-outline { width: 2rem; padding: 0; }
}

/* Icon und Farbe stehen nebeneinander: das gewaehlte Icon in der gewaehlten
   Farbe ist die Vorschau, die Palette daneben die Auswahl. */
.lib-appearance { display: flex; align-items: flex-start; gap: 1rem; flex-wrap: wrap; }
.lib-appearance-icon { display: flex; flex-direction: column; gap: 0.5rem; align-items: flex-start; }
.lib-appearance-colors { flex: 1; min-width: 12rem; display: flex; flex-direction: column; gap: 0.5rem; }

.swatches { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.swatch {
  width: 1.5rem; height: 1.5rem; flex: none;
  border: 0; border-radius: 999px; padding: 0;
  background: var(--lib-color); cursor: pointer;
  transition: transform .1s;
}
.swatch:hover { transform: scale(1.12); }
.swatch.selected { outline: 2px solid var(--lib-color); outline-offset: 2px; }

/* Das Icon-Raster im Dialog. Es fuellt den Dialogkoerper, nicht umgekehrt:
   die Hoehe kommt von .dialog-tall, gescrollt wird in .dialog-body. */
.icon-grid {
  display: grid; gap: 0.25rem;
  grid-template-columns: repeat(auto-fill, minmax(2.5rem, 1fr));
  padding: 0.125rem;
}

/* Eine Ueberschrift ueber einer Gruppe („Empfohlen", „Alle") laeuft ueber
   die ganze Rasterbreite, statt eine Zelle zu belegen. */
.icon-grid .command-group-label { grid-column: 1 / -1; padding-left: 0.25rem; }
.icon-grid .command-empty { grid-column: 1 / -1; }

.icon-choice {
  aspect-ratio: 1; display: grid; place-items: center;
  border: 1px solid transparent; border-radius: var(--radius-md);
  background: transparent; color: var(--muted-foreground); cursor: pointer;
  transition: background-color .1s, color .1s;

  /* Der vollstaendige Hugeicons-Satz sind rund sechstausend Kacheln mit je
     einem SVG darin. Ausserhalb des sichtbaren Bereichs zeichnet der Browser
     sie damit gar nicht erst; contain-intrinsic-size haelt trotzdem den Platz
     frei, damit die Bildlaufleiste stimmt und nichts springt. */
  content-visibility: auto;
  contain-intrinsic-size: 2.5rem;
}
.icon-choice:hover { background: var(--accent); color: var(--foreground); }
.icon-choice svg { width: 1.125rem; height: 1.125rem; }
.icon-choice.selected {
  color: var(--lib-color, var(--foreground));
  border-color: color-mix(in oklab, var(--lib-color, var(--ring)) 45%, transparent);
  background: color-mix(in oklab, var(--lib-color, var(--ring)) 14%, transparent);
}

/* ---------- Ausgewaehlte Skills, Wissenselemente und Dokumente ----------
   Eine Zeile ist ueberall gleich gebaut: Icon, zweizeiliger Text, rechts die
   Knoepfe. Nur der Inhalt unterscheidet sich. */

.pick-list { display: flex; flex-direction: column; gap: 0.375rem; }

.pick-row {
  display: flex; align-items: center; gap: 0.625rem;
  padding: 0.5rem 0.625rem;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--background);
}
.pick-row-icon {
  flex: none; display: grid; place-items: center;
  width: 1.75rem; height: 1.75rem; color: var(--muted-foreground);
}
.pick-row-icon svg { width: 1rem; height: 1rem; }
.pick-row-main { flex: 1; min-width: 0; }
.pick-name {
  display: block; font-size: 0.875rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pick-sub {
  display: block; font-size: 0.75rem; color: var(--muted-foreground);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pick-row-actions { display: flex; gap: 0.25rem; flex: none; }
.pick-row-actions .btn { width: 1.75rem; height: 1.75rem; color: var(--muted-foreground); }
.pick-row-actions .btn svg { width: 0.875rem; height: 0.875rem; }

.pick-empty {
  padding: 0.875rem; text-align: center;
  border: 1px dashed var(--input); border-radius: var(--radius-md);
  font-size: 0.8125rem; color: var(--muted-foreground);
}

/* Auswahlliste im Dialog: dieselbe Zeile, nur mit dem Symbol der Art davor
   (Skill, Wissen, Prompt) — gewaehlt heisst: Symbol voll und Rahmen in --ring.

   Den Abstand setzt hier der Nachbar-Selektor und nicht wie bei .pick-list ein
   gap: die Zeilen stehen in .command-list, und die haelt ihre Kinder bewusst
   auf Stoss — dort stehen sonst rahmenlose Befehlszeilen. Eine Zeile mit
   Rahmen klebt darin an der naechsten. */
.pick-choice { width: 100%; text-align: left; cursor: pointer; }
.pick-choice + .pick-choice { margin-top: 0.375rem; }
.pick-choice:hover { background: var(--accent); }
.pick-choice .pick-row-icon { color: var(--muted-foreground); opacity: 0.35; }
.pick-choice.selected { border-color: var(--ring); }
.pick-choice.selected .pick-row-icon { opacity: 1; color: var(--foreground); }

/* ---------- Werkzeug-Zeilen ----------
   Kaestchen rechts, Beschriftung buendig mit allem anderen in der Karte.

   In chat.html steht das Kaestchen vorn, und das ist dort richtig: der Dialog
   besteht an der Stelle nur aus Checkboxen, die Zeile reisst gegen nichts aus.
   Hier stehen darueber „KI-Modell", „Kreativitaet" und „Werkzeuge" alle am
   selben linken Rand — eine Zeile, die 26 px weiter rechts anfaengt, sieht
   daneben schlicht verrutscht aus.

   Deshalb eine Ergaenzung zu .check-row und keine Aenderung daran: der Chat
   soll bleiben, wie er ist. */
.check-row-end { justify-content: space-between; gap: 1rem; }
.check-row-end input { order: 2; margin-top: 0.125rem; }
.check-row-end .check-text { flex: 1; min-width: 0; }

/* Die Unterzeile haengt jetzt an einer buendigen Beschriftung, nicht mehr an
   einem eingerueckten Text — der Versatz von 1.625rem zielte auf das Kaestchen,
   das hier nicht mehr links steht. Die Linie bleibt: sie sagt, dass die Stufe
   zur Zeile darueber gehoert. */
.check-row-end + .field-sub { margin-left: 0; }

/* ---------- Gespraechseinstiege ---------- */

.starter-row { display: flex; align-items: center; gap: 0.5rem; }
.starter-row + .starter-row { margin-top: 0.375rem; }
.starter-n {
  flex: none; width: 1.25rem;
  font-size: 0.75rem; color: var(--muted-foreground); text-align: right;
  font-variant-numeric: tabular-nums;
}

/* ---------- Dokumente eines Wissenselements ---------- */

.dropzone {
  display: flex; flex-direction: column; align-items: center; gap: 0.5rem;
  padding: 1.5rem 1rem;
  border: 1px dashed var(--input); border-radius: var(--radius-md);
  color: var(--muted-foreground); font-size: 0.8125rem; text-align: center;
  transition: border-color .12s, background-color .12s;
}
.dropzone svg { width: 1.25rem; height: 1.25rem; }
.dropzone.over { border-color: var(--ring); background: var(--accent); color: var(--foreground); }

/* ==========================================================================
   Flows (flow.html, flow-chat.html)

   Ein Flow ist ein Bild, bevor er eine Liste ist: oben das Eingabefeld,
   unten die Ausgabe, dazwischen die Schritte. Gebaut wird das von
   Library.flowGraphHtml() aus absolut gesetzten Kacheln ueber einem SVG —
   deshalb stehen hier nur Farben, Kanten und Abstaende; die Koordinaten
   rechnet das JavaScript aus und schreibt sie als inline style.

   Der Rahmen scrollt waagerecht, falls viele Schritte nebeneinander stehen.
   Eine Kette waechst nach unten und braucht das nicht mehr.
   ========================================================================== */

/* Der Ablauf liegt auf einer eigenen, leicht grauen Flaeche — die Buehne
   des Diagramms, abgesetzt von den Formularfeldern der Karte. Darauf stehen
   die Schritte weiss. Dunkel waere --muted (0.269) heller als die weisse
   Kachel (--card, 0.205), und die Kacheln saehen aus wie Loecher; dort sinkt
   die Flaeche deshalb auf --background (0) ab. Die Karte ringsum setzt
   --card auf --background — dunkel waeren die Kacheln dann so schwarz wie
   die Flaeche; dort stehen sie deshalb auf --muted. */
.flow-graph {
  --flow-ground: var(--muted);
  overflow-x: auto; overflow-y: hidden;
  padding: 0.75rem;
  background: var(--flow-ground);
  border: 1px solid var(--border); border-radius: var(--radius-lg);
}
.dark .flow-graph { --flow-ground: var(--background); --card: var(--muted); }

/* Mittig im Rahmen. Ist das Bild breiter, wird margin: auto zu 0, und der
   Rahmen scrollt von links an — nichts wird abgeschnitten. */
.flow-canvas { position: relative; margin: 0 auto; }

.flow-edges { position: absolute; left: 0; top: 0; overflow: visible; }
.flow-edges path {
  fill: none;
  stroke: color-mix(in oklab, var(--muted-foreground) 55%, transparent);
  stroke-width: 1.5;
  /* Die Linien laufen rechtwinklig; ohne runde Enden und Ecken sehen die
     Knicke bei 1.5px Staerke ausgefranst aus. */
  stroke-linejoin: round; stroke-linecap: round;
}
.flow-edges marker path {
  fill: color-mix(in oklab, var(--muted-foreground) 55%, transparent);
  stroke: none;
}

/* Eine Kachel im Ablauf. Sie ist ein <div> mit role="button": Knoepfe im
   Knopf gaebe es hier zwar nicht, aber die Kachel liegt ueber dem SVG und
   soll sich wie eine Flaeche verhalten, nicht wie ein Formularelement.

   Grund ist --card: weiss auf der grauen Flaeche des Ablaufs. */
.flow-node {
  position: absolute;
  display: flex; flex-direction: column; justify-content: space-between;
  gap: 0.375rem; padding: 0.5rem 0.625rem;
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--card); color: inherit; text-align: left;
  box-shadow: var(--shadow-xs);
  cursor: pointer;
  transition: border-color .12s, box-shadow .12s, background-color .12s;
}
.flow-node:hover { border-color: var(--ring); box-shadow: var(--shadow-sm); }

/* Der Stift in der Kopfzeile (nur flow.html): sagt beim Ueberfahren, dass
   ein Klick die Einstellungen oeffnet. opacity statt display, damit der
   Titel beim Ueberfahren nicht kuerzer wird und springt. Ohne Zeiger steht
   er dauerhaft da — siehe den (hover: none)-Block am Dateiende. */
.flow-node-edit {
  flex: none; display: grid; place-items: center;
  color: var(--muted-foreground);
  opacity: 0; transition: opacity .12s;
}
.flow-node-edit svg { width: 0.875rem; height: 0.875rem; }
.flow-node:hover .flow-node-edit,
.flow-node:focus-visible .flow-node-edit,
.flow-node.selected .flow-node-edit { opacity: 1; }

/* Schritt hinzufuegen — rund, auf der Linie in die Ausgabe. Rund, weil es
   weder Schritt (Kachel) noch Ende (Pille) ist; deckend, damit die Linie
   hinter ihm verschwindet und er auf ihr zu sitzen scheint. */
.flow-add {
  position: absolute;
  display: grid; place-items: center; padding: 0;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--card); color: var(--muted-foreground);
  box-shadow: var(--shadow-xs);
  cursor: pointer;
  transition: border-color .12s, box-shadow .12s, color .12s;
}
.flow-add svg { width: 0.875rem; height: 0.875rem; }
.flow-add:hover { border-color: var(--ring); color: var(--foreground); box-shadow: var(--shadow-sm); }

/* ---------- Ziehen (flow.html) ----------
   Die gezogene Kachel haengt am Zeiger (transform aus js/flow.js), liegt
   ueber allem und laesst Zeigerereignisse durch. Die Marken liegen darunter,
   die Kachel ist dafuer leicht durchscheinend: man sieht, wohin sie faellt.
   Andersherum lag die gefangene Marke als harter Balken quer ueber dem
   Titel. z-index 2/1 innerhalb der Leinwand, siehe Stapelordnung. */
.flow-canvas .flow-node[data-step] { user-select: none; -webkit-user-select: none; }
.flow-node.dragging {
  z-index: 2;
  opacity: .8;
  box-shadow: var(--shadow-lg);
  pointer-events: none;
  transition: none;
}
.flow-canvas.is-dragging { cursor: grabbing; }
.flow-canvas.is-dragging .flow-node-edit { opacity: 0; }

/* Die Ablagemarken: unsichtbar, bis gezogen wird; dann blass alle, die
   etwas veraendern wuerden, und kraeftig die, die den Zeiger gerade faengt.
   Waagerecht unter bzw. ueber einer Kachel, senkrecht in einer Gasse. */
.flow-drop {
  position: absolute; z-index: 1;
  height: 4px; transform: translate(-50%, -50%);
  border-radius: 999px;
  background: color-mix(in oklab, var(--primary) 22%, transparent);
  pointer-events: none;
  display: none;
}
.flow-drop-v { width: 4px; }
.flow-canvas.is-dragging .flow-drop { display: block; }
.flow-canvas.is-dragging .flow-drop.is-noop { display: none; }
.flow-drop.active {
  background: var(--primary);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--primary) 18%, transparent);
}
.flow-node:focus-visible { outline: var(--focus-ring); outline-offset: 1px; }
.flow-node.selected {
  border-color: var(--primary);
  box-shadow: 0 0 0 1px var(--primary);
}

/* Ein Schritt, dessen Ausgabe nur weitergereicht wird, steht nicht in der
   Antwort — das soll man ihm ansehen, ohne den Text zu lesen. Er bleibt
   weiss: er ist ein Schritt wie jeder andere, nur sein Ergebnis ist leise. */
.flow-node-quiet { border-style: dashed; border-color: var(--input); }

.flow-node-head { display: flex; align-items: center; gap: 0.4375rem; min-width: 0; }
.flow-node-n {
  flex: none; min-width: 1rem;
  font-size: 0.6875rem; color: var(--muted-foreground);
  font-variant-numeric: tabular-nums;
}
.flow-node-title {
  flex: 1; min-width: 0;
  font-size: 0.8125rem; font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.flow-node-foot {
  display: flex; align-items: center; gap: 0.375rem; min-width: 0;
  font-size: 0.6875rem; color: var(--muted-foreground);
}
.flow-node-model { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.flow-node-badges { display: flex; gap: 0.25rem; flex: none; }
.flow-badge { display: grid; place-items: center; color: var(--muted-foreground); }
.flow-badge svg { width: 0.75rem; height: 0.75rem; }

/* Eingabe und Ausgabe. Keine Schritte, sondern die beiden Enden — deshalb
   als Pille statt als weisse Kachel, ohne Hover und ohne Klick.

   Hell ist der Grund weiss wie ein Schritt (--card), dunkel eine Stufe heller
   als die Flaeche (Vordergrund zu 10 % hineingemischt). Dazu ein kraeftigerer
   Rand und Text in --foreground: Im Grau der Flaeche und in
   --muted-foreground war die Pille kaum zu sehen — und Anfang und Ende sind
   die beiden Punkte, an denen man das Bild zu lesen beginnt. */
.flow-endpoint {
  flex-direction: row; align-items: center; justify-content: center; gap: 0.4375rem;
  padding: 0 0.75rem;
  background: var(--card);
  border-color: color-mix(in oklab, var(--foreground) 22%, var(--flow-ground));
  border-radius: 999px;
  color: var(--foreground); font-size: 0.75rem; font-weight: 500;
  box-shadow: none; cursor: default;
}
.flow-endpoint:hover {
  border-color: color-mix(in oklab, var(--foreground) 22%, var(--flow-ground));
  box-shadow: none;
}
.dark .flow-endpoint { background: color-mix(in oklab, var(--foreground) 10%, var(--flow-ground)); }
.flow-endpoint-icon { display: grid; place-items: center; color: var(--muted-foreground); }
.flow-endpoint-icon svg { width: 0.875rem; height: 0.875rem; }

.flow-empty {
  padding: 1.5rem 1rem; text-align: center;
  border: 1px dashed var(--input); border-radius: var(--radius-md);
  font-size: 0.8125rem; color: var(--muted-foreground);
}

/* ---------- Die Schritte als Liste (Panel im Flow-Chat) ---------- */

.flow-steps { display: flex; flex-direction: column; gap: 0.375rem; }

.flow-step {
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.4375rem 0.5rem;
  border: 1px solid var(--border); border-radius: var(--radius-md);
}
.flow-step-n {
  flex: none; width: 1rem; text-align: right;
  font-size: 0.6875rem; color: var(--muted-foreground);
  font-variant-numeric: tabular-nums;
}
.flow-step-text { min-width: 0; flex: 1; }
.flow-step-title {
  display: block; font-size: 0.8125rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.flow-step-sub {
  display: block; font-size: 0.6875rem; color: var(--muted-foreground);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Das Panel im Flow-Chat traegt zwei Bloecke untereinander und scrollt —
   anders als das Prompt-Panel, in dem ein einziges Feld die Hoehe fuellt. */
.side-panel-body-stack {
  flex-direction: column; gap: 0.75rem;
  overflow-y: auto;
}
.flow-graph-panel { flex: none; }

/* ---------- Die Einstellungen eines Schritts (flow.html) ----------
   Ein Dialog, keine Karte unter dem Ablauf: wer auf eine Kachel klickt, will
   genau diesen Schritt einstellen und dabei nicht wegscrollen. Breiter als
   ein gewoehnlicher Dialog, weil darin Schalterleisten mit drei langen
   Beschriftungen stehen. Die Hoehe waechst mit dem Inhalt bis an den Rand
   des Fensters; danach scrollt der Koerper, Kopf und Fuss bleiben stehen. */
.dialog-step { max-width: 40rem; }

/* ---------- Der Stand eines Durchlaufs ----------
   Steht unter der wachsenden Antwort. Ohne sie saehe ein Flow mit einem
   langen zweiten Schritt aus wie ein haengender Chat. */

.flow-run {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.375rem;
  margin: 0.25rem 0 0.5rem;
  font-size: 0.75rem; color: var(--muted-foreground);
}
.flow-run-label { display: inline-flex; align-items: center; gap: 0.3125rem; }
.flow-run-label svg { width: 0.8125rem; height: 0.8125rem; }

.flow-run-step {
  display: inline-flex; align-items: center; gap: 0.3125rem;
  padding: 0.125rem 0.5rem;
  border: 1px solid var(--border); border-radius: 999px;
  max-width: 14rem;
}
.flow-run-step svg { width: 0.75rem; height: 0.75rem; flex: none; }
.flow-run-step span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.flow-run-step.is-wait { opacity: 0.55; }
.flow-run-step.is-active {
  border-color: var(--primary); color: var(--foreground);
  background: color-mix(in oklab, var(--primary) 12%, transparent);
}
.flow-run-step.is-done { color: var(--foreground); }
.flow-run-step.is-error {
  border-color: var(--destructive); color: var(--destructive);
}

/* ---------- Kleinkram in den Dialogen ---------- */

/* „Name + Anlegen" nebeneinander im Dialog „Schritt hinzufuegen". */
.row-inline { display: flex; gap: 0.5rem; align-items: center; }
.row-inline .input { flex: 1; min-width: 0; }

/* Ueber der Liste im selben Dialog: der obere Block soll nicht die halbe
   Hoehe fuer sich beanspruchen. */
.dialog-body-tight { flex: none; padding-bottom: 0.75rem; }

/* ==========================================================================
   HTML Apps (html-apps.html, html-app.html, html-app-run.html)

   Eine HTML App laeuft in einem <iframe> ohne allow-same-origin — einem
   eigenen, undurchsichtigen Ursprung. Warum, steht bei Library.htmlApps.mount
   in js/library.js; hier steht nur, wie der Rahmen darum aussieht.

   Und das ist die eigentliche Regel dieses Abschnitts: Der Rahmen faerbt
   nichts. Was in dem iframe steht, bringt sein eigenes Aussehen mit, und die
   Vorschau soll es zeigen und nicht kommentieren. Deshalb weisser Grund
   statt --card, auch im dunklen Theme: eine App ohne eigene Hintergrundfarbe
   ist durchsichtig, und sie wuerde sonst je nach Theme anders aussehen als
   im eigenen Fenster.
   ========================================================================== */

.app-frame {
  display: block; width: 100%; height: 100%;
  border: 0; background: #fff;
}

/* ---------- Die Vorschau im Editor ---------- */

.app-preview {
  height: 26rem;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  overflow: hidden;
  background: #fff;
}

@media (max-width: 48rem) { .app-preview { height: 18rem; } }

/* Das Dokument selbst. Feste Schrittweite, keine Silbentrennung, und der
   Zeilenumbruch bleibt aus (wrap="off" am Element): eine umgebrochene
   Codezeile sieht aus wie zwei. */
.code-area {
  font-family: var(--font-mono); font-size: 0.8125rem; line-height: 1.55;
  tab-size: 2; white-space: pre; overflow: auto;
}

/* ---------- Das Fenster, in dem eine App laeuft ---------- */

/* Kein .layout, keine Seitenleiste: dieses Fenster ist das Gehaeuse um eine
   fremde Seite. Die Leiste oben ist alles, was von Meteor-AI uebrig bleibt —
   schmaler als eine .topbar, damit die App den Platz bekommt. */
.app-run { display: flex; flex-direction: column; height: 100dvh; }

.app-run-bar {
  display: flex; align-items: center; gap: 0.5rem;
  height: 2.75rem; flex: none;
  padding: 0 0.5rem 0 0.75rem;
  border-bottom: 1px solid var(--border);
  background: var(--background);
}
.app-run-spacer { flex: 1; }

.app-run-title {
  min-width: 0;
  font-size: 0.8125rem; font-weight: 500;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.app-run-stage { flex: 1; min-height: 0; background: #fff; }

/* „Wird geladen", „gibt es nicht mehr", ein Fehler aus der Datenbank. Steht
   auf dem Grund der Buehne, weil dort sonst die App stuende. */
.app-run-note {
  display: grid; place-items: center; height: 100%;
  padding: 2rem 1.5rem; text-align: center;
  font-size: 0.875rem; color: var(--muted-foreground);
  background: var(--background);
}

/* ==========================================================================
   Admin (admin.html)

   Die Seite baut sonst nur aus vorhandenen Teilen: die Reiter wie
   `settings.html`, alles darin aus dem `stats-*`-Satz von `statistics.html`.
   Eigenes braucht nur die Kontotabelle — sie hat als einzige Knoepfe in den
   Zeilen.
   ========================================================================== */

/* Sieben Spalten passen bei 1280 px Fensterbreite nicht in den
   Inhaltsbereich; die Tabelle scrollt dann waagerecht wie die auf der
   Statistikseite. Ausgerechnet die Knoepfe stuenden damit ausserhalb des
   Sichtfelds — also bleibt ihre Spalte am rechten Rand stehen. Der Schatten
   links davon zeigt, dass darunter noch etwas weiterlaeuft. */
.admin-cell {
  position: sticky; right: 0;
  background: var(--card);
  box-shadow: -1px 0 0 var(--border);
}
.stats-table tbody tr:hover td.admin-cell { background: var(--accent); }

/* Nicht umbrechen: sonst steht je Knopf eine Zeile und die Tabelle wird
   doppelt so hoch wie ihr Inhalt. */
.admin-actions { display: inline-flex; gap: 0.25rem; flex-wrap: nowrap; }

/* Fehlermeldungen sind laenger als Chattitel — `stats-clip` mit seinen
   16 rem schnitte die meisten schon vor dem Verb ab. Breiter, aber nicht
   unbegrenzt: was hier nicht hineinpasst, steht vollstaendig im Dialog
   hinter dem Auge am Zeilenende. */
.admin-msg { max-width: 30rem; overflow: hidden; text-overflow: ellipsis; }

@media (max-width: 900px) {
  .admin-msg { max-width: 16rem; }
}

/* ==========================================================================
   Info & Support (support.html)

   Wie admin.html baut die Seite fast nur aus Vorhandenem: die Reiter wie
   `settings.html`, die Modelltabellen aus dem `stats-*`-Satz von
   `statistics.html`. Eigenes braucht nur zweierlei — der Aufklapper fuer
   eine Anleitung und die Begriffsliste darueber.
   ========================================================================== */

/* ---------- Begriffe ---------- */

/* Zweispaltig, solange Platz ist: der Begriff steht dann neben seiner
   Erklaerung und nicht darueber, und die Liste liest sich als Liste. */
.support-terms { display: grid; gap: 0.875rem; }
.support-term { display: grid; grid-template-columns: 9rem 1fr; gap: 0.75rem; align-items: start; }
.support-term dt {
  display: flex; align-items: center; gap: 0.375rem;
  font-size: 0.8125rem; font-weight: 600;
}
.support-term dt svg { width: 0.875rem; height: 0.875rem; color: var(--muted-foreground); flex: none; }
.support-term dd {
  margin: 0;
  font-size: 0.8125rem; color: var(--muted-foreground); line-height: 1.6;
}
.support-term dd b { color: var(--foreground); font-weight: 500; }

/* ---------- Aufklapper: eine Anleitung, eine eigene Anfrage ----------

   Nachgebaut ist der Gedankengang im Chat (.reasoning) — dasselbe Verhalten
   soll gleich aussehen. Anders ist nur der Rahmen: hier stehen mehrere
   untereinander in einer Karte, dort steht einer allein ueber der Antwort.
   Deshalb keine Flaeche, sondern nur eine Trennlinie zwischen ihnen. */
.guide { border-top: 1px solid var(--border); }
.guide:first-of-type { border-top: 0; }
.guide > summary {
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.6875rem 0.125rem; cursor: pointer;
  font-size: 0.8125rem; list-style: none;
}
.guide > summary::-webkit-details-marker { display: none; }
.guide > summary:hover { color: var(--primary); }
.guide > summary > [data-icon] svg { width: 0.9375rem; height: 0.9375rem; color: var(--muted-foreground); flex: none; }
.guide-title { flex: 1; min-width: 0; font-weight: 500; }
.guide-chevron { margin-left: auto; transition: transform .15s; }
.guide-chevron svg { width: 0.875rem; height: 0.875rem; color: var(--muted-foreground); }
.guide[open] .guide-chevron { transform: rotate(180deg); }

.guide-body {
  padding: 0 0.125rem 0.875rem;
  font-size: 0.8125rem; color: var(--muted-foreground); line-height: 1.65;
}
.guide-body > :first-child { margin-top: 0; }
.guide-body > :last-child { margin-bottom: 0; }
.guide-body p { margin: 0 0 0.625rem; }
.guide-body ol, .guide-body ul { margin: 0 0 0.625rem; padding-left: 1.25rem; display: grid; gap: 0.375rem; }
.guide-body li { padding-left: 0.125rem; }
.guide-body b { color: var(--foreground); font-weight: 500; }
.guide-body .hint { margin-top: 0.75rem; }

/* ---------- Modelltabellen ---------- */

.support-search { flex: 1; min-width: 0; max-width: 22rem; }

.support-model { display: block; font-weight: 500; }
.support-id {
  display: block; margin-top: 0.125rem;
  font-family: var(--font-mono); font-size: 0.6875rem; color: var(--muted-foreground);
}

.support-abilities { display: inline-flex; flex-wrap: wrap; gap: 0.25rem; }
.support-abilities .badge svg { width: 0.6875rem; height: 0.6875rem; }
.support-none { font-size: 0.75rem; color: var(--muted-foreground); }

.support-empty { text-align: center; color: var(--muted-foreground); padding: 1.5rem 0; }

/* ---------- Support-Karten (support.html und admin.html) ----------

   Eine Karte je Anfrage, gebaut von js/support-card.js — dieselbe auf
   beiden Seiten. Regeln und Gruende: DESIGN.md, „Support-Karten". */

.support-list { display: flex; flex-direction: column; gap: 1rem; }

.support-card {
  display: grid; gap: 0.5rem;
  padding: 1.25rem 1.5rem;
}
/* Noch von keinem Admin geöffnet: ein Ring halb in --primary, dazu der Punkt
   vor dem Betreff (.dot-before). Der Ring allein waere zu leise, der Punkt
   allein auf einer langen Liste zu klein. Als box-shadow, weil die Karte
   keinen Rand mehr hat und einer sie um 2 px vergrößerte. */
.support-card-unseen { box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--primary) 45%, transparent); }

/* Der Stand steht vor dem Betreff und nicht dahinter: danach sucht das Auge
   beim Ueberfliegen der Liste, nicht nach dem Text. */
/* Der Betreff bricht um statt abzuschneiden: er ist hoechstens 200 Zeichen
   lang, und in einer Karte ist Platz fuer eine zweite Zeile. */
/* Oben ausgerichtet: bricht der Betreff um, stehen Stand, Zeitpunkt und
   Menue auf Hoehe seiner ersten Zeile und nicht mittig neben dem Absatz. */
.support-card-head { display: flex; align-items: flex-start; gap: 0.625rem; min-width: 0; }
.support-card-head > .support-state { margin-top: 0.0625rem; }
.support-card-title {
  flex: 1; min-width: 0;
  font-size: 0.875rem; font-weight: 600; line-height: 1.4;
  overflow-wrap: anywhere;
}
.support-card-when {
  flex: none; font-size: 0.6875rem; line-height: 1.25rem; color: var(--muted-foreground);
  font-variant-numeric: tabular-nums;
}
.support-card-menu { flex: none; display: inline-flex; margin-top: -0.125rem; }

.support-card-meta { font-size: 0.75rem; color: var(--muted-foreground); }
/* Die Ticket-ID: Mono wie andere Kennungen (.support-id), in der Textfarbe —
   sie ist das, was man abschreibt oder vorliest. */
.support-card-ticket {
  font-family: var(--font-mono); font-size: 0.6875rem; color: var(--foreground);
  font-variant-numeric: tabular-nums;
}
.support-card-gone { font-style: italic; }

.support-card-body { font-size: 0.8125rem; line-height: 1.55; }

.support-card-note {
  display: flex; align-items: flex-start; gap: 0.375rem;
  font-size: 0.75rem; color: var(--muted-foreground);
}
.support-card-note svg { width: 0.875rem; height: 0.875rem; flex: none; margin-top: 0.0625rem; }
.support-card-note b { font-weight: 600; }

.support-card-wait { font-size: 0.75rem; color: var(--muted-foreground); }

.support-card-foot { display: flex; gap: 0.5rem; padding-top: 0.25rem; }

.support-state {
  flex: none; padding: 0.0625rem 0.4375rem;
  border-radius: 999px; border: 1px solid var(--border);
  font-size: 0.6875rem; font-weight: 600; color: var(--muted-foreground);
}
/* Review ist der eine Stand, bei dem der Absender etwas tun soll — lesen.
   Deshalb gefüllt wie die Zahl in der Seitenleiste, die ihn hierher führt. */
.support-state-review {
  border-color: var(--primary); background: var(--primary); color: var(--primary-foreground);
}
.support-state-erledigt { opacity: .7; }

/* Die Antwort des Supports unter der Anfrage. Abgesetzt durch eine Linie
   links, wie .field-sub: sie gehört zur Anfrage, ist aber nicht ihr Text. */
.support-reply {
  padding: 0.625rem 0.75rem;
  border-left: 2px solid var(--primary); border-radius: 0 var(--radius-md) var(--radius-md) 0;
  background: var(--muted);
  font-size: 0.8125rem; line-height: 1.55;
}
.support-reply-head { font-size: 0.75rem; font-weight: 600; margin-bottom: 0.25rem; }

/* Der Text, wie er abgeschickt wurde — Absaetze und Zeilenumbrueche bleiben
   stehen, sonst laese sich eine Fehlerbeschreibung nicht wiedererkennen.
   overflow-wrap: eine eingefuegte Adresse ohne Leerzeichen sprengte sonst
   die Karte. */
.support-text { white-space: pre-wrap; overflow-wrap: anywhere; }

/* ---------- Schmale Fenster ---------- */

@media (max-width: 640px) {
  /* Untereinander: 9rem fuer den Begriff und daneben noch ein lesbarer Satz
     gehen sich auf einem Telefon nicht aus. */
  .support-term { grid-template-columns: 1fr; gap: 0.25rem; }
  .support-search { max-width: none; }

  /* Stand, Betreff, Zeitpunkt und Menue in einer Zeile schnitten den Betreff
     auf dem Telefon nach wenigen Zeichen ab. Er bekommt deshalb eine eigene
     Zeile darunter und darf umbrechen. */
  .support-card-head { flex-wrap: wrap; row-gap: 0.375rem; }
  .support-card-when { margin-left: auto; }
  .support-card-title { flex-basis: 100%; order: 3; }
}

/* ==========================================================================
   Willkommen und Meldungen (js/notices.js, js/clouds.js)

   Zwei Dialoge mit einer Wolkenfläche oben: das Willkommen nach der
   Registrierung (sechs Schritte) und die Meldungen, die ein Superadmin an
   alle schreibt. Die Fläche ist die der Website — dieselben Wolken, dieselben
   Paletten, darauf ein Schaubild aus der App oder das Logo.
   ========================================================================== */

/* Wolken. Dekoration, keine Tokens: Die Paletten stehen nur hier, je fünf
   Farben von dunkel nach hell, js/clouds.js liest sie aus --cloud-1 … -5.
   Werte wie im css/site.css der Website — dort ist ihre Begründung. Fehlen
   Skript oder WebGL, bleibt der Verlauf aus denselben Farben stehen. Der
   Schleier in --background (hell 35 %, dunkel 50 %) nimmt den Wolken im
   dunklen Design das Leuchten. */
[data-clouds="aurora"] { --cloud-1: #1a6fe3; --cloud-2: #6699e6; --cloud-3: #80b3ff; --cloud-4: #b3d9ff; --cloud-5: #e6f2ff; }
[data-clouds="iris"]   { --cloud-1: #6f78d8; --cloud-2: #9196e6; --cloud-3: #b0b4f2; --cloud-4: #d7d8fa; --cloud-5: #f3f3ff; }
[data-clouds="lagoon"] { --cloud-1: #3f8fc4; --cloud-2: #62aec6; --cloud-3: #93cfd6; --cloud-4: #c6e9ea; --cloud-5: #eef9f9; }
[data-clouds="dusk"]   { --cloud-1: #8a78d0; --cloud-2: #a996dc; --cloud-3: #c6b3e6; --cloud-4: #e3d8f2; --cloud-5: #f8f4fc; }
[data-clouds="mist"]   { --cloud-1: #6f87a8; --cloud-2: #8fa3bf; --cloud-3: #b2c2d6; --cloud-4: #d6e0eb; --cloud-5: #f3f6f9; }
[data-clouds] {
  position: relative; isolation: isolate; overflow: hidden;
  background: linear-gradient(0deg, var(--cloud-2), var(--cloud-4) 70%, var(--cloud-5));
}
[data-clouds]::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: color-mix(in oklab, var(--background) 35%, transparent);
}
.dark [data-clouds]::before { background: color-mix(in oklab, var(--background) 50%, transparent); }
.clouds {
  position: absolute; inset: 0; z-index: -2; width: 100%; height: 100%;
  opacity: 0; transition: opacity .6s ease;
}
.clouds[data-ready] { opacity: 1; }

/* Der Dialog: die Fläche läuft bis an die runden Ecken, deshalb schneidet
   er ab. Gescrollt wird in .dialog-body bzw. .welcome-slides. */
.notice-dialog { overflow: hidden; }
.welcome-dialog { max-width: 40rem; }

/* Das Kreuz liegt auf den Wolken und braucht dort einen Grund. */
.notice-x {
  z-index: 1;
  background: color-mix(in oklab, var(--popover) 75%, transparent);
  color: var(--foreground);
  border-radius: 999px;
}
.notice-x:hover { background: var(--popover); }

.notice-hero {
  display: flex; align-items: center; justify-content: center;
  height: 15rem; flex: none;
}
.notice-hero.is-short { height: 8.5rem; }
/* Mit Schaubild: das Fenster steht unten auf der Fläche und läuft dort aus,
   wie auf der Funktionsseite. contain: size am Fenster, damit es die Fläche
   füllt statt sie aufzudrücken. */
.notice-hero.has-mock { align-items: flex-end; padding: 2rem 2.5rem 0; }

.notice-hero-brand { display: flex; align-items: center; gap: 0.75rem; }
/* Frei auf den Wolken, ohne Kachel: die Innenformen sind ausgespart
   (logoCutout() in js/notices.js), dort scheinen die Wolken durch. */
.notice-logo { display: grid; place-items: center; color: var(--foreground); }
.notice-logo svg { width: 2.75rem; height: 2.75rem; }
.notice-hero.is-short .notice-logo svg { width: 2.25rem; height: 2.25rem; }
.notice-wordmark { font-size: 1.5rem; font-weight: 600; letter-spacing: -0.01em; color: var(--foreground); }

/* ---------- Meldung ---------- */

/* Mittig, in dieser Reihenfolge: Datum klein, Titel, Untertitel, Text. Der
   Text behält seine Zeilenumbrüche (pre-line) — der Admin schreibt ihn als
   Klartext in ein Feld. */
.notice-body { text-align: center; padding: 1.5rem 1.75rem 1.75rem; }
.notice-date { display: block; font-size: 0.75rem; color: var(--muted-foreground); }
.notice-title { margin-top: 0.375rem; font-size: 1.25rem; font-weight: 600; line-height: 1.3; overflow-wrap: anywhere; }
.notice-subtitle { margin-top: 0.375rem; font-size: 0.9375rem; font-weight: 500; color: var(--muted-foreground); overflow-wrap: anywhere; }
.notice-text { margin-top: 1rem; font-size: 0.875rem; line-height: 1.6; color: var(--foreground); white-space: pre-line; overflow-wrap: anywhere; }
.notice-preview-hint { font-size: 0.75rem; color: var(--muted-foreground); }

/* ---------- Willkommen ---------- */

/* Alle Schritte liegen übereinander in einer Rasterzelle; sichtbar ist nur
   der aktuelle. So ist der Dialog so hoch wie sein längster Schritt und
   bleibt es — Fuß und „Weiter“ springen beim Blättern nicht (DESIGN.md,
   „Bewegung“). Ohne Einblenden: der neue Schritt käme sonst
   aus dem Weißen, und der Dialog blitzte bei jedem Klick auf. */
.welcome-slides { display: grid; flex: 1; min-height: 0; overflow-y: auto; }
.welcome-slide { grid-area: 1 / 1; display: flex; flex-direction: column; visibility: hidden; }
.welcome-slide.is-current { visibility: visible; }

.welcome-text { text-align: center; padding: 1.5rem 2rem 1.75rem; }
.welcome-label {
  display: inline-flex; align-items: center; gap: 0.375rem;
  font-size: 0.75rem; font-weight: 500; color: var(--muted-foreground);
}
.welcome-label svg { width: 0.875rem; height: 0.875rem; }
.welcome-title { margin-top: 0.375rem; font-size: 1.25rem; font-weight: 600; line-height: 1.3; }
.welcome-desc { margin: 0.5rem auto 0; max-width: 30rem; font-size: 0.875rem; line-height: 1.6; color: var(--muted-foreground); }

.welcome-dots { display: flex; align-items: center; gap: 0.125rem; }
/* Der Knopf ist größer als der Punkt, damit er auch mit dem Finger trifft. */
.welcome-dot { display: grid; place-items: center; width: 1.25rem; height: 1.5rem; border-radius: var(--radius-sm); }
.welcome-dot::before {
  content: ""; width: 0.4375rem; height: 0.4375rem; border-radius: 999px;
  background: var(--border); transition: width .15s, background-color .15s;
}
.welcome-dot:hover::before { background: var(--muted-foreground); }
.welcome-dot.is-current { width: 1.75rem; }
.welcome-dot.is-current::before { width: 1.25rem; background: var(--primary); }
.welcome-next { min-width: 6.5rem; }

/* ---------- Schaubilder auf der Fläche ----------

   Fenster der App im Kleinen, Bausteine wie die .ui-*-Klassen der Website,
   hier .mock-*. Keine Zahlen: Werte stehen als Platzhalter (.mock-line). */
.mock {
  display: flex; flex-direction: column;
  width: 100%; max-width: 24rem; height: 100%;
  contain: size; overflow: hidden;
  background: var(--card); color: var(--card-foreground);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  box-shadow: var(--shadow-lg);
  font-size: 0.75rem; font-weight: 500; line-height: 1.4;
  text-align: left;
}
.mock svg { width: 0.875rem; height: 0.875rem; flex: none; }
.mock-bar { display: flex; align-items: center; gap: 0.5rem; padding: 0.625rem 0.875rem; border-bottom: 1px solid var(--border); }
.mock-bar b { font-weight: 600; }
.mock-keys, .mock-end-chip { display: flex; gap: 0.25rem; margin-left: auto; }
.mock kbd { padding: 0.0625rem 0.375rem; background: var(--secondary); border-radius: var(--radius-sm); font-family: var(--font-sans); font-size: 0.625rem; }
.mock-dots { display: flex; gap: 0.3125rem; margin-right: 0.125rem; }
.mock-dots span { width: 0.4375rem; height: 0.4375rem; background: var(--border); border-radius: 999px; }
.mock-body { display: flex; flex-direction: column; gap: 0.625rem; padding: 0.875rem; }
.mock-muted { color: var(--muted-foreground); font-weight: 400; }
.mock-line { display: block; height: 0.4375rem; background: color-mix(in oklab, var(--foreground) 10%, transparent); border-radius: 999px; }
.mock-line.w-90 { width: 90%; }
.mock-line.w-75 { width: 75%; }
.mock-line.w-60 { width: 60%; }
.mock-line.w-40 { width: 40%; }
.mock-chips { display: flex; flex-wrap: wrap; gap: 0.3125rem; }
.mock-chip {
  display: inline-flex; align-items: center; gap: 0.3125rem;
  padding: 0.1875rem 0.5625rem; background: var(--secondary);
  border-radius: 999px; font-size: 0.6875rem; white-space: nowrap;
}
.mock-chip svg { width: 0.75rem; height: 0.75rem; }
.mock-chip.is-active { background: var(--primary); color: var(--primary-foreground); }
.mock-tile {
  display: grid; place-items: center; flex: none;
  width: 1.75rem; height: 1.75rem;
  background: var(--secondary); border-radius: var(--radius-md);
}
.mock-head { display: flex; align-items: center; gap: 0.625rem; }
.mock-head b, .mock-head .mock-muted { display: block; }
.mock-head b { font-weight: 600; }
.mock-field { display: flex; flex-direction: column; gap: 0.3125rem; }
.mock-box { display: flex; flex-direction: column; gap: 0.4375rem; padding: 0.5625rem 0.6875rem; background: var(--secondary); border-radius: var(--radius-md); }
.mock-split { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; }
.mock-switch { display: inline-flex; flex: none; width: 1.75rem; height: 1rem; padding: 0.125rem; background: var(--primary); border-radius: 999px; }
.mock-switch::after { content: ""; width: 0.75rem; height: 0.75rem; margin-left: auto; background: var(--primary-foreground); border-radius: 999px; }

/* Chat: die Modellauswahl als Tabelle. */
.mock-table {
  display: grid; grid-template-columns: minmax(0, 1.4fr) repeat(2, minmax(0, 1fr));
  align-items: center; gap: 0.75rem 1rem; padding: 0.875rem;
}
.mock-table > span { display: flex; align-items: center; gap: 0.5rem; min-width: 0; }
.mock-table > span > .mock-line { width: 70%; }
.mock-label { color: var(--muted-foreground); font-size: 0.6875rem; font-weight: 400; }

/* Flow: Schritte untereinander, je mit Ziel der Ausgabe. */
.mock-steps { gap: 0; align-items: stretch; }
.mock-node {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 0.5rem;
  padding: 0.5rem 0.625rem; background: var(--card);
  border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-xs);
}
.mock-node b { overflow: hidden; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }
.mock-end { align-self: center; padding: 0.25rem 0.625rem; background: var(--secondary); border-radius: 999px; font-size: 0.6875rem; }
.mock-link { align-self: center; width: 2px; height: 0.625rem; background: var(--border); }

/* HTML App: eine Checkliste, die läuft. */
.mock-checks { display: flex; flex-direction: column; gap: 0.625rem; }
.mock-checks > span { display: flex; align-items: center; gap: 0.5rem; }
.mock-check {
  display: grid; flex: none; place-items: center; width: 1.125rem; height: 1.125rem;
  background: var(--primary); color: var(--primary-foreground); border-radius: 999px;
}
.mock .mock-check svg { width: 0.6875rem; height: 0.6875rem; }
.mock-check.is-off { background: var(--card); box-shadow: inset 0 0 0 1.5px var(--border); }
.mock-button { padding: 0.4375rem; background: var(--primary); color: var(--primary-foreground); border-radius: 999px; text-align: center; }

/* Library: Kacheln mit Tag-Filter. */
.mock-tiles { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.625rem; }
.mock-tiles > span { display: flex; flex-direction: column; gap: 0.4375rem; padding: 0.625rem; border: 1px solid var(--border); border-radius: var(--radius-lg); }
.mock-tiles .mock-tile { width: 1.5rem; height: 1.5rem; }
.mock-tiles b { font-weight: 600; }

/* Zeile mit Zeichen, zwei Platzhaltern und Wert rechts (Verläufe, Nutzer). */
.mock-row { display: flex; align-items: center; gap: 0.625rem; }
.mock-row > span:nth-child(2) { display: flex; flex-direction: column; gap: 0.3125rem; flex: 1; min-width: 0; }
.mock-row b { font-weight: 600; }
.mock-avatar { flex: none; width: 1.75rem; height: 1.75rem; border-radius: 999px; background: var(--primary); opacity: .85; }

/* Statistik: Säulen, die unten auf der Fläche stehen. */
.mock-bars { display: flex; align-items: flex-end; gap: 0.25rem; height: 5rem; }
.mock-bars > span { flex: 1; min-height: 0.25rem; background: var(--primary); border-radius: var(--radius-sm) var(--radius-sm) 0 0; opacity: .85; }

/* Support: eine Anfrage als kleine Karte. */
.mock-ticket {
  display: flex; flex-direction: column; gap: 0.4375rem;
  padding: 0.625rem 0.75rem; border: 1px solid var(--border); border-radius: var(--radius-lg);
}
.mock-ticket b { font-weight: 600; }

/* Skill: nummerierte Schritte der Anleitung. */
.mock-steps-list { gap: 0.625rem; }
.mock-step { display: flex; align-items: flex-start; gap: 0.5rem; }
.mock-step > span:last-child { display: flex; flex-direction: column; gap: 0.3125rem; flex: 1; min-width: 0; }
.mock-step b { font-weight: 600; }
.mock-num {
  display: grid; flex: none; place-items: center; width: 1.125rem; height: 1.125rem;
  background: var(--primary); color: var(--primary-foreground);
  border-radius: 999px; font-size: 0.625rem; font-weight: 600;
}

/* User Prompt: die Auswahl über dem Eingabefeld, ein Eintrag gewählt. */
.mock-picker { gap: 0.125rem; padding: 0.3125rem; }
.mock-pick {
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.375rem 0.5rem; border-radius: var(--radius-md);
}
.mock-pick b { font-weight: 500; }
.mock-pick.is-active { background: var(--card); box-shadow: var(--shadow-xs); }
.mock-composer {
  display: flex; align-items: flex-end; gap: 0.5rem;
  padding: 0.625rem 0.625rem 0.625rem 0.875rem;
  border: 1px solid var(--border); border-radius: var(--radius-xl);
}
.mock-composer-text { display: flex; flex-direction: column; gap: 0.375rem; flex: 1; min-width: 0; }
.mock-composer-text b { font-weight: 500; }
.mock-send {
  display: grid; flex: none; place-items: center; width: 1.5rem; height: 1.5rem;
  background: var(--primary); color: var(--primary-foreground); border-radius: 999px;
}

/* Wissen: Dateien mit Art und Haken, darüber die Ablage. */
.mock-drop {
  display: flex; align-items: center; justify-content: center; gap: 0.5rem;
  padding: 0.625rem; color: var(--muted-foreground); font-weight: 400;
  border: 1px dashed var(--border); border-radius: var(--radius-lg);
}
.mock-file { display: flex; align-items: center; gap: 0.625rem; }
.mock-file > span:nth-child(2) { display: flex; flex-direction: column; gap: 0.3125rem; flex: 1; min-width: 0; }
.mock-file b { font-weight: 600; }
.mock-file-kind {
  display: grid; flex: none; place-items: center; width: 2rem; height: 2rem;
  background: var(--secondary); border-radius: var(--radius-md);
  font-size: 0.5625rem; font-weight: 700; letter-spacing: .03em;
}

/* Telefon (unter 640 px wie der Dialogfuß): die Fläche wird flacher, das
   Fenster schmaler, und der Fuß stapelt anders als jeder andere Dialog —
   Punkte oben, darunter Zurück und Weiter nebeneinander; bei der Meldung
   der Haken über dem Knopf statt darunter. */
@media (max-width: 640px) {
  .notice-hero { height: 11rem; }
  .notice-hero.is-short { height: 7rem; }
  .notice-hero.has-mock { padding: 1.25rem 1.25rem 0; }
  .welcome-text { padding: 1.25rem 1.25rem 1.5rem; }
  .mock-table { grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); }
  .mock-table > :nth-child(3n) { display: none; }
  .welcome-foot { flex-direction: column; align-items: center; }
  .welcome-foot .dialog-foot-right { flex-direction: row; width: 100%; }
  .welcome-foot .dialog-foot-right .btn { flex: 1; width: auto; }
  .notice-dialog .dialog-foot:not(.welcome-foot) { flex-direction: column; }
}

/* ==========================================================================
   Geräte ohne Zeiger — ganz am Ende, und zwar mit Absicht

   Touch kennt kein :hover. Knöpfe, die erst beim Überfahren erscheinen, wären
   dort überhaupt nicht erreichbar: Der Tap öffnet die Zeile, der Hover kommt
   nie. Sie stehen deshalb dauerhaft da.

   Warum dieser Block hinter allen Abschnitten steht: Er hebt `opacity: 0`
   wieder auf, und zwar mit derselben Spezifität, mit der es gesetzt wurde
   (eine Klasse). Bei gleicher Spezifität gewinnt die spätere Regel — stünde
   er weiter oben, gälte er nur für die Listen, die VOR ihm im Stylesheet
   stehen. Genau das war der Fall: .msg-actions (1612) und .chat-item-actions
   (635) lagen davor und funktionierten, die Ablage (2634) lag dahinter und
   blieb unsichtbar. Am Ende gilt er für jeden Abschnitt, auch für den
   nächsten, den jemand anlegt.

   Einen roten Papierkorb gibt es in den Zeilen nicht mehr — Löschen steht im
   Drei-Punkte-Menü (js/row-menu.js), und dauerhaft sichtbar ist nur dessen
   neutraler Knopf.

   .chats-row-actions und .chats-group-acts fehlen hier: die Zeile baut sich
   dafuer um (Knoepfe neben die Zeit, groessere Trefferfelder) und hat einen
   eigenen Block (max-width: 640px), (hover: none) — siehe „Alle Chats".
   .lib-card-del steht in seinem eigenen Abschnitt, weil es dort um eine
   Kachel geht und nicht um eine Zeile.

   Die eingeklappte Seitenleiste tauscht Logo und Ausklapp-Knopf beim
   Ueberfahren. Ohne Zeiger kaeme der Tausch nie — der Knopf laege dann
   unsichtbar ueber dem Logo und taete beim Antippen etwas, das niemand
   angekuendigt hat. Er steht deshalb gleich an dessen Stelle.
   ========================================================================== */

@media (hover: none) {
  .msg-actions, .chat-item-actions,
  .saved-item-acts, .saved-folder-acts, .flow-node-edit { opacity: 1; }
}

/* Nur ab 1025 px: .sidebar-rail haengt auch schmal an der Seite, wenn die
   Leiste am Schreibtisch eingeklappt war — dort ist sie aber eine Schublade
   mit Logo und Kreuz nebeneinander, und das Logo muss bleiben. */
@media (min-width: 1025px) and (hover: none) {
  .sidebar-rail .brand { opacity: 0; }
  .sidebar-rail .sidebar-collapse { opacity: 1; }
}

/* ==========================================================================
   Eingabefelder auf Touchschirmen: 16 px

   Safari auf dem iPhone vergroessert die ganze Seite, sobald ein Feld mit
   weniger als 16 px Schrift den Fokus bekommt — und zoomt danach nicht von
   selbst zurueck. Bei 14 px (.input) und 15 px (.composer-input) passierte
   das bei jeder Eingabe. Abschalten liesse es sich nur mit maximum-scale im
   viewport, und das nimmt allen das Vergroessern mit zwei Fingern.

   Am Dateiende aus demselben Grund wie der Block davor: :is() traegt die
   Spezifitaet einer Klasse, und gegen die Feldklassen weiter oben gewinnt bei
   Gleichstand die spaetere Regel. Wer eine neue Feldklasse mit eigener
   Schriftgroesse anlegt, traegt sie hier ein.
   ========================================================================== */

@media (hover: none) and (pointer: coarse) {
  :is(.input, .composer-input, .stats-select, .side-panel-text, .tag-input,
      .pgen-out, .code-area, .chat-item-title-input, .chats-row-title-input) {
    font-size: 1rem;
  }
}
