/* Hausregel für alle Blätter unter public/ (#160): Steht ein Selektor in
   derselben Datei ein zweites Mal auf oberster Ebene, muss unmittelbar vor
   einer der beiden Fassungen ein Kommentar stehen, der die Marke `Doppelung:`
   trägt und sagt, warum es zwei sind. Es gibt keinen Bauschritt, der zwei
   Fassungen zusammenführt — es entscheidet die Reihenfolge im Text, und wer
   dann die erste ändert, sieht nichts passieren. Wo die zweite Fassung die
   erste bloß überschreibt, gehört sie eingefaltet statt kommentiert.
   Durchgesetzt von tests/Feature/CssDuplicateSelectorTest.php; alle
   absichtlichen Doppelungen zeigt `grep -rn "Doppelung:" public/`.
   Diese Datei ist zugleich die erste Fundstelle: `button`, `input`, `select`,
   `textarea`, `th` und `.password-toggle:hover` stehen hier mehrfach — je
   mit Notiz an ihrer Stelle. */

/* Die Palette wohnt seit #108 in color-tokens.css (auf jeder Seite als erstes
   Stylesheet geladen). Bis zum 2026-09-20 stand hier ein `:root` mit acht
   Kurz-Aliassen (`--bg`, `--s`, `--s2`, `--l`, `--t`, `--m`, `--b`,
   `--green`) „für project-ui.css und admin-layout.css" — von denen fünf
   niemand mehr las und die übrigen drei nur admin-settings.css; das liest
   jetzt die Namen selbst, und ColorTokenConsistencyTest lässt keinen Alias
   außerhalb der Token-Datei mehr zu (#397, Gruppe K). */

* { box-sizing: border-box; }
/* `hidden` muss hidden bleiben (#171). Das `display:none` des Browsers steht
   in dessen Voreinstellung und verliert damit gegen **jede** eigene
   display-Regel — `.modal label {display:grid}` reichte, und ein
   ausgeschaltetes Optionalfeld stand trotzdem im Dialog. Doppelte Auswahl
   statt Quelltextreihenfolge, weil `.check {display:flex !important}` sonst
   gleichauf läge. */
[hidden][hidden] { display: none !important; }
html { min-width: 320px; background: var(--canvas); }
body { margin: 0; background: var(--canvas); color: var(--text); font: var(--weight-regular) var(--font-base)/var(--leading-base) var(--font-sans); }
button, input, select, textarea { font: inherit; -webkit-tap-highlight-color: transparent; }
a, summary { -webkit-tap-highlight-color: transparent; }
/* Eine Linkfarbe, eine Regel fuers Unterstreichen (#224). Ohne diese Zeile gab
   der Browser besuchten Links sein eigenes Violett — dieselbe Beschriftung war
   je nach Verlauf des Lesenden mal blau, mal violett, und das las sich wie
   zwei verschiedene Arten von Link. Das schwaechstmoegliche Muster: jede
   Komponente, die eine eigene Farbe setzt, gewinnt weiterhin.

   Doppelung: `a` steht eine Zeile ueber dieser Gruppe schon einmal — dort
   zusammen mit `summary` fuer das Antippen auf Beruehrbildschirmen. Die Farbe
   gehoert nicht dorthin: `summary` ist kein Link und wuerde blau.

   Es gab hier einen Tag lang zusaetzlich `a:visited`. Das war gleich doppelt
   falsch: ueberfluessig, weil eine Autorenregel den Browservorgabewert fuer
   besuchte Links ohnehin schlaegt — und schaedlich, weil eine Pseudoklasse die
   Spezifitaet auf (0,1,1) hebt und damit `nav a` (0,0,2) ueberstimmt. Die
   Seitenleiste faerbte sich also blau, sobald ihre Ziele einmal besucht waren.
   Beim Durchsehen frisch gerenderter Seiten war das unsichtbar: Dort ist kein
   Link besucht. */
a { color: var(--link); text-decoration: none; }
a:hover { color: var(--link-hover); }
/* Unterstrichen wird nur, was im Fliesstext steht. Die Regel galt einen Tag
   lang fuer *jeden* Link beim Ueberfahren und legte damit einen Strich unter
   Seitenleiste, Projektzeilen und Kacheln — Dinge, die keine Fliesstextlinks
   sind und vorher zu Recht keinen hatten.

   Sie hing danach allein am Elternelement, und das ist zu wenig (#224):
   Derselbe Verweis bekam einen Strich oder keinen, je nachdem, ob jemand ihn
   in einen Absatz oder in ein `div` gesetzt hatte. „Berechtigungen" auf der
   Desktop-Seite stand unterstrichen, „Speicherbereiche verwalten" auf dem
   Dashboard nicht — dieselbe Sorte Verweis, zwei Erscheinungen.

   Die Regel lautet deshalb ab jetzt nicht mehr „im Absatz", sondern:
   **Fliesstext wird unterstrichen, ein Bedienelement nie** — auch dann nicht,
   wenn es mitten in einem Absatz steht. Ein Knopf, ein Abzeichen und ein Chip
   sind Bedienelemente; sie tragen ihre Form selbst und brauchen den Strich
   nicht, der bei ihnen nur wie ein Fehler aussaehe. (Der Pfeil-Verweis
   `.shape-link` stand hier bis zum 2026-09-13 mit in der Liste und ist
   gefallen — der Name ist der Link, ohne Pfeil.) */
p a, dd a, .hint a { text-decoration: underline; text-underline-offset: 2px; }
:is(p, dd, .hint) a:is(.button-link, .ui-badge, .tag-chip) { text-decoration: none; }
::selection { background: var(--brand-soft); color: var(--text); }

/* Die Rasterspalten der Schale stehen genau hier (#234). Sie standen in drei
   Blättern — 232px hier, 230px in admin-layout.css, das Token in
   navigation-shell.css —, und welche galt, entschied allein die Reihenfolge im
   <head>. Wer die falsche änderte, sah nichts passieren. Das Token bleibt die
   Quelle der Breite; der Rückfallwert trägt die Oberflächen, die
   navigation-shell.css gar nicht laden (Anmeldung, Anlieferung, Fehlerseite). */
.shell { display: grid; grid-template-columns: var(--global-nav-width, 232px) minmax(0, 1fr); min-height: 100vh; }
aside { display: flex; flex-direction: column; min-width: 0; padding: var(--space-5) var(--space-3) var(--space-4); border-right: 1px solid var(--border); background: var(--sidebar); }
/* Die Leiste steht, der Inhalt scrollt (#307). Vorher war sie eine gewöhnliche
   Rasterspalte: Sie wuchs auf die Höhe des Hauptbereichs mit und wanderte beim
   Blättern nach oben aus dem Bild — auf einer langen Verwaltungsseite waren
   Navigation und der Fuß mit „Lokaler Server / Abmelden" nach zwei Umdrehungen
   des Rades nicht mehr erreichbar.

   `height` statt `min-height`, und das ist der Punkt: Ein Rasterelement wird
   ohne eigene Höhe auf die Zeilenhöhe gestreckt, und ein klebendes Element, das
   so hoch ist wie sein Scrollbereich, hat nichts, woran es kleben könnte. Erst
   die feste Höhe macht die Leiste kürzer als die Seite. `overflow-y` gehört
   dazu, weil eine Navigation auch länger als das Fenster sein kann;
   `.side-bottom` hängt über `margin-top: auto` weiter an ihrem unteren Rand.

   Dieselbe Lösung trug die Seitenleiste des Projektbereichs seit jeher
   (`.workspace-nav` in workspace.css) — die Verwaltung zog nach, statt eine
   zweite zu erfinden; seit dem 2026-09-11 ist diese Regel die einzige, weil
   der Projektbereich dieselbe Schale benutzt (#353).

   Auf `.shell > aside` eingeschränkt und nicht an die Regel darüber gehängt:
   Der nackte Selektor trifft auch die `<aside>` *im* Inhalt — die Regelpalette,
   den Neustart-Kasten, die Bereichsliste der Dateiansicht. Eine Seitenhöhe mit
   eigenem Rollbalken wäre dort keine Verbesserung, sondern ein Schaden.
   Unterhalb von 960px gilt sie ebenfalls nicht: Dort ist die Leiste eine
   Schublade (`position: fixed` in admin-layout.css, gleiche Spezifität, später
   im <head>). */
.shell > aside { position: sticky; top: 0; height: 100dvh; overflow-y: auto; }
/* Nur die Lage von Bildmarke und Wortmarke, wenn niemand angemeldet ist —
   derselbe Einzug und Abstand wie im `summary` des Bereichsmenüs, das
   angemeldet an dieser Stelle steht, damit die Marke nicht springt. Der
   Kasten trägt seit dem 2026-09-11 dieselbe Klasse wie das Menü (Höhe, Reihe
   aus navigation-shell.css), ist aber kein `details`: Nur ihm fehlen Polster
   und Lücke, die das Menü an seinem `summary` führt. Die Form kommt aus
   `.wordmark` (ui-surfaces.css). */
.shell > aside > .global-app-switcher:not(details) { gap: var(--space-2); padding: 0 var(--space-3); }
nav { display: grid; gap: var(--space-1); }
/* Gruppenkopf der Seitenleiste: 12/500, gedämpft, keine Versalien. Zehn Pixel
   Versalien mit Sperrung waren die am schlechtesten lesbare Schrift der Seite
   (2026-09-10); Abstand nach oben trennt die Gruppen, nicht die Schriftform.
   `.admin-nav summary` (admin-nav.css) und `.workspace-project small`
   (admin-layout.css) tragen dieselbe Form. */
nav > small { margin: var(--space-4) var(--space-3) var(--space-1); color: var(--text-subtle); font-size: var(--font-sm); font-weight: var(--weight-medium); }
/* Die Höhe kommt aus `--control-h`, nicht aus dem Polster (Welle 3, 2026-09-13):
   Mit 7px 10px war die Zeile 36 (Hauptebene, 14 px) beziehungsweise 35
   (Untereinträge, 13 px) hoch und das `min-height: 34px` griff nie; 8 px
   Polster aus der Skala hätten 38/37 ergeben, 27 Einträge mal 2 px längere
   Seitenleiste. 4 px Polster tragen den Text (30 px), die Mindesthöhe trägt
   die Zeile — 36 auf beiden Ebenen, die Untereinträge rücken um 1 an
   `--control-h`. `.nav-locked` (admin-nav.css) hat dieselbe Geometrie. */
nav a { display: flex; align-items: center; gap: var(--space-2); min-height: var(--control-h); padding: var(--space-1) var(--space-3); border: 1px solid transparent; border-radius: var(--radius-sm); color: var(--text-muted); text-decoration: none; }
/* Das Symbol kommt aus dem Bogen (components/icon.blade.php, 16 px). */
nav a .icon { flex: 0 0 16px; color: var(--text-subtle); }
nav a:hover { border-color: color-mix(in srgb, var(--brand) 18%, transparent); background: var(--surface-hover); color: var(--text); }
nav a.active { border-color: color-mix(in srgb, var(--brand) 34%, transparent); background: var(--surface-selected); box-shadow: inset 2px 0 var(--brand); color: var(--text); font-weight: var(--weight-semibold); }
nav a:hover .icon, nav a.active .icon { color: var(--brand); }
.side-bottom { display: grid; gap: var(--space-1); margin-top: auto; padding: var(--space-4) var(--space-3) 0; border-top: 1px solid var(--border); color: var(--text-subtle); font-size: var(--font-xs); }
/* Der stille Knopf „Abmelden" (#397) rückt um sein eigenes Polster nach
   links: So steht sein Wort unter „Lokaler Server" und seine Hoverfläche
   bündig mit den Zeilen der Leiste, die dasselbe Polster tragen. Bis dahin
   trug `.link` hier eine eigene Regel — sechs `!important`, die den Knopf
   zum Link zurückbauten. */
.side-bottom form { margin: 0 0 0 calc(-1 * var(--space-3)); }

/* Die Breite ist `--shell-max` (color-tokens.css, „Maße") — dasselbe Token,
   das `.shell > main` in admin-layout.css seit dem 2026-09-13 las, während
   hier die 1600 noch als Zahl stand (#397, Gruppe K). */
main { width: 100%; max-width: var(--shell-max); min-width: 0; margin: 0; padding: var(--space-6) var(--space-7) var(--space-8); }
/* Die Geometrie der Kopfzeile steht genau hier (#234) — Höhe, Einzug,
   Trennlinie, Fläche. admin-layout.css schrieb sie ein zweites Mal ab, um sie
   klebrig zu machen; dort steht jetzt nur noch, was das Kleben ausmacht. Die
   Höhe kommt aus demselben Token wie die Topleiste von Viewer und
   Projektbereich, damit die drei Oberflächen nicht auseinanderlaufen. */
main > header { position: relative; z-index: 12; display: flex; justify-content: space-between; align-items: center; min-height: var(--global-topbar-height, 58px); margin: calc(-1 * var(--space-6)) calc(-1 * var(--space-7)) var(--space-6); padding: var(--space-3) var(--space-7); border-bottom: 1px solid var(--border); background: color-mix(in srgb, var(--canvas) 94%, transparent); }
small { margin: 0; color: var(--text-muted); }
h1 { margin: 1px 0 0; font-size: var(--font-xl); line-height: var(--leading-tight); letter-spacing: -.025em; }
/* Die Überschrift der Topleiste allein trägt die Display-Stufe (2026-09-11):
   Seitenname und Kennzahl sind die zwei größten Dinge der Seite, und sie sind
   es aus demselben Grund — Größe ist Rang, kein Schmuck. Das `h1` der
   Anmeldung und der Außenseiten bleibt bei 24, dort gibt es keine Kennzahl,
   neben der es stehen müsste. Unter 600 nimmt responsive.css es zurück.
   Die Regel selbst (`.global-topbar-title h1`) steht seit dem 2026-09-11 in
   navigation-shell.css, weil auch die Dokumente ohne dieses Blatt (Dateien,
   Sounds, Lizenzen) dieselbe Kopfzeile tragen — und `.global-topbar-title
   {min-width:0}` seit dem 2026-09-20 nur noch dort: Jede Seite mit
   `<x-topbar>` lädt navigation-shell.css, die Kopie hier tat nie etwas
   (#397, Gruppe K). */
h2 { letter-spacing: -.015em; }
code { color: var(--text); font: var(--font-sm)/var(--leading-base) var(--font-mono); }

/* Doppelung: `button`, `input`, `select` und `textarea` stehen in dieser
   Datei bewusst mehrfach, und die Fassungen sind drei Aussagen: ganz oben die
   Rückstellung, die jedes Bedienelement der Seite betrifft (Schrift,
   Tipp-Hervorhebung); hier der gemeinsame Kasten aller Bedienfelder; darunter
   je Gattung, was nur sie unterscheidet — eine Mindestbreite für Eingaben,
   Farbe und Zeiger für Schaltflächen. Zusammengefaltet ergäbe das eine Regel,
   in der man die Gattungsunterschiede erst wieder heraussuchen müsste (#160).

   `.button-link` und die Varianten `.primary`/`.secondary`/`.quiet`/`.danger`
   stehen seit 2026-09-11 nicht mehr hier, sondern klassengebunden in
   ui-surfaces.css (Abschnitt „Bedienelemente"): Die Viewer-Dokumente laden
   app.css nicht und brauchen den Knopf trotzdem. Hier bleibt der nackte
   `button` als Sekundärform der Panel-Schale — und nur er. Stünde
   `.button-link` hier noch einmal, schlüge es bei gleicher Spezifität das
   spätere `.primary` von dort, weil app.css nach ui-surfaces.css lädt. */
input, select, textarea, button {
    min-height: var(--control-h);
    padding: var(--space-1) var(--space-3);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    background: var(--surface-recessed);
    color: var(--text);
}
/* Doppelung: `textarea` steht oben in der Gruppe mit dem Kasten aller Felder;
   hier nimmt es allein das senkrechte Polster zurück auf 8.
   4 px senkrecht, nicht 8 (Welle 3, 2026-09-13): 14 px Schrift mal 1,45 sind
   20 px, mit 8 px Polster und Rand 38 — zwei über `--control-h` und über dem
   Knopf daneben, dessen Regel unten das Polster auf 0 setzt. Mit 4 px trägt
   die Mindesthöhe das Feld (36), und der Browser zentriert den Text darin.
   Das Textfeld bekommt seine Höhe nicht aus einer Zeile; dort bleiben 8 px,
   damit die erste Zeile nicht an der Kante klebt. Dieselbe Rechnung an
   `.form-field > input` in ui-surfaces.css. */
textarea { padding-block: var(--space-2); }
/* Doppelung: Oben (Grundstil) steht, wie diese Elemente SCHRIFT erben und
   sich beim Antippen verhalten — eine Aussage ueber alle Formularelemente
   ueberhaupt. Hier steht ihre Gestalt. Zusammengelegt ergaebe das eine
   Regel, die zwei Fragen beantwortet, und der Grundstil muss vor allem
   Uebrigen stehen, die Gestalt nach den Flaechen. */
input, select { min-width: 200px; }
input::placeholder, textarea::placeholder { color: var(--text-subtle); }
input:hover, select:hover, textarea:hover { border-color: var(--border-strong); }
/* Der nackte Knopf ist die Sekundärform (2026-09-10). 192 von 454 <button>
   trugen keine Klasse und wurden alle zur Markenfläche — eine Seite mit fünf
   blauen Knöpfen hat kein Primärziel. Primär ist jetzt, wer `.primary` sagt,
   und eine Ansicht sagt es genau einmal (docs/ENTSCHEIDUNGEN.md); die
   Varianten und `.button-link` formt ui-surfaces.css. */
/* Doppelung: derselbe Grund wie bei `input, select` darueber — oben die
   geerbte Schrift, hier die Gestalt des Knopfes. */
button { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); padding: 0 var(--space-3); border-color: var(--border-strong); background: var(--surface-2); color: var(--text); font-size: var(--font-md); font-weight: var(--weight-medium); text-decoration: none; cursor: pointer; }
button:hover { border-color: var(--border-strong); background: var(--surface-hover); color: var(--text); }

/* `.table-wrap` selbst (Rand, Radius, Fläche, Scrollen) steht seit 2026-09-11
   in ui-surfaces.css, damit die Viewer-Dokumente dieselbe Hülle tragen. */
/* Aktionen in einer Tabellenzelle — Verlängern samt Feldern, Widerrufen.
   Ohne dieses Muster ist jedes <form> in der Zelle ein Block: Die Spalte
   kollabiert im Auto-Layout auf fast nichts, während die Eingaben auf ihrer
   200px-Mindestbreite bestehen und über die Tabellenkante ragen (Fund #171,
   Anlieferungszeile). `nowrap` gibt der Spalte eine ehrliche Mindestbreite;
   wird es eng, scrollt die .table-wrap, statt dass Bedienelemente abreißen. */
.table-actions { white-space: nowrap; text-align: right; }
.table-actions form { display: inline-flex; align-items: center; gap: var(--space-2); vertical-align: middle; }
/* Nicht schrumpfen: Schrumpfbare Flex-Kinder melden der Tabelle fast keine
   Mindestbreite — die Spalte kollabiert, und die Felder ragen trotzdem über
   die Kante. Erst mit fester Grundgröße ist die Zellbreite eine Zusage. */
.table-actions form > * { flex: 0 0 auto; }
/* Auch neben einem Knopf ohne eigenes Formular: Nicht jede Aktion schickt
   etwas ab — „Bearbeiten" öffnet nur einen Dialog und stünde sonst am
   Nachbarformular an. */
.table-actions form + form, .table-actions button + form, .table-actions form + button { margin-left: var(--space-2); }

/* `.row-action` — der Symbolknopf der Papierkorbzeile (#224) — ist entfallen
   (#397): Seit #354 ist er `<x-button variant="icon">` (`.icon-action`,
   interaction-feedback.css), und die rote Tönung der löschenden Aktion trägt
   das Bauteil selbst. */

.table-actions input { min-width: 0; }
.table-actions input[type="datetime-local"] { width: 12.5em; }
.table-actions input[type="number"] { width: 5.5em; }
table { width: 100%; min-width: 760px; border-collapse: collapse; }
/* Doppelung: `th` steht bewusst zweimal — erst das Zellenmaß, das Kopf und
   Rumpf teilen, dann das, was allein den Kopf zum Kopf macht. Beides in eine
   Regel zu falten hieße, die fünf Maße für `td` abzuschreiben (#160). */
th, td { height: 42px; padding: var(--space-2) var(--space-4); border-bottom: 1px solid var(--border); text-align: left; vertical-align: middle; }
th { background: var(--surface-recessed); color: var(--text-subtle); font-size: var(--font-sm); font-weight: var(--weight-medium); letter-spacing: 0; text-transform: none; }
tbody tr:last-child td { border-bottom: 0; }
td small { display: block; margin-top: 2px; }
.status { display: inline-flex; align-items: center; gap: var(--space-2); white-space: nowrap; }
.status::before { width: 7px; height: 7px; flex: 0 0 auto; border-radius: var(--radius-round); background: var(--info); content: ""; }
/* Feste Farbzuordnung des Zustandspunktes, damit dieselbe Aussage auf jeder
   Seite dieselbe Farbe hat (#224). Es sind fuenf, und es sind nur diese fuenf:
   `.status.active` und `.status.archived` waren die zweite Fassung von „ok"
   und „neutral" und standen in fuenfzehn Ansichten neben ihnen — mit dem
   Nebeneffekt, dass die Beschriftung dort kleingeschrieben blieb, weil sie
   nicht durch `<x-status-dot>` lief. Wer einen Zustand zeigt, nimmt das
   Bauteil; es kennt die gewachsenen Tonnamen und bildet sie hierauf ab. */
.status.is-neutral::before { background: var(--text-subtle); }
.status.is-info::before { background: var(--info); }
.status.is-ok::before { background: var(--success); }
.status.is-warn::before { background: var(--warning); }
.status.is-danger::before { background: var(--danger-color); }
/* Doppelung: Der Ladezustand des Dateiimports ist der einzige bewegte Punkt im
   Projekt — die Farbe steht schon oben, hier kommt die Bewegung dazu, und
   `integrations.blade.php` tauscht die Klasse beim Aktualisieren aus. */
.status.status-downloading::before { background: var(--info); animation: status-download-pulse 1.35s ease-in-out infinite; }
.status.status-completed::before { background: var(--success); }
.status.status-paused::before { background: var(--warning); }
.status.status-failed::before { background: var(--danger-color); }
.status.status-cancelled::before { background: var(--text-subtle); }
@keyframes status-download-pulse { 0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--brand) 15%, transparent); filter: brightness(.78); } 50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--brand) 6%, transparent); filter: brightness(1.45); } }
@media (prefers-reduced-motion: reduce) { .status.status-downloading::before { animation: none; } }

/* Der Dialog — `dialog`, `::backdrop`, das Raster `.modal`, Kopfzeile,
   Fußzeile, Kreuz — steht seit dem 2026-09-13 klassen- und elementgebunden in
   ui-surfaces.css (Abschnitt „Dialog"), wie Knopf und Feld: Die Viewer-Dokumente
   und die Außenflächen laden app.css nicht und sollen `<x-dialog>` trotzdem
   rufen können (#360). Hier bleibt nur, was die Anmeldekarte mit dem Dialog
   teilt: die Beschriftung über dem Feld. */
.login label { display: grid; gap: var(--space-2); }

/* Die tastbare Mindesthöhe hängt an der Zeigerart, nicht allein an der Breite
   (#234). Ein iPad hochkant ist 768 CSS-Pixel und fiel damit durch jede
   Breitenschranke: ein Berührungsgerät ohne 44px-Flächen. `pointer: coarse`
   fragt, worauf es ankommt — womit gezielt wird —, und die Breitenschranke
   bleibt daneben stehen für das schmale Fenster auf dem Schreibtisch, wo eine
   Bedienfläche aus demselben Grund nicht winzig sein soll.
   Die vier Knopfklassen und das Feld von `<x-form-field>` stehen mit drin
   (2026-10-02, #487): Ihre Grundregeln in ui-surfaces.css (0-1-0, 0-1-1)
   schlugen sonst `button` und `input` hier, während `.button-link` und das
   Passwortfeld (kein direktes Kind von `.form-field`) die 44 bekamen — ein
   Knopf stand mit 36 neben einem Link mit 44, der Benutzername mit 36 über
   dem Passwort mit 44. Gleiche Spezifität, dieses Blatt lädt später. Klein
   bleiben das Ankreuzfeld (`.form-field.is-check > input`, 0-2-1) und der
   Symbolknopf (`.icon-action`, auch `.danger`): interaction-feedback.css
   lädt nach diesem Blatt. */
@media (pointer: coarse), (max-width: 760px) {
    nav a, nav summary, button, .button-link, .primary, .secondary, .quiet, .danger, input, select, textarea, .form-field > input, .form-field > select, .form-field > textarea { min-height: var(--control-h-touch); }
    .mobile-nav-toggle { width: var(--control-h-touch); min-width: var(--control-h-touch); min-height: var(--control-h-touch); }
}
/* Die Dialoge der Dateioperationen (.operation-dialog) wohnen in
   interaction-feedback.css: sie erscheinen auch auf Oberflächen ohne diese
   Datei — etwa den Viewer-Dateien —, und dort sahen sie bis #102 nach
   Browser-Voreinstellung aus. */

.login-body { display: grid; min-height: 100vh; place-items: center; padding: var(--space-5); }
/* Rahmen aus `.ui-card` (#160). Eigen bleibt, was eine Anmeldekarte von einer
   Inhaltskarte unterscheidet: Sie steht allein auf leerer Fläche, trägt deshalb
   Schatten und Radius des Schwebenden und eine feste Breite, und sie polstert
   selbst — ein `.ui-card-body` gäbe es hier nicht, das Formular IST der Körper. */
.login { display: grid; gap: var(--space-4); width: min(390px, 100%); padding: var(--space-6); border-radius: var(--radius-lg); box-shadow: var(--shadow-3); }
.login h1 { margin: 0; }
/* Nur der Satz direkt in der Karte. Hinweis und Fehler unter einem Feld
   (`.form-field`) tragen ihre Farbe selbst — als Nachfahrenregel färbte
   `.login p` den Fehler grau, weil sie `.field-problem` überstach. */
.login > p { margin: 0; color: var(--text-muted); }
/* Eine Adresse im Satz („Mit deinem Passkey geht es unter …") bricht, wo sie
   muss: Punkte sind für den Browser keine Umbruchstelle, und den Namen wählt
   der Betreiber (`MOUNTING_TLS_HOSTNAME`). */
.login > p a { overflow-wrap: anywhere; }
/* Der zweite Schritt (#491, auth/two-factor.blade.php): Dort ist die Karte
   kein Formular, sondern trägt drei — Bestätigungscode, hinter einer Klappe
   der Wiederherstellungscode, und „Abbrechen". Die ersten beiden bekommen
   das Raster, das auf /login die Karte selbst hat. Die Klappe ist ein
   `<details>` ohne Skript; ihr Auslöser ist ein leiser Satz, kein Knopf —
   der eine Primärknopf der Karte ist „Bestätigen". */
.login-step { display: grid; gap: var(--space-4); }
.login-alternative > summary { color: var(--text-muted); cursor: pointer; }
.login-alternative[open] > summary { margin-bottom: var(--space-4); }
/* Auf dem Berührungsgerät ist die Klappe der einzige Griff zum
   Wiederherstellungscode und bekommt deshalb die 44px der Berührregel oben —
   als Satz polstert sie sich selbst, wie der Rechtsfuß. Offen rückt das Feld
   auf den Abstand der Karte heran. Hinter der Grundregel, weil diese sonst
   den Abstand wieder setzte. */
@media (pointer: coarse), (max-width: 760px) {
    .login-alternative > summary { padding-block: var(--space-3); }
    .login-alternative[open] > summary { margin-bottom: var(--space-1); }
}
/* „Abbrechen" ist leise: kein Rand, keine Fläche, nur das Wort — und das
   steht bündig mit Feld und Klappe, die Hover-Fläche ragt ins Polster der
   Karte. */
.login-cancel > .quiet { margin-inline-start: calc(-1 * var(--space-3)); }
/* Der Passkey als zweiter Weg (#493): auf /login unter „Anmelden", im
   zweiten Schritt neben dem Code. „oder" ist ein leiser Satz zwischen zwei
   Linien, kein Knopf; die Meldung des Skripts (`data-passkey-status`) steht
   am Knopf, wo man hinsieht, wenn er nichts tut. */
.login-passkey { display: grid; gap: var(--space-3); }
.login-or { display: flex; align-items: center; gap: var(--space-3); margin: 0; color: var(--text-muted); font-size: var(--font-sm); }
.login-or::before, .login-or::after { content: ""; flex: 1; border-top: 1px solid var(--border); }
.login-passkey-status { margin: 0; color: var(--danger-color); font-size: var(--font-sm); }
/* „Desktop-App verbinden" (#495, desktop/connect.blade.php): dieselbe Karte,
   dazu ein Titel unter der Marke und der Abgleichcode als eigener Kasten —
   groß und in Festbreite, weil er Zeichen für Zeichen mit der App verglichen
   wird, und mit der Frage darunter, die der Kasten stellt. Der eine
   Primärknopf der Karte bleibt „Verbinden". */
.desktop-connect-title { margin: 0; font-size: var(--font-lg); }
.desktop-connect-match { display: grid; gap: var(--space-2); justify-items: center; padding: var(--space-4); border: 1px solid var(--border); border-radius: var(--radius-md); text-align: center; }
.desktop-connect-match > p { margin: 0; color: var(--text-muted); }
.desktop-connect-match > .desktop-connect-code { color: var(--text); font-family: var(--font-mono); font-size: var(--font-2xl); font-weight: var(--weight-semibold); }
/* Die Karte als Außenfläche (2026-09-13, #357): Passwortkarten der Freigabe
   und der Anlieferung, „nimmt nichts mehr an", Einladung, Fehlerseiten. Wer
   sie sieht, hat kein Konto — Satz, Feld und Knopf stehen in `--font-outer`,
   Beschriftung und Hinweis bei `--font-base` als Boden
   (docs/Oberflaeche/Typografie-und-Form.md). Anmeldung und Einrichtung
   tragen die Klasse nicht: Wer dort steht, richtet die Anlage ein oder
   arbeitet mit ihr, und `/setup` steht auf keiner Gateway-Liste. */
.login-body.is-outer { font-size: var(--font-outer); line-height: var(--leading-base); }
.login-body.is-outer :is(.form-field > input, .primary, .button-link, .ui-callout, .error-page .code) { font-size: var(--font-outer); }
.login-body.is-outer :is(.form-field > label, .form-field-hint, .field-problem) { font-size: var(--font-base); }
.error-page { justify-items: center; text-align: center; }
.error-page .code { font-weight: var(--weight-semibold); color: var(--text); }
/* Der Rechtsfuß ist kein Teil der zentrierten Meldung: volle Breite, linksbündig,
   wie auf den anderen Karten (2026-09-11). */
.error-page > .ui-legal-foot { justify-self: stretch; text-align: left; }

.setup-heading p { margin: 0; color: var(--text-muted); }
.setup-form { display: grid; grid-template-columns: minmax(0, 1fr) 290px; gap: var(--space-4); }
/* Rahmen, Radius und Fläche kommen aus `.ui-card` (#160) — die drei Namen
   standen Wort für Wort auf denselben Werten. Übrig bleibt hier nichts; die
   Polsterung von `.setup-main` steht wie zuvor in der Zeile darunter. */
.setup-main { display: grid; gap: var(--space-4); padding: var(--space-5); }
.setup-main > label { display: grid; gap: var(--space-2); }
.setup-heading { display: flex; justify-content: space-between; align-items: center; gap: var(--space-3); }
/* `.panel-title` ist entfallen (#397): Der letzte Aufrufer, der Kopf der
   Einladungsliste in admin/access, ging am 2026-09-01 mit dem Rechte-Umbau;
   die Kopfzeile eines Kastens ist seither der Kopf der `<x-card>`. */
.setup-heading h3, .setup-aside h3 { margin: 0; }
.mount-editor { display: grid; gap: var(--space-2); }
/* Eine Zeile, zwei Ebenen: „was es ist" (Name, Ordnername) über „wo es liegt"
   (Speicherbereich, Buchstabe, Viewer). Vorher standen alle fünf Felder in
   einer Reihe gleichrangig nebeneinander — der Laufwerksbuchstabe, den fast
   niemand setzt, so laut wie der Name, den jeder setzt. */
.mount-edit { display: grid; grid-template-columns: 34px minmax(0, 1fr) auto; gap: var(--space-3); align-items: start; padding: var(--space-4); border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-recessed); }
.mount-identity, .mount-placement { grid-column: 2; display: grid; gap: var(--space-2); align-items: end; }
/* Doppelung: Die Zeile darueber sagt, was beide Reihen gemeinsam haben
   (Spalte, Raster, Abstand); hier steht, worin sie sich unterscheiden —
   die Spaltenaufteilung. Zusammengelegt muesste jede Reihe das Gemeinsame
   wiederholen. */
.mount-identity { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); }
/* Doppelung: dieselbe Aufteilung wie eine Zeile hoeher — gemeinsame
   Grundregel oben, eigene Spaltenaufteilung hier. */
.mount-placement { grid-template-columns: minmax(0, 1fr) 132px auto; }
.mount-edit label { display: grid; gap: var(--space-2); }
.mount-edit .remove-mount { grid-area: 1 / 3; }
.mount-order { grid-area: 1 / 1 / span 2; align-self: center; color: var(--info); font-size: var(--font-lg); }
.mount-edit input, .mount-edit select { width: 100%; min-width: 0; }
.collab-switch { display: flex !important; align-items: center; white-space: nowrap; }
.collab-switch input, .check input { min-width: auto; }
/* Projektvorlagen (#134). Der Zeileneditor steht im Dialog untereinander statt
   in der fünfspaltigen Aufteilung der Projektanlage — dort ist keine Breite
   dafür, und die Ordnerliste braucht ohnehin eine eigene Zeile. */
.template-mounts { display: grid; gap: var(--space-4); }
/* Je Arbeitsbereich eine Gruppe mit Legende und Haarlinie — kein Kasten mit
   Fläche im Dialog (Welle 4, `.template-mount-edit` ist gefallen). Die Legende
   steht per `float` in der Fläche der Gruppe, wie in `.licensing-curation`. */
.template-mounts > fieldset + fieldset { padding-top: var(--space-4); border-top: 1px solid var(--border); }
.template-mount legend { float: left; width: 100%; margin: 0 0 var(--space-1); }
.template-mount-pair { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); }
/* Das Feld ohne Hinweis darf nicht auf die Höhe des Nachbarn mit Hinweis
   wachsen: Rasterkinder strecken sich, und das Feld darin mit. */
.template-mount-pair > .form-field { align-content: start; }
@media (max-width: 600px) { .template-mount-pair { grid-template-columns: 1fr; } }
.template-mount-actions { display: flex; justify-content: flex-end; }
.template-folders { margin: 0; padding-left: var(--space-5); color: var(--text-muted); font-size: var(--font-sm); }
.template-folders li { margin-bottom: 2px; }
/* Die Vorlagenwahl im Dialog (#134) — untereinander wie die übrigen Felder. */
/* Die Felder bringen ihre eigene Gestalt mit (`.form-field`, ui-surfaces.css);
   hier bleibt nur der Abstand zwischen ihnen. Die zweite Zeile — `label` als
   Raster — war die Form der umschließenden Beschriftung und hat seit #397
   keinen Aufrufer mehr. */
.template-picker { display: grid; gap: var(--space-2); }
.setup-aside { display: grid; gap: var(--space-3); align-self: start; padding: var(--space-5); }
.hint { color: var(--text-muted); line-height: var(--leading-base); }
/* `.mount-panel`, `.mount-row`, `.mount-icon` — die alte Seitenleiste der
   Arbeitsbereiche auf der Projektseite — sind entfallen (#397): Seit dem
   2026-08-31 steht dort die Reiterleiste (`.mount-tab`, project-ui.css), und
   `ProjectMountContextActionsTest` hält fest, dass die Leiste nicht
   zurückkommt. */
.check { display: flex !important; align-items: center; gap: var(--space-2); }

.password-input { position: relative; display: block; }
.password-input input { width: 100%; padding-right: var(--space-8); }
/* Doppelung: `.password-toggle:hover` steht bewusst zweimal. Oben MUSS es
   stehen: Der Umschalter ist ein <button>, und `button:hover` weiter oben färbt
   Rahmen und Fläche in Markenblau — nur wenn der Zustand in der Grundregel
   mitgenannt ist, bleibt das Auge im Feld unsichtbar. Übrig bleibt darunter
   genau die eine Zeile, die den Zustand vom Ruhezustand unterscheidet (#160).
   Quadratisch mit dem 20-px-Auge in der Mitte, ohne Polster (Welle 3): 7 px
   rundum machten die Höhe zu 34, 8 px aus der Skala zu 36 — und 36 deckt
   im 36-px-Feld beide Ränder. Der Kasten liest deshalb die Feldhöhe abzüglich
   der beiden 1-px-Ränder (#397, Gruppe K), statt die 34 selbst zu sagen. */
.password-toggle, .password-toggle:hover, .password-toggle:active { position: absolute; top: 50%; right: 3px; width: calc(var(--control-h) - 2px); min-height: calc(var(--control-h) - 2px); padding: 0; border: 0; background: transparent; box-shadow: none; color: var(--text-muted); transform: translateY(-50%); }
.password-toggle:hover { color: var(--text); }
.password-toggle svg { display: block; width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.password-toggle .eye-closed { display: none; }
.password-toggle[aria-pressed="true"] .eye-open { display: none; }
.password-toggle[aria-pressed="true"] .eye-closed { display: block; }
.login input.live-valid, .login input.live-valid:focus { border-color: var(--success); box-shadow: 0 0 0 1px color-mix(in srgb, var(--success) 30%, transparent); }
.login input.live-invalid, .login input.live-invalid:focus { border-color: var(--danger-color); box-shadow: 0 0 0 1px color-mix(in srgb, var(--danger-color) 30%, transparent); }
.field-shake { animation: field-shake 150ms linear 1; }
@keyframes field-shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-4px); } 50% { transform: translateX(4px); } 75% { transform: translateX(-2px); } }

@media (max-width: 900px) {
    .setup-form { grid-template-columns: 1fr; }
    .setup-aside { order: -1; }
    .mount-identity, .mount-placement { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 760px) {
    main { padding: var(--space-5) var(--space-4) var(--space-7); }
    main > header { margin: calc(-1 * var(--space-5)) calc(-1 * var(--space-4)) var(--space-5); padding: var(--space-3) var(--space-4); }
    .storage { grid-template-columns: 1fr; }
    .mount-edit { grid-template-columns: 28px minmax(0, 1fr) auto; }
    /* Ruecknahme, keine Loeschung: Die 760px oben sind oberhalb dieser Breite
       die ehrliche Zusage, dass eine vielspaltige Liste nicht in sich
       zusammenfaellt. Am Telefon sind sie der Grund, warum ein Viertel der
       Tabelle hinter einem Seitwaertsschub liegt. Der Boden geht nicht
       verloren: Die nowrap-Zellen (`.status`, `.table-actions`) geben das
       Min-Content-Mass vor, ab dem `.table-wrap` weiterhin scrollt. */
    .table-wrap table { min-width: 0; }
}
