/* =========================================================
   VANTEX Dashboard-Theme

   Farben, Abstände, Schriftgrößen, Radien und Bewegung stehen in
   `static/tokens.css` und werden von `base.html` davor eingebunden –
   dieselbe Datei nutzt die Verkaufsseite. Hier steht nur, was das
   Dashboard allein hat: Ebenen, Zustandsflächen, Topbar-Höhe.
   ========================================================= */

:root {
    /* ---- Ebenen ---------------------------------------------------------
       Jede Fläche war `--surface` mit 1 px `--surface-2` – eine Karte in
       einer Karte war von ihrer Umgebung nicht zu unterscheiden, und die
       Tiefe kam aus 19 einzeln hingeschriebenen Schatten. Drei Flächen,
       drei Schatten: `--shadow-1` für Erhebungen im Textfluss, `--shadow-2`
       für alles, was über dem Inhalt schwebt (Menü, Panel), `--shadow-3`
       für das, was den Rest verdeckt (Befehlspalette, Dialog).

       Sie bleiben hier und nicht in `tokens.css`: Tiefe braucht eine Seite,
       die Karten stapelt – die Verkaufsseite ist eine durchgehende Fläche. */
    --surface-sunken: #14141C;   /* tiefer: Eingabefelder, Zellen, Log */
    --surface-raised: #22222E;   /* höher: Karte in Karte, Zeilen, Chips */
    --shadow-1: 0 2px 8px rgba(0, 0, 0, .28);
    --shadow-2: 0 18px 40px rgba(0, 0, 0, .45);
    --shadow-3: 0 30px 70px rgba(0, 0, 0, .6);
    /* Der Ring beim Tastaturfokus – dieselbe Stärke überall, sonst wandert
       das Auge beim Durchtabben. */
    --focus-ring: 0 0 0 3px rgba(123, 92, 255, .2);
    --danger-ring: 0 0 0 3px rgba(239, 68, 68, .2);
    /* Höhe der Topbar. Alles, was beim Scrollen stehen bleibt (Tabellenkopf,
       Server-Navigation), setzt darauf auf, sonst verschwände es dahinter.
       `dashboard.js` misst den echten Wert und schreibt ihn an `<html>`;
       dieser Vorgabewert gilt ohne JavaScript und ist bewusst großzügig. */
    --topbar-h: 76px;
    /* Einziges Icon, das nicht als Element im Markup steht: es sitzt vor
       Dateinamen, die auch von JavaScript als reiner Text gesetzt werden
       (siehe `.ticket-att-link::before` weiter unten). */
    --icon-paperclip: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18.4 11.6 12 18a4 4 0 0 1-5.7-5.7l7.4-7.4a2.7 2.7 0 0 1 3.8 3.8l-7.4 7.4a1.35 1.35 0 0 1-1.9-1.9l6.6-6.6'/%3E%3C/svg%3E");
}

* { box-sizing: border-box; }

body {
    margin: 0;
    min-height: 100vh;
    font-family: "Raleway", "Segoe UI", system-ui, sans-serif;
    background: var(--bg);
    color: var(--text);
    line-height: 1.5;
}

/* Hintergrund
   Das Foto (`dashboard-bg.jpg`, 125 KB) lag als `cover fixed` unter *jeder*
   Seite – also auch unter jeder Datentabelle und jedem zwölfteiligen
   Formular. Ein Foto hinter einer Zahlenkolonne macht die Zahlen nicht
   schöner, nur unruhiger, und `background-attachment: fixed` lässt mobile
   Browser bei jedem Scrollschritt neu zeichnen.

   Der Regelfall ist deshalb ein ruhiger Schimmer in den Markenfarben. Er
   sitzt auf einer eigenen, fest stehenden Ebene statt am `body`: so wird er
   einmal gezeichnet und beim Scrollen nicht neu berechnet. Das Foto bleibt
   dort, wo eine Seite Fläche und keine Daten zeigt – Anmeldung und
   Serverauswahl (`<body class="mit-bild">`). */
body::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background:
        radial-gradient(1100px 620px at 12% -8%, var(--primary-bg-strong), transparent 62%),
        radial-gradient(900px 520px at 92% 2%, var(--info-bg), transparent 60%);
}
body.mit-bild::before {
    /* Dunkles Overlay über dem Foto, damit Karten und Text lesbar bleiben. */
    background:
        linear-gradient(rgba(13, 13, 20, .58), rgba(13, 13, 20, .8)),
        url("dashboard-bg.jpg") center top / cover no-repeat;
}

a { color: inherit; text-decoration: none; }

/* Icons (siehe templates/_icons.html)
   Größe und Farbe kommen vom umgebenden Text – ein Icon in einer Überschrift
   wächst also mit, eines in einem roten Knopf wird rot. `flex: none` hält es
   davon ab, in Flex-Zeilen zusammengequetscht zu werden. */
.icon {
    width: 1.05em;
    height: 1.05em;
    flex: none;
    vertical-align: -.16em;
}
/* `.icon-btn` stand hier, um ein Icon ohne Beschriftung mittig zu setzen.
   Seit `.btn` selbst `inline-flex` ist, tut das die Basis – die Klasse hatte
   nur noch eine Aufgabe: die Grundlinienverschiebung des Icons zurückzunehmen,
   die in einer Flex-Zeile ohnehin nicht greift. */


/* Hinweiszeile mit Warnzeichen: die Farbe schlägt das graue `.subtle` */
.note-warn {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    color: var(--warning);
}

/* Topbar */
.topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--sp-4) var(--sp-5);
    background: rgba(26, 26, 36, .85);
    backdrop-filter: blur(12px);
    border-bottom: 1px solid var(--surface-2);
    position: sticky;
    top: 0;
    z-index: 50;
}
.brand {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-3);
    font-weight: 800;
    font-size: var(--fs-md);
    letter-spacing: .28em;
}
.brand svg { width: 26px; height: 26px; }
.user-box { display: flex; align-items: center; gap: var(--sp-4); }
.user-name { color: var(--muted); font-size: var(--fs-sm); font-weight: 600; }

/* -------------------------------------------------------------
   Inhaltsbreite – zwei, nach dem, was auf der Seite steht

   Es gab eine: 860 px, ab 1024 px wachsend. Für eine Liste mit sechs
   Spalten zu wenig, für ein Formular mit einer Spalte zu viel – eine
   Seite wie „Mein Konto" war auf einem breiten Bildschirm eine 1280 px
   breite Karte mit einer 460-px-Spalte darin und einer leeren rechten
   Hälfte.

   Die Vorgabe ist die **breite** Spalte (Listen, Tabellen, die
   Konfiguration); Seiten, die nur ein Formular oder einen kurzen Text
   zeigen, sagen im Template `{% block breite %}container-schmal{% endblock %}`.
   Eine Seite mit Tabelle darf das nicht – festgehalten in
   `tests/test_breiten.py`.

   Der Sprung liegt jetzt auf der Stufe M (900 px) statt auf 1024: dort
   wird die Server-Navigation zur 210-px-Seitenspalte. Zwischen 900 und
   1024 nahm sie dem Inhalt bisher ein Viertel weg, ohne dass die Spalte
   dafür breiter wurde. `clamp` hält die Untergrenze: die Rechnung darf
   die Spalte nie schmaler machen als die 860 px darunter. */
.container { max-width: 860px; margin: 0 auto; padding: var(--sp-6) var(--sp-5) var(--sp-8); }
@media (min-width: 900px) {
    .container { max-width: clamp(860px, calc(100vw - 160px), 1280px); }
}
/* Ein Formular hat eine Spalte, egal wie breit das Fenster ist. Die Karte
   hört deshalb bei 620 px auf – und das Formular darin nimmt dann die
   volle Breite, statt sich ein zweites Mal auf 460 zu begrenzen.
   Die Regel steht **nach** der Media Query: gleiche Spezifität, spätere
   Zeile, also gilt sie auch auf breiten Fenstern. */
.container-schmal { max-width: 620px; }
.container-schmal .stack-form { max-width: none; }

/* Die drei Stufen stehen jetzt wieder auseinander (1.75 / 1.375 / 1.125 rem).
   Vorher lagen sie bei 1.6 / 1.15 / 1.0 – ein h3 war von Fließtext nur an der
   Schriftstärke zu erkennen. Große Zeilen brauchen weniger Durchschuss als
   `body` mit 1.5: sonst fällt eine zweizeilige Überschrift auseinander. */
h1 { font-size: var(--fs-2xl); line-height: 1.2; margin: 0 0 var(--sp-2); font-weight: 800; }
h2 { font-size: var(--fs-xl); line-height: 1.25; margin: 0 0 var(--sp-4); font-weight: 800; }
h3 { font-size: var(--fs-lg); line-height: 1.3; margin: var(--sp-5) 0 var(--sp-3); font-weight: 700; }
.subtle { color: var(--muted); font-size: var(--fs-sm); }

/* -------------------------------------------------------------
   Knöpfe

   Hier standen zwei Systeme nebeneinander. `.btn-small` war keine Größe
   von `.btn`, sondern eine eigenständige Regel weiter unten – 30-mal
   ohne `.btn` benutzt, `.btn` 25-mal ohne sie. Wo beide zusammentrafen,
   gewann schlicht die spätere Regel:
   - `btn btn-ghost btn-small` (8×) bekam von `.btn-small` wieder
     `border: none` und eine Füllung – der Umriss-Knopf sah aus wie ein
     gefüllter, „Suche aufheben" wie „Suchen".
   - `btn btn-primary btn-small` (3×, darunter „Ausführen" in der
     Mod-Queue) verlor den Verlauf an `.btn-small`s Füllung. Die
     wichtigste Aktion der Seite sah aus wie eine Nebensache.
   - `class="btn"` allein (Antwort im Ticket) hatte gar keine Fläche:
     weiße Schrift auf der grauen Vorgabefläche des Browsers.

   Jetzt eine Basis und drei Achsen, die sich nicht ins Gehege kommen –
   Fläche, Größe, Form. Eine Fläche gehört immer dazu:
     Fläche  .btn-primary | .btn-neutral | .btn-ghost | .btn-danger
     Größe   (Vorgabe)    | .btn-sm      | .btn-lg
     Form    .btn-round   (der 22-px-Kreis in Listen)
   Festgenagelt in `tests/test_knoepfe.py`.
*/
.btn {
    /* `inline-flex` statt `inline-block`: der Abstand zwischen Icon und
       Beschriftung kommt damit aus `gap` und muss nicht als Rand am Icon
       hängen (das traf sonst auch Icons ohne Beschriftung). */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
    /* Waagerecht zwei Stufen mehr als senkrecht – ein Knopf mit gleichem
       Abstand ringsum wirkt gedrungen. */
    padding: var(--sp-3) var(--sp-5);
    border-radius: var(--radius-md);
    /* Durchsichtige Kante statt gar keiner: sonst wäre der Umriss-Knopf
       2 px höher als der gefüllte daneben. */
    border: 1px solid transparent;
    font: inherit;
    font-size: var(--fs-sm);
    font-weight: 700;
    line-height: 1.2;
    white-space: nowrap;
    cursor: pointer;
    color: #fff;
    transition: transform var(--motion-fast) var(--ease),
                box-shadow var(--motion-fast) var(--ease),
                background var(--motion-fast) var(--ease),
                border-color var(--motion-fast) var(--ease),
                color var(--motion-fast) var(--ease);
}

/* ---- Größe */
.btn-sm { padding: var(--sp-1) var(--sp-3); font-size: var(--fs-xs); border-radius: var(--radius-sm); }
.btn-lg { padding: var(--sp-4) var(--sp-6); font-size: var(--fs-md); }

/* ---- Fläche */
.btn-primary {
    background: var(--grad-text);
    box-shadow: 0 4px 18px var(--primary-glow);
}
.btn-primary:hover { transform: translateY(-1px); box-shadow: 0 8px 26px var(--primary-glow-strong); }
/* Der Regelfall in Listen und Tabellen: sichtbar, aber ohne Anspruch auf
   die Hauptrolle. Das war die Fläche, die `.btn-small` mitbrachte. */
.btn-neutral { background: var(--tint-3); }
.btn-neutral:hover { background: var(--tint-4); }
.btn-ghost {
    background: transparent;
    border-color: var(--surface-2);
    color: var(--text);
}
.btn-ghost:hover { border-color: var(--primary); color: var(--link); }
.btn-danger { background: var(--danger-fill); }
.btn-danger:hover { background: var(--danger-fill-hover); }

/* ---- Form: der runde Kleinstknopf am Ende einer Zeile (Wort entfernen,
   Verwarnung löschen). Kein Platz für Beschriftung, deshalb braucht er
   ein `aria-label` – geprüft in `tests/test_barrierefreiheit.py`. */
.btn-round {
    width: 22px;
    height: 22px;
    padding: 0;
    border-radius: 50%;
    font-size: var(--fs-2xs);
}
.btn-round .icon { width: 12px; height: 12px; }

/* Ein Knopf, der nicht kann, hebt sich auch nicht an. */
.btn:disabled, .btn[disabled] { opacity: .45; cursor: not-allowed; }
.btn:disabled:hover, .btn[disabled]:hover { transform: none; }

/* Rückmeldungen nach einer Aktion
   Die Kategorie (success / error / warning / info) steuert Farbe und Zeichen.
   `.flash` ohne Kategorie bleibt grün – so sehen die fest im Template
   stehenden Hinweisbanner aus, die keine Aktionsmeldung sind. */
.flash {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-3);
    background: var(--success-bg);
    border: 1px solid var(--success);
    color: var(--success);
    font-weight: 600;
    padding: var(--sp-3) var(--sp-4);
    border-radius: var(--radius-sm);
    margin-bottom: var(--sp-3);
}
.flash-text { flex: 1; min-width: 0; word-break: break-word; }
.flash-icon {
    flex: none;
    width: 20px; height: 20px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: var(--fs-xs);
    font-weight: 800;
    /* Gefüllter Kreis in der Farbe der Kategorie … */
    background: currentColor;
}
/* … das Zeichen darin in der Hintergrundfarbe ausgestanzt. Die Farbe muss
   am ::before hängen: auf dem Kreis selbst würde sie `currentColor` und
   damit auch dessen Füllung überschreiben. */
.flash-icon::before { content: "i"; color: var(--bg); }

.flash-success { background: var(--success-bg); border-color: var(--success); color: var(--success); }
.flash-success .flash-icon::before { content: "✓"; }
.flash-error   { background: var(--danger-bg); border-color: var(--danger);  color: var(--danger-text); }
.flash-error .flash-icon::before { content: "✕"; }
.flash-warning { background: var(--warning-bg); border-color: var(--warning); color: var(--warning); }
.flash-warning .flash-icon::before { content: "!"; }
.flash-info    { background: var(--info-bg); border-color: var(--secondary); color: var(--secondary); }

.flash-close {
    flex: none;
    border: none;
    background: none;
    color: inherit;
    opacity: .55;
    font-size: 1.25rem;
    line-height: 1;
    padding: 0 var(--sp-0);
    cursor: pointer;
}
.flash-close:hover { opacity: 1; }
/* Ohne JavaScript hat der Knopf keine Wirkung – dann gar nicht erst zeigen */
html:not(.js) .flash-close { display: none; }
/* Erfolgsmeldungen blenden sich nach ein paar Sekunden selbst aus */
.flash.is-fading { opacity: 0; transition: opacity var(--motion-slow) var(--ease); }

/* Login */
.login-card {
    max-width: 420px;
    margin: var(--sp-8) auto;
    text-align: center;
    background: var(--surface);
    padding: var(--sp-6);
    border-radius: var(--radius-lg);
    border: 1px solid var(--surface-2);
    box-shadow: var(--shadow-2);
}

/* Bot-Status (Heartbeat) */
.status-card {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    background: var(--surface);
    border: 1px solid var(--surface-2);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-1);
    padding: var(--sp-4) var(--sp-5);
    margin-bottom: var(--sp-5);
}
.status-card.status-online { border-color: var(--success-border); }
.status-dot {
    width: 10px; height: 10px; border-radius: 50%;
    background: var(--muted); flex-shrink: 0;
}
.status-online .status-dot { background: var(--success); box-shadow: 0 0 10px var(--success); }
.status-offline .status-dot { background: var(--danger); box-shadow: 0 0 8px rgba(239, 68, 68, .6); }
.status-head { display: flex; flex-direction: column; }
.status-head strong { font-size: var(--fs-md); }
.status-label { font-size: var(--fs-xs); color: var(--muted); font-weight: 700; }
.status-online .status-label { color: var(--success); }
.status-offline .status-label { color: var(--danger-text); }
.status-metrics {
    display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4);
    margin-left: auto; color: var(--muted); font-size: var(--fs-sm);
}
.status-metrics span { display: inline-flex; align-items: center; gap: var(--sp-2); }

/* Guild grid */
.guild-section {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    margin: var(--sp-6) 0 0;
    font-size: var(--fs-lg);
}
.guild-count {
    background: var(--surface-2);
    color: var(--muted);
    border-radius: var(--radius-pill);
    padding: 1px var(--sp-2);
    font-size: var(--fs-xs);
    font-weight: 700;
}
.guild-section-hint { margin: var(--sp-2) 0 0; }
.guild-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: var(--sp-4);
    margin-top: var(--sp-4);
}
/* Die ganze Karte ist der Link – gleiche Höhe je Reihe, Knopf unten bündig,
   damit lange Servernamen die Reihe nicht verspringen lassen. */
.guild-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    position: relative;
    overflow: hidden;
    background: var(--surface);
    border: 1px solid var(--surface-2);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-1);
    padding: var(--sp-5) var(--sp-4) var(--sp-4);
    text-align: center;
    color: var(--text);
    transition: transform var(--motion-fast) var(--ease), border-color var(--motion-fast) var(--ease),
                box-shadow var(--motion-fast) var(--ease), background var(--motion-fast) var(--ease);
}
/* Kopfband hinter dem Serverbild. Vorher waren die Karten eine gleichmäßig
   graue Fläche mit einem Kreis darauf – dieselbe Fläche, derselbe Rand,
   zwölfmal nebeneinander. Das Band gibt der Karte oben und unten, statt sie
   nur zu füllen; es liegt hinter dem Inhalt und ist beim Zeigen deutlicher. */
.guild-card::before {
    content: "";
    position: absolute;
    inset: 0 0 auto;
    height: 64px;
    background: linear-gradient(var(--primary-bg), transparent);
    transition: opacity var(--motion-fast) var(--ease);
    opacity: .7;
}
.guild-card > * { position: relative; }
.guild-card:hover {
    transform: translateY(-2px);
    border-color: var(--primary-border);
    box-shadow: var(--shadow-2);
    color: var(--text);
}
.guild-card:hover::before { opacity: 1; }
.guild-icon { margin-bottom: var(--sp-3); line-height: 0; }
/* Der Ring hebt das Serverbild vom Kopfband ab – gerade dunkle Logos
   verschwammen sonst damit. */
.guild-icon .avatar {
    box-shadow: 0 0 0 2px var(--surface), 0 0 0 3px var(--primary-border);
}
.guild-name {
    font-weight: 700;
    margin-bottom: var(--sp-4);
    word-break: break-word;
    line-height: 1.3;
}
/* Knopf-Optik ohne eigenes <a> (die Karte ist bereits der Link) */
.guild-cta {
    margin-top: auto;
    width: 100%;
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--radius-sm);
    font-size: var(--fs-sm);
    font-weight: 700;
    border: 1px solid var(--surface-2);
    color: var(--muted);
}
.guild-cta-open { background: var(--grad-text); border-color: transparent; color: #fff; }
.guild-card:hover .guild-cta { border-color: var(--primary); color: var(--text); }
.guild-card:hover .guild-cta-open { color: #fff; }
/* Server ohne Bot treten zurück, bleiben aber lesbar. Der gestrichelte Rand
   sagt „hier fehlt noch etwas" – deutlicher als eine bloß blassere Karte,
   die man für einen Ladezustand halten kann. Kein Kopfband, kein Schatten:
   die Karte liegt noch nicht auf der Seite. */
.guild-card-invite {
    background: transparent;
    border-style: dashed;
    box-shadow: none;
}
.guild-card-invite::before { display: none; }
.guild-card-invite .avatar { opacity: .7; box-shadow: none; }
.guild-card-invite:hover { box-shadow: var(--shadow-1); }
.guild-card-invite:hover .avatar { opacity: 1; }
.empty { text-align: center; margin-top: var(--sp-8); color: var(--muted); }

/* -------------------------------------------------------------
   Seitenkopf: Krümelpfad und Überschrift (`_seitenkopf.html`)

   Vorher: ein einzelner Rückweg und darunter eine `<h1>` mit dem Namen
   des Servers – auf 22 Seiten dieselbe. Der Pfad sagt beides zugleich:
   wo man ist und wie man stufenweise zurückkommt.

   Er steht **über** der Überschrift und ist leise: er ist Orientierung,
   nicht Inhalt. Die Stufen tragen keine Linkfarbe, sondern werden erst
   beim Zeigen deutlich – dieselbe Zurückhaltung wie beim Personen-Link.
   ------------------------------------------------------------- */
.seitenkopf { margin-bottom: var(--sp-5); }
.kruemel {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--sp-2);
    margin-bottom: var(--sp-1);
    font-size: var(--fs-sm);
    color: var(--muted);
}
.kruemel a { color: var(--muted); }
.kruemel a:hover { color: var(--link); }
/* Das Trennzeichen steht noch leiser als die Stufen: es trägt keine
   Bedeutung, es hält sie auseinander. */
.kruemel-trenner { color: var(--surface-2); }
/* Die Zahl neben der Überschrift (vorher ein Abzeichen in der `<h2>` der
   Karte darunter): sie gehört zur Überschrift, ist aber nicht die
   Überschrift. */
.seitenkopf-zusatz {
    margin-left: var(--sp-2);
    font-size: var(--fs-lg);
    font-weight: 700;
    color: var(--muted);
}
/* -------------------------------------------------------------
   Kartenkopf: Überschrift links, Zusatz rechts

   Dieselbe Zeile war dreimal beschrieben – `.chart-head`, `.setup-head`
   und `.keys-head`, jede mit eigenem `gap` (8 / 12 / 12 px) und eigener
   Ausrichtung. Nebeneinander sah man den Unterschied nicht, hintereinander
   schon: die Überschrift der Statistik saß zwei Pixel anders als die der
   Einrichtung. Dazu ein `style="margin:0"` von Hand im Template, weil `h2`
   seinen unteren Abstand mitbrachte.

   Gezeichnet wird der Kopf über `kartenkopf()` aus `_karte.html`; wer eine
   neue Karte mit Zusatz baut, soll nicht wieder `.foo-head` erfinden.
   ------------------------------------------------------------- */
.card-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--sp-2) var(--sp-4);
    margin-bottom: var(--sp-4);
}
/* Der Abstand nach unten gehört jetzt dem Kopf, nicht der Überschrift –
   sonst schöbe die Überschrift die Zeile auseinander, in der sie steht. */
.card-head h2 { margin: 0; }
.card-head h2 .icon { margin-right: var(--sp-2); }
/* Was rechts steht, ist der leisere Teil: Legende, Zähler, Zeitraum. Die
   Farbe hing vorher an `.chart-legend` bzw. `.setup-count` – zwei Klassen
   für dieselbe Rolle. Wer lauter sein muss (die aktive Zeitraum-Pille),
   sagt es selbst. */
.card-head-neben {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--sp-2) var(--sp-4);
    color: var(--muted);
    font-size: var(--fs-xs);
}

.card {
    background: var(--surface);
    border: 1px solid var(--surface-2);
    border-radius: var(--radius-lg);
    padding: var(--sp-5);
    margin-bottom: var(--sp-5);
    /* Die Karte liegt auf der Seite, sie ist nicht Teil von ihr. Bisher trug
       das allein die 1-px-Kante – auf dem Hintergrundbild war sie oft kaum
       auszumachen. */
    box-shadow: var(--shadow-1);
}
/* Karte in einer Karte: die innere trägt keinen zweiten Schatten, sondern
   die höhere Fläche. Zwei Schatten übereinander sehen aus wie ein Fehler. */
.card .card { box-shadow: none; background: var(--surface-raised); }
/* -------------------------------------------------------------
   Formulare: ein Stapel, ein Feld, eine Reihe

   Es waren drei Systeme für dieselbe Sache „Beschriftung über dem Feld":
   `.fields` mit `.field`-Blöcken (4 Templates), `.stack-form` mit
   umschließenden `<label>`-Elementen (13) und `.form-row` (5) – wobei
   `.form-row` in *diesem* Stylesheet **gar nicht vorkam**. Die Regel dazu
   stand nur in `landing.css`, das die Dashboard-Seiten nie laden. Fünf
   Formulare setzten also seit jeher eine Reihe voraus, die es nicht gab:
   das Emoji-Feld der Bewerbungsarten stand über dem Namensfeld statt
   daneben, und die `style="flex:2 1 260px"` der Störungsmeldung wirkten
   auf einen Container, der kein Flexbox war.

   Die beiden Auszeichnungen bleiben – `<label for>` neben dem Feld und
   `<label>` um das Feld sind beide richtiges HTML –, aber der Rhythmus
   steht nur noch einmal da.
   ------------------------------------------------------------- */
.fields, .stack-form { display: grid; gap: var(--sp-4); }
/* Beschriftung: gleiche Schrift, gleicher Abstand von 8 px zum Feld –
   einmal als Außenabstand (`.field` hat Beschriftung und Feld
   nebeneinander), einmal als Rasterlücke (`.stack-form` hat sie
   ineinander). */
.field label, .stack-form > label { font-weight: 600; font-size: var(--fs-sm); }
.field label { display: block; margin-bottom: var(--sp-2); }
.stack-form > label { display: grid; gap: var(--sp-2); }
.stack-form > button { justify-self: start; }

/* Zwei Felder nebeneinander. `flex-wrap` statt einer Media Query: die
   Reihe bricht um, wenn ihre Felder nicht mehr nebeneinanderpassen –
   das hängt an der Kartenbreite, nicht an der des Fensters. */
.field-row { display: flex; flex-wrap: wrap; gap: var(--sp-3); }
.field-row > * { flex: 1 1 200px; min-width: 0; }
.field-row > .field-kurz { flex: 0 1 140px; }   /* Zahl, Emoji, Kürzel */
.field-row > .field-breit { flex-grow: 2; }     /* der Titel neben der Auswahl */
/* Ein Knopf am Ende der Reihe (Mitgliedersuche) wächst nicht mit: er ist
   so breit wie sein Wort. */
.field-row > .btn { flex: 0 0 auto; }

.field-help { color: var(--muted); font-size: var(--fs-xs); margin: var(--sp-2) 0 0; }

/* Fehler am Feld (`_feldfehler.html`): die Meldung steht dort, wo etwas zu
   ändern ist – bei einem zwölfteiligen Formular sagt ein Hinweis oben nicht,
   welches Feld gemeint war. Die Farbe allein trägt das nicht: der Rahmen ist
   nur die Verstärkung, die Aussage steht als Satz daneben. */
.field-error { color: var(--danger-text); font-size: var(--fs-sm); margin: var(--sp-2) 0 0; font-weight: 600; }
input[aria-invalid="true"], select[aria-invalid="true"], textarea[aria-invalid="true"] {
    border-color: var(--danger);
}
input[aria-invalid="true"]:focus, select[aria-invalid="true"]:focus,
textarea[aria-invalid="true"]:focus {
    border-color: var(--danger);
    box-shadow: var(--danger-ring);
}

input[type="text"], input[type="number"], input[type="password"],
input[type="search"], select, textarea {
    width: 100%;
    padding: var(--sp-3);
    background: var(--surface-sunken);
    border: 1px solid var(--surface-2);
    border-radius: var(--radius-sm);
    color: var(--text);
    font: inherit;
    font-size: var(--fs-sm);
}
textarea { resize: vertical; line-height: 1.5; }
input:focus, select:focus, textarea:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: var(--focus-ring);
}
/* Autofill vom Browser/Passwortmanager ans dunkle Design anpassen */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus {
    -webkit-text-fill-color: var(--text);
    -webkit-box-shadow: 0 0 0 1000px var(--surface-sunken) inset;
    caret-color: var(--text);
}

.switch-row { display: flex; align-items: center; gap: var(--sp-3); cursor: pointer; }
.switch-row input { width: 18px; height: 18px; accent-color: var(--primary); }
.switch-row span { font-weight: 600; }

.checkbox-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: var(--sp-2) var(--sp-4); max-height: 260px; overflow-y: auto; padding: var(--sp-1) var(--sp-0); }
.checkbox-row { display: flex; align-items: center; gap: var(--sp-2); cursor: pointer; }
.checkbox-row input { width: 16px; height: 16px; accent-color: var(--primary); }

/* Wortfilter */
.wordfilter { margin-top: var(--sp-5); border-top: 1px solid var(--surface-2); padding-top: var(--sp-2); }
.word-list { list-style: none; padding: 0; margin: 0 0 var(--sp-4); display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.word-list li {
    display: flex; align-items: center; gap: var(--sp-2);
    background: var(--surface-raised); border: 1px solid var(--surface-2);
    padding: var(--sp-1) var(--sp-1) var(--sp-1) var(--sp-3); border-radius: var(--radius-pill); font-size: var(--fs-sm);
}
.add-word { display: flex; gap: var(--sp-2); }
.add-word input { flex: 1; }

/* -------------------------------------------------------------
   Konfigurationsseite: Werkzeugleiste, Sektionen, Speichern-Leiste
   Suche und Auf-/Zuklappen brauchen JavaScript – ohne JS bleibt die
   Werkzeugleiste ausgeblendet und alle Sektionen sind offen.
   ------------------------------------------------------------- */
.config-tools { display: none; }
.js .config-tools {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    flex-wrap: wrap;
    margin-bottom: var(--sp-4);
}
.config-search { flex: 1; min-width: 200px; }
.config-tool-btns { display: flex; gap: var(--sp-2); }
.cfg-search-empty { margin: 0 0 var(--sp-4); }

/* Sektion = <details class="card">: Kopfzeile ist die Klapp-Fläche */
.cfg-section > .cfg-section-head {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    cursor: pointer;
    list-style: none;
    padding: var(--sp-0) 0;
    border-radius: var(--radius-sm);
}
.cfg-section > .cfg-section-head::-webkit-details-marker { display: none; }
.cfg-section > .cfg-section-head:hover h2 { color: var(--link); }
.cfg-section > .cfg-section-head h2 {
    margin: 0; flex: 1; min-width: 0;
    transition: color var(--motion-fast) var(--ease);
}
/* Pfeil rechts, dreht sich beim Aufklappen */
.cfg-section > .cfg-section-head::after {
    content: "";
    flex: none;
    width: 8px; height: 8px;
    border-right: 2px solid var(--muted);
    border-bottom: 2px solid var(--muted);
    transform: rotate(-45deg);
    transition: transform var(--motion-fast) var(--ease);
}
.cfg-section[open] > .cfg-section-head::after { transform: rotate(45deg); }
.cfg-section[open] > .cfg-section-head { margin-bottom: var(--sp-4); }
/* Ohne JS gibt es keinen Grund, eine Klapp-Optik vorzugaukeln */
html:not(.js) .cfg-section > .cfg-section-head { cursor: default; }
html:not(.js) .cfg-section > .cfg-section-head::after { display: none; }

.cfg-section-meta {
    flex: none;
    font-size: var(--fs-2xs);
    font-weight: 700;
    color: var(--warning);
    background: var(--warning-bg);
    border: 1px solid var(--warning-border);
    border-radius: var(--radius-pill);
    padding: 1px var(--sp-2);
}
.cfg-section-meta:empty { display: none; }
.cfg-section.has-changes { border-color: var(--warning-border); }

/* Geändertes Feld bekommt eine farbige Kante */
.field.is-changed {
    border-left: 2px solid var(--warning);
    padding-left: var(--sp-3);
    margin-left: -12px;
}

/* Auf breiten Fenstern zwei Feldspalten. Ein einzeiliges Eingabefeld wird
   von 400 auf 800 px nicht lesbarer, nur länger – die gewonnene Breite geht
   deshalb in eine zweite Spalte statt in längere Zeilen. Textflächen,
   Kanal-Listen und der Wortfilter behalten die volle Breite. Browser ohne
   `:has()` bekommen wie bisher eine Spalte. */
@media (min-width: 1180px) {
    .config-form .fields { grid-template-columns: 1fr 1fr; column-gap: var(--sp-6); }
    .config-form .fields > .field:has(textarea),
    .config-form .fields > .field:has(.checkbox-list) { grid-column: 1 / -1; }
}

/* Save bar – bleibt beim Scrollen unten stehen */
.save-bar {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--sp-4);
    flex-wrap: wrap;
    margin-top: var(--sp-2);
}
.config-form .save-bar {
    position: sticky;
    bottom: 0;
    z-index: 20;
    /* Der negative Seitenrand hebt genau die eigene Polsterung auf, damit
       die Leiste über die volle Breite der Karte deckt. */
    margin: var(--sp-2) calc(-1 * var(--sp-2)) 0;
    padding: var(--sp-4) var(--sp-2) var(--sp-3);
    /* Oben weicher Übergang, unten deckend – der Inhalt scrollt sichtbar
       darunter durch, ohne mit der Leiste zu verschwimmen. */
    background: linear-gradient(rgba(13, 13, 20, 0), rgba(13, 13, 20, .97) 40%);
    backdrop-filter: blur(4px);
}
.save-state { color: var(--muted); font-size: var(--fs-sm); font-weight: 600; }
.save-state.is-dirty { color: var(--warning); }

/* Guild-Navigation
   Schmal (Standard): Pillen-Liste über dem Inhalt, nach Bereichen gruppiert.
   Ab 900 px: Seitenspalte links neben dem Inhalt – siehe unten. */
.guild-nav {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2) var(--sp-4);
    margin-bottom: var(--sp-5);
    border-bottom: 1px solid var(--surface-2);
    padding-bottom: var(--sp-3);
}
.guild-nav-group { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }
.guild-nav-label {
    color: var(--muted);
    font-size: var(--fs-2xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .08em;
    margin-right: var(--sp-0);
}
.guild-nav a {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-4);
    border-radius: var(--radius-sm);
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--muted);
    background: var(--surface);
    border: 1px solid var(--surface-2);
    transition: color var(--motion-fast) var(--ease), border-color var(--motion-fast) var(--ease), background var(--motion-fast) var(--ease);
}
/* Das Icon bleibt eine Spur blasser als die Beschriftung – auf der aktiven
   Seite tragen beide dieselbe Farbe. */
.guild-nav a .icon { opacity: .75; }
.guild-nav a:hover .icon,
.guild-nav a.active .icon { opacity: 1; }
.guild-nav a:hover { color: var(--text); border-color: var(--primary); }
.guild-nav a.active {
    color: #fff;
    background: var(--grad-text);
    border-color: transparent;
    box-shadow: 0 4px 14px var(--primary-glow);
}

/* Seitenspalte ab Tablet-Breite. `:has()` spart den Umbau aller 17
   Server-Templates: die Leiste bleibt ein Geschwister-Element von
   Überschrift und Karten, wird hier aber in eine eigene Rasterspalte
   gelegt. Browser ohne `:has()` bekommen unverändert die Leiste oben. */
@media (min-width: 900px) {
    .container:has(> .guild-nav) {
        display: grid;
        grid-template-columns: 210px minmax(0, 1fr);
        column-gap: var(--sp-5);
        row-gap: 0;
        align-items: start;
    }
    /* Alles außer der Leiste in die Inhaltsspalte; die leeren Rasterzeilen
       der Leiste bleiben ohne row-gap höhenlos. */
    .container:has(> .guild-nav) > * { grid-column: 2; min-width: 0; }
    .container:has(> .guild-nav) > .guild-nav {
        grid-column: 1;
        grid-row: 1 / span 30;
        position: sticky;
        /* Unter der Topbar, nicht dahinter – vorher standen hier 20 px ab
           Fensterkante, die Leiste schob sich also unter die Marke. */
        top: calc(var(--topbar-h) + var(--sp-3));
        /* Auf niedrigen Fenstern scrollt die Leiste in sich selbst,
           statt unten abgeschnitten zu werden. */
        max-height: calc(100vh - var(--topbar-h) - var(--sp-5));
        overflow-y: auto;
        display: block;
        border-bottom: none;
        border-right: 1px solid var(--surface-2);
        padding: 0 var(--sp-4) 0 0;
        margin-bottom: 0;
    }
    /* Die Gruppen bekommen eine Trennlinie statt bloß etwas Luft: bei fünf
       Gruppen und siebzehn Einträgen sind acht Pixel Abstand kein sichtbarer
       Unterschied zum Abstand zwischen zwei Einträgen. */
    .guild-nav-group { display: block; margin-bottom: var(--sp-4); }
    .guild-nav-group + .guild-nav-group {
        border-top: 1px solid var(--border-soft);
        padding-top: var(--sp-4);
    }
    .guild-nav-group:last-child { margin-bottom: 0; }
    .guild-nav-label { display: block; margin: 0 0 var(--sp-2) var(--sp-3); }
    .container:has(> .guild-nav) > .guild-nav a {
        display: flex;
        padding: var(--sp-2) var(--sp-3);
        margin-bottom: var(--sp-1);
        font-size: var(--fs-sm);
        background: transparent;
        border-color: transparent;
    }
    /* Vorher war das Zeigen `background: var(--surface)` – dieselbe Farbe,
       die inzwischen jede Karte trägt, auf einer Seite, die keine flache
       Fläche mehr ist. Eine Aufhellung wirkt unabhängig vom Untergrund. */
    .container:has(> .guild-nav) > .guild-nav a:hover {
        background: var(--tint-2);
        border-color: var(--border-soft);
        color: var(--text);
    }
    .container:has(> .guild-nav) > .guild-nav a.active {
        background: var(--grad-text);
        border-color: transparent;
        /* Derselbe Schein wie bei der Pillen-Fassung: der aktive Eintrag ist
           die einzige Stelle der Leiste, die erhöht liegt. */
        box-shadow: 0 4px 14px var(--primary-glow);
    }
}

/* Datentabellen
   Seit die Listen serverseitig blättern, stehen 50 Zeilen auf einer Seite.
   Drei Dinge fehlten dafür: eine Kopfzeile, die beim Scrollen stehen bleibt,
   eine Zeile, die sich unter dem Zeiger zu erkennen gibt, und Trennlinien,
   die die Tabelle nicht in ein Gitter zerlegen. */
.data-table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
.data-table th {
    text-align: left;
    padding: var(--sp-2) var(--sp-3);
    color: var(--muted);
    font-weight: 700;
    text-transform: uppercase;
    font-size: var(--fs-2xs);
    letter-spacing: .06em;
    /* Die Kopfzeile trennt kräftiger als die Zeilen darunter: sie ist die
       einzige Linie, die etwas anderes meint als „nächster Datensatz". */
    border-bottom: 1px solid var(--surface-2);
}
/* Die Kopfzeile bleibt unter der Topbar stehen. `--topbar-h` misst
   `dashboard.js` und schreibt sie an `<html>`; der Vorgabewert hier gilt
   ohne JavaScript und ist bewusst großzügig – ein paar Pixel Luft sind
   harmlos, ein paar Pixel zu wenig verstecken die Beschriftung.
   Der Hintergrund muss deckend sein, sonst scheinen die Zeilen durch. */
.data-table thead th {
    position: sticky;
    top: var(--topbar-h);
    z-index: 2;
    background: var(--surface);
    /* `border-collapse: collapse` zieht die Kante der Kopfzeile mit dem
       Rest der Tabelle zusammen – beim Kleben bleibt sie dann zurück.
       Als innerer Schatten gehört sie zur Zelle und wandert mit. */
    box-shadow: inset 0 -1px 0 var(--surface-2);
}
.data-table td {
    padding: var(--sp-3);
    border-bottom: 1px solid var(--border-soft);
    vertical-align: middle;
}
.data-table tr:last-child td { border-bottom: none; }
/* Nur wo es einen Zeiger gibt: auf dem Handy blieben sonst nach jeder
   Berührung Zeilen hell stehen – und dort ist die Zeile ohnehin eine Karte
   mit eigener Fläche. */
@media (hover: hover) {
    .data-table tbody tr:hover td { background: var(--tint-1); }
}
.data-table td.muted { color: var(--muted); }
.data-table td.rank { font-weight: 800; color: var(--secondary); width: 40px; }
/* Zahlenspalten stehen rechts: in einer Spalte will man Einer unter Einern
   sehen. Die feste Ziffernbreite dafür steht weiter unten bei den anderen
   Zahlen – sie gilt nicht nur in Tabellen. */
.data-table th.num, .data-table td.num { text-align: right; }
.data-table form { margin: 0; }

/* -------------------------------------------------------------
   Feste Ziffernbreite

   Raleway ist eine proportionale Schrift: die 1 ist schmaler als die 8.
   In den Tabellenspalten war das längst geregelt, überall sonst nicht –
   und genau dort ändern sich die Zahlen im Betrieb:
   - die Statistik-Karte springt, wenn die Mitgliederzahl von 999 auf
     1000 geht,
   - `timeago.js` schreibt jede Minute „vor 4/5/6 Minuten" neu und
     verschiebt dabei den Text dahinter,
   - `queue_status.js` zieht Zeitstempel nach, während man hinsieht,
   - die Blätterleiste wackelt beim Wechsel von Seite 9 auf 10.

   `tabular-nums` gibt jeder Ziffer dieselbe Breite; die Zahl ändert sich
   dann, ohne dass sich etwas verschiebt. Nur für Zahlen, nicht für
   Fließtext: dort sind proportionale Ziffern besser zu lesen.
   ------------------------------------------------------------- */
.data-table td.num, .data-table th.num, .data-table td.rank,
.stat-value, .stat-trend, .stat-sub, .setup-count, .nav-badge,
.pager-info, .pager-num, .chart-axis,
time.ts, .rmm-metrics, .status-metrics {
    font-variant-numeric: tabular-nums;
}

/* -------------------------------------------------------------
   Rundes Bild: Avatar, Serverbild, Marke

   `border-radius: 50%` stand elfmal im Stylesheet, daneben eine feste
   Größe – 26, 28, 30, 32, 40, 64, 88 px, jede an genau einer Stelle
   ausgedacht. Allein das VANTEX-Logo erschien in drei Größen, je nachdem
   welche Seite man ansah. Fünf Bauteile hießen verschieden und taten
   dasselbe: `.member-avatar`, `.member-avatar-sm`, `.ts-avatar`,
   `.guild-icon img`, `.brand-logo`.

   Die Größen bleiben Zahlen, keine Rasterstufen: ein Avatar ist ein
   Bauteilmaß (siehe der Kommentar zur Radienskala in `tokens.css`). Neu
   ist nur, dass es dieselben Zahlen sind.

   `.avatar-initial` ist die Fassung ohne Bild: der Anfangsbuchstabe auf
   dem Verlauf. Discord liefert nicht für jeden ein Bild, und ein leerer
   Kreis sagt weniger als ein Buchstabe.
   ------------------------------------------------------------- */
.avatar {
    display: block;
    border-radius: 50%;
    object-fit: cover;
    flex: none;
    width: 32px; height: 32px;
}
.avatar-xs { width: 28px; height: 28px; }   /* Marke in der Kopfzeile */
.avatar-sm { width: 32px; height: 32px; }   /* Zeile in einer Liste */
.avatar-md { width: 40px; height: 40px; }   /* Transkript */
.avatar-lg { width: 64px; height: 64px; }   /* Server-Kachel */
.avatar-xl { width: 88px; height: 88px; }   /* Mitglieder-Karte */
.avatar-initial {
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--grad-text); color: #fff; font-weight: 800;
}
.avatar-xs.avatar-initial, .avatar-sm.avatar-initial { font-size: var(--fs-xs); }
.avatar-lg.avatar-initial, .avatar-xl.avatar-initial { font-size: var(--fs-2xl); }

/* Name mit Bild als eine Einheit (`_person.html`). Ohne Bild bleibt es
   der Link allein – die meisten Listen führen nur ID und Namen mit. */
.person { display: inline-flex; align-items: center; gap: var(--sp-2); min-width: 0; }

/* Leiser Textlink: Personen-Namen in Listen (`_person.html`) und Hinweise,
   die weiterführen. Bewusst ohne Linkfarbe – volle Farbe machte aus jeder
   Tabellenspalte ein Feld leuchtender Namen. Erkennbar bleibt der Link an
   der gepunkteten Linie, deutlich wird er beim Zeigen. */
.person-link, .text-link {
    text-decoration: underline dotted;
    text-decoration-color: rgba(154, 154, 171, .55);
    text-underline-offset: 3px;
}
.person-link:hover, .text-link:hover,
.person-link:focus-visible, .text-link:focus-visible {
    color: var(--link);
    text-decoration-color: currentColor;
}
/* -------------------------------------------------------------
   Badges: eine Fläche, sechs Bedeutungen

   Dieselbe Geschichte wie beim Knopf: `.badge` stand zweimal im
   Stylesheet – hier und 70 Zeilen weiter unten, wo die zweite Regel
   Schriftgröße und Umbruch der ersten überschrieb. Daneben lagen zwei
   Sätze Varianten, die dasselbe meinten und es verschieden machten:
   `.badge-on/-off/-wait` färbten **nur die Schrift** und behielten die
   violette Fläche der Basis („Online" war grüne Schrift auf einer
   Markenpille), `.badge-green/-yellow/-red` färbten Fläche, Kante und
   Schrift. Der Druckblock kannte nur den ersten Satz.

   Die Namen sagen jetzt die **Bedeutung**, nicht die Farbe: ein
   Hell-Design tauscht Farbwerte, keine Klassennamen. Ohne Variante ist
   die Pille neutral – das ist der Regelfall (Zähler hinter einer
   Überschrift, Aktionsname im Protokoll, „Bot").
   ------------------------------------------------------------- */
.badge {
    display: inline-block;
    padding: var(--sp-0) var(--sp-3);
    border-radius: var(--radius-pill);
    background: var(--primary-bg);
    border: 1px solid var(--primary-border);
    font-size: var(--fs-xs);
    font-weight: 700;
    white-space: nowrap;
    color: var(--text);
}
.badge-on     { background: var(--success-bg); border-color: var(--success-border); color: var(--success); }
.badge-off    { background: var(--tint-2);     border-color: var(--border-soft);    color: var(--muted); }
.badge-wait   { background: var(--warning-bg); border-color: var(--warning-border); color: var(--warning); }
.badge-danger { background: var(--danger-bg);  border-color: var(--danger-border);  color: var(--danger-text); }
.badge-info   { background: var(--info-bg);    border-color: var(--info-border);    color: var(--secondary); }
/* Eine Pille, die schaltet (Befehl an/aus): der Knopf bringt Fläche,
   Rand und Schriftart des Browsers mit. Vorher stand das als
   `style="background:none;border:none;cursor:pointer"` im Template. */
button.badge { cursor: pointer; font-family: inherit; }

.footer { text-align: center; color: var(--muted); font-size: var(--fs-xs); padding: var(--sp-5); }
.footer-claim { color: var(--link); font-weight: 700; letter-spacing: .12em; }

/* Topbar-Link (z. B. Logs) */
.topbar-link { color: var(--muted); font-size: var(--fs-sm); font-weight: 700; }
.topbar-link:hover { color: var(--link); }
.nav-badge { background: var(--primary-fill); color: #fff; border-radius: var(--radius-pill);
             font-size: var(--fs-2xs); padding: 1px var(--sp-2); margin-left: var(--sp-0); }

/* -------------------------------------------------------------
   Pillenreihe: wenige Möglichkeiten, eine davon ist aktiv

   Fünfmal dasselbe Bauteil, fünfmal eigene Werte: der Zeitraum der
   Statistik (`.range-pill`), der Zeitraum über den Listen
   (`.filter-knopf`), der Statusfilter der Mod-Queue (der stand als
   `<style>` **im Template** und blieb damit von jeder Prüfung
   unbehelligt), der Bot-Umschalter und der Sprachumschalter. Vier
   färbten die aktive Pille violett, eine blau – auf dem Bildschirm sah
   das aus, als bedeute Blau etwas anderes als Violett.

   Zwei Fassungen, eine Bedeutung:
     .pillen               freie Reihe (Filter über einer Liste)
     .pillen-eingefasst    eingefasster Umschalter in der Topbar
   Der Verzicht auf einen Farbwechsel bei `:hover` ist Absicht: die
   Schriftstärke bleibt gleich, sonst hüpfte die Reihe bei jedem Klick.
   ------------------------------------------------------------- */
.pillen { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
.pillen-eingefasst {
    display: inline-flex;
    flex-wrap: nowrap;
    gap: var(--sp-0);
    padding: var(--sp-1);
    border-radius: var(--radius-pill);
    background: var(--tint-2);
}
.pille {
    padding: var(--sp-1) var(--sp-3);
    border: 1px solid var(--surface-2);
    border-radius: var(--radius-pill);
    color: var(--muted);
    font-size: var(--fs-xs);
    font-weight: 700;
    text-decoration: none;
    white-space: nowrap;
}
.pille:hover { border-color: var(--primary); color: var(--text); }
.pille.active {
    background: var(--primary-fill);
    border-color: var(--primary-fill);
    color: #fff;
}
/* Im eingefassten Umschalter trägt schon die Fassung die Kante. */
.pillen-eingefasst .pille { border-color: transparent; }
.pillen-eingefasst .pille:hover { border-color: transparent; color: var(--link); }
/* Der Sprachumschalter zeigt zwei Kürzel, keine Wörter. */
.pille-klein { font-size: var(--fs-2xs); font-weight: 800; letter-spacing: .04em;
               padding: var(--sp-1) var(--sp-2); }
/* Der Bot-Umschalter schickt ein Formular ab (siehe `button.badge`). */
button.pille { cursor: pointer; font-family: inherit; background: none; }

/* Bots-Verwaltung: Aktions-Buttons und Anlege-Formular
   (die Status-Pillen stehen oben beim Badge-System) */
.actions-cell { display: flex; gap: var(--sp-2); justify-content: flex-end; align-items: center; }

/* Ein Formular hat eine Spalte – auch in einer Karte, die breiter ist.
   Der Rest von `.stack-form` steht oben beim Formularsystem; in der
   schmalen Inhaltsspalte hebt `.container-schmal` diese Grenze auf. */
.stack-form { max-width: 460px; }

/* Zugriff verwalten: Rollen-Zeilen mit Rechte-Checkboxen */
.mt { margin-top: var(--sp-5); }
.role-row {
    display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
    padding: var(--sp-3) 0; border-top: 1px solid var(--border-soft);
}
.role-form { display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap; flex: 1; }
.role-name { font-weight: 800; min-width: 120px; }
.role-perms { display: flex; gap: var(--sp-4); flex-wrap: wrap; }
.perm-check { display: flex; align-items: center; gap: var(--sp-1); font-size: var(--fs-sm); font-weight: 600; white-space: nowrap; }
.perm-check input { width: auto; }

/* Admin-Login / Konto / Admin-Center */
.auth-card { max-width: 400px; margin: var(--sp-6) auto; }
.auth-alt { margin-top: var(--sp-4); text-align: center; }
.qr-box { background: #fff; padding: var(--sp-3); border-radius: var(--radius-md); width: max-content; margin: var(--sp-3) 0; }
.qr-box svg { display: block; width: 200px; height: 200px; }

.user-row { padding: var(--sp-4) 0; border-top: 1px solid var(--border-soft); }
.user-head { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; margin-bottom: var(--sp-3); }
.user-form { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; margin-bottom: var(--sp-2); }
.user-form select, .user-form input { width: auto; }
.user-actions { display: flex; gap: var(--sp-3); flex-wrap: wrap; align-items: center; }

/* Übersicht: Statistik-Karten */
/* -------------------------------------------------------------
   Einrichtungs-Fortschritt (overview.html)
   Steht ganz oben, solange etwas fehlt, und verschwindet, sobald
   alle Grundschritte erledigt sind.
   ------------------------------------------------------------- */
.setup-card { margin-bottom: var(--sp-5); }
.setup-count { font-weight: 700; }
.setup-bar {
    height: 6px;
    border-radius: var(--radius-xs);
    background: var(--surface-2);
    overflow: hidden;
    margin: var(--sp-3) 0 var(--sp-3);
}
.setup-bar-fill {
    display: block;
    height: 100%;
    background: var(--grad);
    border-radius: var(--radius-xs);
}
.setup-lead { margin: 0 0 var(--sp-4); }
.setup-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-2); }
/* Die ganze Zeile ist der Link – ein kleines „Einrichten" am Rand wäre auf
   dem Handy ein Ziel von 60 px in einer Zeile von 300. */
.setup-step {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    border: 1px solid var(--surface-2);
    border-radius: var(--radius-sm);
    background: var(--tint-1);
    transition: border-color var(--motion-fast) var(--ease), background var(--motion-fast) var(--ease);
}
.setup-step:hover {
    border-color: var(--primary);
    background: var(--primary-bg);
}
.setup-step-name { font-weight: 600; }
.setup-step-go { color: var(--link); font-size: var(--fs-xs); font-weight: 700; }

.stat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: var(--sp-4);
    margin-bottom: var(--sp-5);
}
.stat-card {
    background: var(--surface);
    border: 1px solid var(--surface-2);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-1);
    padding: var(--sp-4);
    display: flex;
    flex-direction: column;
    gap: var(--sp-1);
}
.stat-label { color: var(--muted); font-size: var(--fs-xs); font-weight: 700; }
.stat-value { font-size: var(--fs-2xl); font-weight: 800; line-height: 1.2; }
.stat-sub {
    color: var(--muted);
    font-size: var(--fs-xs);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-1) var(--sp-3);
}
.stat-sub a:hover { color: var(--link); }
/* Die Bewegung der letzten sieben Tage. Grün und Rot nur dort, wo „mehr"
   eindeutig besser ist – bei Verwarnungen bleibt die Zahl grau: eine rote
   Verwarnungszahl läse sich wie ein Vorwurf an das Team, das die Arbeit
   gemacht hat. */
.stat-trend { display: inline-flex; align-items: center; gap: var(--sp-1); font-weight: 700; }
.stat-trend.is-up { color: var(--success); }
.stat-trend.is-down { color: var(--danger-text); }
.stat-trend .icon { width: 1em; height: 1em; }

/* -------------------------------------------------------------
   Charts (Übersicht + Analytics)

   Die Farben standen als `#7B5CFF` und `#9A9AAB` in den Templates: ein
   SVG-Präsentationsattribut nimmt kein `var(…)` entgegen. Über eine
   CSS-Regel auf dieselbe Form geht es doch – und damit stehen die
   Chart-Farben wieder da, wo alle anderen stehen.
   ------------------------------------------------------------- */
.dot {
    display: inline-block;
    width: 9px; height: 9px;
    border-radius: 50%;
    margin-right: var(--sp-2);
}
.dot-purple { background: var(--primary); }
.dot-blue { background: var(--secondary); }
.dot-green { background: var(--success); }
.dot-red { background: var(--danger); }
.activity-chart { width: 100%; height: auto; display: block; overflow: visible; }
.chart-grid { stroke: var(--surface-2); stroke-width: 1; }
/* Die Größe steht in Einheiten des `viewBox`, nicht in Bildschirmpixeln:
   das SVG wird auf die Kartenbreite gestaucht, die Schrift also mit. Bei
   640 Einheiten Breite und rund 790 px Anzeige sind 11 Einheiten etwa
   13,5 px – auf dem Handy dagegen nur noch 5. Siehe unten. */
.chart-axis { fill: var(--muted); font-size: 11px; }
@media (max-width: 599.98px) {
    /* Auf schmalen Bildschirmen bleiben Anfang, Mitte und Ende stehen; die
       Marken dazwischen tragen `chart-axis-weg` und fallen weg. Der Rest
       wird größer, damit er die Stauchung übersteht. */
    .chart-axis { font-size: 20px; }
    .chart-axis-weg { display: none; }
}
.chart-line-mod, .chart-line-warn, .chart-line-up, .chart-line-down {
    fill: none;
    stroke-width: 2.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.chart-line-mod { stroke: var(--primary); }
.chart-line-warn { stroke: var(--secondary); stroke-width: 2; }
.chart-line-up { stroke: var(--success); }
.chart-line-down { stroke: var(--danger); stroke-width: 2; }
/* Die Fläche unter der Linie: derselbe Farbton, oben angedeutet, unten
   ausgelaufen. `stop-color` erbt die Linienfarbe über dieselbe Klasse. */
.chart-area-top { stop-color: var(--primary); stop-opacity: .28; }
.chart-area-bottom { stop-color: var(--primary); stop-opacity: 0; }
.chart-area-top.chart-line-warn, .chart-area-bottom.chart-line-warn { stop-color: var(--secondary); }
.chart-area-top.chart-line-up, .chart-area-bottom.chart-line-up { stop-color: var(--success); }
.chart-area-top.chart-line-down, .chart-area-bottom.chart-line-down { stop-color: var(--danger); }
/* Ein Tag ist ein Ziel, kein Punkt: die Spalte reicht über die volle Höhe
   und trägt den `<title>`, den der Browser als Kurzinfo zeigt. Sichtbar
   wird sie nur unter dem Zeiger. */
.chart-tag { fill: var(--text); fill-opacity: 0; }
@media (hover: hover) {
    .chart-tag:hover { fill-opacity: .06; }
}

/* Der Zeitraum-Umschalter der Statistik nimmt die Pillenreihe von oben;
   nur das Icon im Export-Knopf braucht hier noch Abstand. */
.pille .icon { margin-right: var(--sp-2); }

/* Analytics: Kanal-Heatmap & Funnel (Balkenlisten) */
.heatmap, .funnel { display: flex; flex-direction: column; gap: var(--sp-3); }
.heat-row, .funnel-row { display: flex; align-items: center; gap: var(--sp-3); }
.heat-name, .funnel-label {
    flex: 0 0 140px; font-weight: 600; font-size: var(--fs-sm);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.heat-bar, .funnel-bar {
    flex: 1; height: 12px; border-radius: var(--radius-pill);
    background: var(--surface-2); overflow: hidden;
}
.heat-fill { display: block; height: 100%; background: var(--grad); border-radius: var(--radius-pill); }
.funnel-fill { display: block; height: 100%; background: var(--success); border-radius: var(--radius-pill); }
.heat-val, .funnel-val { flex: 0 0 auto; font-size: var(--fs-sm); font-weight: 700; min-width: 44px; text-align: right; }
@media (max-width: 599.98px) {
    .heat-name, .funnel-label { flex-basis: 90px; }
}

/* Rückmeldung der Aktions-Queue (Dashboard -> Bot) */
.queue-note {
    padding: var(--sp-3) var(--sp-4);
    border-radius: var(--radius-sm);
    margin-bottom: var(--sp-3);
    font-size: var(--fs-sm);
    font-weight: 600;
}
.queue-note-wait {
    background: var(--warning-bg);
    border: 1px solid var(--warning-border);
    color: var(--warning);
}
.queue-note-danger {
    background: var(--danger-bg);
    border: 1px solid var(--danger-border);
    color: var(--danger-text);
}
.queue-actions-h {
    margin: var(--sp-5) 0 var(--sp-3);
    font-size: var(--fs-md);
}
.queue-error {
    color: var(--danger-text);
    font-size: var(--fs-xs);
    margin-top: var(--sp-1);
    word-break: break-word;
}
.queue-error[hidden] { display: none; }

/* Selbstaktualisierung (queue_status.js): eine gerade umgesprungene Zeile
   hebt sich kurz hervor. Ohne das ändert sich ein Wort mitten in der Tabelle,
   und niemand sieht es. Die Farbe klingt ab, die Zeile bleibt lesbar. */
.queue-actions tr.is-neu { animation: queue-neu 2.5s ease-out 1; }
@keyframes queue-neu {
    from { background: rgba(88, 101, 242, .22); }
    to   { background: transparent; }
}
/* Rückweg nach dem Löschen (_rueckgaengig.html). Ruhig gehalten: hier ist
   nichts schiefgegangen, es gibt nur noch einen Weg zurück. */
.undo-bar {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    flex-wrap: wrap;
    padding: var(--sp-3) var(--sp-4);
    margin-bottom: var(--sp-4);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    border: 1px solid var(--surface-2);
    font-size: var(--fs-sm);
}
.undo-text { color: var(--muted); font-weight: 600; }

/* -------------------------------------------------------------
   Mod-Queue: die Fallkarten

   Standen als `<style>`-Block im Template – 20 Zeilen mit rohen Werten
   (.5rem neben .75rem neben 1rem, ein `rgba(127,127,127,.08)` ohne
   Verwandtschaft zur Palette, `border-radius: 12px` neben der
   Radienskala). Im Template greift keine der Prüfungen aus
   `test_designsystem.py`: die lesen `style.css` und `landing.css`.

   Ein Fall ist eine Karte in einer Karte und trägt deshalb die höhere
   Fläche statt eines zweiten Schattens – dieselbe Regel wie bei
   `.card .card`. Die Aktionszeile darunter ist eine `.field-row`; hier
   bleibt nur ihr Abstand nach oben.
   ------------------------------------------------------------- */
.case {
    background: var(--surface-raised);
    border: 1px solid var(--surface-2);
    border-radius: var(--radius-md);
    padding: var(--sp-4);
    margin-bottom: var(--sp-4);
}
.case-top {
    display: flex; align-items: center; flex-wrap: wrap;
    gap: var(--sp-2); margin-bottom: var(--sp-2);
}
.case-top .who { font-weight: 600; }
.case-content {
    background: var(--surface-sunken);
    border-left: 3px solid var(--primary);
    border-radius: var(--radius-sm);
    padding: var(--sp-2) var(--sp-3);
    margin: var(--sp-2) 0;
    font-size: var(--fs-sm);
    white-space: pre-wrap;
    word-break: break-word;
}
.case-meta { font-size: var(--fs-xs); }
.case-actions { margin-top: var(--sp-3); align-items: center; }

/* Der Hinweis, dass die Fallliste darüber nicht mehr stimmt. Er ersetzt kein
   automatisches Neuladen – im Grund-Feld tippt womöglich gerade jemand. */
.queue-reload {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    flex-wrap: wrap;
    padding: var(--sp-3) var(--sp-4);
    margin-bottom: var(--sp-3);
    border-radius: var(--radius-sm);
    background: rgba(88, 101, 242, .10);
    border: 1px solid rgba(88, 101, 242, .45);
    font-size: var(--fs-sm);
    font-weight: 600;
}
.queue-reload[hidden] { display: none; }


/* Log-Ansicht */
.log-card { padding: var(--sp-3); }
.log-view {
    margin: 0;
    max-height: 520px;
    overflow: auto;
    background: var(--surface-sunken);
    border: 1px solid var(--surface-2);
    border-radius: var(--radius-sm);
    padding: var(--sp-4);
    font-family: Consolas, "Cascadia Mono", monospace;
    font-size: var(--fs-xs);
    line-height: 1.55;
    color: var(--text);
    white-space: pre-wrap;
    word-break: break-word;
}

/* Fehlerseiten */
.error-page { text-align: center; padding: var(--sp-8) 0; }
.error-code {
    font-size: 6rem;
    font-weight: 800;
    line-height: 1;
    background: var(--grad);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    margin-bottom: var(--sp-2);
}
.error-page h1 { margin-bottom: var(--sp-3); }
.error-page .subtle { max-width: 420px; margin: 0 auto; }
.error-actions {
    display: flex;
    justify-content: center;
    gap: var(--sp-3);
    flex-wrap: wrap;
    margin-top: var(--sp-5);
}

/* -------------------------------------------------------------
   Topbar-Menü für schmale Fenster
   Der Burger ist nur mit JavaScript sinnvoll; ohne JS bleibt die
   Linkleiste sichtbar und bricht wie bisher um.
   ------------------------------------------------------------- */
.nav-toggle { display: none; }

@media (max-width: 899.98px) {
    .js .nav-toggle {
        display: flex;
        flex-direction: column;
        justify-content: center;
        gap: var(--sp-1);
        width: 40px; height: 36px;
        padding: 0 var(--sp-2);
        border: 1px solid var(--surface-2);
        border-radius: var(--radius-sm);
        background: transparent;
        cursor: pointer;
    }
    .js .nav-toggle span {
        display: block;
        height: 2px;
        border-radius: var(--radius-xs);
        background: var(--text);
        transition: transform var(--motion-base) var(--ease), opacity var(--motion-base) var(--ease);
    }
    .js .nav-toggle.is-open span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
    .js .nav-toggle.is-open span:nth-child(2) { opacity: 0; }
    .js .nav-toggle.is-open span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

    /* Links klappen als Panel unter der Leiste auf */
    .js .topbar-nav {
        position: absolute;
        top: 100%;
        left: 0;
        right: 0;
        display: none;
        flex-direction: column;
        align-items: stretch;
        gap: var(--sp-1);
        padding: var(--sp-3) var(--sp-4) var(--sp-4);
        /* Deckend – ein halbdurchsichtiges Menü über dem Hintergrundbild
           wird schnell unleserlich. */
        background: var(--surface);
        border-bottom: 1px solid var(--surface-2);
        box-shadow: var(--shadow-2);
        max-height: calc(100vh - 64px);
        overflow-y: auto;
    }
    .js .topbar-nav.is-open { display: flex; }
    .js .topbar-nav .topbar-link {
        padding: var(--sp-3) var(--sp-2);
        border-radius: var(--radius-sm);
        font-size: var(--fs-md);
    }
    .js .topbar-nav .topbar-link:hover { background: var(--tint-2); }
    .js .topbar-nav .user-name { display: block; padding: var(--sp-1) var(--sp-2); }
    .js .topbar-nav .pillen-eingefasst { align-self: flex-start; flex-wrap: wrap; }
    .js .topbar-nav > form { display: block; margin-top: var(--sp-2); }
    .js .topbar-nav > form .btn { width: 100%; }
}

/* Mobile */
@media (max-width: 599.98px) {
    .topbar { padding: var(--sp-3) var(--sp-4); }
    .brand { letter-spacing: .2em; font-size: var(--fs-md); }
    .user-name { display: none; }
    .container { padding: var(--sp-5) var(--sp-4) var(--sp-7); }

    .card { padding: var(--sp-4); overflow-x: auto; }
    .data-table { min-width: 480px; }

    /* Tabellen als Karten: eine Karte je Zeile, Spaltenname links.
       `data-label` setzt dashboard.js aus dem Tabellenkopf – ohne JS
       greifen die beiden Regeln oben (Seitwärts-Scroll wie bisher). */
    .js .card:has(> .data-table.has-labels),
    .js .card:has(> * > .data-table.has-labels) { overflow-x: visible; }
    .js .data-table.has-labels { min-width: 0; display: block; }
    .js .data-table.has-labels thead { display: none; }
    .js .data-table.has-labels tbody { display: block; }
    .js .data-table.has-labels tr {
        display: block;
        background: var(--surface-raised);
        border: 1px solid var(--surface-2);
        border-radius: var(--radius-sm);
        padding: var(--sp-1) var(--sp-3);
        margin-bottom: var(--sp-3);
    }
    /* Reine Kopfzeile ohne <thead> (einige Templates) ausblenden */
    .js .data-table.has-labels tr:not(:has(td)) { display: none; }
    /* Spaltenname über dem Wert statt daneben: Zellen enthalten hier auch
       Formulare, Buttons und mehrzeilige Inhalte – gestapelt bleibt das in
       jedem Fall lesbar. */
    .js .data-table.has-labels td {
        display: block;
        width: auto !important;
        padding: var(--sp-2) 0;
        text-align: left !important;
        border-bottom: 1px solid var(--surface-2);
    }
    .js .data-table.has-labels tr td:last-child { border-bottom: none; }
    .js .data-table.has-labels td[data-label]::before {
        content: attr(data-label);
        display: block;
        margin-bottom: var(--sp-1);
        color: var(--muted);
        font-size: var(--fs-2xs);
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: .06em;
    }
    /* Aktions-Spalten (meist ohne Überschrift) bleiben rechtsbündig */
    .js .data-table.has-labels td:not([data-label]) .row-actions,
    .js .data-table.has-labels td:not([data-label]) .actions-cell { justify-content: flex-end; }

    .status-card { flex-wrap: wrap; padding: var(--sp-4); }
    .status-metrics { margin-left: 0; width: 100%; }

    .guild-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
    .guild-card { padding: var(--sp-4) var(--sp-3) var(--sp-4); }
    .guild-cta { font-size: var(--fs-xs); padding: var(--sp-2); }
    .guild-nav { gap: var(--sp-1); }
    .guild-nav a { padding: var(--sp-2) var(--sp-3); font-size: var(--fs-sm); }

    .save-bar .btn-lg { width: 100%; }
    .error-code { font-size: 4.5rem; }

    .embed-grid { grid-template-columns: 1fr; }
    .embed-preview-wrap { position: static; }
}

/* Embed-Editor */
.embed-grid {
    display: grid;
    grid-template-columns: 1fr minmax(280px, 380px);
    gap: var(--sp-5);
    align-items: start;
}
.embed-preview-wrap { position: sticky; top: 20px; }
.embed-preview-wrap .field-help { display: block; margin-bottom: var(--sp-2); }
.embed-preview {
    background: var(--surface);
    border: 1px solid var(--surface-2);
    border-left: 4px solid var(--surface-2);
    border-radius: var(--radius-sm);
    padding: var(--sp-4);
    min-height: 60px;
}
.embed-preview-title {
    font-weight: 700;
    color: var(--text);
    margin-bottom: var(--sp-2);
    word-break: break-word;
}
.embed-preview-desc {
    color: #dcddde;
    font-size: var(--fs-sm);
    line-height: 1.5;
    white-space: pre-wrap;
    word-break: break-word;
}
.embed-preview-img {
    display: block;
    max-width: 100%;
    border-radius: var(--radius-sm);
    margin-top: var(--sp-3);
}
.embed-preview-footer {
    color: var(--muted);
    font-size: var(--fs-xs);
    margin-top: var(--sp-3);
}
.row-actions { display: flex; gap: var(--sp-2); align-items: center; justify-content: flex-end; }
.row-actions form { margin: 0; }
.embed-inputs input[disabled] { opacity: .6; cursor: not-allowed; }
.embed-inputs textarea#description { min-height: 300px; resize: vertical; }

/* Custom-Commands */
.cmd-form { margin-top: var(--sp-2); }
.cmd-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); }
.cmd-embed-fields, .cmd-text-fields { display: grid; gap: var(--sp-4); }
@media (max-width: 599.98px) {
    .cmd-row { grid-template-columns: 1fr; }
}

/* -------- RMM-Monitoring -------- */
.rmm-dot {
    width: 12px; height: 12px; border-radius: 50%;
    background: var(--muted); flex: none; display: inline-block;
}
.rmm-dot.on { background: var(--success); box-shadow: 0 0 10px var(--success); }
.rmm-dot.off { background: var(--danger); box-shadow: 0 0 8px rgba(239, 68, 68, .6); }

.rmm-banner {
    display: flex; align-items: center; gap: var(--sp-4);
    padding: var(--sp-4) var(--sp-5); border-radius: var(--radius-lg);
    background: var(--surface); border: 1px solid var(--surface-2);
    border-left: 5px solid var(--success); margin-bottom: var(--sp-2);
}
.rmm-banner.alert { border-left-color: var(--danger); }
.rmm-banner h2 { margin: 0; font-size: var(--fs-xl); }
.rmm-banner p { margin: var(--sp-0) 0 0; }

.rmm-alertbar {
    display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap;
    margin: var(--sp-3) var(--sp-0) 0; font-size: var(--fs-sm);
}
.rmm-alertbar form { margin: 0 0 0 auto; }
.rmm-alertbar code {
    background: var(--surface-sunken); border: 1px solid var(--surface-2);
    border-radius: var(--radius-xs); padding: 1px var(--sp-2);
}

.rmm-section { margin: var(--sp-6) 0 var(--sp-3); font-size: var(--fs-lg); }

.rmm-grid {
    display: grid; gap: var(--sp-4);
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}
.rmm-card {
    background: var(--surface); border: 1px solid var(--surface-2);
    border-radius: var(--radius-lg); padding: var(--sp-4);
    display: flex; flex-direction: column; gap: var(--sp-3);
}
.rmm-card.is-down { border-color: var(--danger-border); }
.rmm-card-head { display: flex; align-items: center; gap: var(--sp-3); }
.rmm-card-title { flex: 1; min-width: 0; }
.rmm-card-title strong { display: block; }
.rmm-card-title .muted { font-size: var(--fs-xs); }

.rmm-metrics {
    display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--sp-2) var(--sp-4);
    font-size: var(--fs-sm);
}
.rmm-metrics .muted { display: block; font-size: var(--fs-2xs); }

.rmm-timeline {
    display: flex; gap: 1px; height: 22px; border-radius: var(--radius-xs); overflow: hidden;
}
.rmm-timeline .seg { flex: 1 1 auto; }
.rmm-timeline .seg-up { background: var(--success); }
.rmm-timeline .seg-down { background: var(--danger); }
.rmm-timeline .seg-none { background: var(--surface-2); }

.rmm-actions { display: flex; gap: var(--sp-2); }
.rmm-actions form { margin: 0; }

.rmm-panels {
    display: grid; gap: var(--sp-4); margin-top: var(--sp-5);
    grid-template-columns: 1fr 1fr;
}
@media (max-width: 899.98px) { .rmm-panels { grid-template-columns: 1fr; } }

.rmm-errorlist { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-2); }
.rmm-errorlist li {
    display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-xs);
    padding-bottom: var(--sp-2); border-bottom: 1px solid var(--surface-2);
}
.rmm-errorlist li:last-child { border-bottom: none; }
.rmm-err-inst { color: var(--muted); font-weight: 700; white-space: nowrap; }
.rmm-errorlist code {
    color: #dcddde; overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap; flex: 1; min-width: 0;
}

/* Ticket-Antworten aus dem Dashboard */
.ticket-list-actions { display: flex; gap: var(--sp-2); justify-content: flex-end; }
.ticket-chat {
    display: flex; flex-direction: column; gap: var(--sp-3);
    max-height: 55vh; overflow-y: auto; padding-right: var(--sp-1);
}
.ticket-msg {
    background: var(--surface-raised); border: 1px solid var(--surface-2);
    border-radius: var(--radius-sm); padding: var(--sp-3);
}

/* -------------------------------------------------------------
   Transkript: der gespeicherte Verlauf

   Stand als zweiter `<style>`-Block im Template (`transcript.html`) –
   dieselbe Geschichte wie bei der Mod-Queue: 14 px neben 12 px neben
   8 px, `0.8rem` neben der Schriftskala, und von keiner Prüfung erfasst.

   Es bleibt bewusst eine eigene Darstellung neben `.ticket-msg`: dort
   ist jede Nachricht eine Blase in einem Gespräch, an dem man teilnimmt,
   hier ein Protokoll mit Avatar-Spalte, das man liest. Die Werte kommen
   jetzt aus derselben Skala.
   ------------------------------------------------------------- */
.ts-log { display: flex; flex-direction: column; gap: var(--sp-4); margin-top: var(--sp-3); }
.ts-msg { display: flex; gap: var(--sp-3); align-items: flex-start; }
.ts-body { min-width: 0; flex: 1; }
.ts-head { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-0); }
.ts-author { font-weight: 600; }
.ts-time { font-size: var(--fs-xs); }
.ts-content { white-space: pre-wrap; word-wrap: break-word; }
.ts-attach-img {
    display: block; max-width: 320px; max-height: 240px;
    border-radius: var(--radius-sm); margin-top: var(--sp-2);
}
.ts-attach { display: inline-block; margin-top: var(--sp-1); }
.ticket-msg.is-bot { border-color: var(--primary); }
.ticket-msg-head {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--sp-3); margin-bottom: var(--sp-1); font-size: var(--fs-sm);
}
.ticket-msg-author { font-weight: 700; }
.ticket-msg-body { white-space: pre-wrap; word-break: break-word; }
.ticket-att { max-width: 260px; max-height: 200px; border-radius: var(--radius-sm); margin-top: var(--sp-2); }
.ticket-att-link { display: inline-block; margin-top: var(--sp-2); color: var(--secondary); }

/* Büroklammer vor einem Dateinamen. Als Maske aus dem Stylesheet statt als
   Zeichen im Text: `ticket_view.js` baut dieselben Zeilen nach und setzt sie
   bewusst über `textContent` – dort kann kein SVG stehen. Die Maske nimmt
   `currentColor` an, das Zeichen hat also immer die Farbe der Zeile. */
.ticket-att-link::before,
.ticket-status-att::before,
.ts-attach::before {
    content: "";
    display: inline-block;
    width: 1em;
    height: 1em;
    margin-right: var(--sp-2);
    vertical-align: -.16em;
    background: currentColor;
    -webkit-mask: var(--icon-paperclip) center / contain no-repeat;
    mask: var(--icon-paperclip) center / contain no-repeat;
}
.ticket-reply-form { display: flex; flex-direction: column; gap: var(--sp-2); }
.ticket-reply-row { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
.ticket-file { font-size: var(--fs-sm); color: var(--muted); max-width: 60%; }
.ticket-status { margin-top: var(--sp-4); border-top: 1px solid var(--surface-2); padding-top: var(--sp-4); }
.ticket-status h3 { font-size: var(--fs-md); margin-bottom: var(--sp-3); }
.ticket-status-list { list-style: none; display: flex; flex-direction: column; gap: var(--sp-2); }
.ticket-status-item {
    background: var(--surface-raised); border: 1px solid var(--surface-2);
    border-left-width: 3px; border-radius: var(--radius-sm); padding: var(--sp-2) var(--sp-3);
}
.ticket-status-item.is-done { border-left-color: var(--success); }
.ticket-status-item.is-failed { border-left-color: var(--danger); }
.ticket-status-item.is-pending { border-left-color: var(--warning); }
.ticket-status-line {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--sp-3); font-size: var(--fs-xs); margin-bottom: var(--sp-1);
}
/* Der Zustellstatus einer Ticket-Antwort meint dasselbe wie die Mod-Queue
   („gesendet / fehlgeschlagen / wartet") und trug dafür bis hierher einen
   eigenen Satz Klassen. Er nimmt jetzt das Badge-System; nur der Abstand
   in der Zeile bleibt hier. */
.ticket-status-msg { white-space: pre-wrap; word-break: break-word; font-size: var(--fs-sm); }
.ticket-status-att { font-size: var(--fs-sm); color: var(--secondary); margin-top: var(--sp-1); }
.ticket-status-error { color: var(--danger-text); font-size: var(--fs-xs); margin-top: var(--sp-1); }
.ticket-reply-form textarea {
    flex: 1; width: 100%; padding: var(--sp-3);
    background: var(--surface-sunken); border: 1px solid var(--surface-2);
    border-radius: var(--radius-sm); color: var(--text);
    font: inherit; font-size: var(--fs-sm); resize: vertical;
}
.ticket-reply-form textarea:focus {
    outline: none; border-color: var(--primary);
    box-shadow: var(--focus-ring);
}

/* -------------------------
   Mitglieder-Ansicht
   ------------------------- */
.member-head { display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap; }
.member-head .avatar { border: 2px solid var(--surface-2); }
.member-ident { min-width: 0; }
.member-ident h2 { margin: 0 0 var(--sp-1); }
.member-ident p { margin: var(--sp-0) 0; }

/* Rollen-/Chip-Listen (Mitglied, Selfrole-Panels) */
.role-chips { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-2); }
.chip {
    background: var(--surface-2); border-radius: var(--radius-pill);
    padding: var(--sp-1) var(--sp-3); font-size: var(--fs-xs); color: var(--muted); white-space: nowrap;
}

/* Panel-Bearbeitung ausklappen (nur in Karten – das FAQ der Landingpage
   bringt seine eigene Optik mit) */
.card > details > summary {
    cursor: pointer; font-size: var(--fs-sm); font-weight: 600;
    color: var(--muted); margin-top: var(--sp-3);
}
.card > details > summary:hover { color: var(--text); }
.card > details[open] > summary { margin-bottom: var(--sp-3); }
.stack-form select[multiple] { min-height: 160px; }

/* -------------------------------------------------------------
   Topbar-Menüs (Verwaltung, Konto)
   Bewusst <details>/<summary>: das klappt ohne JavaScript nativ auf.
   dashboard.js schließt die Menüs nur zusätzlich bei Klick daneben
   oder mit Escape.
   ------------------------------------------------------------- */
.topmenu { position: relative; }
.topmenu > summary {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    cursor: pointer;
    list-style: none;
    color: var(--muted);
    font-size: var(--fs-sm);
    font-weight: 700;
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--radius-sm);
    border: 1px solid transparent;
    white-space: nowrap;
}
.topmenu > summary::-webkit-details-marker { display: none; }
/* Eigener Pfeil statt des Browser-Dreiecks – dreht sich beim Öffnen */
.topmenu > summary::after {
    content: "";
    width: 6px; height: 6px;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: translateY(-2px) rotate(45deg);
    transition: transform var(--motion-fast) var(--ease);
}
.topmenu[open] > summary::after { transform: translateY(1px) rotate(-135deg); }
.topmenu > summary:hover { color: var(--text); border-color: var(--surface-2); }
.topmenu[open] > summary { color: var(--text); border-color: var(--surface-2); }

.topmenu-panel {
    display: flex;
    flex-direction: column;
    gap: var(--sp-0);
    padding: var(--sp-2);
    min-width: 190px;
}
.topmenu-panel .topbar-link {
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--radius-sm);
    font-size: var(--fs-sm);
}
.topmenu-panel .topbar-link:hover { background: var(--tint-2); }
.topmenu-label {
    color: var(--muted);
    font-size: var(--fs-2xs);
    font-weight: 800;
    letter-spacing: .1em;
    text-transform: uppercase;
    padding: var(--sp-3) var(--sp-3) var(--sp-1);
}
.topmenu-panel .lang-switch { align-self: flex-start; margin: 0 var(--sp-3); }
.topmenu-panel > form { margin-top: var(--sp-2); }
.topmenu-panel > form .btn { width: 100%; }

/* -------------------------------------------------------------
   Server-Wechsler neben der Marke
   Sitzt bewusst links, nicht im Burger-Panel: er sagt zugleich, auf
   welchem Server man gerade ist. Das Panel öffnet nach links (der
   Knopf steht am linken Rand, ein rechtsbündiges Menü liefe aus dem
   Bild) und scrollt – wer 30 Server betreut, bekommt sonst eine
   Liste über die ganze Höhe.
   ------------------------------------------------------------- */
.guild-switch { margin-right: auto; margin-left: var(--sp-4); }
.guild-switch > summary { color: var(--text); }
.guild-switch-name {
    max-width: 200px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.guild-switch-list {
    display: flex;
    flex-direction: column;
    gap: var(--sp-0);
    max-height: min(52vh, 380px);
    overflow-y: auto;
}
.guild-switch-all {
    margin-top: var(--sp-2);
    padding-top: var(--sp-2);
    border-top: 1px solid var(--surface-2);
    color: var(--link);
}
/* Das Panel schwebt in jeder Breite. Die Regel weiter oben gilt erst ab
   901 px, weil die übrigen Topbar-Menüs darunter im aufgeklappten Burger
   liegen und dort im Fluss bleiben sollen – der Wechsler liegt nie im
   Burger, sein Panel würde sonst die Leiste auseinanderdrücken. */
.guild-switch[open] > .topmenu-panel {
    position: absolute;
    top: calc(100% + 8px);
    left: 0;
    right: auto;
    z-index: 60;
    min-width: 220px;
    background: var(--surface);
    border: 1px solid var(--surface-2);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-2);
}
/* Auf dem Handy bleibt nur das Zeichen: der Name frisst sonst die halbe
   Leiste, und daneben müssen noch Suchknopf und Burger hin. */
@media (max-width: 599.98px) {
    .guild-switch-name { display: none; }
    .guild-switch { margin-left: var(--sp-2); }
}

/* Das Kürzel des Nutzers ersetzt auf schmalen Fenstern den Namen; es ist
   dieselbe Fassung wie die Kachel eines Servers ohne Bild, nur klein. */

/* Ab Tablet-Breite schwebt das Menü unter der Leiste. Darunter liegt es
   im aufgeklappten Burger-Panel und bleibt einfach im Fluss. */
@media (min-width: 900px) {
    .topmenu[open] > .topmenu-panel {
        position: absolute;
        top: calc(100% + 8px);
        right: 0;
        z-index: 60;
        background: var(--surface);
        border: 1px solid var(--surface-2);
        border-radius: var(--radius-lg);
        box-shadow: var(--shadow-2);
    }
}
@media (max-width: 899.98px) {
    .js .topbar-nav .topmenu { width: 100%; }
    .js .topbar-nav .topmenu > summary { justify-content: space-between; width: 100%; }
    .js .topbar-nav .topmenu-panel { padding: var(--sp-1) 0 var(--sp-2) var(--sp-3); }
}

/* -------------------------------------------------------------
   Befehlspalette (Strg + K)
   Ohne JavaScript nutzlos: der Auslöser hängt deshalb an `html.js`,
   die Ziele bleiben über die normale Navigation erreichbar.
   ------------------------------------------------------------- */
.palette-trigger { display: none; }
.js .palette-trigger {
    /* Schiebt alles Nachfolgende an den rechten Rand – der Knopf sitzt
       direkt neben dem Logo, nicht im Burger-Panel. */
    margin: 0 auto 0 var(--sp-4);
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-3);
    border: 1px solid var(--surface-2);
    border-radius: var(--radius-pill);
    background: var(--tint-2);
    color: var(--muted);
    font: inherit;
    font-size: var(--fs-sm);
    font-weight: 600;
    cursor: pointer;
    transition: border-color var(--motion-fast) var(--ease), color var(--motion-fast) var(--ease);
}
.js .palette-trigger:hover { border-color: var(--primary); color: var(--text); }
/* Lupe rein aus CSS – kein zusätzliches Bild für ein 14-px-Symbol */
.palette-trigger-icon {
    width: 11px; height: 11px;
    border: 2px solid currentColor;
    border-radius: 50%;
    position: relative;
    flex: none;
}
.palette-trigger-icon::after {
    content: "";
    position: absolute;
    right: -4px; bottom: -3px;
    width: 6px; height: 2px;
    background: currentColor;
    transform: rotate(45deg);
    border-radius: var(--radius-xs);
}
.palette-trigger-key {
    font: inherit;
    font-size: var(--fs-2xs);
    font-weight: 700;
    letter-spacing: .06em;
    color: var(--muted);
    background: var(--surface-2);
    border-radius: var(--radius-sm);
    padding: var(--sp-0) var(--sp-2);
}

.palette-backdrop {
    position: fixed;
    inset: 0;
    z-index: 100;
    display: flex;
    justify-content: center;
    align-items: flex-start;
    padding: 12vh var(--sp-4) var(--sp-4);
    background: rgba(6, 6, 12, .68);
    backdrop-filter: blur(3px);
}
.palette-backdrop[hidden] { display: none; }
body.palette-open { overflow: hidden; }

.palette {
    width: min(620px, 100%);
    max-height: 70vh;
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border: 1px solid var(--surface-2);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-3);
    overflow: hidden;
}
.palette-head {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-4);
    border-bottom: 1px solid var(--surface-2);
}
/* Zeigt beim Eintauchen, in welchem Server man gerade sucht */
.palette-crumb {
    flex: none;
    max-width: 40%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    background: var(--surface-2);
    color: var(--text);
    font-size: var(--fs-xs);
    font-weight: 700;
    padding: var(--sp-1) var(--sp-3);
    border-radius: var(--radius-pill);
}
.palette-input {
    flex: 1;
    min-width: 0;
    border: none;
    background: none;
    color: var(--text);
    font: inherit;
    font-size: var(--fs-lg);
    padding: var(--sp-3) 0;
    outline: none;
}
.palette-input::placeholder { color: var(--muted); }

.palette-list {
    list-style: none;
    margin: 0;
    padding: var(--sp-2);
    overflow-y: auto;
    flex: 1;
}
.palette-group {
    color: var(--muted);
    font-size: var(--fs-2xs);
    font-weight: 800;
    letter-spacing: .12em;
    text-transform: uppercase;
    padding: var(--sp-3) var(--sp-3) var(--sp-1);
}
.palette-item {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--radius-sm);
    cursor: pointer;
}
.palette-item.is-active { background: var(--primary-bg-strong); }
.palette-item-label { font-weight: 600; }
.palette-item-sub {
    color: var(--muted);
    font-size: var(--fs-xs);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.palette-item-key {
    margin-left: auto;
    flex: none;
    color: var(--muted);
    font-size: var(--fs-2xs);
    font-weight: 700;
    background: var(--surface-2);
    border-radius: var(--radius-sm);
    padding: var(--sp-0) var(--sp-2);
    opacity: 0;
}
.palette-item.is-active .palette-item-key { opacity: 1; }
.palette-note { color: var(--muted); padding: var(--sp-4) var(--sp-3); font-size: var(--fs-sm); }

.palette-foot {
    border-top: 1px solid var(--surface-2);
    padding: var(--sp-2) var(--sp-4);
    color: var(--muted);
    font-size: var(--fs-2xs);
}

@media (max-width: 599.98px) {
    /* Auf dem Handy zählt der Platz: nur die Lupe, kein Text, kein Kürzel */
    .js .palette-trigger-label,
    .js .palette-trigger-key { display: none; }
    .palette-backdrop { padding: 8vh var(--sp-3) var(--sp-3); }
    .palette-foot { display: none; }
}

/* -------------------------------------------------------------
   Tastenkürzel-Übersicht (Taste ?, shortcuts.js)
   Entsteht erst im Browser. Der Link in der Fußzeile hängt deshalb
   an `html.js` – ohne JavaScript öffnete er nichts.
   ------------------------------------------------------------- */
.footer-keys { display: none; }
.js .footer-keys { display: inline; color: var(--muted); }
.js .footer-keys:hover { color: var(--link); }

kbd {
    font: inherit;
    font-size: var(--fs-xs);
    font-weight: 700;
    line-height: 1;
    color: var(--muted);
    background: var(--surface-2);
    border-radius: var(--radius-sm);
    padding: var(--sp-1) var(--sp-2);
    white-space: nowrap;
}

.keys-backdrop {
    position: fixed;
    inset: 0;
    z-index: 100;
    display: flex;
    justify-content: center;
    align-items: flex-start;
    padding: 10vh var(--sp-4) var(--sp-4);
    background: rgba(6, 6, 12, .68);
    backdrop-filter: blur(3px);
}
.keys-backdrop[hidden] { display: none; }
body.keys-open { overflow: hidden; }

.keys-box {
    width: min(520px, 100%);
    max-height: 76vh;
    overflow-y: auto;
    background: var(--surface);
    border: 1px solid var(--surface-2);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-3);
    padding: var(--sp-1) var(--sp-5) var(--sp-5);
}
/* Im Überblendfenster bleibt der Kopf beim Scrollen stehen – sonst wäre
   beim Blättern durch die Kürzel nicht mehr zu sehen, worin man blättert. */
.card-head-fix {
    align-items: center;
    margin-bottom: 0;
    position: sticky;
    top: 0;
    background: var(--surface);
    padding: var(--sp-4) 0 var(--sp-2);
}
.card-head-fix h2 { font-size: var(--fs-lg); }
.keys-close {
    background: none;
    border: 0;
    color: var(--muted);
    font-size: var(--fs-xl);
    line-height: 1;
    cursor: pointer;
    padding: 0 var(--sp-1);
}
.keys-close:hover { color: var(--text); }
.keys-group {
    color: var(--muted);
    font-size: var(--fs-2xs);
    font-weight: 800;
    letter-spacing: .1em;
    text-transform: uppercase;
    margin: var(--sp-4) 0 var(--sp-2);
}
.keys-list { list-style: none; margin: 0; padding: 0; }
.keys-row {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-2) 0;
    border-bottom: 1px solid var(--surface-2);
}
.keys-row:last-child { border-bottom: 0; }
.keys-combo {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    flex: none;
    min-width: 108px;
}
.keys-sep { color: var(--muted); font-size: var(--fs-2xs); }
.keys-label { font-size: var(--fs-sm); }
.keys-note { color: var(--muted); font-size: var(--fs-sm); margin: var(--sp-4) 0 0; }

/* -------------------------------------------------------------
   Werkzeugleiste langer Tabellen (table_tools.js)
   Suchfeld und sortierbare Spaltenköpfe. Beides entsteht erst im
   Browser – ohne JavaScript bleibt die Tabelle wie bisher.
   ------------------------------------------------------------- */
.table-tools {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    flex-wrap: wrap;
    margin-bottom: var(--sp-3);
}
.table-filter {
    flex: 1;
    min-width: 180px;
    max-width: 320px;
    padding: var(--sp-2) var(--sp-3);
    border: 1px solid var(--surface-2);
    border-radius: var(--radius-sm);
    background: var(--tint-2);
    color: var(--text);
    font: inherit;
    font-size: var(--fs-sm);
}
.table-filter:focus { outline: none; border-color: var(--primary); }
.table-count { color: var(--muted); font-size: var(--fs-xs); font-weight: 600; }
.table-empty { margin: var(--sp-3) 0 0; }
/* Wichtig gegenüber der Karten-Ansicht auf dem Handy, die den Zeilen
   selbst ein `display` gibt. */
.table-row-hidden { display: none !important; }

/* -------------------------------------------------------------
   Suchleiste über einer blätternden Liste (`_suche.html`)
   Sieht aus wie die Werkzeugleiste darüber, ist aber etwas anderes:
   ein echtes Formular, das den Server fragt und über alle Seiten
   sucht. Deshalb steht sie auch ohne JavaScript da.
   ------------------------------------------------------------- */
.listensuche {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    flex-wrap: wrap;
    margin-bottom: var(--sp-4);
}
.listensuche-feld {
    flex: 1;
    min-width: 180px;
    max-width: 320px;
    padding: var(--sp-2) var(--sp-3);
    border: 1px solid var(--surface-2);
    border-radius: var(--radius-sm);
    background: var(--tint-2);
    color: var(--text);
    font: inherit;
    font-size: var(--fs-sm);
}
.listensuche-feld:focus { border-color: var(--primary); }
.listensuche-treffer {
    color: var(--muted);
    font-size: var(--fs-xs);
    font-weight: 600;
}
/* Auf dem Handy darf das Feld die ganze Breite haben – die Knöpfe rutschen
   darunter, statt sich mit dem Feld um 60 px zu streiten. */
@media (max-width: 599.98px) {
    .listensuche-feld { max-width: none; flex-basis: 100%; }
}

/* -------------------------------------------------------------
   Filterleisten über einer blätternden Liste (`_filter.html`)
   Zeitraum als Knopfreihe, größere Auswahlen als Auswahlfeld.
   Gefiltert wird serverseitig – die Leiste steht deshalb auch
   ohne JavaScript da und tut, was sie soll.
   ------------------------------------------------------------- */
.filterleiste {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    flex-wrap: wrap;
    margin-bottom: var(--sp-3);
}
.filterleiste-titel {
    color: var(--muted);
    font-size: var(--fs-xs);
    font-weight: 700;
    /* Etwas Abstand zur ersten Möglichkeit: die Beschriftung gehört zur
       Reihe, ist aber kein Teil von ihr. */
    margin-right: var(--sp-0);
}
/* Der Zeitraum über einer Liste ist dieselbe Pillenreihe wie die der
   Statistik – nur war die aktive Pille hier blau statt violett. Zwei
   Farben für „das ist gerade gewählt" lasen sich wie zwei Bedeutungen. */
.filter-auswahl {
    padding: var(--sp-2) var(--sp-3);
    border: 1px solid var(--surface-2);
    border-radius: var(--radius-sm);
    background: var(--tint-2);
    color: var(--text);
    font: inherit;
    font-size: var(--fs-sm);
    max-width: 280px;
}
.filter-auswahl:focus { border-color: var(--primary); }

/* -------------------------------------------------------------
   CSV-Knopf unter einer Liste (`_export.html`)
   Steht unter der Tabelle neben der Blätterleiste: erst sehen,
   was dasteht, dann mitnehmen.
   ------------------------------------------------------------- */
.export-zeile {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    flex-wrap: wrap;
    margin-top: var(--sp-4);
}
.export-hinweis { font-size: var(--fs-xs); }

/* -------------------------------------------------------------
   Mehrfachauswahl in Listen (`_sammelaktion.html`)
   ------------------------------------------------------------- */
.bulk-col { width: 34px; text-align: center; }
.bulk-check { width: 16px; height: 16px; accent-color: var(--primary); cursor: pointer; }
.bulk-bar {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    flex-wrap: wrap;
    margin-top: var(--sp-4);
    padding: var(--sp-3) var(--sp-4);
    border: 1px solid var(--surface-2);
    border-radius: var(--radius-sm);
    background: var(--tint-1);
}
.bulk-all {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    font-size: var(--fs-sm);
    font-weight: 600;
    cursor: pointer;
}
.bulk-all input { width: 16px; height: 16px; accent-color: var(--primary); }
.bulk-count { color: var(--muted); font-size: var(--fs-xs); font-weight: 700; }
/* Ohne JavaScript steht die Leiste immer da – dann gibt es weder eine
   Zählung noch ein „alle auswählen", und ein verstecktes Formular wäre
   schlicht nicht bedienbar. Mit JavaScript erscheint sie erst, wenn etwas
   ausgewählt ist. */
html.js .bulk-bar { display: none; }
html.js .bulk-bar.is-active { display: flex; }
/* In der Mod-Queue sitzt das Kästchen in der Kopfzeile einer Karte, nicht
   in einer Tabellenspalte. */
.case-top .bulk-check { margin-right: var(--sp-0); }

/* Blätterleiste unter langen Listen (`_pager.html`).
   Links, keine Knöpfe: eine Seite ist eine Adresse – sie soll sich
   lesezeichnen, in einem neuen Tab öffnen und zurücknavigieren lassen. */
.pager {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--sp-3) var(--sp-4);
    margin-top: var(--sp-4);
    padding-top: var(--sp-4);
    border-top: 1px solid var(--surface-2);
}
.pager-info { color: var(--muted); font-size: var(--fs-sm); font-weight: 600; }
.pager-pages { display: flex; align-items: center; gap: var(--sp-1); flex-wrap: wrap; }
.pager-num, .pager-step {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 34px;
    height: 34px;
    padding: 0 var(--sp-3);
    border-radius: var(--radius-sm);
    border: 1px solid transparent;
    color: var(--muted);
    font-size: var(--fs-sm);
    font-weight: 700;
    transition: color var(--motion-fast) var(--ease), background var(--motion-fast) var(--ease), border-color var(--motion-fast) var(--ease);
}
.pager-num:hover, .pager-step:hover {
    color: var(--text);
    background: var(--surface-2);
}
.pager-num.is-current {
    background: var(--grad-text);
    border-color: transparent;
    color: #fff;
}
/* „Zurück“ auf der ersten Seite bleibt stehen, statt zu verschwinden –
   sonst rutschen die Zahlen bei jedem Blättern eine Position weiter. */
.pager-step.is-off { opacity: .35; }
.pager-step.is-off:hover { color: var(--muted); background: none; }
.pager-gap { color: var(--muted); padding: 0 var(--sp-0); }

/* Zeitangaben (`zeit_element` in app.py, relativ gemacht von timeago.js).
   Kein Umbruch: „vor 5 Minuten“ mitten entzweigerissen liest sich schlecht.
   Die gepunktete Linie gibt es erst, wenn wirklich etwas im Titel steht, das
   nicht schon dasteht – also nur in der relativen Form. */
time.ts { white-space: nowrap; }
time.ts-relativ {
    cursor: help;
    border-bottom: 1px dotted currentColor;
}

/* Spaltenkopf als Knopf – dadurch auch mit der Tastatur bedienbar */
.th-sort {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    border: none;
    background: none;
    padding: 0;
    margin: 0;
    font: inherit;
    color: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
    cursor: pointer;
}
.th-sort:hover { color: var(--text); }
.th-sort:focus-visible { outline: 2px solid var(--primary); outline-offset: 3px; }
/* Pfeil: blass, solange nicht nach dieser Spalte sortiert wird */
.th-sort-arrow {
    width: 5px; height: 5px;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: translateY(-2px) rotate(45deg);
    opacity: .25;
}
.th-sort:hover .th-sort-arrow { opacity: .6; }
th[aria-sort="ascending"] .th-sort-arrow {
    opacity: 1;
    transform: translateY(1px) rotate(-135deg);
}
th[aria-sort="descending"] .th-sort-arrow { opacity: 1; }
th[aria-sort="ascending"] .th-sort,
th[aria-sort="descending"] .th-sort { color: var(--link); }

@media (max-width: 599.98px) {
    .table-filter { max-width: none; }
}

/* -------------------------------------------------------------
   Rückfrage vor heiklen Aktionen (confirm_dialog.js)
   Ersetzt window.confirm(): gleiche Schrift, gleiche Farben, und
   der Text passt zur Sprache der Seite. Ohne JavaScript gibt es
   keine Rückfrage – wie vorher, denn window.confirm() lief auch
   nur mit JavaScript.
   ------------------------------------------------------------- */
.confirm-backdrop {
    position: fixed;
    inset: 0;
    /* über der Befehlspalette (100), falls doch einmal beides offen ist */
    z-index: 110;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--sp-4);
    background: rgba(6, 6, 12, .68);
    backdrop-filter: blur(3px);
}
.confirm-backdrop[hidden] { display: none; }
body.confirm-open { overflow: hidden; }

.confirm-box {
    width: min(440px, 100%);
    background: var(--surface);
    border: 1px solid var(--surface-2);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-3);
    padding: var(--sp-5) var(--sp-5) var(--sp-4);
}
.confirm-title { font-size: var(--fs-lg); margin: 0 0 var(--sp-2); font-weight: 800; }
.confirm-text {
    margin: 0 0 var(--sp-5);
    color: var(--muted);
    font-size: var(--fs-md);
    /* Lange Namen ohne Leerzeichen (Befehls-Trigger) sprengen den Kasten nicht */
    overflow-wrap: anywhere;
}
.confirm-actions { display: flex; justify-content: flex-end; gap: var(--sp-3); }
.confirm-actions .btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 3px; }

@media (max-width: 599.98px) {
    /* Untereinander, bestätigen oben – der Daumen liegt näher am Abbrechen. */
    .confirm-actions { flex-direction: column-reverse; }
    .confirm-actions .btn { width: 100%; }
}

/* -------------------------
   Ladezustand beim Absenden (dashboard.js)
   Der geklickte Knopf zeigt, dass etwas läuft. Nur eine Zugabe:
   ohne JavaScript wird die Klasse nie gesetzt.
   ------------------------- */
.is-busy {
    /* Kein zweiter Klick – gesperrt wird zusätzlich in dashboard.js */
    pointer-events: none;
    opacity: .8;
}
.is-busy::after {
    content: "";
    display: inline-block;
    width: .78em;
    height: .78em;
    margin-left: .55em;
    vertical-align: -.08em;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    animation: busy-spin .6s linear infinite;
}
@keyframes busy-spin { to { transform: rotate(360deg); } }

/* -------------------------
   Sprung zum Inhalt
   Erster Tabstopp der Seite; sichtbar erst, wenn er den Fokus hat.
   ------------------------- */
.skip-link {
    position: absolute;
    left: 12px;
    z-index: 100;
    padding: var(--sp-3) var(--sp-4);
    border-radius: var(--radius-sm);
    background: var(--surface);
    border: 1px solid var(--primary);
    color: var(--text);
    font-size: var(--fs-sm);
    font-weight: 700;
    /* Aus dem Blickfeld, aber im Tab-Fluss und für Bildschirmleser da.
       Bewusst über `top` und nicht über `transform`: `display: none` oder
       `visibility: hidden` nähmen ihn aus der Tab-Reihenfolge, und `top`
       lässt sich – anders als eine Verschiebung – ohne laufende
       Bildberechnung nachmessen. */
    top: -100px;
    transition: top var(--motion-fast) var(--ease);
}
.skip-link:focus { top: 12px; }

/* -------------------------
   Ein Fokusring für alles Bedienbare
   Vorher verließ sich das Dashboard auf die Voreinstellung des
   Browsers – die sieht in jedem Browser anders aus und geht auf dem
   dunklen Hintergrund teils unter. Formularfelder behalten ihren
   eigenen Schimmer (weiter oben), hier kommen Knöpfe, Links und
   Menüs dazu.
   ------------------------- */
a:focus-visible,
button:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 3px;
    border-radius: var(--radius-xs);
}
/* Das Ziel des Sprunglinks ist nur zum Fokussieren da (tabindex="-1"),
   damit die Tastatur danach im Inhalt weiterläuft. Ein Rahmen um die
   ganze Seite wäre dabei nur verwirrend. */
#inhalt:focus, #inhalt:focus-visible { outline: none; }
/* Die Schnellsuche hatte gar keinen Hinweis auf den Fokus. Ein Ring um das
   Feld wäre hier Lärm – es bekommt den Fokus beim Öffnen von selbst. Statt
   dessen färbt sich die Trennlinie der Kopfzeile. */
.palette-input:focus-visible { outline: none; }
.palette-head:focus-within { border-bottom-color: var(--primary); }

/* -------------------------
   Weniger Bewegung, wenn das System darum bittet
   ------------------------- */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
        scroll-behavior: auto !important;
    }
    /* Ausnahme: der Ladezeiger ist die einzige Rückmeldung, dass etwas
       läuft – er dreht sich weiter, nur ruhiger. */
    .is-busy::after {
        animation: busy-spin 1.4s linear infinite !important;
    }
}

/* -------------------------
   Leerer Zustand (_empty.html)
   Ersetzt die frühere graue Zeile am Rand. Ruhig gehalten: in den
   meisten Fällen ist „nichts da" das gute Ergebnis (keine Warnungen,
   keine offenen Fälle) – das soll nicht wie ein Fehler aussehen.
   ------------------------- */
.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-3);
    text-align: center;
    padding: var(--sp-6) var(--sp-5) var(--sp-6);
    color: var(--muted);
}
.empty-state-icon {
    width: 38px;
    height: 38px;
    opacity: .4;
}
.empty-state-text {
    margin: 0;
    font-size: var(--fs-md);
    /* Bricht lange Erklärtexte auf eine angenehme Zeilenlänge um */
    max-width: 46ch;
}
/* Leisere zweite Zeile, z. B. „Tippfehler im Namen?" */
.empty-state-hint {
    margin: -6px 0 0;
    font-size: var(--fs-sm);
    opacity: .75;
    max-width: 46ch;
}
.empty-state .btn { margin-top: var(--sp-0); }

/* =========================================================
   Druck

   Im ganzen Stylesheet stand bis hierher kein einziges `@media print`.
   Wer eine Verwarnungsliste, ein Ticket-Transkript oder das Audit-Log
   ausdruckte oder als PDF ablegte, bekam die Topbar, die Server-
   Navigation, die Filterleiste und die Blätterleiste mit aufs Blatt –
   und je nach Browser entweder eine schwarze Seite oder, weil die
   meisten Hintergründe beim Drucken wegfallen, hellgraue Schrift auf
   weißem Papier. Also unlesbar in beide Richtungen.

   Gedruckt wird der Inhalt: Überschrift, Tabelle, Text. Alles, was der
   Bedienung dient – Knöpfe, Filter, Suche, Sortierköpfe, Menüs –, ist
   auf Papier ohne Funktion und fällt weg.

   Die Farben werden hier umgedreht statt einzeln überschrieben: seit die
   Marke in `tokens.css` steht und dieses Stylesheet danach geladen wird,
   genügt ein zweites `:root`. Das ist derselbe Griff, den ein
   Hell/Dunkel-Umschalter bräuchte – nur für Papier.
   ========================================================= */
@media print {
    :root {
        --bg: #fff;
        --surface: #fff;
        --surface-raised: #fff;
        --surface-sunken: #fff;
        --surface-2: #c8c8d0;
        --text: #000;
        --muted: #444;
        --link: #000;
        --border-soft: #d0d0d8;
        --shadow-1: none;
        --shadow-2: none;
        --shadow-3: none;
        --tint-1: transparent;
        --tint-2: transparent;
        --tint-3: transparent;
        --tint-4: transparent;
    }

    @page { margin: 16mm; }

    /* Der Schimmer bzw. das Foto liegt auf einer eigenen festen Ebene –
       ohne dieses `none` deckte sie das ganze Blatt ab. */
    body::before { display: none; }
    body { background: #fff; color: #000; }

    /* Bedienung: auf Papier ohne Funktion. */
    .topbar,
    .guild-nav,
    .footer,
    .skip-link,
    .flash-close,
    .listensuche,
    .filterleiste,
    .pager,
    .bulk-bar,
    .bulk-check,
    .undo-bar,
    .save-bar,
    .config-tools,
    .queue-reload,
    .export-zeile,
    .actions-cell,
    .btn,
    .palette-trigger,
    .palette-backdrop,
    .keys-backdrop,
    .confirm-backdrop {
        display: none !important;
    }

    /* Der Sortierkopf bleibt als Beschriftung stehen – nur das Pfeilchen
       und die Linkfarbe gehen. Ein Spaltenname darf nicht fehlen, bloß
       weil man ihn auf Papier nicht anklicken kann. */
    .th-sort { color: inherit; text-decoration: none; }
    .th-sort-arrow { display: none; }

    /* Die Seite bekommt ihre volle Breite: die Server-Navigation stand in
       einer eigenen Rasterspalte, die jetzt leer wäre. */
    .container {
        max-width: none;
        padding: 0;
        display: block;
    }

    .card {
        border: 1px solid var(--surface-2);
        box-shadow: none;
        /* Eine Karte soll nicht mitten in der Überschrift umbrechen. */
        break-inside: avoid;
        page-break-inside: avoid;
    }

    /* Der klebende Tabellenkopf hat auf Papier keinen Sinn – dafür soll
       er sich auf jeder Folgeseite wiederholen. */
    .data-table thead th {
        position: static;
        background: transparent;
        box-shadow: none;
        border-bottom: 1px solid var(--surface-2);
    }
    .data-table thead { display: table-header-group; }
    .data-table tr { break-inside: avoid; page-break-inside: avoid; }
    .data-table td, .data-table th { color: #000; }

    /* Links im Fließtext: Unterstreichung statt Farbe, sonst sind sie auf
       einem Schwarzweiß-Ausdruck von gewöhnlichem Text nicht zu trennen. */
    .card a { text-decoration: underline; }

    /* Die Linien der Charts tragen ihre Farbe als `stroke` – auf Papier
       ohne Hintergrund bleiben sie sichtbar, die Fläche darunter nicht.
       Das Raster wird kräftiger, sonst verschwindet es im Druck. */
    .chart-grid { stroke: #999; }
    .chart-axis { fill: #333; }
    .chart-tag { display: none; }

    /* Zustände tragen auf Papier keine Farbe mehr (Hintergründe fallen in
       den meisten Browsern weg) – die Kante bleibt und trägt sie. Seit
       alle Zustände `.badge` heißen, genügt eine Zeile; vorher standen
       hier drei der sechs Varianten, die anderen drei blieben bunt. */
    .badge { color: #000; }
}
