/* ==========================================================================
   Breakpoint-Leiter (AP4a — Responsive-Grundlage für die gesamte App)
   ==========================================================================
   Gewachsen sind bisher punktuelle @media-Regeln bei 480/640/700/768/
   860/900px — jede für sich sinnvoll motiviert (der Punkt, an dem ein
   bestimmtes Grid/Toolbar nicht mehr passt), aber ohne gemeinsamen Raster.
   Ab jetzt gilt folgende Leiter als Referenz für NEUE @media-Regeln in
   dieser Datei (und in den Folge-Arbeitspaketen P4b/Pläne, P4c/Download,
   P4d/Abnahme). Getestet wird durchgängig bei genau diesen 8 Breiten:
   375, 390, 480, 768, 1024, 1280, 1440, 1920px.

     Mobile        ≤ 480px   (kleine Handys, Hochformat — 375/390/480)
     Tablet        ≤ 768px   (Hochformat-Tablet/große Handys quer — 768)
     Tablet-Wide   ≤ 900px   (Shell-Ebene: App-Grids mit 2+ Spalten
                              kollabieren hier auf 1 Spalte — kein
                              eigener Testpunkt in den 8 Breiten, liegt
                              zwischen 768 und 1024)
     Desktop       ≤ 1439px  (Laptop/Standard-Desktop — 1024/1280)
     Wide          ≥ 1440px  (große/ultraweite Displays — 1440/1920;
                              aktuell keine eigenen @media-Regeln nötig,
                              Basis-Styles samt max-width-Spalten skalieren
                              bereits sauber; Stufe für künftige
                              "mehr Platz nutzen"-Regeln reserviert)

   Konvention: `@media (max-width: 480px)` / `(max-width: 768px)` /
   (max-width: 900px)` für die drei unteren Stufen; `Desktop` ist der
   Basiszustand ohne eigene Query; `(min-width: 1440px)` für gezielte
   Wide-Erweiterungen.

   Bewusst NICHT auf die Leiter gezwungen (Risiko einer Regression ohne
   Nutzen höher als der Konsistenzgewinn — siehe AP4a-Auftrag):
   - 640px (2FA-Enrollment-Grid, Recovery-Code-Grid, Settings-Modal-Padding)
     und 700px (Plugins-Dokumentation Funktionsreferenz-Grid): beide
     kollabieren jeweils ein *einzelnes* Inhalts-Grid genau an dem Punkt,
     an dem dessen zwei Spalten nicht mehr nebeneinander passen — das ist
     komponentenspezifisch, nicht Layout-strukturell, und unverifiziert
     verschieben würde nur Risiko ohne echten Nutzen bringen.
   - 480px (Team-Mitglieder-Zeile in den Settings): liegt exakt auf der
     Mobile-Grenze, keine Änderung nötig.
   860px (Dashboard-Bento-Grid) wurde dagegen auf 900px angehoben, um sich
   mit der bereits vorhandenen 900px-Regel (.plugins-layout) zu
   vereinheitlichen — beides sind strukturell dieselbe Frage ("kollabiert
   die App-Shell von zwei/mehr Spalten auf eine"), und die Änderung macht
   das Bento-Grid nur in einem schmalen, ungetesteten Zwischenbereich
   (861–900px) FRÜHER einspaltig (konservativer, nie riskanter).
   ========================================================================== */

/* ==========================================================================
   Tokens
   ==========================================================================
   Die Design-Token stehen seit dem Aufräumen in EINER Datei: tokens.css.
   Sie standen vorher hier UND (als Kopie) in consent.css — wer nur eine
   der beiden anfasste, bekam eine Anmeldeseite, die anders aussah als die
   App dahinter. Jedes Template, das style.css laedt, laedt tokens.css
   davor (siehe base.html).
   ========================================================================== */

/* design-system: migriert */
/* ==========================================================================
   Optik-Stufe 2 (2026-10-11, docs/design/DESIGNRICHTUNG.md, AP3a)
   ==========================================================================
   Seit diesem Stand trägt die Datei die Marke oben: keine festen Hexfarben,
   keine Bewegung mit fester Dauer außerhalb eines prefers-reduced-motion-
   Blocks (Wächter: tests/test_design_system_css.py). Dauern kommen aus
   --dur-*, Federn aus --spring, Wege werden mit --motion-scale multipliziert,
   damit "weniger Bewegung" aus tokens.css überall greift.

   Was sich gegenüber Stufe 1 geändert hat und warum:
   - Flächen sind deckend und flach (--panel, Haarlinie --separator). Der
     frühere "Liquid Glass"-Umschalter (.lg-active mit Verschiebemaske im
     backdrop-filter) entfällt hier: Sidebar ist jetzt Material "thick",
     Listen, Karten und das Suchfeld sind deckend, eine Lichtbrechung hinter
     ihnen hätte nichts mehr zu brechen. liquid-glass.js bedient nur noch
     den Editor (.master-viz-block, editor.css).
   - Primärknopf auf --accent-fill mit weißer Schrift, Rückmeldung beim
     Drücken (:active, --press-scale, --dur-press) statt Anheben beim Hover.
   - Kein seitliches Einfliegen mehr, nur ein einmaliges Einblenden.
   - Kachelraster und Cursor-Licht nur noch auf Sperrseite und Dashboard.
   ========================================================================== */

html { color-scheme: dark; }

/* Fokusring für alles, was per Tastatur erreichbar ist. :where() hält die
   Spezifität bei null: jede bestehende, genauere Regel (auch in editor.css)
   gewinnt weiterhin, hier wird nur die Lücke geschlossen, wo bisher gar kein
   sichtbarer Fokus war. Der Abstand stellt den Ring auf den Grund statt auf
   den gefüllten Knopf (Ring gegen --accent-fill nur 1.85:1). */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
}

/* Echte Checkboxen und Radio-Knöpfe statt weißer Browser-Standards: das
   dunkle Farbschema oben färbt die leere Box dunkel, accent-color den
   gesetzten Zustand im Primärton. */
input[type="checkbox"],
input[type="radio"] {
  accent-color: var(--accent-fill);
  width: 16px;
  height: 16px;
  margin: 0;
  flex-shrink: 0;
  cursor: pointer;
}

/* Passwort-Auge IM Feld (DESIGNRICHTUNG C1): password-toggle.js legt einen
   Flex-Wrapper mit width:100% um das Feld; ohne flex:1 behielt das Feld
   seine Eigenbreite und das Auge stand weit rechts daneben. */
.password-toggle-wrap > input {
  flex: 1 1 auto;
  width: 100%;
  min-width: 0;
}

/* --------------------------------------------------------------------
   Einführungstour (static/js/onboarding.js, Plan Phase 18) — Spotlight per
   box-shadow-Trick: die "Aussparung" ist einfach eine transparente Box mit
   einem riesigen box-shadow, der den Rest des Viewports abdunkelt. Kein
   Overlay-Hintergrund nötig, kein clip-path/mask.
   -------------------------------------------------------------------- */
.onboarding-overlay {
  position: fixed;
  inset: 0;
  z-index: 2000;
}
.onboarding-spotlight {
  position: fixed;
  border-radius: var(--radius-m);
  border: 2px solid var(--accent);
  box-shadow: 0 0 0 9999px var(--scrim);
  pointer-events: none;
  transition: top var(--dur-spring) var(--spring), left var(--dur-spring) var(--spring),
    width var(--dur-spring) var(--spring), height var(--dur-spring) var(--spring);
}
.onboarding-tooltip {
  position: fixed;
  width: 320px;
  max-width: calc(100vw - 40px);
  background: var(--panel-elevated);
  border: 1px solid var(--separator);
  border-radius: var(--radius-m);
  padding: var(--space-4) var(--space-5);
  box-shadow: var(--material-edge), var(--shadow-l);
  transition: top var(--dur-spring) var(--spring), left var(--dur-spring) var(--spring);
}
.onboarding-step-count {
  margin: 0 0 var(--space-1);
  font-size: var(--size-footnote);
  font-variant-numeric: tabular-nums;
  color: var(--text-dim);
}
.onboarding-title {
  margin: 0 0 var(--space-2);
  font-family: var(--font-text);
  font-size: var(--size-title-3);
  line-height: var(--leading-title-3);
  font-weight: 600;
  color: var(--text-primary);
}
.onboarding-text {
  margin: 0 0 var(--space-4);
  font-size: var(--size-body);
  line-height: var(--leading-body);
  color: var(--text-secondary);
}
.onboarding-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}
.onboarding-skip {
  background: none;
  border: none;
  padding: 0;
  color: var(--text-secondary);
  font-size: var(--size-footnote);
  cursor: pointer;
  text-decoration: underline;
}
.onboarding-skip:hover { color: var(--text-primary); }
.onboarding-nav { display: flex; gap: var(--space-2); }
.onboarding-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--control-h-m);
  border-radius: var(--radius-s);
  padding: 0 var(--space-4);
  font-size: var(--size-callout);
  font-weight: 600;
  cursor: pointer;
  border: 1px solid transparent;
  transition: background var(--dur-fast) var(--ease), transform var(--dur-press) var(--ease);
}
.onboarding-btn:active:not(:disabled) { transform: scale(var(--press-scale)); }
.onboarding-btn-primary { background: var(--accent-fill); color: var(--on-accent-fill); }
.onboarding-btn-primary:hover { background: var(--accent-fill-hover); }
.onboarding-btn-primary:active { background: var(--accent-fill-pressed); }
.onboarding-btn-secondary {
  background: var(--fill-2);
  color: var(--text-primary);
}
.onboarding-btn-secondary:disabled { opacity: 0.45; cursor: default; }
.onboarding-btn-secondary:not(:disabled):hover { background: var(--fill-3); }

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text-primary);
  font-family: var(--font-body);
}

/* body absichtlich OHNE eigenes height:100% (stand hier bis zu diesem
   Arbeitspaket, siehe AP "Scroll-Jank"): mit einer festen Prozenthöhe bleibt
   die eigene Box von body exakt auf Viewporthöhe stehen, auch wenn der
   Inhalt weit darüber hinausragt — die Seite scrollt dann zwar trotzdem
   (overflow:visible lässt den Inhalt sichtbar überlaufen), aber alles, was
   sich prozentual/über inset:0 auf body als Containing Block bezieht
   (#bgTileStatic unten), bliebe auf Viewporthöhe geklemmt
   statt auf Dokumenthöhe zu wachsen. Nachgemessen (Playwright, 800x600,
   3000px hoher Inhalt): mit height:100% blieb eine absolut positionierte
   Fläche bei 600px hängen, ganz ohne height (= auto, wächst mit dem
   Inhalt) korrekt auf 3000px. Genau dieses Wachsen ist die Voraussetzung
   dafür, dass der Hintergrund unten nativ vom Compositor mitgescrollt
   werden kann, statt dass JavaScript den Versatz pro Frame nachrechnet. */
/* Ohne min-height bleibt body exakt so hoch wie sein Inhalt (siehe
   Kommentar oben zu height:auto). Auf Seiten, deren Inhalt kürzer als der
   Viewport ist, endete #bgTileStatic (inset:0 auf body) dann mittendrin —
   unten blieb ein Streifen ohne Kachelraster. min-height ist bewusst KEINE
   feste Höhe: sie setzt nur eine Untergrenze, über die body für langen
   Inhalt weiterhin frei hinauswächst (das Wachsen selbst bleibt exakt die
   Voraussetzung aus dem Kommentar oben, unangetastet). */
body {
  position: relative;
  isolation: isolate;
  min-height: 100vh;
  font-size: var(--size-body);
  line-height: var(--leading-body);

  /* Seit Optik-Stufe 2 (2026-10-11) kein Glow-Feld und kein Abdunkler
     (body::after) mehr: Der Grund ist einfarbig --bg (DESIGNRICHTUNG 2.7,
     "ruhiger, deckender Grund"). Falls hier je wieder ein Verlauf liegen
     soll: als background-image von body, nicht als eigene, absolut
     positionierte Ebene. Eine solche Ebene ging nach dem Transform in den
     Scrollbereich ein und erzeugte waagerechten Überlauf
     (tests/e2e/test_plans_checkout.py::
     test_plaene_page_has_no_horizontal_overflow_at_reference_widths). */
  background: var(--bg);
}

/* Statische Hälfte des Kachelrasters (static/js/tile-background.js legt das
   Element per JS an, id="bgTileStatic" — es steht nicht im Template, weil
   es nur für Browser mit aktiviertem JavaScript existieren soll). Liegt wie
   vorher ÜBER dem Glow-Feld (body-eigenes background-image bzw.
   body::after, z-index:-1) und UNTER
   dem interaktiven Cursor-Licht (#bgCanvas, ebenfalls z-index:0 — die
   Reihenfolge kommt hier über die DOM-Position, #bgTileStatic wird vor dem
   Canvas eingefügt) und dem Inhalt (.app-shell, z-index:1).

   position: absolute (wie das Glow-Feld oben jetzt als background-image von
   body selbst liegt): das Raster ist als Bild dokumenthoch und scrollt
   dadurch nativ mit, statt dass
   JavaScript pro Scroll-Frame nachzeichnet. Ein einzelnes, dokumenthohes
   <canvas> ginge dafür NICHT (siehe tile-background.js, buildTileImage()):
   Chromium deckelt eine Canvas-Kante bei ~16384px (bei dpr 2 wären das gut
   8000 CSS-Pixel Dokumenthöhe, auf einer langen Seite schnell erreicht),
   und die Zeichenkosten wüchsen mit der Seitenlänge statt konstant zu
   bleiben. Stattdessen erzeugt JS EINMALIG ein kleines, nahtlos kachelbares
   Tonfeld (siehe TILE_CELLS) und setzt es hier als sich wiederholendes
   background-image — background-size kommt dynamisch aus JS (hängt von der
   aktuellen Zellgröße `cell` ab), background-repeat ist statisch. */
#bgTileStatic {
  position: absolute;
  inset: 0;
  z-index: 0;
  background-repeat: repeat;
  opacity: 0.25;
  pointer-events: none;
  /* tile-background.js begrenzt die Höhe auf Sperrseite bzw. Dashboard
     (data-limited); die Maske lässt das Raster dort weich auslaufen statt
     an einer harten Kante zu enden. */
}
#bgTileStatic[data-limited] {
  bottom: auto;
  -webkit-mask-image: linear-gradient(to bottom, black calc(100% - 240px), transparent);
  mask-image: linear-gradient(to bottom, black calc(100% - 240px), transparent);
}

/* Interaktive Hälfte: Cursor-Licht, nachlaufender Zeiger, Klickwellen. Bleibt
   viewportgroß und `position: fixed` — das ist genau der Teil, der sich
   wirklich pro Frame ändert, eine dokumenthohe Fläche brächte er nicht. */
#bgCanvas {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0.25;
  transition: opacity var(--dur-slow) var(--ease);
}
/* tile-background.js setzt das, sobald Sperrseite/Dashboard aus dem Bild
   gescrollt sind: das Licht blendet aus und die Zeichenschleife ruht. */
#bgCanvas[data-dormant] { opacity: 0; }

/* Kachelraster und Cursor-Licht NUR auf Sperrseite und Dashboard
   (Nutzerentscheidung 4, 2026-10-11), gedämpft auf 25 %, und auf Seiten, die
   sich per `data-reactive-bg` an der Seitenwurzel ausdrücklich anmelden
   (Turnierseite, Nutzerwunsch 2026-10-24). Alle anderen Seiten (Pläne,
   Download, Admin, MCP, lokale Startseite, Editor) stehen auf ruhigem,
   deckendem --bg. Bei weniger Transparenz oder mehr Kontrast entfällt die
   Ebene ganz: sie ist reine Dekoration. Dieselbe Selektorliste steht in
   tile-background.js (hosts). */
body:not(:has(#lock-section, #dashboard-section, [data-reactive-bg])) :is(#bgCanvas, #bgTileStatic) {
  display: none;
}
@media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
  #bgCanvas,
  #bgTileStatic { display: none; }
}

a { text-decoration: none; color: inherit; }
button { font-family: inherit; cursor: pointer; }
input, select, textarea { font-family: inherit; }

/* ==========================================================================
   Layout shell
   ========================================================================== */
.app-shell {
  display: flex;
  min-height: 100vh;
  /* Hebt den Inhalt über das fixierte Hintergrund-Canvas (#bgCanvas, z-index 0) */
  position: relative;
  z-index: 1;
}

/* ==========================================================================
   Sidebar (Optik-Stufe 2, 2026-10-11, DESIGNRICHTUNG 2.7)
   ==========================================================================
   - Ab 1200 px standardmäßig ausgeklappt mit Beschriftung, darunter als
     Symbolleiste (Nutzerentscheidung 8). Den Standard trägt das CSS selbst:
     base.html rendert KEIN data-expanded, solange niemand umgeschaltet hat
     (":not([data-expanded])" unten). shell.js schreibt beim Start denselben
     Wert ins Attribut, den das CSS schon zeigt, deshalb springt beim Laden
     nichts.
   - Kein width-Übergang mehr (der rechnete in jedem Frame das Layout der
     ganzen Seite neu). Die Breite wechselt in einem Schritt, die
     Beschriftungen blenden über opacity nach. Symbole stehen in beiden
     Zuständen an derselben Stelle: Der Innenabstand der Einträge ist aus
     der eingeklappten Breite gerechnet, das Symbol sitzt also immer mittig
     in der Symbolleiste und wandert beim Umschalten nicht.
   - Unter 1200 px legt sich die ausgeklappte Leiste ÜBER den Inhalt
     (negativer Außenabstand), statt ihn wegzuschieben: im Fluss belegt sie
     weiter nur die Symbolbreite, der Inhalt rechts bleibt stehen.
   - Material "thick": dichteste Stufe, weil die Leiste eine große,
     strukturelle Fläche ist (Finder-Seitenleiste).
   ========================================================================== */
.sidebar {
  --sb-pad: var(--space-3);
  /* Innenabstand, der das 18-px-Symbol in der Symbolbreite mittig stellt */
  --sb-item-pad: calc((var(--sidebar-w-collapsed) - 2 * var(--sb-pad) - 18px) / 2);
  width: var(--sidebar-w);
  flex-shrink: 0;
  background: var(--material-thick-bg);
  backdrop-filter: var(--material-thick-filter);
  -webkit-backdrop-filter: var(--material-thick-filter);
  border-right: 1px solid var(--separator);
  display: flex;
  flex-direction: column;
  padding: var(--space-4) var(--sb-pad);
  /* Bleibt beim Scrollen der langen Abschnittsseite stehen, damit die
     Navigation immer erreichbar ist. sticky statt fixed: die Sidebar bleibt
     dadurch ein normales Flex-Kind, .main behält also automatisch die
     richtige Restbreite (bei fixed müsste man sie per margin-left an die
     ein-/ausklappbare Breite koppeln). */
  position: sticky;
  top: 0;
  z-index: 20;
  height: 100vh;
  /* Ohne dies würde das Flex-Standardverhalten (align-items: stretch) die
     Sidebar auf die volle Höhe des langen Inhalts dehnen — dann gäbe es
     nichts mehr zu "sticken" und sie würde doch mit wegscrollen. */
  align-self: flex-start;
  /* Reicht der Platz nicht (kleine Fenster), darf die Sidebar in sich
     scrollen statt Inhalte abzuschneiden. overflow-x bleibt hidden, damit
     beim Einklappen nichts seitlich heraussteht. */
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  scrollbar-width: thin;
}

.sidebar-top {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--control-h-m);
  margin-bottom: var(--space-5);
  /* Knopf mittig in der Symbolbreite, siehe --sb-item-pad */
  padding-left: calc((var(--sidebar-w-collapsed) - 2 * var(--sb-pad) - 28px) / 2);
}

/* Seitenleisten-Symbolknopf statt des früheren Faders (DESIGNRICHTUNG N2:
   der iOS-Schalter sah nach einer Einstellung aus, nicht nach dem Knopf,
   der die Leiste öffnet). */
.sidebar-toggle {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  border-radius: var(--radius-s);
  background: transparent;
  color: var(--text-secondary);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease),
    transform var(--dur-press) var(--ease);
}
.sidebar-toggle svg { width: 18px; height: 18px; }
.sidebar-toggle:hover { background: var(--fill-2); color: var(--text-primary); }
.sidebar-toggle:active { background: var(--fill-3); transform: scale(var(--press-scale)); }

.brand {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  white-space: nowrap;
  transition: opacity var(--dur-fast) var(--ease);
}

.brand-mark {
  display: flex;
  align-items: center;
  flex-shrink: 0;
}

.brand-mark svg {
  display: block;
  width: 20px;
  height: 20px;
}

.brand-name {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--size-body);
  letter-spacing: var(--tracking-headline);
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
}

.sidebar-nav {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  margin-bottom: var(--space-6);
}

.nav-group {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* Gruppenköpfe wie in den macOS-Systemeinstellungen: klein, gedämpft, in
   Satzschreibung. Eingeklappt bleiben sie als Abstand stehen. */
.nav-group-title {
  display: block;
  padding: 0 var(--sb-item-pad) var(--space-1);
  font-size: var(--size-caption);
  line-height: var(--leading-caption);
  letter-spacing: var(--tracking-caption);
  font-weight: 600;
  color: var(--text-dim);
  white-space: nowrap;
  transition: opacity var(--dur-fast) var(--ease);
}

.nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: var(--control-h-m);
  padding: 0 var(--sb-item-pad);
  border: none;
  border-radius: var(--radius-s);
  background: transparent;
  color: var(--text-secondary);
  font: inherit;
  font-size: var(--size-callout);
  font-weight: 500;
  text-align: left;
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease),
    transform var(--dur-press) var(--ease);
}

.nav-item:hover { background: var(--fill-1); color: var(--text-primary); }
.nav-item:active { background: var(--fill-2); transform: scale(var(--press-scale)); }

.nav-item.is-active {
  background: var(--fill-2);
  color: var(--text-primary);
}
.nav-item.is-active .nav-icon { color: var(--accent); }

.nav-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.nav-icon svg { width: 18px; height: 18px; }

.nav-label {
  overflow: hidden;
  text-overflow: ellipsis;
  transition: opacity var(--dur-fast) var(--ease);
}

@media (pointer: coarse) {
  .nav-item,
  .sidebar-toggle { min-height: var(--hit-min); }
}

/* Speicherbelegung (AP-OC F1, 2026-10-12, PLAN-ordner-cloud 5)
   Ein Link auf den Abschnitt "Cloud Storage": Titel, "x von y" und Balken.
   Die Dateiliste darunter ist entfallen. Zustände wie ein Sidebar-Eintrag:
   Hover --fill-2, gedrückt --fill-3 mit --press-scale, Fokusring aus der
   globalen :focus-visible-Regel. Warnstufe (ab 85 %) und Gefahr (ab 95 %)
   färben den Balken und zeigen zusätzlich einen Hinweistext, die Farbe ist
   nie die einzige Aussage. Eingeklappt verschwindet der Block (Regeln unter
   "Eingeklappt: Symbolleiste"), per visibility auch aus der Tab-Reihenfolge. */
.sidebar-storage {
  display: block;
  margin-top: auto;
  padding: var(--space-3);
  background: var(--fill-1);
  border-radius: var(--radius-m);
  color: inherit;
  text-decoration: none;
  transition: opacity var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease),
    transform var(--dur-press) var(--ease);
}
.sidebar-storage:hover { background: var(--fill-2); }
.sidebar-storage:hover .storage-title { color: var(--text-primary); }
.sidebar-storage:active { background: var(--fill-3); transform: scale(var(--press-scale)); }

.storage-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.storage-title {
  font-size: var(--size-footnote);
  font-weight: 600;
  color: var(--text-secondary);
  white-space: nowrap;
  transition: color var(--dur-fast) var(--ease);
}

/* Systemschrift mit Tabellenziffern statt Mono (DESIGNRICHTUNG 2.4, Meta). */
.storage-value {
  font-size: var(--size-caption);
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

.storage-bar {
  display: block;
  height: 6px;
  border-radius: var(--radius-full);
  background: var(--panel-deep);
  overflow: hidden;
}

.storage-bar-fill {
  display: block;
  height: 100%;
  width: 0%;
  border-radius: var(--radius-full);
  background: linear-gradient(90deg, var(--accent-teal), var(--accent));
  transition: width var(--dur-slow) var(--ease), background var(--dur-fast) var(--ease);
}
.sidebar-storage[data-level="warning"] .storage-bar-fill { background: var(--warning); }
.sidebar-storage[data-level="danger"] .storage-bar-fill { background: var(--danger); }

.storage-hint {
  display: block;
  margin-top: var(--space-2);
  font-size: var(--size-caption);
  line-height: var(--leading-caption);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
}
.storage-hint[hidden] { display: none; }
.sidebar-storage[data-level="warning"] .storage-hint { color: var(--warning); }
.sidebar-storage[data-level="danger"] .storage-hint { color: var(--danger); }

/* Mehr Kontrast: der Balken bekommt eine Kante, damit die leere Spur auch
   ohne Farbunterschied als Spur lesbar bleibt. */
@media (prefers-contrast: more) {
  .storage-bar { box-shadow: inset 0 0 0 1px var(--border-strong); }
  .sidebar-storage { box-shadow: inset 0 0 0 1px var(--border); }
}
@media (forced-colors: active) {
  .storage-bar { border: 1px solid CanvasText; }
  .storage-bar-fill { background: Highlight; }
}

/* Account */
.sidebar-account {
  margin-top: var(--space-3);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-1) 0 0 calc((var(--sidebar-w-collapsed) - 2 * var(--sb-pad) - 32px) / 2);
}

.account-avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--fill-2);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-size: var(--size-callout);
  font-weight: 600;
  color: var(--text-primary);
  flex-shrink: 0;
}

.account-meta {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  white-space: nowrap;
  transition: opacity var(--dur-fast) var(--ease);
}

.account-name {
  font-size: var(--size-callout);
  font-weight: 500;
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Profilbild + Name verlinken zu den Einstellungen (Plan.md §G1.1) —
   "Abmelden" bleibt ein eigener, separat klickbarer Link daneben. */
.sidebar-account-link {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex: 1 1 auto;
  min-width: 0;
  border-radius: var(--radius-s);
  text-decoration: none;
  color: inherit;
}
.sidebar-account-link:hover .account-name { color: var(--accent); }

.account-action {
  font-size: var(--size-footnote);
  color: var(--accent);
  flex-shrink: 0;
  transition: opacity var(--dur-fast) var(--ease);
}
.account-action:hover { text-decoration: underline; }

/* Gäste: Registrieren (gefüllt) und Anmelden (sekundär, mit Kante) als
   Knöpfe in voller Breite (Nutzer 2026-10-19: „klarer und präsenter“).
   Eingeklappt (Regeln unten) werden daraus zwei Symbolknöpfe. */
.sidebar-account-guest {
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-2);
  padding: var(--space-1) 0 0;
}
.sidebar-account-guest .sidebar-auth-btn {
  min-height: var(--control-h-l);
  font-size: var(--size-body);
  white-space: nowrap;
}
.sidebar-auth-btn svg { width: 18px; height: 18px; flex-shrink: 0; }
.sidebar-auth-login { box-shadow: inset 0 0 0 1px var(--border-strong); }

/* --- Eingeklappt: Symbolleiste ---------------------------------------------
   Zweimal derselbe Regelsatz: einmal für den ausdrücklich gewählten Zustand
   (data-expanded="false"), einmal für den Standard unter 1200 px, solange
   shell.js das Attribut noch nicht gesetzt hat. Beschriftungen bleiben im
   DOM und damit Namen der Links für Screenreader (tooltips.js zeigt sie beim
   Überfahren); Speicher-Block und Kontozeile verschwinden per visibility
   auch aus der Tab-Reihenfolge.

   AP4a: "Abmelden" bekommt display:none statt nur opacity — sonst blieb der
   Link im Flex-Fluss, beanspruchte dank flex-shrink:0 mehr Breite, als die
   eingeklappte Sidebar hergibt, und drückte das Profilbild (den Auslöser
   des Einstellungs-Modals) auf 0 px zusammen.

   Seit der mobilen Kopfleiste (2026-10-25) gelten beide Regelsätze nur
   oberhalb von 768 px: darunter ist die Sidebar ein Menü von oben und
   immer beschriftet (siehe "Mobile Kopfleiste" unten). Für Breiten ab
   769 px ändert die Einschränkung nichts. */
@media (min-width: 768.02px) {
  .sidebar[data-expanded="false"] { width: var(--sidebar-w-collapsed); }
  .sidebar[data-expanded="false"] .brand { display: none; }
  .sidebar[data-expanded="false"] :is(.nav-label, .nav-group-title) { opacity: 0; }
  .sidebar[data-expanded="false"] :is(.sidebar-storage, .account-meta) { opacity: 0; visibility: hidden; }
  .sidebar[data-expanded="false"] .account-action { display: none; }
  .sidebar[data-expanded="false"] .sidebar-auth-btn { padding: 0; min-width: 0; }
  .sidebar[data-expanded="false"] .sidebar-auth-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
}

@media (min-width: 768.02px) and (max-width: 1199.98px) {
  .sidebar:not([data-expanded]) { width: var(--sidebar-w-collapsed); }
  .sidebar:not([data-expanded]) .brand { display: none; }
  .sidebar:not([data-expanded]) :is(.nav-label, .nav-group-title) { opacity: 0; }
  .sidebar:not([data-expanded]) :is(.sidebar-storage, .account-meta) { opacity: 0; visibility: hidden; }
  .sidebar:not([data-expanded]) .account-action { display: none; }
  .sidebar:not([data-expanded]) .sidebar-auth-btn { padding: 0; min-width: 0; }
  .sidebar:not([data-expanded]) .sidebar-auth-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

  /* Ausgeklappt unter 1200 px: liegt über dem Inhalt. Im Fluss bleibt nur
     die Symbolbreite belegt (negativer Außenabstand), .main springt nicht.
     Ersetzt die frühere position:fixed-Regel für <= 480 px, die .main beim
     Aufklappen auf volle Breite springen ließ. */
  .sidebar[data-expanded="true"] {
    margin-right: calc(var(--sidebar-w-collapsed) - var(--sidebar-w));
    z-index: 80;
    box-shadow: var(--shadow-l);
  }
}

/* ==========================================================================
   Mobile Kopfleiste, Sidebar als Menü von oben (2026-10-25, Nutzerwunsch)
   ==========================================================================
   Bis 768 px (Stufe "Tablet" der Breakpoint-Leiter) belegt die Sidebar
   eingeklappt keinen Platz mehr. Oben steht eine schlanke Leiste
   (.mobile-bar, 48 px = --space-12) mit Menüknopf, Marke und Seitentitel;
   der Knopf klappt DIESELBE Sidebar als Menü von oben aus. Ab 769 px ist
   die Leiste display:none und alles bleibt wie zuvor.

   - Die Leiste steht im Fluss und scrollt mit (nicht sticky): so liegt sie
     nie über den klebenden Vollbild-Bühnen der Gast-Startseite (Teaser,
     hero3d, beide sticky top:0), der Auswahlleiste im Dateibrowser oder den
     Sprungzielen der Einseitenseite.
   - Das Menü ist fixiert und beginnt an der Unterkante der Leiste
     (--nm-nav-top, gemessen von shell.js beim Öffnen, Rückfall 48 px). Es
     kommt unter der Leiste hervor und geht denselben Weg zurück: nur
     transform und opacity, Feder --spring. CSS-Übergänge starten beim
     Umkehren vom aktuellen Wert, das Menü lässt sich also mitten in der
     Bewegung wieder umdrehen. Bei weniger Bewegung macht --motion-scale 0
     daraus eine reine Überblendung.
   - visibility folgt verzögert, damit das geschlossene Menü weder in der
     Tab-Reihenfolge noch für Screenreader existiert.
   - Flächen deckend (DESIGNRICHTUNG 2): Leiste und Menü auf --panel-deep,
     dem Grundton des Sidebar-Materials, Abdunklung --scrim.
   - Ohne JavaScript (.no-js) steht die Sidebar offen im Fluss unter der
     Leiste, der Menüknopf fehlt: die Navigation bleibt erreichbar.
   Stapel innerhalb von .app-shell: Leiste 95 > Menü 90 > Abdunklung 85.
   .app-shell selbst liegt auf z-index 1, Dialoge (1000), Toasts (500),
   Tour (2000) und Lightbox (1100) hängen am body und bleiben darüber. */
.mobile-bar,
.mobile-nav-scrim { display: none; }

@media (max-width: 768px) {
  .app-shell { flex-direction: column; }

  .mobile-bar {
    position: relative;
    z-index: 95;
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-height: calc(var(--space-12) + env(safe-area-inset-top, 0px));
    padding: env(safe-area-inset-top, 0px) max(var(--space-2), env(safe-area-inset-right, 0px)) 0 max(var(--space-2), env(safe-area-inset-left, 0px));
    background: var(--panel-deep);
    border-bottom: 1px solid var(--separator);
  }

  .mobile-bar-toggle {
    width: var(--hit-min);
    height: var(--hit-min);
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: none;
    border-radius: var(--radius-s);
    background: transparent;
    color: var(--text-secondary);
    transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease),
      transform var(--dur-press) var(--ease);
  }
  .mobile-bar-toggle:hover,
  .mobile-bar-toggle[aria-expanded="true"] { background: var(--fill-2); color: var(--text-primary); }
  .mobile-bar-toggle:active { background: var(--fill-3); transform: scale(var(--press-scale)); }
  .mobile-bar-toggle svg { width: 20px; height: 20px; overflow: visible; }

  /* Drei Striche werden zum Kreuz: die äußeren drehen um die Mitte, der
     mittlere blendet aus. Nur transform/opacity, umkehrbar wie das Menü. */
  .mobile-bar-line {
    transform-box: view-box;
    transform-origin: 12px 12px;
    transition: transform var(--dur-spring) var(--spring), opacity var(--dur-fast) var(--ease);
  }
  .mobile-bar-toggle[aria-expanded="true"] .mobile-bar-line-top { transform: rotate(45deg) translateY(5px); }
  .mobile-bar-toggle[aria-expanded="true"] .mobile-bar-line-mid { opacity: 0; }
  .mobile-bar-toggle[aria-expanded="true"] .mobile-bar-line-bot { transform: rotate(-45deg) translateY(-5px); }

  .mobile-bar-brand {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-width: 0;
  }
  .mobile-bar-mark {
    display: block;
    width: 20px;
    height: 20px;
    flex-shrink: 0;
  }
  .mobile-bar-title {
    font-family: var(--font-text);
    font-size: var(--size-headline);
    line-height: var(--leading-headline);
    letter-spacing: var(--tracking-headline);
    font-weight: var(--weight-semibold);
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .mobile-nav-scrim {
    display: block;
    position: fixed;
    inset: var(--nm-nav-top, var(--space-12)) 0 0 0;
    z-index: 85;
    background: var(--scrim);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity var(--dur-base) var(--ease), visibility 0s linear var(--dur-base);
  }
  .nm-nav-open .mobile-nav-scrim {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transition: opacity var(--dur-base) var(--ease), visibility 0s;
  }

  .sidebar {
    --sb-item-pad: var(--space-3);
    position: fixed;
    top: var(--nm-nav-top, var(--space-12));
    left: 0;
    right: 0;
    z-index: 90;
    width: auto;
    height: auto;
    max-height: calc(100vh - var(--nm-nav-top, var(--space-12)));
    max-height: calc(100dvh - var(--nm-nav-top, var(--space-12)));
    margin: 0;
    padding: var(--space-4) max(var(--space-4), env(safe-area-inset-right, 0px)) calc(var(--space-4) + env(safe-area-inset-bottom, 0px)) max(var(--space-4), env(safe-area-inset-left, 0px));
    background: var(--panel-deep);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border-right: none;
    border-bottom: 1px solid var(--separator);
    border-radius: 0 0 var(--radius-l) var(--radius-l);
    box-shadow: var(--shadow-l);
    transform: translateY(calc(-100% * var(--motion-scale)));
    opacity: 0;
    visibility: hidden;
    transition: transform var(--dur-spring) var(--spring), opacity var(--dur-base) var(--ease),
      visibility 0s linear var(--dur-spring);
  }
  .sidebar[data-expanded="true"] {
    transform: none;
    opacity: 1;
    visibility: visible;
    transition: transform var(--dur-spring) var(--spring), opacity var(--dur-fast) var(--ease), visibility 0s;
  }

  /* Der erste Abschnitt der Einseitenseite (Gäste: #lock-section,
     Angemeldete: #dashboard-section) beginnt direkt unter der Leiste. Sein
     Sprungziel (scrollspy.js springt beim Laden zu data-initial-section) und
     seine Rastposition sollen der Seitenanfang samt Leiste sein; mit dem
     üblichen Abstand von --space-4 stand die Leiste sonst 32 px aus dem Bild
     (gemessen auf der Gast-Startseite bei 375 px). Ein Abstand von einer
     Bildschirmhöhe wird vom Browser auf 0 begrenzt. */
  :is(#lock-section, #dashboard-section) { scroll-margin-top: 100vh; }

  /* Knopf und Marke stehen in der Leiste, nicht doppelt im Menü. */
  .sidebar-top { display: none; }
  .sidebar-nav { margin-bottom: var(--space-5); }
  .sidebar-storage { margin-top: 0; }
  .sidebar-account { padding-left: 0; }

  /* Hintergrund steht still, solange das Menü offen ist. Hatte die Seite
     eine Scrollleiste, hält scrollbar-gutter ihren Platz frei (shell.js
     setzt nm-nav-gutter nur dann), damit beim Öffnen nichts springt. */
  html.nm-nav-open { overflow: hidden; }
  html.nm-nav-open.nm-nav-gutter { scrollbar-gutter: stable; }

  .no-js .mobile-bar-toggle { display: none; }
  .no-js .sidebar {
    position: static;
    max-height: none;
    transform: none;
    opacity: 1;
    visibility: visible;
    border-radius: 0;
    box-shadow: none;
  }
}

/* Querformat-Telefone und Hochformat-Tablets: Gruppen in zwei Spalten,
   damit das Menü auch bei 360 px Höhe ohne langes Scrollen auskommt. */
@media (min-width: 481px) and (max-width: 768px) {
  .sidebar-nav {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: start;
    gap: var(--space-5) var(--space-6);
  }
  /* Einstellungen (Gruppe ohne Kopf) als eigene Zeile, sonst stünde sie
     um die Kopfhöhe versetzt neben "Verwaltung". */
  .sidebar-nav > .nav-group:last-child { grid-column: 1 / -1; }
}

@media (max-width: 768px) and (prefers-contrast: more) {
  .mobile-bar,
  .sidebar { border-color: var(--border-strong); }
}

/* ==========================================================================
   Main
   ========================================================================== */
.main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  /* Oben etwas mehr Luft als früher (war 28px): die entfernte Topbar (AP3)
     brachte vorher selbst noch etwas Abstand mit, ohne sie klebte der
     Seitentitel sonst zu dicht am oberen Rand. */
  padding: 40px 40px 48px;
}

.app-footer {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: 24px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
  font-size: var(--size-footnote);
  color: var(--text-dim);
  max-width: 860px;
  /* Als Flex-Item von .main sonst ohne eigene Breitenangabe auf
     Content-Breite geschrumpft — max-width allein reicht nicht, es braucht
     zusätzlich width:100%, um die 860px-Spalte tatsächlich auszufüllen. */
  width: 100%;
  margin-inline: auto;
}

.app-footer-links {
  display: flex;
  /* Ohne flex-wrap behalten die drei (unbrechbaren Einzelwort-)Links immer
     ihre volle Eigenbreite (~299px) und stoßen unterhalb von ~480px
     Spaltenbreite über den rechten Rand von .app-footer hinaus — das war
     die Ursache des dokumentweiten horizontalen Overflows bei 390px
     (siehe Breakpoint-Leiter oben). flex-wrap erlaubt den Links, bei
     schmaler Spalte auf eine zweite Zeile umzubrechen statt überzulaufen. */
  flex-wrap: wrap;
  gap: 8px 18px;
}

/* Alle Footer-Links sehen gleich aus, auch "Vertrag widerrufen" und
   "Verträge hier kündigen" (.app-footer-link-action ist nur Kennung, ohne
   eigene Optik): --text-dim auf dem Grund über 4,5:1 (check_contrast.py),
   Fokusring über die globale :focus-visible-Regel, Zielhöhe mindestens 24px
   (WCAG 2.5.8), auf schmalen Schirmen 44px (Breakpoint unten). */
.app-footer-links a {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  color: var(--text-dim);
  text-decoration: none;
}
.app-footer-links a:hover { color: var(--text-primary); text-decoration: underline; }

/* Nur für Screenreader; gleiche Definition wie consent.css. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Skip-Link: außerhalb des Bildes, bei Fokus oben links sichtbar. */
.skip-link {
  position: fixed;
  top: 8px;
  left: 8px;
  z-index: 10000;
  padding: 10px 16px;
  background: var(--panel, #181b22);
  color: var(--text-primary);
  border-radius: 8px;
  transform: translateY(-200%);
}
.skip-link:focus,
.skip-link:focus-visible {
  transform: none;
  outline: var(--focus-ring-width) solid var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
}
/* Programmatischer Fokus per Skip-Link: kein Ring um den ganzen Inhalt. */
.main:focus { outline: none; }

/* ==========================================================================
   Scroll-Abschnitte — die fünf früheren Einzelseiten auf einer Seite
   ========================================================================== */
.scroll-section {
  position: relative;
  min-height: calc(100vh - 120px);
  display: flex;
  flex-direction: column;
  padding: var(--space-10) 0;
  /* War früher auf die sticky Topbar abgestimmt (80px) — die ist mit AP3
     entfallen, ein kleiner Wert bleibt trotzdem sinnvoll: etwas Luft über
     dem Sprungziel, statt dass die Überschrift exakt am Viewport-Rand klebt. */
  scroll-margin-top: var(--space-4);
  /* clip statt hidden: schneidet weiterhin seitlich ab, legt aber keinen
     eigenen Scrollcontainer an (sonst wären Fokusringe und Schatten am Rand
     abgeschnitten und position:sticky im Inhalt wirkungslos). */
  overflow-x: clip;
}

/* Raster an den Abschnitten (Stufe 6, 2026-10-12, Nutzerentscheidung 5
   "Einseitenscroll: Mischung"). Man scrollt frei durch, aber wer in der Nähe
   eines Abschnittsanfangs loslässt, landet sauber auf ihm. "proximity" statt
   "mandatory": Lange Abschnitte (Projektliste, Plugins) lassen sich weiter
   in Ruhe lesen, der Browser rastet nur nahe am Anfang ein. Ein Abschnitt,
   der höher als das Fenster ist, scrollt in sich frei (Scroll-Snap-Regel
   für übergroße Rastflächen). Der Abstand oben kommt aus scroll-margin-top.
   :has() begrenzt das Raster auf die Einseitenseite; /download, /plaene
   und der Editor scrollen wie bisher. Die schwebenden Vor/Zurück-Knöpfe
   gibt es nicht mehr (scrollspy.js baut sie seit Stufe 6 nicht mehr). */
html:has(.scroll-section) { scroll-snap-type: y proximity; }
.scroll-section { scroll-snap-align: start; }

/* --------------------------------------------------------------------------
   Eintritt (Optik-Stufe 2, DESIGNRICHTUNG 2.9 / C5)
   --------------------------------------------------------------------------
   Vorher flogen Titel und Blöcke 70–90 px von der Seite herein, federten
   über und kamen bei jedem erneuten Scrollen wieder. Jetzt: einmaliges,
   kurzes Aufblenden mit 8 px Weg nach oben, Feder ohne Überschwingen.

   Wie "einmalig" funktioniert: Die Animation hängt ab dem Laden an den
   Blöcken, steht aber auf "paused" — fill-mode backwards hält sie dabei im
   Startbild (unsichtbar). scrollspy.js setzt .is-visible, sobald der
   Abschnitt ins Bild kommt; dann läuft sie einmal durch. Danach ist sie
   beendet; seit Stufe 6 setzt scrollspy.js die Klasse ohnehin nur einmal
   und beobachtet den Abschnitt danach nicht mehr. Nach dem Ende bleibt durch
   "backwards" kein transform/opacity am Element hängen, also auch kein
   zusätzlicher Stapelkontext.

   Animiert werden nur die Blöcke der obersten Ebene, nicht jede Kachel und
   jede Zeile: main.js baut Kacheln bei jeder Sucheingabe neu, die würden
   sonst bei jedem Tastendruck erneut einblenden.

   Unter reduzierter Bewegung ist --motion-scale 0 (tokens.css): Der Weg
   entfällt, es bleibt ein reines Überblenden. */
@keyframes nm-enter {
  from {
    opacity: 0;
    transform: translateY(calc(8px * var(--motion-scale)));
  }
}

/* Titel und Untertitel blenden nur über (Stufe 6, "ruhige Abschnittstitel"):
   Die Überschrift ist der Anker, an dem das Auge beim Einrasten ankommt; sie
   soll nicht mitwandern. Den kleinen Weg nach oben machen nur die Blöcke
   darunter. */
@keyframes nm-fade {
  from { opacity: 0; }
}

.scroll-section :is(.page-title, .page-subtitle, .section-toolbar, .bento-grid, .plugins-layout, .section-group),
.scroll-section :is(.list-panel, .storage-usage-panel, .section-group-head--with-actions):not(.section-group *) {
  animation: nm-enter var(--dur-spring) var(--spring) backwards paused;
}
.scroll-section.is-visible :is(.page-title, .page-subtitle, .section-toolbar, .bento-grid, .plugins-layout, .section-group),
.scroll-section.is-visible :is(.list-panel, .storage-usage-panel, .section-group-head--with-actions):not(.section-group *) {
  animation-play-state: running;
}
.scroll-section :is(.page-title, .page-subtitle) { animation-name: nm-fade; }
/* Leichte Staffelung, damit Titel und Inhalt nicht als ein Block kommen */
.scroll-section .page-subtitle { animation-delay: 30ms; }
.scroll-section .section-toolbar { animation-delay: 60ms; }
.scroll-section :is(.bento-grid, .plugins-layout, .section-group),
.scroll-section :is(.list-panel, .storage-usage-panel, .section-group-head--with-actions):not(.section-group *) {
  animation-delay: 90ms;
}

.no-js .scroll-section :is(.page-title, .page-subtitle, .section-toolbar, .bento-grid, .plugins-layout, .section-group, .list-panel, .storage-usage-panel, .section-group-head--with-actions),
.no-js .lock-screen .lock-inner > * {
  animation: none;
}

/* ==========================================================================
   Sperrbildschirm / Startbereich — immer der erste Abschnitt
   ==========================================================================
   Neu gestaltet am 2026-10-19 (Nutzer: „passender zum rest“, „keine
   unnötigen eyebrows“). Bausteine statt eigener Optik:
   - Überschrift wie die Seitentitel von /plaene und /download (Anzeige-
     schrift, ruhiger senkrechter Verlauf), nur größer. Der frühere
     Dreifarbverlauf und das Etikett „NoCheats Studio“ darüber sind weg.
   - Knöpfe sind .btn-primary/.btn-secondary; die Hauptaktionen bekommen
     nur mehr Höhe (48 px) und eine Kante am Anmelden-Knopf, damit er als
     Knopf lesbar ist und nicht als Fläche im Raster verschwindet.
   - Die drei Kacheln sind Karten wie im Rest der App (DESIGNRICHTUNG 2.4:
     deckend, Trennlinie, kein Glas), Symbole in der Farbleiter der Bento-
     Kacheln.
   Alle Werte aus tokens.css. */
.lock-screen {
  min-height: calc(100vh - 60px);
  justify-content: center;
  align-items: center;
  text-align: center;
  padding: var(--space-6) 0 var(--space-12);
}

.lock-inner {
  max-width: var(--content-max-narrow);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-5);
}

.lock-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(2.25rem, 5.2vw, 3.5rem);
  font-weight: var(--weight-bold);
  line-height: var(--leading-display);
  letter-spacing: var(--tracking-display);
  color: var(--text-primary);
  text-wrap: balance;
}
/* Fokusziel von „Nach oben“, kein Bedienelement: kein Ring um die Zeile. */
.lock-title:focus { outline: none; }

/* Dieselbe Behandlung wie .plans-page/.download-page .page-title (siehe
   dort, auch zum Kontrast: dunkelster Punkt 9,7:1 gegen --bg). */
@supports ((background-clip: text) or (-webkit-background-clip: text)) {
  .lock-title {
    background-image: linear-gradient(
      180deg,
      var(--text-primary) 0%,
      color-mix(in srgb, var(--accent) 45%, var(--text-primary)) 100%
    );
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
}

/* Slogan unter der Überschrift (2026-10-19, Nutzer: „nochmal der
   Slogan“). Eine ruhige Zeile, kein Etikett: Anzeigeschrift wie die
   Überschrift, aber eine Stufe kleiner als diese und größer als die
   Einleitung, im Akzentton aufgehellt. Rückt etwas an die Überschrift heran,
   damit beide als ein Block lesen. Kontrast gegen --bg rund 11:1 (gemessen
   am gerenderten Ton, 2026-10-19). */
.lock-claim {
  margin: calc(var(--space-2) * -1) 0 0;
  max-width: 32em;
  font-family: var(--font-display);
  font-size: var(--size-title-2);
  font-weight: var(--weight-medium);
  line-height: 1.35;
  letter-spacing: -0.005em;
  color: color-mix(in srgb, var(--accent) 55%, var(--text-primary));
  text-wrap: balance;
}

.lock-lead {
  margin: 0;
  max-width: 36em;
  font-size: var(--size-title-3);
  line-height: 1.55;
  color: var(--text-secondary);
  text-wrap: pretty;
}

/* Hauptaktionen: Registrieren gefüllt, Anmelden sekundär mit Kante. */
.lock-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-3);
  margin-top: var(--space-1);
}

/* Doppelte Klasse bzw. Elternbezug: .btn-primary/.btn-secondary stehen
   weiter unten in dieser Datei und gewännen bei gleichem Gewicht. */
:is(.lock-actions, .teaser-end) .lock-cta {
  min-height: calc(var(--control-h-l) + var(--space-2));
  padding: 0 var(--space-8);
  font-size: var(--size-headline);
  font-weight: var(--weight-semibold);
}
.lock-cta-login { box-shadow: inset 0 0 0 1px var(--border-strong); }

/* Weitere Wege: Discord, Google, VST-Plugin. Eine Stufe leiser. */
.lock-alt {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2);
}
.lock-alt .lock-alt-btn {
  min-height: var(--control-h-l);
  padding: 0 var(--space-4);
}
.lock-alt-btn svg {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}

.lock-facts {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-3);
  width: 100%;
}

/* Die drei Kacheln sind Links mit der Bento-Klasse .tile (Fläche, Rand in
   --tile-accent, Schatten, Drücken mit --press-scale, alles aus dem
   Abschnitt „Bento grid“ weiter unten). .lock-fact legt nur Aufbau und das
   Reagieren darüber (Nutzer 2026-10-19: „Die Bentokacheln sollen Reaktiv
   sein“):
   - Überfahren (nur mit echtem Zeiger, @media (hover: hover)) und
     Tastaturfokus heben die Kachel an, färben den Rand im Akzent und
     blenden ein Glanzlicht ein.
   - Das Glanzlicht folgt dem Zeiger (--mx/--my aus static/js/lock_tiles.js,
     Mechanik wie main.js:initTileGlare). Ohne Zeiger, ohne JavaScript und
     bei reduzierter Bewegung steht es ruhig oben in der Mitte.
   - Reduzierte Bewegung: --motion-scale 0 nimmt den Weg (Anheben, Pfeil),
     Rand und Licht bleiben als reine Farbänderung.
   - Mehr Kontrast: kein Glanzlicht, dafür der volle Akzentrand.
   Specificity: .lock-facts .lock-fact (0,2,0) schlägt .tile (0,1,0). */
.lock-facts li { display: flex; min-width: 0; }
.lock-facts li:nth-child(1) { --fact-accent: var(--tone-2); }
.lock-facts li:nth-child(2) { --fact-accent: var(--tone-4); }
.lock-facts li:nth-child(3) { --fact-accent: var(--tone-6); }

.lock-facts .lock-fact {
  --tile-accent: var(--fact-accent);
  --fact-lift: calc(var(--motion-scale) * -3px);
  flex: 1 1 auto;
  min-width: 0;
  flex-direction: row;
  align-items: flex-start;
  justify-content: flex-start;
  gap: var(--space-3);
  padding: var(--space-4);
  text-align: left;
  text-decoration: none;
  color: var(--text-secondary);
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
    box-shadow var(--dur-base) var(--ease), transform var(--dur-spring) var(--spring);
}

/* Glanzlicht: über der Fläche, unter dem Inhalt. */
.lock-facts .lock-fact::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: radial-gradient(
    260px circle at var(--mx, 50%) var(--my, 0%),
    color-mix(in srgb, var(--tile-accent) 20%, transparent),
    transparent 70%
  );
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease);
}
.lock-fact > svg,
.lock-fact > span { position: relative; z-index: 1; }

@media (hover: hover) {
  .lock-facts .lock-fact:hover {
    transform: translateY(var(--fact-lift));
    border-color: color-mix(in srgb, var(--tile-accent) 55%, var(--separator));
    box-shadow: var(--shadow-l);
  }
  .lock-facts .lock-fact:hover::after { opacity: 1; }
  .lock-fact:hover .lock-fact-arrow { transform: translateX(calc(var(--motion-scale) * 3px)); }
}
.lock-facts .lock-fact:focus-visible {
  transform: translateY(var(--fact-lift));
  background: var(--tile-bg-hover);
  border-color: color-mix(in srgb, var(--tile-accent) 55%, var(--separator));
  box-shadow: var(--shadow-l);
}
.lock-facts .lock-fact:focus-visible::after { opacity: 1; }

@media (prefers-contrast: more) {
  .lock-facts .lock-fact::after { display: none; }
  .lock-facts .lock-fact:is(:hover, :focus-visible) { border-color: var(--tile-accent); }
}

.lock-fact > svg {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  color: var(--tile-accent);
}

.lock-fact > span {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
  font-size: var(--size-footnote);
  line-height: var(--leading-footnote);
  color: var(--text-secondary);
}

.lock-fact strong {
  font-size: var(--size-body);
  font-weight: var(--weight-semibold);
  color: var(--text-primary);
}

/* Wohin die Kachel führt: kleine Zeile im Akzent (Töne der Farbleiter,
   alle >= 5,7:1 gegen den Grund), Pfeil wandert beim Überfahren mit. */
.lock-fact .lock-fact-go {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  margin-top: var(--space-1);
  font-weight: var(--weight-semibold);
  color: var(--tile-accent);
}
.lock-fact-arrow {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  transition: transform var(--dur-spring) var(--spring);
}

/* „Teaser ansehen“ (#teaserPlay): Beschriftung über einem runden Pfeil,
   ohne Dauer-Wippen (eine Endlosschleife zieht den Blick und stört bei
   Bewegungsempfindlichkeit). teaser_scroll.js blendet ihn über .is-away
   aus, sobald gescrollt wurde. */
.lock-more {
  margin-top: var(--space-3);
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  background: none;
  border: none;
  border-radius: var(--radius-m);
  color: var(--text-secondary);
  font: inherit;
  font-size: var(--size-callout);
  font-weight: var(--weight-medium);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease),
              opacity var(--dur-base) var(--ease),
              visibility 0s linear;
}
.lock-more[hidden] { display: none; }
.lock-more svg {
  box-sizing: content-box;
  width: 20px;
  height: 20px;
  padding: var(--space-3);
  border-radius: var(--radius-full);
  background: var(--fill-2);
  box-shadow: inset 0 0 0 1px var(--border-strong);
  transition: background var(--dur-fast) var(--ease), transform var(--dur-press) var(--ease);
}
.lock-more:hover { color: var(--text-primary); }
.lock-more:hover svg { background: var(--fill-3); }
.lock-more:active svg { transform: scale(var(--press-scale)); }
.lock-more.is-away {
  opacity: 0;
  visibility: hidden;
  transition: color var(--dur-fast) var(--ease),
              opacity var(--dur-base) var(--ease),
              visibility 0s linear var(--dur-base);
}

@media (max-width: 759.98px) {
  .lock-facts { grid-template-columns: 1fr; }
  .lock-facts .lock-fact { padding: var(--space-3) var(--space-4); }
}

/* Schmal: Hauptaktionen untereinander in voller Breite, damit beide groß
   und sofort erkennbar bleiben. */
@media (max-width: 479.98px) {
  .lock-actions { flex-direction: column; align-self: stretch; }
  .lock-actions .lock-cta { width: 100%; }
  .lock-alt { flex-direction: column; align-self: stretch; }
  .lock-lead { font-size: var(--size-headline); }
  .lock-claim { font-size: var(--size-title-3); }
}

/* Der Sperrbildschirm ist beim Laden sofort da, nur ein sanftes,
   einmaliges Aufblenden der Teile nacheinander (Mechanik wie oben). */
.lock-screen .lock-inner > * {
  animation: nm-enter var(--dur-spring) var(--spring) backwards paused;
}
.lock-screen.is-visible .lock-inner > * { animation-play-state: running; }
.lock-screen .lock-inner > :nth-child(2) { animation-delay: 30ms; }
.lock-screen .lock-inner > :nth-child(3) { animation-delay: 60ms; }
.lock-screen .lock-inner > :nth-child(4) { animation-delay: 90ms; }
.lock-screen .lock-inner > :nth-child(5) { animation-delay: 120ms; }
.lock-screen .lock-inner > :nth-child(n+6) { animation-delay: 150ms; }

/* Suchfeld der Abschnitts-Toolbars. Der Klassenname stammt aus der früheren
   globalen Topbar (AP3) und bleibt, weil Vorlagen und Tests ihn nutzen.
   Seit Optik-Stufe 2 ein normales Eingabefeld (deckend, Feldrand), kein
   Glas mehr: liquid-glass.js zielt nur noch auf .master-viz-block. */
.topbar-search {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  background: var(--field-bg);
  border: 1px solid var(--field-border);
  border-radius: var(--radius-s);
  min-height: var(--control-h-m);
  padding: 0 var(--space-3);
  width: 320px;
  max-width: 100%;
  color: var(--text-dim);
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}

.topbar-search:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent);
}

.topbar-search svg { width: 16px; height: 16px; flex-shrink: 0; }

/* Der Rahmen um das Feld zeigt den Fokus (focus-within oben), das innere
   Feld selbst braucht deshalb keinen eigenen Ring. */
.topbar-search input {
  background: none;
  border: none;
  outline: none;
  color: var(--text-primary);
  font-size: var(--size-body);
  width: 100%;
  min-width: 0;
  padding: var(--space-1) 0;
}
.topbar-search input:focus-visible { outline: none; }
.topbar-search input::placeholder { color: var(--text-dim); }

/* ==========================================================================
   Knöpfe (DESIGNRICHTUNG 2.5)
   ==========================================================================
   Drei Höhen aus den Tokens (24/32/40), gefüllt nur für die Hauptaktion.
   Gedrückt-Zustand auf :active (sofort beim Drücken, nicht erst beim
   Loslassen), kein Anheben beim Überfahren mehr. */
.btn-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--control-h-m);
  padding: 0 var(--space-4);
  background: var(--accent-fill);
  color: var(--on-accent-fill);
  border: none;
  border-radius: var(--radius-s);
  font-weight: 600;
  font-size: var(--size-callout);
  line-height: 1.2;
  text-decoration: none;
  transition: background var(--dur-fast) var(--ease), transform var(--dur-press) var(--ease);
}
.btn-primary:hover { background: var(--accent-fill-hover); }
.btn-primary:active { background: var(--accent-fill-pressed); transform: scale(var(--press-scale)); }
.btn-primary:disabled,
.btn-primary[aria-disabled="true"] { opacity: 0.45; cursor: not-allowed; transform: none; }
.btn-primary svg { width: 16px; height: 16px; flex-shrink: 0; }

/* ==========================================================================
   Bento grid — zentriert, ergibt ein durchgängiges Rechteck
   ========================================================================== */
/* Seit dem Topbar-Umbau steckt hier zusätzlich zum .bento-grid noch die
   .section-toolbar (Suchfeld + "Neu"-Button) drin — als Flex-Zeile lägen
   beide nebeneinander statt Toolbar oben, Grid darunter, daher column. */
.grid-wrap {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--space-8) 0;
}

/* Toolbar exakt so breit wie das Bento-Grid, damit beide Kanten bündig
   abschließen; nur das direkte Kind, damit Toolbars in Projekte/Storage/
   Plugins (.page-wrap) unberührt bleiben. */
.grid-wrap > .section-toolbar {
  width: 100%;
  max-width: 940px;
}

.bento-grid {
  display: grid;
  /* Live-Abnahme 2026-10-12: Mit 220px als Obergrenze ergaben vier Spalten
     plus Abstände 916px, die Toolbar darüber aber 940px; der "Neues
     Projekt"-Knopf stand 24px über die rechte Kachelkante hinaus. 1fr füllt
     die 940px, beide Kanten schließen bündig. */
  grid-template-columns: repeat(4, minmax(140px, 1fr));
  grid-auto-rows: 132px;
  gap: var(--space-3);
  width: 100%;
  max-width: 940px;
}

/* Kacheln (Optik-Stufe 2, DESIGNRICHTUNG 2.6): deckende Karten mit
   Trennlinie statt Glas. Weggefallen sind der Unschärfe-Filter, der
   Glanzrand (::before), das dem Cursor folgende Glanzlicht (::after) und
   das Anheben beim Überfahren. Überfahren hebt jetzt Fläche und Rand leicht
   an, Drücken skaliert kurz (--press-scale). Die Kachel-"Art" färbt weiter
   über --tile-accent Rand und Wellenform. */
.tile {
  position: relative;
  border-radius: var(--radius-l);
  background: var(--tile-bg);
  --tile-accent: var(--glow-1);
  border: 1px solid var(--separator);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: var(--space-4);
  cursor: pointer;
  box-shadow: var(--shadow-s);
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
    box-shadow var(--dur-base) var(--ease), transform var(--dur-press) var(--ease);
}

.tile:hover {
  background: var(--tile-bg-hover);
  border-color: color-mix(in srgb, var(--tile-accent) 45%, var(--separator));
  box-shadow: var(--shadow-m);
}
.tile:active:not(:has(.play-control:active)) { transform: scale(var(--press-scale)); }

/* Jede Kachel-"Art" bekommt ihre eigene Stufe der Farbleiter (tokens.css)
   als Akzentfarbe — jede Art setzt nur --tile-accent. */
.tile[data-type="mix"] { color: var(--type-mix); --tile-accent: var(--type-mix); }
.tile[data-type="master"] { color: var(--type-master); --tile-accent: var(--type-master); }
.tile[data-type="new"] { color: var(--type-new); --tile-accent: var(--type-new); }
.tile[data-type="local-recent"] {
  color: var(--type-local-recent);
  --tile-accent: var(--type-local-recent);
  border-color: color-mix(in srgb, var(--tile-accent) 30%, var(--separator));
}

/* Plaketten in Satzschreibung statt Versalien, Systemschrift statt Mono */
.tile-type-badge {
  position: relative;
  z-index: 2;
  align-self: flex-start;
  font-size: var(--size-caption);
  line-height: var(--leading-caption);
  font-weight: 600;
  letter-spacing: var(--tracking-caption);
  padding: 2px var(--space-2);
  border-radius: var(--radius-full);
  margin-bottom: auto;
}
.tile-type-badge-mix {
  color: var(--type-mix);
  background: color-mix(in srgb, var(--type-mix) 16%, transparent);
}
.tile-type-badge-master {
  color: var(--type-master);
  background: color-mix(in srgb, var(--type-master) 16%, transparent);
}
.tile-type-badge-local-recent {
  color: var(--type-local-recent);
  background: color-mix(in srgb, var(--type-local-recent) 16%, transparent);
}

/* Wellenform als Textur im oberen Teil der Kachel. Diagnosebefund: Name und
   Datum unten standen direkt auf der Wellenform, das Datum unter 4,5:1.
   Die Maske blendet die Wellenform zur Textzone hin aus, dort steht die
   Schrift auf der deckenden Kachelfläche (DESIGNRICHTUNG 2.6). */
.tile-thumb {
  position: absolute;
  inset: 0;
  opacity: 0.35;
  color: currentColor;
  pointer-events: none;
  overflow: hidden; /* Wichtig, damit das SVG beim Verschieben nicht herausragt */
  -webkit-mask-image: linear-gradient(to bottom, black 0 40%, transparent 72%);
  mask-image: linear-gradient(to bottom, black 0 40%, transparent 72%);
}

/* Wellenform-Laufband. Entwurf des Nutzers (2026-10-11, Commits e331129c
   bis ae986224): Laufband mit zwei gleichen Wellenformen, 60 s je Runde,
   Pause beim Überfahren, schneller Zustand .fast mit 3 s, weicher Übergang
   von 0,6 s. Seine Werte und Kommentare stehen unverändert (mit "Nutzer:").
   Fertiggestellt in AP3a, jeweils mit "AP3a:" markiert:
   - Die Animation steht in prefers-reduced-motion: no-preference. Unter
     reduzierter Bewegung steht die Wellenform still (DESIGNRICHTUNG 4),
     und der Design-Wächter verlangt Laufzeiten außerhalb dieses Blocks
     aus Tokens.
   - Die Pause hängt am Überfahren der Kachel. .tile-thumb hat
     pointer-events: none, ein :hover auf dem Band selbst greift nie.
   - .fast setzt main.js beim Klick auf die Kachel (speedUpWaveform). Ein
     transition auf animation-duration wirkt in keinem Browser, weil
     animation-* nicht animierbar ist. Den Übergang von 0,6 s fährt
     deshalb main.js über die Abspielrate, an derselben Stelle des Bands.
   - Kachel-Meta: Die Maske von .tile-thumb blendet die Wellenform ab 72 %
     der Kachelhöhe aus; das Band läuft darin, Datum und Name stehen
     weiter auf deckender Fläche (Kontrast wie ohne Laufband). */
/* Nutzer: Neuer Wrapper für die Animation */
.tile-thumb-track {
  display: flex;
  width: 200%;
  height: 100%;
}

@media (prefers-reduced-motion: no-preference) {
  .tile-thumb-track {
    /* Nutzer: Standard-Geschwindigkeit (z. B. normal langsam) */
    animation: scroll-left 60s linear infinite;
  }

  /* Nutzer: Animation pausieren, wenn die Maus darüber ist.
     AP3a: am Überfahren der Kachel; nach dem Klick (.fast) läuft es weiter. */
  .tile:hover .tile-thumb-track:not(.fast) {
    animation-play-state: paused;
  }

  .tile-thumb-track.fast {
    animation-duration: 3s; /* Nutzer: Viel schneller (Wert anpassen nach Wunsch) */
  }
}

.tile-thumb svg {
  width: 50%; /* Nutzer: Jedes SVG nimmt genau die Hälfte des Tracks ein */
  height: 100%;
  flex-shrink: 0;
}

/* Nutzer: Keyframes für die endlose Schleife */
@keyframes scroll-left {
  0% {
    transform: translateX(0);
  }
  100% {
    transform: translateX(-50%); /* Nutzer: Nach der Hälfte springt es unsichtbar auf 0 zurück */
  }
}

.tile-name {
  position: relative;
  z-index: 1;
  font-family: var(--font-text);
  font-weight: 600;
  font-size: var(--size-headline);
  line-height: var(--leading-headline);
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tile-meta {
  position: relative;
  z-index: 1;
  margin-top: 2px;
  font-size: var(--size-footnote);
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
}

/* Größenvarianten (unregelmäßiges Bento-Muster, 4x4-Raster ohne Lücken,
   vertikal dominant: "Neues Projekt" als hohe linke Spalte) */
.tile-new      { grid-column: 1 / 3; grid-row: 1 / 4; }
.tile-recent-1 { grid-column: 3 / 5; grid-row: 1 / 2; }
.tile-recent-2 { grid-column: 3 / 4; grid-row: 2 / 3; }
.tile-recent-3 { grid-column: 3 / 4; grid-row: 3 / 4; }
.tile-empty-1  { grid-column: 4 / 5; grid-row: 2 / 4; }
.tile-empty-2  { grid-column: 1 / 3; grid-row: 4 / 5; }
.tile-empty-3  { grid-column: 3 / 5; grid-row: 4 / 5; }

/* Aktionskacheln "Neues Projekt" und "App herunterladen" / "Lokale DAW
   öffnen" (AP3a, Nutzerauftrag 2026-10-11). Bauform: Symbol bzw.
   Illustration, Titel, Untertitel und ein Knopf auf --accent-fill. Jede
   Kachel hat ihren eigenen Akzent aus der Tonleiter (--type-new /
   --type-local): Rand, Symbol und ein Lichtschein aus der Ecke. Der Knopf
   ist das Tastaturziel; die Kachel selbst bleibt per Maus ganz klickbar.
   Überfahren hebt das Symbol mit der Feder an (Weg über --motion-scale,
   unter reduzierter Bewegung 0), Drücken skaliert die Kachel wie alle
   anderen (--press-scale) und dunkelt den Knopf (--accent-fill-pressed). */
.tile-new {
  --tile-accent: var(--type-new);
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  text-align: center;
  color: var(--text-primary);
  background:
    radial-gradient(120% 70% at 50% 0%, color-mix(in srgb, var(--tile-accent) 22%, transparent), transparent 70%),
    color-mix(in srgb, var(--accent-fill) 10%, var(--panel));
  border-color: color-mix(in srgb, var(--tile-accent) 35%, var(--separator));
}
.tile-new:hover {
  background:
    radial-gradient(120% 70% at 50% 0%, color-mix(in srgb, var(--tile-accent) 30%, transparent), transparent 72%),
    color-mix(in srgb, var(--accent-fill) 14%, var(--panel));
  border-color: color-mix(in srgb, var(--tile-accent) 55%, var(--separator));
}

/* Das frühere Diagonal-Raster entfällt: Die Kachel soll als Aktion lesbar
   sein, nicht als Dekoration. */
.tile-thumb-new { display: none; }

.tile-action-art,
.tile-action-text,
.tile-cta {
  position: relative;
  z-index: 1;
}

.tile-action-art {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--tile-accent);
  transition: transform var(--dur-spring) var(--spring);
}
.tile-new .tile-action-art svg {
  width: 96px;
  height: 72px;
}
.tile-new:hover .tile-action-art {
  transform: translateY(calc(var(--motion-scale) * -4px)) scale(calc(1 + var(--motion-scale) * 0.04));
}

.tile-action-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.tile-new .tile-new-label {
  font-family: var(--font-text);
  font-size: var(--size-title-3);
  line-height: var(--leading-title-3);
  font-weight: 600;
  color: var(--text-primary);
}

.tile-action-sub,
.tile-local .tile-local-hint {
  font-size: var(--size-footnote);
  line-height: var(--leading-footnote);
  color: var(--text-secondary);
}
.tile-action-sub { max-width: 34ch; text-wrap: balance; }

/* Handlungsknopf: Füllung, Hover und Drücken wie .btn-primary. Hover der
   ganzen Kachel hellt ihn mit auf, weil die ganze Kachel die Aktion ist. */
.tile-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--control-h-l);
  padding: 0 var(--space-5);
  border: 0;
  border-radius: var(--radius-full);
  background: var(--accent-fill);
  color: var(--on-accent-fill);
  font: inherit;
  font-size: var(--size-callout);
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), transform var(--dur-spring) var(--spring);
}
.tile-cta svg { width: 16px; height: 16px; flex-shrink: 0; }
.tile:hover .tile-cta,
.tile-cta:hover { background: var(--accent-fill-hover); }
.tile:active .tile-cta { background: var(--accent-fill-pressed); }
.tile.is-busy .tile-cta { opacity: 0.6; cursor: progress; }

/* "Lokale DAW"-Kachel: breite Kachel, deshalb waagerecht — Symbol, Text,
   Knopf. Reicht die Breite nicht (zweispaltiges Raster), bricht der Knopf
   in eine zweite Zeile um. Eigener Akzent --type-local (Cyan). Verhalten
   je nach Umgebung siehe main.js: buildLocalDawTile. */
.tile[data-type="local"] { --tile-accent: var(--type-local); }
.tile-local {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  align-content: center;
  justify-content: flex-start;
  gap: var(--space-2) var(--space-4);
  text-align: left;
  background:
    radial-gradient(60% 140% at 0% 50%, color-mix(in srgb, var(--tile-accent) 14%, transparent), transparent 70%),
    var(--tile-bg);
  border-color: color-mix(in srgb, var(--tile-accent) 25%, var(--separator));
}
.tile-local:hover {
  background:
    radial-gradient(60% 140% at 0% 50%, color-mix(in srgb, var(--tile-accent) 20%, transparent), transparent 72%),
    var(--tile-bg-hover);
  border-color: color-mix(in srgb, var(--tile-accent) 50%, var(--separator));
}
.tile-local .tile-action-art {
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  border-radius: var(--radius-m);
  background: color-mix(in srgb, var(--tile-accent) 16%, transparent);
}
.tile-local .tile-action-art svg { width: 24px; height: 24px; }
.tile-local:hover .tile-action-art {
  transform: translateY(calc(var(--motion-scale) * -2px)) scale(calc(1 + var(--motion-scale) * 0.06));
}
.tile-local .tile-action-text { flex: 1 1 140px; }
.tile-local .tile-new-label {
  font-family: var(--font-text);
  font-size: var(--size-headline);
  line-height: var(--leading-headline);
  font-weight: 600;
  color: var(--text-primary);
}
.tile-local .tile-cta {
  min-height: var(--control-h-m);
  padding: 0 var(--space-4);
}

/* Leere Platzhalter-Kacheln — nicht interaktiv (kein Hover, kein Drücken,
   kein Zeiger-Cursor). Seit Stufe 6 nur noch der letzte Rückfall, wenn die
   Füllkacheln (unten) aufgebraucht sind. */
.tile-empty {
  border: 1px dashed var(--border-soft);
  background: transparent;
  box-shadow: none;
  align-items: center;
  justify-content: center;
  color: var(--text-dim);
  cursor: default;
  gap: var(--space-1);
}

.tile-empty:hover,
.tile-empty:active {
  transform: none;
  border-color: var(--border-soft);
  background: transparent;
  box-shadow: none;
}

.tile-empty .tile-empty-dots {
  font-size: var(--size-title-3);
  letter-spacing: 4px;
  opacity: 0.4;
}

.tile-empty .tile-type-badge {
  position: absolute;
  top: var(--space-3);
  left: var(--space-3);
  margin-bottom: 0;
}

.tile-empty-caption {
  font-size: var(--size-footnote);
  color: var(--text-secondary);
  text-align: center;
  padding: 0 var(--space-2);
}

/* --------------------------------------------------------------------------
   Füllkacheln (Stufe 6, 2026-10-12, Nutzerentscheidung 7): geteiltes
   Projekt, Speicher und Einladungen. Aufbau und Reihenfolge in main.js
   (Kopf von initBentoGrid).
   - Geteilt und Speicher sind echte Links (<a>): Tab-Ziel, Mittelklick.
   - Einladungen tragen einen Knopf in --accent-fill (wie die Aktionskacheln).
   - Farben: "Geteilt" in --accent (Link-Blau, 7:1 auf der Kachel), Speicher
     und Einladungen neutral. Neue --type-* Token wären ein AP1-Nachtrag;
     die Rollen hier sind keine neue Kachelart der Farbleiter.
   -------------------------------------------------------------------------- */
a.tile {
  color: var(--text-primary);
  text-decoration: none;
}

.tile[data-type="shared"] { color: var(--accent); --tile-accent: var(--accent); }
.tile-type-badge-shared {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
}
.tile-type-badge-neutral {
  color: var(--text-secondary);
  background: var(--fill-2);
}

/* Speicher: Zahl, Balken, Prozent. Die Farbe wechselt erst nahe am Limit
   und ist nie die einzige Aussage (Prozent steht daneben). */
.tile-storage { gap: var(--space-2); }
.tile-storage-of {
  font-weight: 400;
  font-size: var(--size-footnote);
  color: var(--text-secondary);
}
.tile-storage-bar {
  position: relative;
  z-index: 1;
  display: block;
  height: 6px;
  border-radius: var(--radius-full);
  background: var(--fill-2);
  overflow: hidden;
}
.tile-storage-fill {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: var(--accent);
}
.tile-storage[data-level="warning"] .tile-storage-fill { background: var(--warning); }
.tile-storage[data-level="danger"] .tile-storage-fill { background: var(--danger); }
.tile-storage .tile-meta { margin-top: 0; }

/* Einladung: Plakette oben links, Titel und Knopf unten. In schmalen
   Kacheln (1x1) nur Titel und Knopf, in breiten und hohen Kacheln dazu ein
   Satz Erklärung. */
.tile-hint {
  align-items: flex-start;
  gap: var(--space-2);
}
.tile-hint[data-hint="master"] { --tile-accent: var(--type-master); color: var(--type-master); }
/* Stehende Wellenform der Master-Einladung: blasser als bei echten Kacheln. */
.tile-thumb-ghost { opacity: 0.16; }
.tile-thumb-ghost svg { width: 100%; height: 100%; }
.tile-hint .tile-type-badge {
  position: absolute;
  top: var(--space-4);
  left: var(--space-4);
  margin-bottom: 0;
}
.tile-hint-text {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  max-width: 100%;
}
.tile-hint-title {
  font-family: var(--font-text);
  font-weight: 600;
  font-size: var(--size-headline);
  line-height: var(--leading-headline);
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tile-hint-sub {
  font-size: var(--size-footnote);
  line-height: var(--leading-footnote);
  color: var(--text-secondary);
  text-wrap: pretty;
  overflow: hidden;
  display: none;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.tile-hint .tile-cta {
  min-height: var(--control-h-m);
  padding: 0 var(--space-4);
  font-size: var(--size-footnote);
}

/* Breite Kacheln (2x1) im Vier-Spalten-Raster: Text links, Knopf rechts. */
@media (min-width: 901px) {
  .tile-hint:is(.tile-recent-1, .tile-empty-3) {
    flex-direction: row;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-3);
  }
  .tile-hint:is(.tile-recent-1, .tile-empty-3) .tile-hint-text { flex: 1 1 auto; }
  .tile-hint:is(.tile-recent-1, .tile-empty-3, .tile-empty-1) .tile-hint-sub { display: -webkit-box; }
}

/* ==========================================================================
   Play-Control — Sekunden-Eingabe + Play/Pause, in Bento-Kacheln und
   Projektzeilen wiederverwendet (shell.js: createPlayControl)
   ========================================================================== */
.play-control {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.tile .play-control {
  margin-top: var(--space-2);
}

.seek-input {
  width: 52px;
  min-height: var(--control-h-s);
  background: var(--field-bg);
  border: 1px solid var(--field-border);
  border-radius: var(--radius-s);
  padding: 2px var(--space-1);
  color: var(--text-secondary);
  font-family: var(--font-mono);
  font-size: var(--size-footnote);
  font-variant-numeric: tabular-nums;
  transition: border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.seek-input:focus { border-color: var(--accent); color: var(--text-primary); }
.seek-input::-webkit-inner-spin-button { opacity: 0.4; }

.play-toggle-btn {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border-radius: 50%;
  background: var(--fill-2);
  border: none;
  color: var(--text-primary);
  transition: background var(--dur-fast) var(--ease), transform var(--dur-press) var(--ease);
}
.play-toggle-btn svg { width: 14px; height: 14px; }
/* Live-Abnahme 2026-10-12: Das HTML-Attribut hidden blendet ein <svg> nicht
   aus (die UA-Regel [hidden] gilt nur für HTML-Elemente), deshalb standen
   Play- und Pause-Symbol immer nebeneinander. setPlayIcon (shell.js) schaltet
   weiter über hidden um; diese Regel macht es wirksam. */
.play-toggle-btn svg[hidden] { display: none; }
.play-toggle-btn:hover { background: var(--fill-3); }
.play-toggle-btn:active { transform: scale(var(--press-scale)); }

/* ==========================================================================
   Buttons (sekundär / gefährlich) — ergänzend zu .btn-primary
   ========================================================================== */
.btn-secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--control-h-m);
  padding: 0 var(--space-4);
  background: var(--fill-2);
  color: var(--text-primary);
  border: none;
  border-radius: var(--radius-s);
  font-weight: 500;
  font-size: var(--size-callout);
  line-height: 1.2;
  text-decoration: none;
  transition: background var(--dur-fast) var(--ease), transform var(--dur-press) var(--ease);
}
.btn-secondary:hover { background: var(--fill-3); }
.btn-secondary:active { transform: scale(var(--press-scale)); }
.btn-secondary:disabled { opacity: 0.45; cursor: not-allowed; transform: none; }

/* Gefüllter Gefahrenknopf: weiße Schrift auf --danger-fill (5,16:1).
   Vorher dunkle Schrift auf hellem Rot, als Hex-Literal. */
.btn-danger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--control-h-m);
  padding: 0 var(--space-4);
  background: var(--danger-fill);
  color: var(--on-danger-fill);
  border: none;
  border-radius: var(--radius-s);
  font-weight: 600;
  font-size: var(--size-callout);
  line-height: 1.2;
  transition: background var(--dur-fast) var(--ease), transform var(--dur-press) var(--ease);
}
.btn-danger:hover { background: color-mix(in srgb, var(--danger-fill) 88%, black); }
.btn-danger:active { background: color-mix(in srgb, var(--danger-fill) 76%, black); transform: scale(var(--press-scale)); }
.btn-danger:disabled { opacity: 0.45; cursor: not-allowed; transform: none; }

/* ==========================================================================
   Listenseiten (Projekte, Cloud Storage)
   ========================================================================== */
.page-wrap {
  flex: 1;
  padding: var(--space-6) 0 var(--space-8);
  max-width: 860px;
  width: 100%;
  margin-inline: auto;
}

.page-title {
  font-family: var(--font-display);
  font-size: var(--size-title-1);
  line-height: var(--leading-title-1);
  letter-spacing: var(--tracking-title-1);
  font-weight: 600;
  margin: 0 0 var(--space-1);
}

.page-subtitle {
  color: var(--text-secondary);
  font-size: var(--size-body);
  margin: 0 0 var(--space-6);
}

/* --------------------------------------------------------------------
   Anzeige-Überschriften der beiden öffentlichen Seiten (/plaene, /download).

   Die Schriftfamilie war schon dieselbe wie beim Sperrbildschirm-Titel —
   .page-title oben setzt bereits var(--font-display). Was "Deine intelligente
   DAW" heraushebt, ist die Behandlung: engere Laufweite und ein Farbverlauf.
   Genau die bekommen diese beiden Überschriften hier.

   Der Verlauf ist gegenüber .lock-title (text-primary -> teal -> accent)
   bewusst stark zurückgenommen. Dort ist er ein Blickfang auf einer
   Werbefläche und darf laut sein; hier stehen darunter Preise bzw.
   Download-Knöpfe, und eine bunte Überschrift zöge die Aufmerksamkeit von
   dem weg, worum es auf der Seite eigentlich geht. Bleibt: ein Hauch
   Blaustich zum unteren Rand.

   Senkrecht (180deg), nicht schräg wie .lock-title — und das ist kein
   Geschmacksdetail: die Hintergrundfläche eines Block-Elements ist immer so
   breit wie die Spalte, nicht wie der Text. Bei einem waagerechten Verlauf
   über ein kurzes, mittig stehendes "Lokale Version" läge auf den Buchstaben
   nur der linke Anfang des Verlaufs, der Zielton am Ende käme nie zum
   Vorschein. Die Höhe der Fläche entspricht dagegen immer der Textzeile, ein
   senkrechter Verlauf wirkt also unabhängig von Textlänge und Ausrichtung.

   Kontrast bleibt unkritisch: der dunkelste Punkt des Verlaufs liegt bei
   9,7:1 gegen --bg, der hellste ist --text-primary selbst.

   @supports: ohne background-clip:text bliebe von `color: transparent` eine
   unsichtbare Überschrift übrig. Eine Zeile Aufschlag gegen einen Ausfall,
   der die Seite kopflos machte.
   -------------------------------------------------------------------- */
.plans-page .page-title,
.download-page .page-title {
  letter-spacing: -0.02em;
}

@supports ((background-clip: text) or (-webkit-background-clip: text)) {
  .plans-page .page-title,
  .download-page .page-title {
    background-image: linear-gradient(
      180deg,
      var(--text-primary) 0%,
      color-mix(in srgb, var(--accent) 45%, var(--text-primary)) 100%
    );
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
}

/* Karten (DESIGNRICHTUNG 2.4): deckende Fläche, Trennlinie, kein Blur.
   Glas bleibt den schwebenden Ebenen (Sidebar, Dialoge, Toast) vorbehalten. */
.section-group {
  background: var(--panel);
  border: 1px solid var(--separator);
  border-radius: var(--radius-l);
  padding: var(--space-5) var(--space-5) var(--space-6);
  margin-bottom: var(--space-6);
}

.section-group-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding-bottom: var(--space-3);
  margin-bottom: var(--space-4);
  border-bottom: 1px solid var(--separator);
}

/* Variante für Kopfzeilen mit Buttons/Suchfeldern statt nur Titel+Hinweistext
   (z.B. Storage-Gruppe "Dateien", AP3) — align-items:baseline aus der
   Grundklasse würde Buttons/Inputs schief zum Text ausrichten, hier statt-
   dessen vertikal zentriert; flex-wrap, damit die Aktionen bei schmalen
   Breiten umbrechen statt den Titel zu verdrängen. */
.section-group-head--with-actions {
  align-items: center;
  flex-wrap: wrap;
}

/* ==========================================================================
   Werkzeugleisten (AP3: Ersatz der entfernten globalen Topbar) — Tabs/Titel
   links, Aktionen (Suche/Buttons) rechts. Sitzt direkt im jeweiligen
   Abschnitt statt in einer geteilten, sektionsübergreifenden Topbar.
   ========================================================================== */
.section-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-4);
  margin-bottom: var(--space-5);
}
/* Verschachtelte Titel/Tabs bringen ihren eigenen unteren Abstand mit —
   der würde sich mit dem margin-bottom der Leiste selbst addieren. */
.section-toolbar .settings-nav,
.section-toolbar .page-title {
  margin-bottom: 0;
}

.section-toolbar-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* Gruppentitel in Satzschreibung und Textschrift (DESIGNRICHTUNG 2.3):
   Versalien mit Sperrung lasen sich wie Etiketten, nicht wie Überschriften. */
.section-group-title {
  font-family: var(--font-text);
  font-size: var(--size-headline);
  line-height: var(--leading-headline);
  font-weight: 600;
  color: var(--text-primary);
}

.section-group-hint {
  font-size: var(--size-callout);
  color: var(--text-secondary);
}

.section-group--accent {
  box-shadow: inset 0 2px 0 0 var(--group-accent);
}

.list-panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* Phase 10 (Beat Projekte-Tab): eigener "+"-Knopf oberhalb einer Liste, statt
   den geteilten Topbar-Knopf (der immer /projects/new anspringt) umzubiegen. */
.list-panel-head {
  display: flex;
  justify-content: flex-end;
  margin-bottom: var(--space-1);
}

.list-empty {
  color: var(--text-secondary);
  font-size: var(--size-body);
  padding: var(--space-8) 0;
  text-align: center;
}

/* Listenzeilen: deckend, Trennlinie, Überfahren hebt die Fläche leicht an,
   Drücken skaliert kurz. */
.list-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  background: var(--panel);
  border: 1px solid var(--separator);
  border-radius: var(--radius-m);
  padding: var(--space-3) var(--space-3) var(--space-3) var(--space-4);
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
    transform var(--dur-press) var(--ease);
}

.list-row:hover {
  background: var(--panel-elevated);
  border-color: var(--border-soft);
}
.list-row:active:not(:has(button:active, a:active, input:active, select:active, audio:active)) {
  transform: scale(0.99);
}

.list-row-main {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
  flex: 1;
}

.list-row-icon {
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  border-radius: var(--radius-s);
  background: var(--fill-1);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--accent);
}
.list-row-icon svg { width: 18px; height: 18px; }

.list-row-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.list-row-name {
  font-family: var(--font-text);
  font-weight: 600;
  font-size: var(--size-body);
  line-height: var(--leading-body);
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.list-row-badge {
  display: inline-block;
  margin-left: var(--space-2);
  padding: 1px var(--space-2);
  border-radius: var(--radius-full);
  font-size: var(--size-caption);
  font-weight: 500;
  vertical-align: middle;
}
.list-row-badge-viewer {
  background: color-mix(in srgb, var(--warning) 14%, transparent);
  color: var(--warning);
}

/* Metazeile in Textschrift; nur die Ziffern laufen tabellarisch, damit
   Größen und Daten untereinander bündig stehen. */
.list-row-meta {
  font-size: var(--size-footnote);
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.list-row-actions {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  flex-shrink: 0;
}

/* Inline-Vorhören einer Audiodatei (static/js/storage.js: togglePreview) —
   klappt unter der Zeile auf, die Zeile darf dafür umbrechen. */
.list-row-preview {
  flex-basis: 100%;
  padding-top: var(--space-2);
  margin-top: var(--space-1);
  border-top: 1px solid var(--separator);
}
.list-row-preview audio { width: 100%; height: 36px; }
.list-row.is-previewing { border-color: var(--border); }

.icon-btn {
  width: var(--control-h-m);
  height: var(--control-h-m);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border-radius: var(--radius-s);
  background: transparent;
  border: none;
  color: var(--text-secondary);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease),
    transform var(--dur-press) var(--ease);
}
.icon-btn svg { width: 16px; height: 16px; }
.icon-btn:hover { background: var(--fill-2); color: var(--text-primary); }
.icon-btn:active { background: var(--fill-3); transform: scale(var(--press-scale)); }

.icon-btn-danger:hover { color: var(--danger); background: color-mix(in srgb, var(--danger) 14%, transparent); }

@media (pointer: coarse) {
  .icon-btn { width: var(--hit-min); height: var(--hit-min); }
}

/* Ladezustand des "In der Lokalen App öffnen"-Knopfs pro Projektzeile —
   gleiches Muster wie .tile.is-busy (Sidecar-Start kann beim ersten
   Mal ein paar Sekunden dauern). */
.icon-btn.is-busy { pointer-events: none; opacity: 0.5; }

/* Inline-Umbenennen-Formular */
.rename-form {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.rename-input {
  min-height: var(--control-h-m);
  background: var(--field-bg);
  border: 1px solid var(--accent);
  border-radius: var(--radius-s);
  padding: var(--space-1) var(--space-3);
  color: var(--text-primary);
  font-family: var(--font-text);
  font-size: var(--size-body);
  width: 220px;
  max-width: 100%;
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent);
}

/* ==========================================================================
   Cloud-Storage-Seite: Nutzungsanzeige
   ========================================================================== */
.storage-usage-panel {
  background: var(--panel);
  border: 1px solid var(--separator);
  border-radius: var(--radius-m);
  padding: var(--space-4) var(--space-5);
  margin-bottom: var(--space-5);
}

/* Vermeidet Doppel-Rahmen, wenn das Panel innerhalb einer .section-group
   liegt (Task 3 des UI-Redesigns) — die Gruppe übernimmt Rahmen/Hintergrund. */
.section-group > .storage-usage-panel {
  background: none;
  border: 0;
  padding: 0;
  margin-bottom: 0;
}

.storage-usage-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: var(--space-2);
  font-size: var(--size-body);
  color: var(--text-secondary);
}

#storageUsagePercent {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
}

.storage-bar-large {
  height: 10px;
  margin-bottom: 0;
}

/* ==========================================================================
   Cloud-Storage: Ordner-Navigation — Ansichts-Umschalter, Breadcrumb,
   Kachelansicht (docs/cloud-storage-overhaul-plan.md Schritt 3).
   Absichtlich EIGENE Klassennamen "storage-tile*" statt der bestehenden
   ".tile"-Familie: die ist an das Bento-Grid des Dashboards gebunden
   (feste --fx/--fy/--fr-Variablen, grid-column-Platzierung je Kachelname,
   siehe weiter oben) — dieselben Klassennamen hier wiederzuverwenden würde
   beide Kontexte durcheinanderbringen, sobald einer der beiden Blöcke
   geändert wird.
   ========================================================================== */
/* Segmentschalter (DESIGNRICHTUNG 2.5, AP-OC F1 2026-10-12): Spur --fill-1,
   Radius --radius-s, 2 px Innenabstand; Segmente 28 px hoch mit Radius
   --radius-s minus Innenabstand (konzentrisch). Gewählt (aria-pressed bleibt
   die einzige Wahrheit, storage.js setzt es): --surface-3 mit --shadow-s und
   --text-primary, kein Akzent. Trägt Symbol- und Textsegmente; ein
   Textsegment ist jeder Knopf ohne .icon-btn. Liste/Kacheln der Speicher-
   und Projektansicht nutzen ihn, der gemeinsame Browser (F3) ebenso. */
.view-toggle {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  gap: 2px;
  padding: 2px;
  border-radius: var(--radius-s);
  background: var(--fill-1);
}

.view-toggle > button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  height: 28px;
  border: none;
  border-radius: calc(var(--radius-s) - 2px);
  background: transparent;
  color: var(--text-secondary);
  font-size: var(--size-callout);
  font-weight: 500;
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease),
    box-shadow var(--dur-fast) var(--ease), transform var(--dur-press) var(--ease);
}
.view-toggle > button:not(.icon-btn) { padding: 0 var(--space-3); }
.view-toggle .icon-btn { width: 28px; padding: 0; }

.view-toggle > button:hover:not([aria-pressed="true"]) { background: var(--fill-1); color: var(--text-primary); }
.view-toggle > button:active { transform: scale(var(--press-scale)); }
.view-toggle > button:focus-visible { outline-offset: 0; }

.view-toggle > button[aria-pressed="true"] {
  background: var(--surface-3);
  color: var(--text-primary);
  box-shadow: var(--shadow-s);
}

@media (pointer: coarse) {
  .view-toggle > button { height: var(--hit-min); }
  .view-toggle .icon-btn { width: var(--hit-min); }
}
@media (prefers-contrast: more) {
  .view-toggle { box-shadow: inset 0 0 0 1px var(--border); }
  .view-toggle > button[aria-pressed="true"] { box-shadow: inset 0 0 0 1px var(--border-strong); }
}
@media (forced-colors: active) {
  .view-toggle > button[aria-pressed="true"] { outline: 2px solid CanvasText; outline-offset: -2px; }
}

.storage-breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1);
  margin-bottom: var(--space-3);
  font-size: var(--size-callout);
}

.storage-breadcrumb-item {
  background: none;
  border: none;
  min-height: var(--control-h-s);
  padding: 2px var(--space-1);
  border-radius: var(--radius-s);
  color: var(--text-secondary);
  font-size: var(--size-callout);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.storage-breadcrumb-item:hover {
  background: var(--fill-2);
  color: var(--text-primary);
}

.storage-breadcrumb-item:last-child {
  color: var(--text-primary);
  font-weight: 600;
}

.storage-breadcrumb-sep {
  color: var(--text-dim);
}

/* Drag&Drop-Zielhervorhebung: Datei auf Ordner-Zeile/-Kachel ziehen. */
.list-row.is-drop-target,
.storage-tile.is-drop-target {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.storage-tile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: var(--space-3);
}

.storage-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-2);
  background: var(--panel);
  border: 1px solid var(--separator);
  border-radius: var(--radius-m);
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}

.storage-tile:hover {
  border-color: var(--border-soft);
  background: var(--panel-elevated);
}

.storage-tile-thumb {
  width: 100%;
  height: 72px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-s);
  background: var(--fill-1);
  color: var(--accent);
  cursor: pointer;
}
.storage-tile-thumb svg { width: 30px; height: 30px; }
/* Echte Bild-/Video-Thumbnails (Schritt 4) füllen die Kachel statt nur das
   Typ-Icon zu zeigen — object-fit: cover schneidet zu, statt zu verzerren. */
.storage-tile-thumb img,
.storage-tile-thumb video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
  pointer-events: none;
}

.storage-tile-name {
  width: 100%;
  font-family: var(--font-text);
  font-weight: 600;
  font-size: var(--size-callout);
  color: var(--text-primary);
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.storage-tile-meta {
  font-size: var(--size-caption);
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

.storage-tile-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-1);
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}
/* Ohne Zeiger, der überfahren kann (Touch), wären die Aktionen nie zu sehen */
@media (hover: none) {
  .storage-tile-actions { opacity: 1; }
}
.storage-tile:hover .storage-tile-actions,
.storage-tile:focus-within .storage-tile-actions {
  opacity: 1;
}

/* ==========================================================================
   Cloud-Storage: Lightbox/Overlay für Bild, Video und Audio (Schritt 4).
   Höherer z-index als .confirm-overlay (1000) — die Lightbox darf über
   einem Bestätigungsdialog liegen können (z.B. "Ordner löschen?" nach einem
   Löschversuch aus der Lightbox heraus, auch wenn das in der Praxis selten
   zusammentrifft).
   ========================================================================== */
.storage-lightbox-overlay {
  position: fixed;
  inset: 0;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1100;
  padding: 24px;
}

.storage-lightbox {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  max-width: min(1100px, 92vw);
  width: 100%;
}

.storage-lightbox-stage {
  width: 100%;
  min-height: 160px;
  max-height: 78vh;
  display: flex;
  align-items: center;
  justify-content: center;
}

.storage-lightbox-media {
  max-width: 100%;
  max-height: 78vh;
  border-radius: var(--radius-m);
  background: rgba(0, 0, 0, 0.3);
}

video.storage-lightbox-media,
audio.storage-lightbox-media { width: 100%; }

.storage-lightbox-fallback-icon {
  width: 96px;
  height: 96px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-m);
  background: var(--panel-elevated);
  border: 1px solid var(--border-soft);
  color: var(--accent);
}
.storage-lightbox-fallback-icon svg { width: 44px; height: 44px; }

.storage-lightbox-close {
  position: absolute;
  top: -4px;
  right: -4px;
  background: var(--panel-elevated);
  border: 1px solid var(--border-soft);
  color: var(--text-primary);
}

.storage-lightbox-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  background: var(--panel-elevated);
  border: 1px solid var(--border-soft);
  color: var(--text-primary);
  width: 40px;
  height: 40px;
}
.storage-lightbox-nav svg { width: 20px; height: 20px; }
.storage-lightbox-prev { left: -8px; }
.storage-lightbox-next { right: -8px; }

.storage-lightbox-caption {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 10px;
  max-width: 100%;
  color: var(--text-secondary);
  font-size: var(--size-callout);
  text-align: center;
}

.storage-lightbox-title {
  font-family: var(--font-text);
  font-weight: 600;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}

.storage-lightbox-fallback-note {
  color: var(--text-dim);
}

/* ==========================================================================
   Projekt-Arbeitsbereich (Stub): Wiedergabe + Zusammenarbeit
   ========================================================================== */
.badge {
  display: inline-block;
  font-size: var(--size-footnote);
  font-weight: 600;
  color: var(--accent);
  background: var(--accent-soft);
  border-radius: var(--radius-full);
  padding: 2px var(--space-3);
  margin-bottom: var(--space-2);
}

.project-path {
  font-family: var(--font-mono) !important;
  word-break: break-all;
}

.workspace-panel {
  background: var(--panel);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-m);
  padding: 20px 22px;
  margin-bottom: 20px;
}

.workspace-heading {
  font-family: var(--font-text);
  font-size: var(--size-title-3);
  font-weight: 600;
  margin: 0 0 8px;
}

.workspace-hint {
  color: var(--text-secondary);
  font-size: var(--size-body);
  line-height: 1.5;
  margin: 0 0 16px;
}

.workspace-actions {
  display: flex;
  gap: 10px;
}

.workspace-invite-link {
  font-family: var(--font-mono);
  font-size: var(--size-footnote);
  color: var(--accent);
  margin: 14px 0 0;
  word-break: break-all;
}

.back-link {
  display: inline-block;
  margin-top: 8px;
  color: var(--text-secondary);
  font-size: var(--size-body);
}
.back-link:hover { color: var(--accent); }

/* ==========================================================================
   Einstellungen-Seite
   ========================================================================== */
/* Segment-Steuerung (DESIGNRICHTUNG 2.5): gemeinsame Schiene in --fill-1,
   der gewählte Reiter als hellere Fläche mit kleinem Schatten statt
   Akzentfarbe. Der Akzent bleibt so den Aktionen vorbehalten. */
.settings-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  background: var(--fill-1);
  border-radius: var(--radius-s);
  padding: 2px;
  margin-bottom: var(--space-5);
  width: fit-content;
  max-width: 100%;
}

.settings-nav-item {
  display: inline-flex;
  align-items: center;
  min-height: 28px;
  padding: 0 var(--space-3);
  border-radius: calc(var(--radius-s) - 2px);
  font-size: var(--size-callout);
  font-weight: 500;
  color: var(--text-secondary);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.settings-nav-item:hover { color: var(--text-primary); }
.settings-nav-item.is-active {
  background: var(--surface-3);
  color: var(--text-primary);
  box-shadow: var(--shadow-s);
}
.settings-nav-item.tab-btn { background: none; border: none; font: inherit; cursor: pointer; }

.master-project-row-meta-master { color: var(--accent-teal); }
.master-project-row-cta { flex-shrink: 0; }

.suggested-model-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.suggested-model-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  background: var(--fill-1);
  border: 1px solid var(--separator);
  border-radius: var(--radius-m);
  padding: var(--space-3) var(--space-4);
}

.suggested-model-info {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
  min-width: 0;
}

.suggested-model-name {
  font-family: var(--font-text);
  font-weight: 600;
  font-size: var(--size-body);
}

.suggested-model-hint {
  font-size: var(--size-footnote);
  color: var(--text-secondary);
}

.settings-panel {
  background: var(--panel);
  border: 1px solid var(--separator);
  border-radius: var(--radius-l);
  padding: var(--space-5) var(--space-6);
  margin-bottom: var(--space-5);
}

.settings-panel-danger {
  border-color: color-mix(in srgb, var(--danger) 45%, var(--separator));
}

.settings-panel-title {
  font-family: var(--font-text);
  font-size: var(--size-title-3);
  line-height: var(--leading-title-3);
  font-weight: 600;
  margin: 0 0 var(--space-1);
}

.settings-panel-hint {
  color: var(--text-secondary);
  font-size: var(--size-body);
  line-height: 1.5;
  margin: 0 0 var(--space-4);
  max-width: 640px;
}

.settings-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  max-width: 380px;
}

.settings-label {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--size-callout);
  color: var(--text-secondary);
  font-weight: 500;
}

/* Eingabefelder (DESIGNRICHTUNG 2.5): deckendes Feld mit sichtbarem Rand
   (3:1 gegen die Fläche), Höhe 32, Fokus als Akzentrand plus weicher Ring. */
.settings-input {
  min-height: var(--control-h-m);
  background: var(--field-bg);
  border: 1px solid var(--field-border);
  border-radius: var(--radius-s);
  padding: var(--space-1) var(--space-3);
  color: var(--text-primary);
  font-size: var(--size-body);
  outline: none;
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.settings-input:focus,
.confirm-input:focus,
.admin-quota-input:focus,
.admin-quota-override-input:focus,
.admin-plan-select:focus {
  border-color: var(--accent);
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent);
}

.settings-form-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-1);
}

/* Öffentliche Profilseite (Plan.md §G1.3) */
.public-profile-card {
  width: 100%;
  max-width: 420px;
  margin: 0 auto;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}
.public-profile-avatar {
  width: 72px;
  height: 72px;
  font-size: var(--size-title-1);
  margin-bottom: 6px;
}
.public-profile-username {
  color: var(--text-dim);
  font-family: var(--font-mono);
  font-size: var(--size-callout);
  margin: 0;
}
.public-profile-discord {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--text-secondary);
  font-size: var(--size-callout);
  margin: 4px 0 0;
}
.public-profile-bio {
  color: var(--text-secondary);
  font-size: var(--size-body);
  line-height: 1.6;
  white-space: pre-wrap;
  margin: 12px 0 0;
}

.settings-toggle-row {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: var(--size-body);
  color: var(--text-primary);
  cursor: pointer;
  margin-bottom: 6px;
}


.settings-status { font-size: var(--size-footnote); }

.subscription-current {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: var(--size-body);
  font-weight: 600;
  margin-bottom: 16px;
}

.subscription-status-badge {
  font-size: var(--size-caption);
  font-weight: 600;
  padding: 2px var(--space-2);
  border-radius: var(--radius-full);
  background: var(--accent-soft);
  color: var(--accent);
}

.subscription-status-badge.is-past-due {
  background: color-mix(in srgb, var(--accent-red) 18%, transparent);
  color: var(--accent-red);
}
.settings-status-ok { color: var(--status-ok); }
.settings-status-error { color: var(--accent-red); }

.ai-provider-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.ai-provider-row {
  background: var(--fill-1);
  border: 1px solid var(--separator);
  border-radius: var(--radius-m);
  padding: var(--space-3) var(--space-4);
}

.ai-provider-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}

.ai-provider-radio {
  display: flex;
  align-items: center;
  gap: 9px;
  cursor: pointer;
}
.ai-provider-name {
  font-family: var(--font-text);
  font-weight: 600;
  font-size: var(--size-body);
}

.ai-provider-key-row {
  display: flex;
  gap: 8px;
}
.ai-provider-key-row .ai-key-input { flex: 1; }
.ai-provider-key-row .password-toggle-wrap { flex: 1; }

.ai-provider-model-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 10px;
  font-size: var(--size-footnote);
  color: var(--text-secondary);
}
.ai-provider-model-row .ai-model-select { flex: 1; max-width: 320px; }
/* Hinweis unter der Modellwahl (Liste offline oder Abruffehler; settings.js:
   loadAiProviderModelList, 2026-10-11). */
.ai-model-list-hint { margin: 6px 0 0; font-size: 11.5px; color: var(--text-secondary); }
.ai-model-list-hint[hidden] { display: none; }

/* --------------------------------------------------------------------
   KI-Einstellungen als Baustein (ai_settings_panel.js, Plan
   KI-Einstellungen AP4/AP5, 2026-10-19): Seite /settings/ai und Overlay im
   Projekt- und Beat-Editor. Baut auf .ai-provider-row, .settings-input,
   .btn-* und .modal-* auf; neu sind nur Abstände, Raster und die kleinen
   Marken (API/Lokal, Sprache, "Werkzeuge aus"). Alle Werte aus tokens.css;
   Fehler in --danger (Fehlertext), Achtung in --accent-yellow, "Lokal" im
   Akzent (Türkis bleibt laut tokens.css der Wellenform vorbehalten).
   -------------------------------------------------------------------- */
.ai-settings { display: flex; flex-direction: column; gap: var(--space-6); }
.ai-settings-content { display: flex; flex-direction: column; gap: var(--space-6); }
.ai-settings-section { display: flex; flex-direction: column; gap: var(--space-3); }
.ai-settings-heading {
  margin: 0;
  font-family: var(--font-text);
  font-size: var(--size-headline);
  line-height: var(--leading-headline);
  font-weight: var(--weight-semibold);
  color: var(--text-primary);
}
.ai-settings-hint {
  margin: 0;
  max-width: 640px;
  font-size: var(--size-footnote);
  line-height: var(--leading-footnote);
  color: var(--text-secondary);
}
.ai-settings-empty { margin: 0; font-size: var(--size-callout); color: var(--text-secondary); }
.ai-settings-center { text-align: center; }
.ai-settings-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.ai-settings-status,
.ai-custom-row-status,
.ai-custom-form-status {
  margin: 0;
  font-size: var(--size-footnote);
  line-height: var(--leading-footnote);
  color: var(--status-ok);
}
.ai-settings-status:empty,
.ai-custom-row-status:empty,
.ai-custom-form-status:empty { display: none; }
.ai-settings-status.is-error,
.ai-custom-row-status.is-error,
.ai-custom-form-status.is-error { color: var(--danger); }
.ai-custom-row-status { margin-top: var(--space-2); }

/* Kopfzeile: Name links, Marken rechts; auf schmalen Breiten bricht sie um. */
.ai-settings .ai-provider-head { flex-wrap: wrap; gap: var(--space-2); }
.ai-settings .ai-provider-head .badge { margin-bottom: 0; }
.ai-settings .ai-provider-model-row label { flex-shrink: 0; }
.ai-settings .ai-provider-model-row .ai-model-select { min-width: 0; }
.ai-settings .ai-provider-key-row { flex-wrap: wrap; }
.ai-settings .ai-provider-key-row .ai-key-input { min-width: 0; flex: 1 1 180px; }

.ai-custom-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.ai-custom-item { list-style: none; }
.ai-tags { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--space-1); }
.ai-tag {
  display: inline-block;
  font-size: var(--size-caption);
  line-height: var(--leading-caption);
  font-weight: var(--weight-semibold);
  padding: 1px var(--space-2);
  border-radius: var(--radius-full);
  color: var(--text-secondary);
  background: var(--fill-2);
  white-space: nowrap;
}
.ai-tag-local { color: var(--accent); background: var(--accent-soft); }
.ai-tag-warn {
  color: var(--accent-yellow);
  background: color-mix(in srgb, var(--accent-yellow) 16%, transparent);
}
.ai-custom-meta {
  margin: 0;
  font-size: var(--size-footnote);
  color: var(--text-secondary);
  overflow-wrap: anywhere;
}
.ai-custom-url { font-family: var(--font-mono); color: var(--text-primary); }
.ai-custom-actions,
.ai-form-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
}
.ai-custom-actions-spacer { flex: 1 1 auto; }

/* Formular: zwei Spalten, wo Platz ist, sonst eine. */
.ai-custom-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  background: var(--fill-1);
  border: 1px solid var(--accent-soft);
  border-radius: var(--radius-m);
  padding: var(--space-4);
}
.ai-custom-form-title {
  margin: 0;
  font-family: var(--font-text);
  font-size: var(--size-body);
  font-weight: var(--weight-semibold);
}
.ai-form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
  gap: var(--space-3) var(--space-4);
}
.ai-form-wide { grid-column: 1 / -1; }
.ai-form-field { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.ai-form-field > .settings-input,
.ai-form-field > .password-toggle-wrap { width: 100%; box-sizing: border-box; }
.ai-form-label {
  font-size: var(--size-footnote);
  font-weight: var(--weight-semibold);
  color: var(--text-secondary);
}
.ai-form-hint {
  margin: 0;
  font-size: var(--size-caption);
  line-height: var(--leading-caption);
  color: var(--text-secondary);
  overflow-wrap: anywhere;
}
.ai-form-hint[hidden] { display: none; }
.ai-form-fieldset { border: none; margin: 0; padding: 0; min-width: 0; }
.ai-form-fieldset > legend { padding: 0; margin-bottom: var(--space-1); }
.ai-form-choices {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
  gap: var(--space-2);
}
.ai-form-choice {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--separator);
  border-radius: var(--radius-s);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.ai-form-choice:hover { background: var(--fill-2); }
.ai-form-choice:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); }
.ai-form-choice input { margin-top: 2px; }
.ai-form-choice-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: var(--size-footnote);
  color: var(--text-secondary);
}
.ai-form-choice-text strong { color: var(--text-primary); font-size: var(--size-callout); }
.ai-form-inline { margin: var(--space-1) 0 0; font-size: var(--size-callout); }
.ai-form-row { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.ai-form-row > .settings-input { flex: 1 1 200px; min-width: 0; }
.ai-custom-form .settings-input[aria-invalid="true"] { border-color: var(--danger); }

.ai-model-chips {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}
.ai-model-chip {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  max-width: 100%;
  padding: 2px 2px 2px var(--space-2);
  border-radius: var(--radius-full);
  background: var(--fill-2);
  font-family: var(--font-mono);
  font-size: var(--size-caption);
  color: var(--text-primary);
}
.ai-model-chip > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ai-model-chip-remove {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.ai-model-chip-remove svg { width: 12px; height: 12px; }
.ai-model-chip-remove:hover { background: var(--fill-3); color: var(--text-primary); }
.ai-model-chips-empty { font-size: var(--size-footnote); color: var(--text-secondary); }

.ai-agent-row { margin: 0; min-width: 0; }
.ai-agent-row > legend { padding: 0 var(--space-1); }
.ai-agent-fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
  gap: var(--space-3);
  margin-top: var(--space-2);
}
.ai-agent-fields .settings-input { width: 100%; box-sizing: border-box; }

/* Overlay im Editor: breiter als ein Bestätigungsdialog, eigener
   Scrollbereich, auf Telefonbreite randlos über die ganze Fläche. */
.modal-dialog.ai-settings-dialog {
  max-width: 760px;
  height: min(820px, 92vh);
  max-height: none;
  padding: var(--space-5) var(--space-6);
  box-sizing: border-box;
}
.ai-settings-dialog:focus { outline: none; }
.ai-settings-dialog-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  flex-shrink: 0;
}
.ai-settings-dialog-head .modal-title { margin: 0; }
.ai-settings-dialog-sub {
  margin: var(--space-1) 0 0;
  font-size: var(--size-footnote);
  line-height: var(--leading-footnote);
  color: var(--text-secondary);
}
.ai-settings-dialog-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  margin-top: var(--space-4);
  padding-right: var(--space-1);
}
@media (max-width: 480px) {
  /* .modal-overlay steht weiter unten mit gleicher Spezifität. */
  .modal-overlay.ai-settings-overlay { padding: 0; }
  .modal-dialog.ai-settings-dialog {
    height: 100%;
    border: none;
    border-radius: 0;
    padding: var(--space-4);
  }
  .ai-custom-form { padding: var(--space-3); }
}

/* "KI-Einstellungen …" am Ende des Modell-Menüs (editor.js) */
.ai-model-picker-settings {
  border-top: 1px solid var(--separator);
  border-radius: 0;
  margin-top: var(--space-1);
  color: var(--text-secondary);
}

/* ==========================================================================
   Bestätigungsdialog (confirmDialog aus shell.js)
   ========================================================================== */
/* Optik-Stufe 2 (DESIGNRICHTUNG 2.8): Scrim und Dialogfläche aus Tokens,
   Auftritt per Feder (Deckkraft + leichtes Skalieren), Abgang auf demselben
   Weg zurück. shell.js übergibt, wenn motion.js geladen ist, an
   NCMotion.present/dismiss und setzt .nm-motion, dann schweigen die
   Keyframes hier (CSS-Animationen würden die Inline-Werte der Feder sonst
   überdecken). Ohne motion.js laufen die Keyframes als Rückfall.

   Beim Schließen benennt shell.js die Klassen sofort um (.confirm-exit /
   .confirm-exit-dialog) und entfernt das Element nach dem Abgang: Wer nach
   dem Schließen nach .confirm-overlay sucht (Tests, andere Skripte), findet
   den abgehenden Dialog dann schon nicht mehr. */
@keyframes nm-scrim-in { from { opacity: 0; } }
@keyframes nm-scrim-out { to { opacity: 0; } }
@keyframes nm-dialog-in {
  from { opacity: 0; transform: scale(calc(1 - 0.04 * var(--motion-scale))); }
}
@keyframes nm-dialog-out {
  to { opacity: 0; transform: scale(calc(1 - 0.04 * var(--motion-scale))); }
}

.confirm-overlay,
.confirm-exit {
  position: fixed;
  inset: 0;
  background: var(--scrim);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  padding: var(--space-5);
  animation: nm-scrim-in var(--dur-base) var(--ease) backwards;
}

.confirm-dialog,
.confirm-exit-dialog {
  background: var(--panel-elevated);
  border: 1px solid var(--separator);
  border-radius: var(--radius-l);
  padding: var(--space-6);
  max-width: 400px;
  width: 100%;
  box-shadow: var(--material-edge), var(--shadow-l);
  animation: nm-dialog-in var(--dur-spring) var(--spring) backwards;
}

.confirm-exit {
  pointer-events: none;
  animation: nm-scrim-out var(--dur-base) var(--ease) forwards;
}
.confirm-exit-dialog { animation: nm-dialog-out var(--dur-base) var(--ease) forwards; }

.nm-motion,
.nm-motion > .confirm-dialog,
.nm-motion > .confirm-exit-dialog { animation: none; }

.confirm-title {
  font-family: var(--font-text);
  font-size: var(--size-title-3);
  line-height: var(--leading-title-3);
  font-weight: 600;
  margin: 0 0 var(--space-2);
}

.confirm-message {
  font-size: var(--size-body);
  color: var(--text-secondary);
  margin: 0 0 var(--space-5);
  line-height: 1.5;
  /* Mehrzeilige Meldungen (z.B. "diese drei Dateien wurden übersprungen,
     weil …") kommen als Text mit Zeilenumbrüchen. Ohne pre-line fasst HTML
     sie zu einem einzigen Absatz zusammen und die Aufzählung ist weg. */
  white-space: pre-line;
  max-height: 50vh;
  overflow-y: auto;
}

.confirm-input {
  display: block;
  width: 100%;
  box-sizing: border-box;
  min-height: var(--control-h-m);
  background: var(--field-bg);
  border: 1px solid var(--field-border);
  border-radius: var(--radius-s);
  padding: var(--space-1) var(--space-3);
  color: var(--text-primary);
  font-size: var(--size-body);
  font-family: inherit;
  margin: 0 0 var(--space-5);
}

.confirm-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-2);
}

/* ==========================================================================
   Responsive
   ========================================================================== */
/* War (max-width: 860px) — auf die "Tablet-Wide"-Stufe (900px) der
   Breakpoint-Leiter oben angeglichen, damit das Bento-Grid genau wie
   .plugins-layout (ebenfalls 900px) an derselben Shell-Breite auf eine
   Spalte kollabiert. Wirkt sich nur auf den ungetesteten Zwischenbereich
   861-900px aus und macht das Grid dort früher (konservativer) einspaltig
   — keine Regression bei den 8 geprüften Referenzbreiten. */
/* Bento: zwischen 561 und 900 px zwei Spalten (DESIGNRICHTUNG 2.6), erst
   darunter eine. Vorher sprang das Raster bei 900 px von vier direkt auf
   eine Spalte. "Neues Projekt" bleibt die hohe Kachel links, dense füllt
   die übrigen ohne Lücke auf. */
@media (max-width: 900px) {
  .main { padding: var(--space-5); }
  .bento-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-auto-rows: 132px;
    grid-auto-flow: dense;
  }
  .tile-new,
  .tile-recent-1, .tile-recent-2, .tile-recent-3,
  .tile-empty-1, .tile-empty-2, .tile-empty-3 {
    grid-column: auto;
    grid-row: auto;
  }
  /* AP3a: nur die Projekt-Kachel; die Lokale-DAW-Kachel trägt die Klasse
     tile-new mit, liegt aber waagerecht und bleibt eine Zeile hoch. */
  .tile-new:not(.tile-local) { grid-row: span 2; }
  .topbar-search { width: 200px; }
  .section-group { padding: var(--space-4) var(--space-4) var(--space-4); }
}

@media (max-width: 560px) {
  .bento-grid { grid-template-columns: minmax(0, 1fr); grid-auto-rows: 140px; }
  /* Stufe 6: Einladungen sind hier breit genug für Text links, Knopf rechts. */
  .tile-hint {
    flex-direction: row;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-3);
  }
  .tile-hint .tile-hint-text { flex: 1 1 auto; }
  .tile-hint .tile-hint-sub { display: -webkit-box; }
}

/* Reduzierte Bewegung: früher "* { transition: none !important; }" — das
   nahm auch jede Rückmeldung (Hover, Fokus, Farbwechsel) weg und ließ
   Zustände hart umspringen. Jetzt regeln die Tokens das zentral
   (tokens.css: --spring wird zur gewöhnlichen Kurve, --dur-spring kurz,
   --motion-scale 0, --press-scale 1): Wege und Skalierungen entfallen,
   Überblendungen bleiben. Hier nur, was nicht über Tokens läuft. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

/* Download-Seite (/download) */
.download-feature-list {
  margin: 0;
  padding-left: 20px;
  color: var(--text-secondary);
  font-size: var(--size-body);
  line-height: 1.9;
}

/* ==========================================================================
   Admin-Panel
   ========================================================================== */
.admin-health-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 12px;
}

.admin-health-tile {
  background: var(--fill-1);
  border: 1px solid var(--separator);
  border-radius: var(--radius-m);
  padding: var(--space-3) var(--space-4);
  min-width: 0;
}

.admin-health-tile-label {
  display: block;
  font-size: var(--size-footnote);
  color: var(--text-secondary);
  margin-bottom: var(--space-1);
}

.admin-health-tile-value {
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
  font-size: var(--size-title-2);
  font-weight: 600;
  color: var(--text-primary);
  /* Live-Abnahme 2026-10-12: Ein langer Wert ohne Leerzeichen (SMTP-Server
     mit Port unter /admin/sicherheit) lief bei 375px über die Kachel hinaus. */
  overflow-wrap: anywhere;
}
.admin-health-tile-value.is-bad { color: var(--accent-red); }

.admin-table-wrap {
  overflow-x: auto;
}

.admin-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--size-callout);
  font-variant-numeric: tabular-nums;
}

.admin-table th {
  text-align: left;
  padding: var(--space-2) var(--space-3);
  color: var(--text-secondary);
  font-weight: 600;
  font-size: var(--size-footnote);
  border-bottom: 1px solid var(--separator);
}

.admin-table td {
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--separator);
  color: var(--text-secondary);
  vertical-align: middle;
}

.admin-table tr:last-child td { border-bottom: none; }

.admin-quota-input,
.admin-quota-override-input,
.admin-plan-select {
  min-height: var(--control-h-s);
  background: var(--field-bg);
  border: 1px solid var(--field-border);
  border-radius: var(--radius-s);
  padding: 2px var(--space-2);
  color: var(--text-primary);
  font-size: var(--size-callout);
}
.admin-quota-input,
.admin-quota-override-input { width: 80px; }

.admin-table-actions {
  display: flex;
  gap: 6px;
  justify-content: flex-end;
}

.admin-locked-badge {
  color: var(--accent-red);
  font-weight: 600;
}
.admin-active-badge {
  color: var(--accent-teal);
}

/* --------------------------------------------------------------------
   Plugins-Seite (Plan.md §8.3) — Liste + Editor mit Dateireitern
   -------------------------------------------------------------------- */
.plugins-layout {
  display: grid;
  grid-template-columns: minmax(260px, 340px) 1fr;
  gap: 20px;
  align-items: start;
}
@media (max-width: 900px) {
  .plugins-layout { grid-template-columns: 1fr; }
}

.plugin-kind-badge {
  display: inline-block;
  padding: 1px 7px;
  border-radius: 999px;
  font-size: var(--size-caption);
  font-weight: 600;
  background: rgba(255, 255, 255, 0.08);
  color: var(--text-dim);
}
.plugin-kind-badge.is-vst3 {
  background: color-mix(in srgb, var(--danger) 15%, transparent);
  color: var(--danger);
}

.plugin-ui-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 7px;
  border-radius: 999px;
  font-size: var(--size-caption);
  font-weight: 600;
  background: rgba(61, 139, 255, 0.15);
  color: var(--accent);
}

/* Read-only Plugin-Zusammenfassung (Plan.md §E1.2) — ersetzt den früheren
   In-App-Code-Editor. Kein Textfeld, kein Speichern-Knopf: ändern heißt
   exportieren, offline bearbeiten, neues ZIP importieren. */
.plugin-summary {
  background: var(--panel-elevated);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-m);
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.plugin-summary-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
}
.plugin-summary-title { font-size: var(--size-title-3); font-weight: 600; margin: 0; }
.plugin-summary-actions { display: flex; gap: 8px; align-items: center; }
.plugin-summary-meta { font-size: var(--size-footnote); color: var(--text-dim); margin: 0; }
.plugin-summary-description { font-size: var(--size-body); color: var(--text-secondary); margin: 0; }
.plugin-summary-subtitle { font-size: var(--size-callout); font-weight: 600; margin: 10px 0 2px; }
.plugin-summary-params { display: flex; flex-direction: column; gap: 4px; }
.plugin-summary-param-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 10px;
  background: rgba(0, 0, 0, 0.2);
  border: 1px solid var(--border-soft);
  border-radius: 6px;
  font-size: var(--size-footnote);
}
.plugin-summary-param-name { font-family: var(--font-mono); color: var(--text-primary); }
.plugin-summary-param-range { color: var(--text-dim); }
.plugin-summary-test-result { font-size: var(--size-footnote); }
.plugin-summary-hint { font-size: var(--size-footnote); color: var(--text-dim); margin-top: 6px; }

.plugin-editor-error-row {
  text-align: left;
  padding: 6px 8px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--danger) 10%, transparent);
  color: var(--danger);
  font-size: var(--size-footnote);
  font-family: var(--font-mono);
}
.plugin-editor-error-row:hover { background: rgba(255, 90, 90, 0.18); }
.plugin-editor-ok { color: var(--accent-teal); font-size: var(--size-footnote); }

/* Plugin-Dokumentationsseite (Plan.md §8.5) */
.plugins-doku { max-width: 860px; }
.plugins-doku h2 { margin-top: 32px; }
.plugins-doku h3 { margin-top: 20px; }
/* P4d: Fließtext enthält vereinzelt lange, slash-getrennte Begriffsketten
   ohne Leerzeichen (z.B. "Audiogeräten/Aufnahme/Transport") — Browser
   werten "/" per Default-Zeilenumbruch nicht als Umbruchpunkt, wodurch
   das ganze Wort bei schmalen Breiten (375/390px) über den Absatz
   hinausragt und horizontalen Seiten-Overflow erzeugt. */
.plugins-doku p,
.plugins-doku li {
  overflow-wrap: break-word;
}
.plugins-doku code {
  font-family: var(--font-mono);
  background: rgba(0, 0, 0, 0.25);
  padding: 1px 5px;
  border-radius: 4px;
  font-size: var(--size-footnote);
  /* P4d: mehrere <code>-Elemente direkt durch "/" verkettet (z.B.
     list_projects</code>/<code>select_project</code>/<code>current_project)
     haben keinen Leerraum als Umbruchpunkt — ohne overflow-wrap ragt das
     letzte Element bei schmalen Breiten (375/390px) über den Textblock
     hinaus und erzeugt horizontalen Seiten-Overflow. */
  overflow-wrap: anywhere;
}
.plugins-doku-code {
  background: rgba(0, 0, 0, 0.3);
  border: 1px solid var(--border-soft);
  border-radius: 8px;
  padding: 14px;
  overflow-x: auto;
  font-family: var(--font-mono);
  font-size: var(--size-footnote);
  line-height: 1.5;
}
.plugins-doku-funcref {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  background: rgba(0, 0, 0, 0.2);
  border: 1px solid var(--border-soft);
  border-radius: 8px;
  padding: 14px;
  font-size: var(--size-footnote);
  color: var(--text-dim);
}
@media (max-width: 700px) {
  .plugins-doku-funcref { grid-template-columns: 1fr; }
}
.plugins-doku-table-wrap {
  /* P4d: die Tabelle hat mehrere <code>-Zellen, die bei schmalen Breiten
     (375/390/480px) nicht mehr nebeneinander in die Spalten passen — statt
     die Seite selbst horizontal scrollen zu lassen, scrollt nur die
     Tabelle in ihrem eigenen Rahmen (Muster wie .plugins-doku-code). */
  overflow-x: auto;
}
.plugins-doku-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--size-footnote);
  margin: 10px 0 18px;
}
.plugins-doku-table th,
.plugins-doku-table td {
  text-align: left;
  padding: 6px 10px;
  border: 1px solid var(--border-soft);
  vertical-align: top;
}
.plugins-doku-table th {
  background: rgba(0, 0, 0, 0.2);
}

.admin-color-input {
  padding: 4px;
  height: 38px;
  width: 80px;
}
/* ==========================================================================
   Lokale DAW — Startseite (templates/local_home.html)

   Bewusst ohne die Sidebar-Shell der Web-App: die lokale App zeigt nur den
   Projekt-Teil, deshalb ein schlichter zentrierter Bereich statt des
   Scroll-Layouts mit fünf Abschnitten.
   ========================================================================== */
.local-main {
  position: relative;
  z-index: 1;
  max-width: 880px;
  margin: 0 auto;
  padding: 56px 28px 72px;
}

.local-title {
  font-family: var(--font-display);
  font-size: var(--size-title-1);
  font-weight: 600;
  margin: 0 0 6px;
}

.local-subtitle {
  margin: 0 0 24px;
  color: var(--text-dim);
  font-size: var(--size-body);
}

/* Platzhalter-Seite ("Kein Projekt geöffnet") — Projektauswahl läuft über
   das Bento-Grid der Server-Startseite, diese Seite ist nur noch ein
   seltener Randfall (siehe local_home.html-Kommentar). */
.local-empty-main {
  position: relative;
  z-index: 1;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 28px;
}

.local-empty-card {
  max-width: 480px;
  text-align: center;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-l);
  padding: 40px 32px;
}

/* AP7 (2026-10-06): Die lokale Startseite trägt unter der Karte Einstellungs-
   Panels (App-Updates, KI-Zugriff). In der Zeilenanordnung oben quetschten
   sie sich neben die Karte und liefen auf schmalen Fenstern aus dem Bild;
   hier stehen sie untereinander in Kartenbreite. Gescopt auf <main> mit
   Panels, damit listen/profile/project_settings/local_error unverändert
   bleiben. `safe center`: wird die Spalte höher als das Fenster, bleibt der
   obere Teil erreichbar statt nach oben abgeschnitten zu werden. */
.local-empty-main:has(> .settings-panel) {
  flex-direction: column;
  justify-content: center;
  justify-content: safe center;
}
.local-empty-main:has(> .settings-panel) > .local-empty-card,
.local-empty-main > .settings-panel {
  box-sizing: border-box;
  width: min(520px, 100%);
  max-width: none;
}
.local-empty-main > .settings-panel { margin: 16px 0 0; }
@media (max-width: 520px) {
  .local-empty-main:has(> .settings-panel) { padding: 16px; }
  .local-empty-main:has(> .settings-panel) > .local-empty-card { padding: 28px 20px; }
  .local-empty-main > .settings-panel { padding: 18px 16px; }
}

/* --- KI-Zugriff (MCP-Relay), local_home.html #mcpBridgePanel ------------- */
.mcp-relay-status {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin: 10px 0 18px;
  padding: 12px 14px;
  background: var(--panel-deep);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-m);
}
.mcp-relay-dot {
  flex: none;
  width: 10px;
  height: 10px;
  margin-top: 5px;
  border-radius: 50%;
  background: var(--text-dim);
}
.mcp-relay-status[data-tone="ok"] .mcp-relay-dot {
  background: var(--status-ok);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--status-ok) 22%, transparent);
}
.mcp-relay-status[data-tone="busy"] .mcp-relay-dot,
.mcp-relay-status[data-tone="warn"] .mcp-relay-dot { background: var(--accent-yellow); }
@media (prefers-reduced-motion: no-preference) {
  .mcp-relay-status[data-tone="busy"] .mcp-relay-dot { animation: mcp-relay-pulse 1.2s var(--ease) infinite; }
}
.mcp-relay-status[data-tone="error"] { border-color: color-mix(in srgb, var(--accent-red) 45%, var(--border-soft)); }
.mcp-relay-status[data-tone="error"] .mcp-relay-dot { background: var(--accent-red); }
@keyframes mcp-relay-pulse { 50% { opacity: 0.35; } }
@media (prefers-reduced-motion: reduce) {
  .mcp-relay-status[data-tone="busy"] .mcp-relay-dot { animation: none; }
}
.mcp-relay-status-text { min-width: 0; }
.mcp-relay-state {
  margin: 0;
  font-size: var(--size-body);
  line-height: 1.45;
  color: var(--text-secondary);
}
.mcp-relay-headline { color: var(--text-primary); font-weight: 600; }
.mcp-relay-detail {
  margin: 2px 0 0;
  font-size: var(--size-footnote);
  line-height: 1.45;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.mcp-relay-detail:empty { display: none; }
/* Live-Region nicht per display:none verstecken (würde beim Erscheinen
   nicht zuverlässig vorgelesen); leer hat sie schlicht keine Höhe. */
.mcp-relay-panel .settings-status { display: block; }
.mcp-relay-panel .settings-status:not(:empty) { margin: 0 0 4px; }
.mcp-relay-setup-label {
  display: block;
  margin-bottom: 6px;
  font-size: var(--size-footnote);
  font-weight: 500;
  color: var(--text-secondary);
}
.mcp-relay-url-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}
.mcp-relay-url {
  flex: 1 1 240px;
  min-width: 0;
  padding: 8px 10px;
  font-family: var(--font-mono);
  font-size: var(--size-footnote);
  color: var(--text-primary);
  background: var(--panel-deep);
  border: 1px solid var(--border);
  border-radius: var(--radius-s);
  overflow-wrap: anywhere;
  user-select: all;
}
.mcp-relay-setup-hint {
  margin: 10px 0 0;
  font-size: var(--size-footnote);
  line-height: 1.5;
  color: var(--text-secondary);
}
.mcp-relay-setup-hint a { color: var(--accent); text-underline-offset: 2px; }
.mcp-relay-panel a:focus-visible,
.mcp-relay-panel button:focus-visible,
.mcp-relay-panel input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Ladezustand der "Lokale DAW"-Kachel während switch_to_client_ui() läuft
   (VST-Scan + Sidecar-Start können ein paar Sekunden dauern) — sonst wirkt
   ein Klick wirkungslos, weil die Kachel sonst kein optisches Feedback gibt. */
.tile.is-busy {
  pointer-events: none;
  opacity: 0.6;
}


/* --- Phase 9: Sicherheit (2FA + Recovery-Codes) --------------------------- */

.recovery-code-list {
  list-style: none;
  margin: 16px 0 0;
  padding: 16px;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px 16px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 10px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--size-headline);
  letter-spacing: 0.06em;
}

.recovery-code-list li {
  padding: 2px 0;
}

/* Zweispaltiges Grid der 2FA-Einrichtung (QR links, Geheimnis+Formular
   rechts) — bricht auf schmalen Breiten (Settings-Modal < 640px, siehe
   .settings-modal-dialog-Breakpoint weiter unten) auf eine Spalte um, damit
   nichts gequetscht wird. minmax(0, 1fr) auf der rechten Spalte verhindert,
   dass lange Inhalte (z.B. das Geheimnis) die Spalte über ihre Breite
   hinaus aufdrücken. */
.twofa-enrollment-grid {
  display: grid;
  grid-template-columns: minmax(180px, 220px) minmax(0, 1fr);
  align-items: start;
  gap: 28px;
  margin-top: 4px;
}

.twofa-qr-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  text-align: center;
}

/* Heller Kontrastbereich für den QR-Code, unabhängig vom dunklen App-Theme
   drumherum (siehe Kommentar in settings.js showEnrollment()): die Modul-
   farben selbst kommen aus --twofa-qr-fg/--twofa-qr-bg, hier nur der
   Rahmen/Radius/Schatten, der die helle Fläche als eigene kleine Karte vom
   Rest absetzt. */
.twofa-qr {
  --twofa-qr-fg: black;
  --twofa-qr-bg: white;
  width: 208px;
  height: 208px;
  padding: 16px;
  margin: 0;
  background: var(--twofa-qr-bg);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-m);
  box-shadow: 0 8px 24px -14px rgba(0, 0, 0, 0.65);
}

.twofa-qr-svg {
  display: block;
  width: 100%;
  height: 100%;
  shape-rendering: crispEdges;
}

.twofa-otpauth-link {
  display: block;
  width: 100%;
  max-width: 220px;
  text-align: center;
  white-space: normal;
  line-height: 1.4;
}

.twofa-fields-col {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}

.twofa-secret-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
}

.twofa-secret-label {
  flex-shrink: 0;
  color: var(--text-secondary);
  font-size: var(--size-callout);
  font-weight: 500;
}

/* #twoFactorSecret muss laut Testkontrakt ein reines Blattelement bleiben
   (nur der Geheimnis-Text als innerText) — daher hier per ID statt einer
   zusätzlichen Klasse gestylt, analog zu #storageUsagePercent weiter oben
   in dieser Datei. */
#twoFactorSecret {
  min-width: 0;
  word-break: break-all;
  font-family: var(--font-mono);
  font-size: var(--size-body);
  letter-spacing: 0.04em;
  color: var(--text-primary);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: var(--radius-s);
  padding: 6px 10px;
}

.twofa-copy-btn {
  flex-shrink: 0;
}

@media (max-width: 640px) {
  .twofa-enrollment-grid {
    grid-template-columns: 1fr;
    gap: 20px;
  }
}

/* --- Phase 24.1/24.2: API-Tokens ------------------------------------------ */

.settings-list {
  list-style: none;
  margin: 16px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.settings-list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 14px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 10px;
}

.settings-list-meta {
  font-size: var(--size-callout);
  opacity: 0.7;
}

/* --- VST-Suchpfade (Panel #vstPathsPanel, nur im Desktop-Client) ---------
   Drei Gruppen in einem Panel, die NICHT verwechselt werden dürfen:
   Voreinstellungen und eigene Ordner (hier änderbar) gegen die .env-Gruppe
   (nur Anzeige). Die Trennung trägt deshalb eine eigene Kante statt nur
   einer Überschrift — siehe static/js/vst_paths_settings.js. */
.vst-paths-group {
  margin-bottom: 18px;
}

.vst-paths-group-head {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 8px;
}

.vst-paths-group-title {
  font-family: var(--font-text);
  font-size: var(--size-body);
  font-weight: 600;
  color: var(--text-primary);
}

.vst-paths-group-hint {
  font-size: var(--size-footnote);
  line-height: 1.5;
  color: var(--text-dim);
  max-width: 640px;
}

.vst-paths-group-hint code {
  font-family: var(--font-mono);
  font-size: var(--size-footnote);
  color: var(--text-secondary);
}

.vst-paths-list {
  margin-top: 0;
  gap: 6px;
}

.vst-paths-list li {
  padding: 8px 12px;
  gap: 10px;
}

/* Die Beschriftung trägt im Listeneintrag schon den Abstand der Zeile —
   der Eigenabstand von .settings-toggle-row würde die Zeilen auseinander
   schieben. */
.vst-paths-row-label {
  margin-bottom: 0;
  flex: 1;
  min-width: 0;
}

.vst-paths-path {
  font-family: var(--font-mono);
  font-size: var(--size-footnote);
  color: var(--text-primary);
  /* Pfade sind lang und enthalten keine Leerzeichen zum Umbrechen —
     ohne anywhere sprengt ein Netzwerkpfad die Panelbreite. */
  overflow-wrap: anywhere;
  flex: 1;
  min-width: 0;
}

.vst-paths-problem {
  color: var(--accent-yellow);
  opacity: 1;
  white-space: nowrap;
}

.vst-paths-empty {
  color: var(--text-dim);
  font-size: var(--size-footnote);
  justify-content: flex-start;
}

.vst-paths-add {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
  flex-wrap: wrap;
}

.vst-paths-input {
  flex: 1;
  min-width: 220px;
  font-family: var(--font-mono);
  font-size: var(--size-footnote);
}

.vst-paths-remove {
  padding: 4px 10px;
  font-size: var(--size-footnote);
  flex: none;
}

/* Der Wirkungszeitpunkt ist die wichtigste Zeile des Panels und darf nicht
   wie eine weitere Fußnote aussehen. */
.vst-paths-effect {
  margin: 0 0 14px;
  padding: 10px 12px;
  border-left: 2px solid var(--accent);
  background: var(--accent-soft);
  border-radius: var(--radius-s);
  color: var(--text-secondary);
}

.vst-paths-status-warn { color: var(--accent-yellow); }
/* --- Ende VST-Suchpfade -------------------------------------------------
   Die beiden Markierungen umschliessen den Block fuer
   tests/test_vst_paths.py: dort wird geprueft, dass hier ausschliesslich
   var(--…)-Farbtoken stehen und kein neuer Hexwert. */

@media (max-width: 640px) {
  .recovery-code-list {
    grid-template-columns: 1fr;
  }
}

/* ==========================================================================
   Team-Tab (Mehrplatz-Verwaltung, Settings → Team, web/teams.py) — baut auf
   .settings-panel/.settings-list/.settings-form auf, definiert nichts davon
   neu. Klassennamen unter dem Präfix "team-" sind ausschließlich für den
   JS-Teil (settings.js) gedacht, der #teamOwnerView/#teamMemberView zur
   Laufzeit befüllt; die Grundstruktur (Panel, Listen, Formular) kommt aus
   den bestehenden Klassen oben.
   ========================================================================== */

/* Seitenlokale Abstandsskala für den Team-Tab (Muster wie in plans.css:42 —
   es gibt keine globalen --space-*-Variablen im Projekt, deshalb hier eine
   eigene, eng auf [data-settings-panel="team"] gescopte Skala statt neuer
   globaler Tokens). Löst das eigentliche Problem: die vier Blöcke
   "Sitzplätze"/"Mitglieder"/"Offene Einladungen"/"Neue Einladung" lagen
   bisher direkt hintereinander im selben .settings-panel und ihr optischer
   Abstand ergab sich zufällig aus dem unteren Rand der jeweils letzten
   Inhaltszeile (6px/14px/16px/0px) statt aus einer bewussten Entscheidung. */
[data-settings-panel="team"] {
  --team-space-section: 24px; /* Abstand zwischen den vier Abschnitten */
  --team-space-heading: 10px; /* Abstand Überschrift -> Inhalt je Abschnitt */
}

/* .team-section umschließt genau einen der vier Blöcke (Überschrift +
   Inhalt). Der Abstand zum vorherigen Abschnitt kommt einheitlich über
   margin-top; dadurch ist der erste Abschnitt automatisch ohne Top-Margin
   (kein Leerraum vor "Sitzplätze") und keiner der Abschnitte trägt eine
   eigene margin-bottom, der letzte ("Neue Einladung") bleibt also ebenso
   automatisch ohne zusätzlichen Rand nach unten (das Formular endet bündig
   im Panel-Padding). */
.team-section {
  margin-top: var(--team-space-section);
}
.team-section:first-child {
  margin-top: 0;
}

/* Überschrift -> Inhalt innerhalb eines Abschnitts: überschreibt gezielt
   NUR hier den globalen .settings-panel-title-Rand (margin: 0 0 6px, für
   den Konto-/Sicherheits-/KI-Assistent-Tab und das Admin-Panel unverändert
   gelassen, siehe Auftrag), damit alle vier Abschnitte denselben
   Überschrift-Abstand bekommen statt sich auf den jeweils folgenden
   Inhaltstyp (Absatz/Liste/Formular) zu verlassen. */
[data-settings-panel="team"] .team-section > .settings-panel-title {
  margin-bottom: var(--team-space-heading);
}

/* Sitzplatz-/Speicherpool-Zähler: JS setzt diese Klasse zusätzlich auf
   #teamSeatsLine/#teamPoolLine (beides <p class="settings-panel-hint">),
   um Zahl + Label auf eine flexible Zeile zu bringen, die bei sehr
   schmaler Breite sauber umbricht statt zu überlaufen. Der optionale
   Fortschrittsbalken (analog .storage-bar/.storage-bar-fill) ist ein per
   JS eingefügtes Geschwisterelement direkt danach. */
p.team-usage-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 4px 10px;
  margin-bottom: 6px;
}
.team-usage-value {
  font-family: var(--font-mono);
  color: var(--text-dim);
}
.team-usage-bar {
  height: 8px;
  border-radius: 999px;
  background: var(--bg);
  overflow: hidden;
  margin: 0 0 14px;
}
.team-usage-bar-fill {
  display: block;
  height: 100%;
  width: 0%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--accent-teal), var(--accent));
  transition: width var(--dur-slow) var(--ease);
}
/* Optionale Zustände, wenn Plätze/Pool fast bzw. ganz ausgeschöpft sind. */
.team-usage-bar-fill.is-warning { background: var(--accent-yellow); }
.team-usage-bar-fill.is-full { background: var(--accent-red); }

/* Mitgliederzeile (#teamMembersList li): Name links, Speicherwert +
   optionaler kleiner Anteils-Balken in der Mitte, Entfernen-Button
   (.btn-danger, bereits vorhanden) rechts. JS setzt diese Klasse
   zusätzlich auf das jeweilige <li> (neben der Basis aus .settings-list
   li), damit die Zeile bei schmaler Breite umbrechen darf. */
.team-member-row { flex-wrap: wrap; }
.team-member-name {
  flex: 1 1 140px;
  min-width: 0;
  font-weight: 600;
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.team-member-usage {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 120px;
  min-width: 0;
}
.team-member-usage-value {
  font-family: var(--font-mono);
  font-size: var(--size-footnote);
  color: var(--text-dim);
  white-space: nowrap;
}
.team-member-usage-bar {
  flex: 1 1 50px;
  min-width: 32px;
  height: 5px;
  border-radius: 999px;
  background: var(--bg);
  overflow: hidden;
}
.team-member-usage-bar-fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--accent);
}

/* Einladungszeile (#teamInvitesList li): Kanal-Badge, Ablaufdatum,
   gekürzter Link (ellipsis), Kopieren-/Widerrufen-Button. Gleiches Muster
   wie bei .team-member-row — JS setzt die Zeilen-Klasse zusätzlich auf
   das <li>. Kanal-Badges bekommen je Kanal einen dezent eingefärbten,
   bereits im Tokens-Set vorhandenen Akzent (kein neuer Hardcode-Farbwert):
   Link = --accent, E-Mail = --accent-teal, Discord = --brand-discord (der
   vorhandene Lila-Ton passt gut zur Discord-Markenfarbe — eigenes Token,
   seit --type-local Teil der blauen Kachel-Farbleiter ist). */
.team-invite-row { flex-wrap: wrap; }
.team-invite-channel {
  display: inline-block;
  flex: 0 0 auto;
  padding: 1px 8px;
  border-radius: 999px;
  font-size: var(--size-caption);
  font-weight: 600;
  letter-spacing: 0.02em;
  white-space: nowrap;
  background: rgba(255, 255, 255, 0.08);
  color: var(--text-dim);
}
.team-invite-channel.is-link {
  background: var(--accent-soft);
  color: var(--accent);
}
.team-invite-channel.is-email {
  background: color-mix(in srgb, var(--accent-teal) 16%, transparent);
  color: var(--accent-teal);
}
.team-invite-channel.is-discord {
  background: color-mix(in srgb, var(--brand-discord) 18%, transparent);
  color: var(--brand-discord);
}
.team-invite-expiry {
  flex: 0 0 auto;
  font-size: var(--size-footnote);
  color: var(--text-dim);
  white-space: nowrap;
}
.team-invite-link {
  flex: 1 1 140px;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: var(--size-footnote);
  color: var(--text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.team-invite-actions {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 8px;
}

/* Bei sehr schmaler Breite (~400px) bekommen Speicher-/Anteilsanzeige,
   gekürzter Link und Aktionen je eine eigene Zeile statt sich ein enges
   Flex-Wrap-Raster mit dem Badge zu teilen — bleibt lesbar, kein
   horizontales Scrollen der Seite. */
@media (max-width: 480px) {
  .team-member-usage,
  .team-invite-link,
  .team-invite-actions {
    flex-basis: 100%;
  }
}

/* Einladungsformular (#teamInviteForm): nutzt .settings-form/.settings-
   label/.settings-input/.settings-form-actions unverändert — hier nur ein
   Feinschliff für die neue Kombination <select class="settings-input"> +
   <input class="settings-input">, damit der native Auswahlpfeil optisch
   zum restlichen Dark-UI passt statt der grellen Standard-Systemfarbe. */
select.settings-input {
  appearance: none;
  -webkit-appearance: none;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--text-secondary) 50%),
    linear-gradient(135deg, var(--text-secondary) 50%, transparent 50%);
  background-position: right 14px center, right 9px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 30px;
  cursor: pointer;
}

/* ==========================================================================
   Modale (geteilt über die ganze App) — Sample-/Plugin-Picker, Mix & Export,
   und die gemeinsamen Ersatz-Dialoge für alert()/confirm()/prompt()
   (static/js/dialogs.js). War früher nur in editor.css (Audio-Editor) —
   umgezogen nach style.css, weil style.css die einzige CSS-Datei ist, die
   auf jeder Seite geladen wird (Dashboard, Admin, Beat-Editor, lokale DAW,
   Projekt-Einstellungen, Audio-Editor).
   ========================================================================== */
/* Auftritt wie beim Bestätigungsdialog oben (nm-scrim-in/nm-dialog-in).
   Die Modale werden über style.display ein- und ausgeblendet, beim
   Einblenden startet die Animation dadurch jedes Mal neu. Ein gespiegelter
   Abgang braucht JS (Klasse vor dem Ausblenden), das liegt in den Skripten
   der einzelnen Modale und ist hier nicht nachgerüstet. */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: var(--scrim);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  padding: var(--space-5);
  animation: nm-scrim-in var(--dur-base) var(--ease) backwards;
}

.modal-dialog {
  background: var(--panel-elevated);
  border: 1px solid var(--separator);
  border-radius: var(--radius-l);
  padding: var(--space-6);
  width: 100%;
  max-width: 440px;
  max-height: 80vh;
  display: flex;
  flex-direction: column;
  box-shadow: var(--material-edge), var(--shadow-l);
  animation: nm-dialog-in var(--dur-spring) var(--spring) backwards;
}

.modal-title {
  font-family: var(--font-text);
  font-size: var(--size-title-3);
  line-height: var(--leading-title-3);
  font-weight: 600;
  margin: 0 0 var(--space-3);
}

.modal-body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.modal-upload-row { display: flex; }

.modal-search {
  min-height: var(--control-h-m);
  background: var(--field-bg);
  border: 1px solid var(--field-border);
  border-radius: var(--radius-s);
  padding: var(--space-1) var(--space-3);
  color: var(--text-primary);
  font-size: var(--size-body);
}
.modal-search:focus {
  border-color: var(--accent);
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent);
}

.modal-list {
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.modal-list-item {
  min-height: var(--control-h-m);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-s);
  background: var(--fill-1);
  border: 1px solid transparent;
  font-size: var(--size-callout);
  color: var(--text-primary);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  transition: background var(--dur-fast) var(--ease), transform var(--dur-press) var(--ease);
}
.modal-list-item:hover { background: var(--fill-2); }
.modal-list-item:active { background: var(--fill-3); transform: scale(0.99); }

.modal-list-group-title {
  font-family: var(--font-text);
  font-size: var(--size-footnote);
  font-weight: 600;
  color: var(--text-secondary);
  padding: var(--space-3) var(--space-1) 2px;
}
.modal-list-group-title:first-child { padding-top: 0; }

.modal-list-item-badge {
  flex-shrink: 0;
  font-size: var(--size-caption);
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent);
}

/* Der Name selbst darf nie umbrechen und nie ein Abzeichen aus der Zeile
   draengen: er schrumpft (min-width: 0) und endet notfalls mit "…". Ohne das
   sprengt ein langer Plugin-Name die einzeilige Zeile, sobald rechts daneben
   ein Abzeichen steht. */
.modal-list-item-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* "nur lokal" — ein VST3, das nur auf DIESEM Rechner liegt und deshalb
   Mitarbeitern und auf anderen Geraeten fehlt (siehe
   src/client/plugin_mirror.py: local_only_names, nach aussen durchgereicht
   als "local_only" von GET /api/local/plugins/source). Bewusst die
   Achtungsfarbe statt des Akzents, damit es sich auf einen Blick von den
   harmlosen Abzeichen "Eingebaut"/"laedt beim ersten Mal" unterscheidet —
   gleiche Farbwahl wie .marketplace-badge-updated, keine neuen Farbwerte.
   Steht hier und nicht in editor.css, weil es sowohl im Plugin-Browser als
   auch in der Plugin-Kette des Mixers benutzt wird. */
.local-only-badge {
  flex-shrink: 0;       /* darf nie schrumpfen ... */
  white-space: nowrap;  /* ... und nie umbrechen, auch bei langen Namen nicht */
  display: inline-block;  /* auch im Fliesstext (Shell-Auswahl) eine Pille */
  vertical-align: middle;
  font-size: var(--size-caption);
  font-weight: 600;
  letter-spacing: 0.02em;
  line-height: 1.4;
  padding: 1px 6px;
  border-radius: 999px;
  color: var(--accent-yellow);
  background: color-mix(in srgb, var(--accent-yellow) 16%, transparent);
}
/* Im Plugin-Browser exakt die Masse der uebrigen Abzeichen dort — sonst ist
   eine markierte Zeile 3 px hoeher als ihre Nachbarn und die Liste wirkt
   unruhig (gemessen, nicht geschaetzt). */
.modal-list-item .local-only-badge {
  font-size: var(--size-caption);
  line-height: normal;
  padding: 2px 7px;
}
/* Im Fliesstext eines Hinweises braucht es Abstand zum Satz davor. */
.modal-hint .local-only-badge { margin-left: 6px; }

.modal-list-truncated-hint {
  color: var(--text-secondary);
  font-size: var(--size-footnote);
  text-align: center;
  padding: 8px 0 2px;
}

.modal-list-empty {
  color: var(--text-secondary);
  font-size: var(--size-callout);
  text-align: center;
  padding: 20px 0;
}

.modal-hint {
  color: var(--text-secondary);
  font-size: var(--size-footnote);
  margin: 0 0 10px;
}

.modal-actions {
  margin-top: var(--space-4);
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-2);
}

/* ==========================================================================
   Einstellungen-Modal (AP4a) — baut auf .modal-overlay/.modal-dialog auf,
   aber deutlich breiter/höher als ein Bestätigungsdialog: die Panels
   darunter (Profil, Sicherheit, KI-Keys, Team, …) sind lang und brauchen
   einen eigenen Scroll-Bereich innerhalb des Modals statt im Body zu
   scrollen.
   ========================================================================== */
/* Feste Höhe statt max-height (Diagnosebefund): Vorher richtete sich die
   Höhe nach dem Inhalt des gewählten Reiters, das Modal sprang beim
   Reiterwechsel auf und zu. Jetzt bleibt der Rahmen stehen, nur der
   Inhaltsbereich scrollt. */
.modal-dialog.settings-modal-dialog {
  max-width: 900px;
  height: min(720px, 90vh);
  max-height: none;
}
/* Fokus liegt beim Öffnen auf dem Dialog-Wurzelelement selbst (kein
   sinnvoller "erster" Programm-Inhalt bei so vielen Panels) — die übliche
   Focus-Ring-Kontur wäre hier nur ein Rahmen um einen bereits klar
   umrandeten Dialog; die Modalität selbst ist Hinweis genug. */
.settings-modal-dialog:focus { outline: none; }

.settings-modal-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-shrink: 0;
}
.settings-modal-head .page-title,
.settings-modal-head .page-subtitle { margin: 0; }
.settings-modal-head .page-subtitle { margin-top: var(--space-1); }

.modal-close-x {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  padding: 0;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--fill-2);
  border: none;
  color: var(--text-secondary);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease),
    transform var(--dur-press) var(--ease);
}
.modal-close-x svg { width: 14px; height: 14px; }
.modal-close-x:hover { background: var(--fill-3); color: var(--text-primary); }
.modal-close-x:active { transform: scale(var(--press-scale)); }

/* Scrollkante statt harter Linie (DESIGNRICHTUNG 2.8): Der Inhalt blendet
   an Ober- und Unterkante aus, wo er unter Kopf und Fuß weiterläuft. */
.settings-modal-body {
  margin-top: var(--space-3);
  overflow-y: auto;
  padding: var(--scroll-edge-size) var(--space-1) var(--scroll-edge-size) 0;
  -webkit-mask-image: var(--scroll-edge-mask);
  mask-image: var(--scroll-edge-mask);
  overscroll-behavior: contain;
}

.settings-modal-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  flex-shrink: 0;
  margin-top: var(--space-2);
}

.settings-modal-foot-actions {
  display: flex;
  gap: var(--space-2);
  margin-left: auto;
}

/* Die Präferenz-Formulare (Profil, öffentliches Profil) speichern im Sheet
   sofort (Stufe 6, settings.js: autoSave). Ihre eigenen Speichern-Knöpfe
   sind deshalb nur optisch ausgeblendet, NICHT aus dem DOM entfernt:
   autoSave ruft form.requestSubmit() auf, was die bestehenden
   submit-Handler unverändert auslöst. */
#settingsModalOverlay #profileForm > .settings-form-actions > button[type="submit"],
#settingsModalOverlay #publicProfileForm > .settings-form-actions > button[type="submit"] {
  display: none;
}

@media (max-width: 640px) {
  .modal-dialog.settings-modal-dialog {
    height: min(720px, 92vh);
    padding: var(--space-4);
  }
  .settings-modal-foot-actions { width: 100%; justify-content: flex-end; }
}

.settings-autosave-hint { color: var(--text-secondary); font-size: var(--size-footnote); }

/* --------------------------------------------------------------------------
   Sheet mit Kategorienliste (Stufe 6, 2026-10-12, Nutzerentscheidung 6)
   --------------------------------------------------------------------------
   Ab 641 px stehen die Reiter als Liste links (wie die Seitenleiste der
   macOS-Systemeinstellungen), der Inhalt rechts. Nur CSS: Liste und Panels
   bleiben Geschwister in .settings-modal-body. Das Raster legt die Liste in
   Spalte 1 über alle Zeilen, alles andere (Panels, Suchtreffer) in Spalte 2.
   Die Liste klebt beim Scrollen unterhalb der weichen Scrollkante
   (top = --scroll-edge-size), damit die Maske sie nicht anblendet.
   Gewählte Kategorie: hellere Fläche, kein Akzent (wie das Segment oben,
   DESIGNRICHTUNG 2.5). Der Rahmen hat eine feste Höhe (oben), beim
   Kategorienwechsel springt nichts; settings.js setzt den Inhalt auf oben.
   Darunter bleibt es bei der Segment-Steuerung über dem Inhalt. */
@media (min-width: 641px) {
  .settings-modal-body {
    display: grid;
    grid-template-columns: minmax(150px, 190px) minmax(0, 1fr);
    column-gap: var(--space-6);
    align-items: start;
  }
  .settings-modal-body > * { grid-column: 2; min-width: 0; }
  .settings-modal-body > .settings-nav {
    grid-column: 1;
    grid-row: 1 / span 40;
    position: sticky;
    top: var(--scroll-edge-size);
    flex-direction: column;
    flex-wrap: nowrap;
    width: auto;
    margin: 0;
    padding: 0;
    gap: 2px;
    background: none;
  }
  .settings-modal-body > .settings-nav .settings-nav-item {
    justify-content: flex-start;
    width: 100%;
    min-height: var(--control-h-m);
    padding: 0 var(--space-3);
    border-radius: var(--radius-s);
    text-align: left;
  }
  .settings-modal-body > .settings-nav .settings-nav-item:hover { background: var(--fill-1); }
  .settings-modal-body > .settings-nav .settings-nav-item:active { background: var(--fill-2); }
  .settings-modal-body > .settings-nav .settings-nav-item.is-active {
    background: var(--fill-2);
    box-shadow: none;
  }
}

/* Gespiegelter Abgang ohne motion.js (settings.js: close). Mit motion.js
   (.nm-motion) laufen Auftritt und Abgang als Feder, die Keyframes ruhen. */
.settings-modal-overlay.is-closing {
  pointer-events: none;
  animation: nm-scrim-out var(--dur-base) var(--ease) forwards;
}
.settings-modal-overlay.is-closing > .settings-modal-dialog {
  animation: nm-dialog-out var(--dur-base) var(--ease) forwards;
}
.settings-modal-overlay.nm-motion,
.settings-modal-overlay.nm-motion > .settings-modal-dialog { animation: none; }

/* ==========================================================================
   Rückmeldung: Toast und Fortschrittsbalken (shell.js)
   ==========================================================================
   Stand vorher in editor.css und galt dadurch nur auf den Editor-Seiten.
   Beides wird jetzt app-weit benutzt, deshalb hier.
   ========================================================================== */

/* Kurzlebige Rückmeldung (z.B. "Gespeichert ✓") — mittig unten, neutral
   bzw. rot bei Fehler. */
/* Material "regular" (schwebende Ebene über dem Inhalt), Auftritt und
   Abgang auf demselben Weg: von unten herein, nach unten hinaus. */
.editor-toast {
  position: fixed;
  bottom: var(--space-6);
  left: 50%;
  transform: translateX(-50%) translateY(calc(12px * var(--motion-scale)));
  background: var(--material-regular-bg);
  backdrop-filter: var(--material-regular-filter);
  -webkit-backdrop-filter: var(--material-regular-filter);
  border: 1px solid var(--separator);
  box-shadow: var(--material-edge), var(--shadow-m);
  color: var(--text-primary);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-m);
  font-size: var(--size-callout);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-base) var(--ease), transform var(--dur-spring) var(--spring);
  z-index: 500;
  /* max-content: sonst schrumpft der Toast ab left:50% auf die halbe
     Fensterbreite und bricht schon kurze Meldungen (375 px gemessen). */
  width: max-content;
  max-width: min(560px, calc(100vw - 32px));
  box-sizing: border-box;
  text-align: center;
}
.editor-toast.is-visible { opacity: 1; transform: translateX(-50%) translateY(0); }
.editor-toast.is-success { border-color: color-mix(in srgb, var(--success) 45%, var(--separator)); }
.editor-toast.is-error { border-color: color-mix(in srgb, var(--danger) 55%, var(--separator)); color: var(--danger); }

/* Toast mit Aktion (AP-OC F1, 2026-10-12): Text links, ein schlichter Knopf
   rechts ("Rückgängig"). Nur dieser Toast nimmt Zeiger an, und nur solange
   er sichtbar ist; der Knopf ist dann auch per Tab erreichbar (shell.js hält
   die Uhr an, solange Zeiger oder Fokus darin sind). */
.editor-toast.has-action {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-1) var(--space-1) var(--space-1) var(--space-4);
  text-align: left;
}
.editor-toast.has-action.is-visible { pointer-events: auto; }
.editor-toast-text {
  min-width: 0;
  overflow-wrap: break-word;
}
.editor-toast-action {
  flex-shrink: 0;
  min-height: var(--control-h-m);
  padding: 0 var(--space-3);
  border: none;
  border-radius: var(--radius-s);
  background: transparent;
  color: var(--accent);
  font-size: var(--size-callout);
  font-weight: 600;
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease), transform var(--dur-press) var(--ease);
}
.editor-toast-action:hover { background: var(--fill-2); }
.editor-toast-action:active { background: var(--fill-3); transform: scale(var(--press-scale)); }
.editor-toast-action:disabled { cursor: default; }
@media (pointer: coarse) {
  .editor-toast-action { min-height: var(--hit-min); }
}
@media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
  .editor-toast { background: var(--panel-elevated); backdrop-filter: none; -webkit-backdrop-filter: none; }
}
@media (prefers-contrast: more) {
  .editor-toast { border-color: var(--border-strong); }
}

/* Ordnerwähler (shell.js: chooseFolderDialog, AP-OC F1 2026-10-12)
   Im Bestätigungsdialog eine gruppierte Liste (innere Gruppe: --fill-1, ohne
   Rand, --radius-s, DESIGNRICHTUNG 2.5). Zeilen 36 px, Einrückung je Ebene,
   Symbol in --accent. Gewählt: --accent-soft; der Fokusring liegt auf der
   gewählten Zeile, solange die Liste den Fokus hat (aria-activedescendant).
   Gesperrte Zeilen sind gedämpft und nicht wählbar. */
.folder-chooser { max-width: 440px; }
.folder-chooser .confirm-message { margin-bottom: var(--space-3); }
.folder-chooser-list {
  list-style: none;
  margin: 0 0 var(--space-5);
  padding: var(--space-1);
  max-height: min(320px, 45vh);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--fill-1);
  border-radius: var(--radius-s);
}
.folder-chooser-list:focus-visible { outline: none; }
.folder-chooser-option {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 36px;
  padding: 0 var(--space-3) 0 calc(var(--space-3) + var(--level, 0) * var(--space-4));
  border-radius: calc(var(--radius-s) - 4px);
  color: var(--text-primary);
  font-size: var(--size-body);
  cursor: pointer;
  user-select: none;
  transition: background var(--dur-fast) var(--ease);
}
.folder-chooser-option:hover:not([aria-disabled="true"]) { background: var(--fill-1); }
.folder-chooser-option[aria-selected="true"] { background: var(--accent-soft); }
.folder-chooser-list:focus-visible .folder-chooser-option[aria-selected="true"] {
  outline: var(--focus-ring-width) solid var(--focus-ring);
  outline-offset: -2px;
}
.folder-chooser-option[aria-disabled="true"] { color: var(--text-dim); cursor: not-allowed; }
.folder-chooser-icon {
  display: inline-flex;
  flex-shrink: 0;
  color: var(--accent);
}
.folder-chooser-option[aria-disabled="true"] .folder-chooser-icon { color: var(--text-dim); }
.folder-chooser-icon svg { width: 18px; height: 18px; }
.folder-chooser-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.folder-chooser-current {
  margin-left: auto;
  flex-shrink: 0;
  font-size: var(--size-caption);
  font-weight: 600;
  color: var(--text-secondary);
}
@media (pointer: coarse) {
  .folder-chooser-option { min-height: var(--hit-min); }
}
@media (prefers-contrast: more) {
  .folder-chooser-list { box-shadow: inset 0 0 0 1px var(--border-strong); }
  .folder-chooser-option[aria-selected="true"] { box-shadow: inset 0 0 0 1px var(--accent); }
}
@media (forced-colors: active) {
  .folder-chooser-option[aria-selected="true"] { outline: 2px solid Highlight; outline-offset: -2px; }
}

/* Fortschritt bei Übertragungen. Liegt oben am Fensterrand und bleibt dort
   sichtbar, auch wenn der Nutzer scrollt — ein Balken, den man wegscrollen
   kann, beantwortet die Frage "läuft das noch?" nicht. */
.app-progress {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1200;
  background: var(--panel-elevated);
  border-bottom: 1px solid var(--border-strong);
  transform: translateY(-100%);
  transition: transform var(--dur-base) var(--ease);
  pointer-events: none;
}
.app-progress.is-visible { transform: translateY(0); }

.app-progress-inner {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 16px;
  max-width: 900px;
  margin: 0 auto;
  font-size: var(--size-callout);
  color: var(--text-secondary);
}

.app-progress-label { white-space: nowrap; }

.app-progress-track {
  flex: 1;
  height: 6px;
  border-radius: 999px;
  background: var(--panel-deep);
  overflow: hidden;
}

.app-progress-fill {
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: var(--accent);
  transition: width var(--dur-fast) linear;
}
.app-progress-fill.is-error { background: var(--accent-red); }

.app-progress-percent {
  min-width: 96px;
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--text-dim);
  white-space: nowrap;
}

/* Gesamtgröße unbekannt: ein wanderndes Segment statt eines erfundenen
   Prozentwerts. */
.app-progress.is-indeterminate .app-progress-fill { width: 100%; opacity: .5; }
@media (prefers-reduced-motion: no-preference) {
  .app-progress.is-indeterminate .app-progress-fill {
    width: 35%;
    opacity: 1;
    animation: app-progress-slide 1.1s ease-in-out infinite;
  }
}
@keyframes app-progress-slide {
  0%   { margin-left: 0; }
  50%  { margin-left: 65%; }
  100% { margin-left: 0; }
}

/* Bewegung ist hier reine Information, kein Schmuck — wer sie abbestellt
   hat, bekommt den Balken trotzdem, nur ohne Wandern. */
@media (prefers-reduced-motion: reduce) {
  .app-progress { transition: none; }
  .app-progress-fill { transition: none; }
}

/* Ein Bedienelement, das gerade arbeitet (shell.js: setBusy). */
[aria-busy="true"] {
  opacity: .65;
  cursor: progress;
}

/* Ziel für Ziehen-und-Ablegen im Cloud-Speicher (storage.js: initUpload).
   Bewusst ein gestrichelter Rahmen INNEN statt einer Größenänderung — die
   Liste darf beim Darüberziehen nicht springen, sonst zielt man daneben. */
#storageList.is-drop-target {
  outline: 2px dashed var(--accent);
  outline-offset: -6px;
  background: color-mix(in srgb, var(--accent) 6%, transparent);
}

/* ==========================================================================
   Handy hochkant (2026-10-25): globale Basis fuer schmale Bildschirme.
   Nur Ergaenzungen unter 640 px; Desktop bleibt unveraendert.
   Touch-Ziele mind. 44 px, Eingaben 16 px (kein iOS-Zoom), Safe-Area,
   kein seitliches Scrollen, ruhige Bewegung.
   ========================================================================== */
@media (max-width: 640px) {
  html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
  body { overflow-x: clip; }

  .main {
    padding: var(--space-4)
      max(var(--space-4), env(safe-area-inset-right, 0px))
      calc(var(--space-6) + env(safe-area-inset-bottom, 0px))
      max(var(--space-4), env(safe-area-inset-left, 0px));
  }

  /* Fliessende Ueberschriften, enge Zeilenhoehe, negatives Tracking. */
  h1 { font-size: clamp(1.5rem, 6.5vw, 2rem); line-height: 1.15; letter-spacing: -0.02em; }
  h2 { font-size: clamp(1.25rem, 5.2vw, 1.5rem); line-height: 1.2; letter-spacing: -0.015em; }
  h1, h2, h3 { overflow-wrap: anywhere; text-wrap: balance; }
  p, li { overflow-wrap: anywhere; }

  /* iOS zoomt Felder unter 16 px. */
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
  select,
  textarea { font-size: max(16px, 1em); }

  /* Touch-Ziele */
  .nav-item,
  .btn-primary,
  .btn-secondary,
  .account-action,
  .sidebar-account-link { min-height: 44px; }

  .app-footer { flex-direction: column; align-items: flex-start; gap: var(--space-3); }
  .app-footer-links { gap: 0 var(--space-4); }
  .app-footer-links a { display: inline-flex; align-items: center; min-height: 44px; }

  /* Breites bleibt im Container scrollbar statt die Seite zu weiten. */
  table { display: block; max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
  img, video, svg, canvas { max-width: 100%; }
  pre, code { overflow-wrap: anywhere; }
  pre { overflow-x: auto; }

  /* Dialoge werden Bottom-Sheets. */
  dialog[open], .modal-card, .modal-dialog {
    max-width: 100vw;
    max-height: 90vh;
    max-height: 90dvh;
  }

  .local-empty-main {
    min-height: 100vh;
    min-height: 100dvh;
    padding: var(--space-4) max(var(--space-4), env(safe-area-inset-right, 0px))
      calc(var(--space-4) + env(safe-area-inset-bottom, 0px)) max(var(--space-4), env(safe-area-inset-left, 0px));
  }
  .local-empty-card { width: 100%; max-width: 100%; }
}
