/* EventsManagement Admin — hand-written stylesheet */

/* ── Design tokens — shadcn/ui-style semantic system (neutral, OKLCH) ──────
   Surface/foreground PAIRS (dark-ready). The brand/accent colour is
   configurable: it defaults to the neutral primary and is overridden per-tenant
   by an injected `--brand` in the admin <head> (see appearance setting). All the
   legacy token names are kept as ALIASES at the bottom so existing rules work
   unchanged. NOTE: shadcn's `--accent` is a NEUTRAL hover fill (here
   `--shadcn-accent`); the brand/primary action colour is `--brand`. */
:root {
  --background:             oklch(1 0 0);
  --foreground:             oklch(0.145 0 0);
  --card:                   oklch(1 0 0);
  --card-foreground:        oklch(0.145 0 0);
  --popover:                oklch(1 0 0);
  --popover-foreground:     oklch(0.145 0 0);
  --primary:                oklch(0.205 0 0);
  --primary-foreground:     oklch(0.985 0 0);
  --secondary:              oklch(0.97 0 0);
  --secondary-foreground:   oklch(0.205 0 0);
  --muted:                  oklch(0.97 0 0);
  --muted-foreground:       oklch(0.556 0 0);
  --shadcn-accent:          oklch(0.97 0 0);   /* neutral hover fill (ghost/outline) */
  --shadcn-accent-foreground: oklch(0.205 0 0);
  --destructive:            oklch(0.577 0.245 27.325);
  --destructive-foreground: oklch(0.985 0 0);
  --border:                 oklch(0.922 0 0);
  --input:                  oklch(0.922 0 0);
  --ring:                   oklch(0.708 0 0);

  /* Brand / accent — configurable. Default = neutral primary; the admin's
     chosen colour is injected as `--brand` in @Base. Hover/ring derive from it. */
  --brand:            var(--primary);
  --brand-foreground: var(--primary-foreground);
  --brand-hover:      color-mix(in oklch, var(--brand) 88%, black);

  /* radius scale (shadcn default 0.625rem) */
  --radius:    0.625rem;
  --radius-sm: calc(var(--radius) - 4px);
  --radius-md: calc(var(--radius) - 2px);
  --radius-lg: var(--radius);

  /* sidebar (kept dark; token-driven → dark-ready). Slightly darker than the
     neutral --primary so a neutral-default active item stays visible. */
  --sidebar:            oklch(0.17 0 0);
  --sidebar-foreground: oklch(0.97 0 0);
  --sidebar-muted:      oklch(0.72 0 0);
  --sidebar-accent:     oklch(0.27 0 0);          /* hover fill */
  --sidebar-active:     oklch(0.32 0 0);          /* active fill (neutral default; overridden by the accent) */
  --sidebar-border:     oklch(1 0 0 / 10%);

  /* chrome dims — --topbar-height entfernt: die Kopfleiste wurde mit ADR 0013
     durch Sidebar + Breadcrumb ersetzt, das Token hatte keine Verwendung mehr. */
  --sidebar-w:     15.5rem;

  /* ── Typo-Skala ────────────────────────────────────────────────────────
     Es gab Tokens NUR für Farbe. Gezählt waren rund zwanzig verschiedene
     rem-Schriftgrössen, davon SECHS im Bereich 12.5–14px (0.78 / 0.8 / 0.8125 /
     0.82 / 0.85 / 0.875) ohne erkennbaren Unterschied in der Bedeutung — reine
     Drift. Neun Stufen decken alles ab; jede hat eine Aufgabe.
     Neuer Code nimmt diese Tokens, keine Rohwerte. */
  --text-xs:   0.75rem;    /* 12px — Tags, Gruppentitel, kbd            */
  --text-sm:   0.8125rem;  /* 13px — Hilfstexte, kleine Knöpfe, Spaltenköpfe */
  --text-md:   0.875rem;   /* 14px — Knöpfe, Eingaben, Untertitel       */
  --text-base: 0.9375rem;  /* 15px — Fliesstext, Tabellenzellen         */
  --text-lg:   1.0625rem;  /* 17px — Kartenüberschriften                */
  --text-xl:   1.25rem;    /* 20px                                      */
  --text-2xl:  1.5rem;     /* 24px — Seitenüberschrift                  */
  --text-3xl:  2rem;       /* 32px — Kennzahlen                         */
  --text-4xl:  2.5rem;     /* 40px — Fehlercode                         */

  /* ── Abstands-Skala (4-px-Basis) ───────────────────────────────────────
     Die Bestandswerte lagen quer dazu: 0.32 / 0.35 / 0.45 / 0.55 / 0.7 / 0.85rem
     nebeneinander, ohne dass ein Raster erkennbar wäre. */
  --space-1: 0.25rem;  /*  4px */
  --space-2: 0.5rem;   /*  8px */
  --space-3: 0.75rem;  /* 12px */
  --space-4: 1rem;     /* 16px */
  --space-5: 1.5rem;   /* 24px */
  --space-6: 2rem;     /* 32px */
  /* Eingerastet wurde, was die Skala EXAKT trifft (71 Deklarationen) — das
     ändert kein Pixel und macht die Mehrheit explizit. Rund 45 Werte dazwischen
     (0.3 / 0.35 / 0.4 / 0.6 / 0.85 / 1.25rem …) stehen bewusst noch roh da:
     sie blind auf die nächste Stufe zu ziehen wäre ein Layout-Eingriff auf
     zwanzig Seiten für einen Gewinn, den die Skala selbst schon liefert.
     Wer eine dieser Regeln ohnehin anfasst, zieht sie mit auf die Skala. */

  /* ── Ebenen ────────────────────────────────────────────────────────────
     Sieben harte Zahlen quer durch die Datei. Die Reihenfolge stimmte, aber sie
     stand nirgends — wer eine achte Ebene braucht, musste sie sich zusammensuchen. */
  --z-sticky:  50;   /* Breadcrumb-Leiste            */
  --z-popover: 60;   /* Benutzer-Menü                */
  --z-overlay: 100;  /* Dialog-Scrim                 */
  --z-dialog:  101;  /* Dialog                       */
  --z-cmdk:    150;  /* Kommandopalette (über Dialogen) */
  --z-toast:   200;  /* Toast                        */
  --z-skip:    300;  /* Sprungmarke, über allem      */

  /* motion — wurde von .seg als var(--dur-fast, .15s) benutzt, war aber nur in
     site.css definiert und fiel im Admin still auf den Fallback zurück. */
  --dur-fast: 0.15s;

  /* Status-Badges (hell): sehr helle Fläche, satter Text darauf. */
  --badge-ok-bg:   oklch(0.95 0.05 152);
  --badge-ok-fg:   oklch(0.45 0.12 152);
  --badge-info-bg: oklch(0.93 0.05 256);
  --badge-info-fg: oklch(0.42 0.16 262);
  --badge-warn-bg: oklch(0.96 0.06 85);
  --badge-warn-fg: oklch(0.47 0.11 70);

  /* error / destructive surfaces */
  --error-bg:     oklch(0.971 0.013 17.38);  /* ≈ red-50 */
  --error-border: oklch(0.808 0.114 19.57);  /* ≈ red-300 */
  --error-text:   oklch(0.505 0.213 27.52);  /* ≈ red-700 */

  /* shadows — shadcn keeps these minimal */
  --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / .05);
  --shadow:    0 1px 3px 0 rgb(0 0 0 / .10), 0 1px 2px -1px rgb(0 0 0 / .10);

  /* Hint native form controls / scrollbars that this is a light surface
     (overridden to `dark` on the dark-mode selectors below). */
  color-scheme: light;

  /* ── Dark-mode values (Phase 7D) ───────────────────────────────────────
     Single source of truth for the dark palette (shadcn neutral, OKLCH). The
     two dark selectors after :root map the live tokens onto these, so an
     explicit "Dunkel" and OS-driven "System" never drift apart. Sidebar tokens
     are intentionally absent (mode-agnostic — the sidebar is already dark).
     --brand is injected per-tenant on :root and deliberately NOT listed, so an
     accent keeps working in both modes. */
  --d-background:             oklch(0.145 0 0);
  --d-foreground:             oklch(0.985 0 0);
  --d-card:                   oklch(0.205 0 0);
  --d-card-foreground:        oklch(0.985 0 0);
  --d-popover:                oklch(0.205 0 0);
  --d-popover-foreground:     oklch(0.985 0 0);
  --d-primary:                oklch(0.922 0 0);
  --d-primary-foreground:     oklch(0.205 0 0);
  --d-secondary:              oklch(0.269 0 0);
  --d-secondary-foreground:   oklch(0.985 0 0);
  --d-muted:                  oklch(0.269 0 0);
  --d-muted-foreground:       oklch(0.708 0 0);
  --d-shadcn-accent:          oklch(0.269 0 0);
  --d-shadcn-accent-foreground: oklch(0.985 0 0);
  --d-destructive:            oklch(0.704 0.191 22.216);
  --d-destructive-foreground: oklch(0.985 0 0);
  --d-border:                 oklch(1 0 0 / 10%);
  --d-input:                  oklch(1 0 0 / 15%);
  --d-ring:                   oklch(0.556 0 0);
  --d-error-bg:               oklch(0.27 0.07 25);
  --d-error-border:           oklch(0.45 0.13 25);
  --d-error-text:             oklch(0.80 0.12 22);
  /* Status-Badges (dunkel): gedämpfte Fläche knapp über der Karte, heller Text
     darauf — das Gegenteil der hellen Variante, gleiche Wirkung. */
  --d-badge-ok-bg:   oklch(0.28 0.06 152);
  --d-badge-ok-fg:   oklch(0.85 0.13 152);
  --d-badge-info-bg: oklch(0.28 0.06 256);
  --d-badge-info-fg: oklch(0.83 0.11 256);
  --d-badge-warn-bg: oklch(0.30 0.06 85);
  --d-badge-warn-fg: oklch(0.87 0.12 85);

  /* Die Sidebar war im Dunkeln nicht mehr vom Inhalt zu unterscheiden: gemessen
     1.03:1 (im Hellen 19.17:1). Sie bleibt dunkel — eine Spur tiefer als die
     Inhaltsfläche —, aber die Trennung kann im Dunkeln nicht mehr von der FLÄCHE
     kommen: 0.11 gegen 0.145 sind auch nach der Absenkung nur 1.04:1, und weiter
     abdunkeln geht nicht, ohne bei Schwarz zu landen. Die Kante trägt sie
     deshalb allein und ist entsprechend bemessen: 14% weiss ergaben gemessene
     1.33:1 zur Inhaltsfläche — sichtbar auf einem guten Monitor, unsichtbar auf
     einem schlechten. 38% ergeben 3.3:1 und erfüllen damit die 3:1-Schwelle für
     nicht-textliche Abgrenzungen (WCAG 1.4.11). Im Hellen macht das die Fläche
     mit 19.17:1 ohnehin selbst, dort bleibt die Kante dezent. */
  --d-sidebar:        oklch(0.11 0 0);
  --d-sidebar-border: oklch(1 0 0 / 38%);

  --d-shadow-sm:              0 1px 2px 0 rgb(0 0 0 / .35);
  --d-shadow:                 0 1px 3px 0 rgb(0 0 0 / .50), 0 1px 2px -1px rgb(0 0 0 / .45);

  /* ── Legacy aliases — keep every existing rule working unchanged ── */
  --accent:        var(--brand);
  --accent-hover:  var(--brand-hover);
  --accent-text:   var(--brand-foreground);
  --surface:       var(--card);
  --surface-alt:   var(--muted);
  --text:          var(--foreground);
  --text-muted:    var(--muted-foreground);
  --sidebar-bg:    var(--sidebar);
}

/* ── Dark mode (Phase 7D) ──────────────────────────────────────────────────
   The server renders exactly one of theme-light / theme-dark / theme-system on
   <html> from the per-tenant `ui_theme` setting. theme-light is the :root
   default above (unchanged → safe fallback, and it forces light even on a dark
   OS). "Dunkel" applies always; "System" only when the OS prefers dark. The two
   selectors share the --d-* values from :root, so they cannot drift. The
   injected per-tenant --brand on :root is intentionally NOT overridden here, so
   the accent colour keeps driving primary buttons / active nav in both modes;
   --brand-hover flips to mix toward white so hover lightens on dark surfaces. */
html.theme-dark {
  color-scheme: dark;
  --background:               var(--d-background);
  --foreground:               var(--d-foreground);
  --card:                     var(--d-card);
  --card-foreground:          var(--d-card-foreground);
  --popover:                  var(--d-popover);
  --popover-foreground:       var(--d-popover-foreground);
  --primary:                  var(--d-primary);
  --primary-foreground:       var(--d-primary-foreground);
  --secondary:                var(--d-secondary);
  --secondary-foreground:     var(--d-secondary-foreground);
  --muted:                    var(--d-muted);
  --muted-foreground:         var(--d-muted-foreground);
  --shadcn-accent:            var(--d-shadcn-accent);
  --shadcn-accent-foreground: var(--d-shadcn-accent-foreground);
  --destructive:              var(--d-destructive);
  --destructive-foreground:   var(--d-destructive-foreground);
  --border:                   var(--d-border);
  --input:                    var(--d-input);
  --ring:                     var(--d-ring);
  --error-bg:                 var(--d-error-bg);
  --error-border:             var(--d-error-border);
  --error-text:               var(--d-error-text);
  --badge-ok-bg:              var(--d-badge-ok-bg);
  --badge-ok-fg:              var(--d-badge-ok-fg);
  --badge-info-bg:            var(--d-badge-info-bg);
  --badge-info-fg:            var(--d-badge-info-fg);
  --badge-warn-bg:            var(--d-badge-warn-bg);
  --badge-warn-fg:            var(--d-badge-warn-fg);
  --sidebar:                  var(--d-sidebar);
  --sidebar-border:           var(--d-sidebar-border);
  --shadow-sm:                var(--d-shadow-sm);
  --shadow:                   var(--d-shadow);
  --brand-hover:              color-mix(in oklch, var(--brand) 85%, white);
}
@media (prefers-color-scheme: dark) {
  html.theme-system {
    color-scheme: dark;
    --background:               var(--d-background);
    --foreground:               var(--d-foreground);
    --card:                     var(--d-card);
    --card-foreground:          var(--d-card-foreground);
    --popover:                  var(--d-popover);
    --popover-foreground:       var(--d-popover-foreground);
    --primary:                  var(--d-primary);
    --primary-foreground:       var(--d-primary-foreground);
    --secondary:                var(--d-secondary);
    --secondary-foreground:     var(--d-secondary-foreground);
    --muted:                    var(--d-muted);
    --muted-foreground:         var(--d-muted-foreground);
    --shadcn-accent:            var(--d-shadcn-accent);
    --shadcn-accent-foreground: var(--d-shadcn-accent-foreground);
    --destructive:              var(--d-destructive);
    --destructive-foreground:   var(--d-destructive-foreground);
    --border:                   var(--d-border);
    --input:                    var(--d-input);
    --ring:                     var(--d-ring);
    --error-bg:                 var(--d-error-bg);
    --error-border:             var(--d-error-border);
    --error-text:               var(--d-error-text);
  --badge-ok-bg:              var(--d-badge-ok-bg);
    --badge-ok-fg:              var(--d-badge-ok-fg);
    --badge-info-bg:            var(--d-badge-info-bg);
    --badge-info-fg:            var(--d-badge-info-fg);
    --badge-warn-bg:            var(--d-badge-warn-bg);
    --badge-warn-fg:            var(--d-badge-warn-fg);
    --sidebar:                  var(--d-sidebar);
    --sidebar-border:           var(--d-sidebar-border);
    --shadow-sm:                var(--d-shadow-sm);
    --shadow:                   var(--d-shadow);
    --brand-hover:              color-mix(in oklch, var(--brand) 85%, white);
  }
}

/* ── Reset / base ────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

body {
  font-family: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-size: var(--text-base);
  line-height: 1.55;
  color: var(--foreground);
  background: var(--background);
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ── App shell: sidebar + main column ────────────────────────────────── */
.admin { display: flex; align-items: stretch; min-height: 100vh; }

/* Sprungmarke zum Inhalt: acht Navigationslinks stehen auf jeder Seite vor dem
   Inhalt. Nicht `display:none` — das nähme sie auch der Tastatur; stattdessen
   aus dem Sichtfeld geschoben und bei Fokus zurückgeholt. */
.skip-link {
  position: absolute; left: 0.5rem; top: -3rem; z-index: var(--z-skip);
  padding: 0.5rem 0.9rem;
  background: var(--card); color: var(--foreground);
  border: 1px solid var(--border); border-radius: var(--radius-md);
  box-shadow: var(--shadow); font-size: var(--text-md); font-weight: 500;
  transition: top 0.15s;
}
.skip-link:focus, .skip-link:focus-visible { top: 0.5rem; text-decoration: none; }
/* Das Sprungziel bekommt tabindex="-1"; sein Fokusring wäre hier nur Lärm. */
main:focus { outline: none; }

.sidebar {
  display: flex; flex-direction: column; gap: var(--space-5); flex-shrink: 0;
  width: var(--sidebar-w);
  padding: 1.25rem 1rem;
  /* Zwei hartkodierte Literale (#cbd5e1 / #fff) standen hier ausserhalb des
     Token-Systems, obwohl es die passenden Tokens längst gab. */
  background: var(--sidebar-bg); color: var(--sidebar-muted);
  /* Kante zum Inhalt. Im Hellen trennt schon der Helligkeitssprung (19:1);
     im Dunkeln lagen Sidebar und Inhalt bei 1.03:1 — ohne diese Linie war die
     Navigation dort schlicht keine erkennbare Region mehr. */
  border-right: 1px solid var(--sidebar-border);
  position: sticky; top: 0; height: 100vh; overflow-y: auto;
}
.sidebar .brand { font-weight: 700; font-size: var(--text-lg); color: var(--sidebar-foreground); letter-spacing: .02em; padding: var(--space-1) var(--space-2); }
.sidebar .brand:hover { text-decoration: none; opacity: .9; }
/* Sichtbarer Auslöser der Kommandopalette. Sie war nur über ⌘K erreichbar und
   ohne jeden Hinweis in der Oberfläche — praktisch unauffindbar. */
.side-search {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);
  width: 100%; height: auto; padding: .45rem .75rem;
  background: var(--sidebar-accent); color: var(--sidebar-muted);
  border: 1px solid var(--sidebar-border); border-radius: var(--radius-md);
  font-size: var(--text-sm); font-weight: 400; text-align: left;
}
.side-search:hover { background: var(--sidebar-active); color: var(--sidebar-foreground); }
.side-search-kbd {
  font: inherit; font-size: var(--text-xs);
  padding: .05rem .3rem; border-radius: var(--radius-sm);
  border: 1px solid var(--sidebar-border); color: var(--sidebar-muted);
}

.side-nav { display: flex; flex-direction: column; gap: .15rem; flex: 1; }
/* Gruppentitel: trennt tägliche Arbeit von einmaliger Einrichtung. Nur ein
   <span> — die Punkte darunter sind keine Unterliste, sondern dieselbe Ebene. */
.side-group {
  padding: .75rem .75rem .25rem;
  font-size: var(--text-xs); font-weight: 600; text-transform: uppercase; letter-spacing: .04em;
  color: var(--sidebar-muted); opacity: .7;
}
.side-group:first-child { padding-top: 0; }
.side-nav a {
  padding: .5rem .75rem; border-radius: var(--radius-md);
  color: var(--sidebar-muted); font-size: var(--text-md); font-weight: 500;
  transition: background .15s, color .15s;
}
.side-nav a:hover  { background: var(--sidebar-accent); color: var(--sidebar-foreground); text-decoration: none; }
/* active fill: neutral default is a clearly-lighter slate; when an accent colour
   is set, the injected --sidebar-active makes the active item take the accent. */
.side-nav a.active { background: var(--sidebar-active); color: var(--sidebar-foreground); font-weight: 600; }
.side-user { display: flex; flex-direction: column; gap: var(--space-2); border-top: 1px solid var(--sidebar-border); padding-top: 1rem; }
.side-user .user { font-size: var(--text-md); color: var(--sidebar-muted); }
.side-user button { width: 100%; }

.app-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

.breadcrumbs {
  position: sticky; top: 0; z-index: var(--z-sticky);
  display: flex; flex-wrap: wrap; align-items: center; gap: .4rem;
  padding: .8rem 1.5rem; border-bottom: 1px solid var(--border);
  background: var(--surface); font-size: var(--text-sm); color: var(--text-muted);
  box-shadow: var(--shadow-sm);
}
.breadcrumbs a { color: var(--text-muted); }
.breadcrumbs a:hover { color: var(--accent); }
.breadcrumbs [aria-current="page"] { color: var(--text); font-weight: 600; }
.breadcrumbs .sep { opacity: .55; }

/* mobile: sidebar reflows to a top bar (no JS drawer needed) */
@media (max-width: 820px) {
  .admin { flex-direction: column; }
  .sidebar { width: auto; height: auto; position: static; flex-direction: row; flex-wrap: wrap; align-items: center; gap: .5rem .9rem; padding: .75rem 1rem; }
  .side-nav { flex-direction: row; flex-wrap: wrap; }
  .side-user { flex-direction: row; align-items: center; border-top: none; padding-top: 0; }
  .side-user button { width: auto; }
}

/* ── Main content area ───────────────────────────────────────────────── */
.content {
  max-width: 72rem;
  width: 100%;
  margin: 0 auto;
  padding: 2rem 1.5rem;
}
/* Notausgang aus dem Lesemass, gesetzt über NavCtx.Wide — bewusst je Seite.
   Aktuell nutzt ihn KEINE Seite: der RSVP-Formularbaukasten war die einzige und
   damit der einzige der sieben Event-Tabs mit abweichender Breite, was den Inhalt
   beim Tab-Wechsel seitwärts springen liess. Wer ihn wieder setzt, sollte ihn für
   die ganze Tab-Gruppe setzen, nicht für eine einzelne Seite. */
.content-wide { max-width: 96rem; }

.content h1 {
  font-size: var(--text-2xl);
  font-weight: 600;
  margin-bottom: var(--space-5);
  color: var(--text);
}

/* ── Dashboard summary cards ─────────────────────────────────────────── */
.cards {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin-bottom: var(--space-6);
}

.card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-1);
  padding: var(--space-5);
  background: var(--card);
  color: var(--card-foreground);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  min-width: 10rem;
  font-size: var(--text-base);
  font-weight: 500;
  transition: box-shadow 0.15s, border-color 0.15s;
}
/* clickable cards (count/cockpit/quick) get a subtle lift; no accent border */
.card:hover { box-shadow: var(--shadow); text-decoration: none; }
.card .num  { font-size: var(--text-3xl); font-weight: 700; line-height: 1; color: var(--brand); }

/* Die shadcn-Karten-Anatomie (.card-header/-title/-description/-footer) stand
   hier „for opt-in use" und wurde in keiner der 26 Vorlagen je eingelöst. Die
   Karten benutzen durchgehend `h2` + Inhalt. Entfernt, weil ungenutztes CSS beim
   nächsten Umbau als Vorbild missverstanden wird: wer .card-title findet, hält
   das für das gültige Muster und baut die 27. Variante. */

/* ── Vorraum: Anmelden, Passwort vergessen, Passwort neu setzen ───────── */
/* Die Karte war früher SELBST das Formular (`<form class="login-card">`). Seit
   es drei solche Seiten gibt — und zwei davon Text ohne Formular zeigen —, ist
   sie ein Behälter, und das Formular liegt darin. Damit die Abstände in beiden
   Fällen stimmen, erbt das innere Formular die Spaltenaufteilung. */
body.login-page {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  background: var(--surface-alt);
}

.login-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 2.5rem 2rem;
  width: 100%;
  max-width: 22rem;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.login-card > form {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.login-card h1 {
  font-size: var(--text-2xl);
  font-weight: 600;
  text-align: center;
  margin-bottom: var(--space-1);
}

/* Erklärtexte im Vorraum sind keine Randnotiz: hier steht, warum die Seite
   nicht verrät, ob eine Adresse ein Konto hat. Deshalb lesbar zentriert statt
   klein und grau am Rand. */
.login-card .hint { text-align: center; }
.login-card p { margin: 0; }

/* Formularseiten mit einer einzigen Spalte (Passwort ändern): eine Karte über
   die volle Inhaltsbreite lässt drei Passwortfelder verloren wirken. */
.narrow-form { max-width: 30rem; }

/* ── Error message ────────────────────────────────────────────────────── */
.error {
  background: var(--error-bg);
  border: 1px solid var(--error-border);
  border-radius: var(--radius);
  color: var(--error-text);
  padding: 0.5rem 0.75rem;
  font-size: var(--text-md);
}

/* Gegenstück für gute Nachrichten im Vorraum („Passwort geändert"). Der Toaster
   der Anwendung steht dort nicht zur Verfügung — er hängt an @Base. */
.notice {
  background: var(--badge-ok-bg);
  border: 1px solid transparent;
  border-radius: var(--radius);
  color: var(--badge-ok-fg);
  padding: 0.5rem 0.75rem;
  font-size: var(--text-md);
}

/* ── Forms ────────────────────────────────────────────────────────────── */
label {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--text-md);
  font-weight: 500;
  color: var(--text);
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="search"],
input[type="url"],
input[type="number"],
input[type="date"],
select,
textarea {
  width: 100%;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--input);
  border-radius: var(--radius-md);
  font-size: var(--text-md);
  color: var(--foreground);
  background: var(--background);
  outline: none;
  transition: border-color 0.15s, box-shadow 0.15s;
}
/* shadcn-style focus ring — bind to the brand colour so it follows the accent.
   Keep a :focus fallback identical to :focus-visible so nothing regresses. */
input:focus-visible, select:focus-visible, textarea:focus-visible,
input:focus, select:focus, textarea:focus {
  border-color: var(--ring);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--brand) 35%, transparent);
}

/* ── Buttons (shadcn variants + sizes + focus ring) ──────────────────── */
button, .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  height: 2.25rem;
  padding: 0 1rem;
  background: var(--brand);
  color: var(--brand-foreground);
  font-size: var(--text-md);
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  cursor: pointer;
  text-decoration: none;
  transition: background 0.15s, border-color 0.15s, box-shadow 0.15s, color 0.15s;
}
button:hover, .btn:hover { background: var(--brand-hover); text-decoration: none; }
button:focus-visible, .btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--brand) 40%, transparent);
}
/* Links hatten GAR KEINE eigene Fokus-Regel und fielen auf den Browser-Ring
   zurück — auch auf der dunklen Sidebar, wo Chrome ihn als Doppelring zeichnet.
   `currentColor` statt --brand: auf der Sidebar ist die Marke unsichtbar, der
   Ring soll sich aber immer vom Untergrund abheben. */
a:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
/* Ausser bei Elementen, die als Fläche gestaltet sind — dort ist der
   Marken-Ring der Karten/Knöpfe stimmiger. */
a.btn:focus-visible, .card:focus-visible, .quick-link:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--brand) 40%, transparent);
}
button:disabled, .btn:disabled, button[disabled], .btn[disabled] { opacity: 0.5; pointer-events: none; }

/* ── Tables ───────────────────────────────────────────────────────────── */
/* Scroll-Hülle. Ohne sie lief eine breite Tabelle (Gäste: 6 + N Zusatzspalten,
   Empfängerliste: 7) aus dem Viewport und erzeugte einen Scrollbalken am ganzen
   Dokument — die Sidebar wanderte mit.
   BEWUSST NICHT auf .card gesetzt: `overflow-x: auto` zwingt die andere Achse
   ebenfalls auf `auto`, und genau das erzeugte schon einmal den Phantom-
   Scrollbalken der Tableisten (siehe Kommentar bei .tabbar). Ein eigener Wrapper
   hat keine vertikale Überlappung, die er abschneiden könnte. */
.table-wrap { overflow-x: auto; }
table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-base);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
}
th, td { padding: 0.625rem 1rem; text-align: left; border-bottom: 1px solid var(--border); }
th { background: transparent; font-weight: 500; font-size: var(--text-sm); color: var(--muted-foreground); }
tr:last-child td { border-bottom: none; }
tr:hover td { background: var(--muted); }

/* ── Badges (shadcn variants + semantic status colours) ──────────────── */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.125rem 0.5rem;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  font-size: var(--text-xs);
  font-weight: 600;
  line-height: 1.4;
  white-space: nowrap;
}
.badge-default     { background: var(--primary); color: var(--primary-foreground); }
.badge-secondary   { background: var(--secondary); color: var(--secondary-foreground); }
.badge-outline     { background: transparent; color: var(--foreground); border-color: var(--border); }
.badge-destructive { background: var(--destructive); color: var(--destructive-foreground); }
/* Semantische Status-Badges über Tokens statt fester Hell-Modus-Werte.
   Gemessen: die Badge-Fläche stand im Hellen mit 1.14:1 zur Karte — ein
   dezenter Tupfer — und im Dunkeln mit 15.79:1, also praktisch als Leuchtfeld.
   Die Werte waren auf L 0.93–0.96 festgenagelt und wurden vom Dark-Block nie
   angefasst; auf einer Gästeliste mit fünfzig Statusspalten wurde daraus ein
   Lichterteppich. Die Tokens kippen jetzt mit dem Modus. */
.badge-ok    { background: var(--badge-ok-bg);   color: var(--badge-ok-fg);   }
.badge-info  { background: var(--badge-info-bg); color: var(--badge-info-fg); }
.badge-warn  { background: var(--badge-warn-bg); color: var(--badge-warn-fg); }
/* Es gab kein Badge für „kaputt". Im CSV-Import trugen Duplikat und Fehler
   deshalb dasselbe Warn-Gelb — dabei ist ein Duplikat kein Fehler, sondern ein
   bewusstes Nichtstun. Farbe aus den destruktiven Tokens, damit es sich in beide
   Modi einfügt, ohne eine sechste Signalfarbe zu erfinden. */
.badge-error {
  background: color-mix(in oklch, var(--destructive) 14%, var(--background));
  color: var(--destructive);
}
.badge-muted { background: var(--muted); color: var(--muted-foreground); }

/* ── Tag (small inline pill, e.g. "Primär") ──────────────────────────── */
.tag {
  display: inline-block;
  padding: 0.05rem 0.45rem;
  border-radius: var(--radius);
  background: var(--surface-alt);
  border: 1px solid var(--border);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text-muted);
  vertical-align: middle;
}

/* ── Button variants ─────────────────────────────────────────────────── */
.btn-primary { background: var(--brand); color: var(--brand-foreground); border-color: transparent; }
.btn-primary:hover { background: var(--brand-hover); }
.btn-secondary {
  background: var(--secondary);
  color: var(--secondary-foreground);
  border: 1px solid var(--border);
}
.btn-secondary:hover { background: color-mix(in oklch, var(--secondary) 92%, black); }
.btn-outline { background: var(--background); color: var(--foreground); border: 1px solid var(--border); }
.btn-outline:hover { background: var(--shadcn-accent); }
.btn-danger, .btn-destructive { background: var(--destructive); color: var(--destructive-foreground); border-color: transparent; }
.btn-danger:hover, .btn-destructive:hover { background: color-mix(in oklch, var(--destructive) 88%, black); }
.btn-link { background: transparent; color: var(--brand); height: auto; padding: 0; border: none; }
.btn-link:hover { background: transparent; text-decoration: underline; }
/* sizes */
.btn-sm { height: 2rem; padding: 0 0.75rem; font-size: var(--text-sm); }
/* Nur-Icon-Knopf: quadratisch statt textbreit. Vorher waren das `btn-sm` mit
   einem Unicode-Zeichen darin, also je nach Zeichenbreite unterschiedlich breite
   Knöpfe nebeneinander (↑ und ✕ sind nicht gleich breit). Mit `.btn-icon` ist die
   Breite die Höhe — die Reihe ↑ ↓ ✕ steht auf einem Raster.
   `.btn-sm.btn-icon` erbt die kleinere Höhe; die width-Angabe hier muss deshalb
   nach .btn-sm stehen, sonst gewinnt dessen padding wieder. */
.btn-icon { width: 2.25rem; padding: 0; }
.btn-sm.btn-icon { width: 2rem; }

/* Inline-Bearbeitung in Tabellenzeilen (Benutzer: Rolle / Kunde). `select` ist
   global 100% breit und drängte den Bestätigen-Knopf trotz `display:inline` am
   Formular in eine zweite Zeile — daher eine eigene Flex-Zeile mit schrumpfbarer
   Auswahl. */
.row-form { display: flex; align-items: center; gap: 0.4rem; }
.row-form select { width: auto; min-width: 9rem; }

/* ── Ladezustand nativer Formulare (js/pending.js) ───────────────────── */
/* Der echte Submit-Knopf bleibt im DOM und im Formular (ein `disabled`-Knopf
   würde nicht mitgesendet), wird aber ausgeblendet; daneben steht ein reiner
   Anzeige-Klon mit Spinner. */
.is-pending-src { display: none; }
.is-pending { position: relative; pointer-events: none; opacity: 0.75; color: transparent; }
.is-pending::after {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  width: 0.9em; height: 0.9em;
  margin: -0.45em 0 0 -0.45em;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 999px;
  color: var(--brand-foreground);
  animation: pending-spin 0.6s linear infinite;
}
/* Sekundäre/Ghost-Knöpfe stehen auf heller Fläche — dort muss der Ring dunkel sein. */
.is-pending.btn-secondary::after,
.is-pending.btn-outline::after,
.is-pending.btn-ghost::after,
.is-pending.btn-sm:not(.btn-primary):not(.btn-danger)::after { color: var(--foreground); }
@keyframes pending-spin { to { transform: rotate(360deg); } }
/* Vestibulär auslösende Dauerrotation abschalten; der Knopf bleibt erkennbar
   „beschäftigt" (ausgegraut, nicht klickbar). Der Admin hatte bisher gar keinen
   prefers-reduced-motion-Block — dies ist der erste. */
@media (prefers-reduced-motion: reduce) {
  .is-pending::after { animation: none; }
  *, *::before, *::after { transition-duration: 0.001ms !important; }
}

/* Scroll-Sperre, solange ein Dialog offen ist (gesetzt von dialog-focus.js).
   Ohne sie scrollte die Seite unter dem Overlay weiter, sobald das Rad sich
   drehte — der Dialog blieb stehen, der Kontext dahinter lief weg. */
body.dialog-open { overflow: hidden; }

/* ── Druck ───────────────────────────────────────────────────────────── */
/* Es gab keine Druckstile. Die Gästeliste ist das offensichtlichste Druckobjekt
   der Anwendung, und gedruckt kam die 15.5rem breite schwarze Sidebar auf JEDER
   Seite mit — `position: sticky; height: 100vh` macht daraus eine volle schwarze
   Spalte. Dazu Overlays und der Toaster, die über dem Inhalt liegen. */
@media print {
  .sidebar, .skip-link, .toaster,
  .dialog, .dialog-overlay, .cmdk-overlay, .cmdk-panel,
  .breadcrumbs, .tabbar, .list-toolbar, .pager, .bulk-bar,
  .actions, .row-actions, .col-pick { display: none !important; }

  .admin { display: block; }
  .content, .content-wide { max-width: none; padding: 0; }

  /* Karten sind auf dem Papier keine Flächen, sondern Abschnitte.
     `display: block` ist dabei nicht Kosmetik: `.card` ist am Bildschirm ein
     Flex-Container, und ein Flex-Kind wird von seinem INHALT bemessen. Sobald
     der Druck die Aktionsspalte und die Badge-Flächen wegnimmt, schrumpft die
     Gästetabelle auf ihre Textbreite — gemessen 494 statt 785 px, also eine
     Tabelle auf dem linken Drittel des Blattes. Als Block füllt sie die Seite. */
  .card, .stat-card { border: none; box-shadow: none; break-inside: avoid; }
  .card { display: block; padding: 0 0 var(--space-4); }
  .stat-cards { gap: var(--space-4); }

  /* Tabellen dürfen brechen, aber nicht mitten in einer Zeile — und der
     Spaltenkopf wiederholt sich auf jeder Seite. */
  .table-wrap { overflow: visible; }
  table { border-color: #999; }
  thead { display: table-header-group; }
  tr { break-inside: avoid; }

  /* Statusfarben tragen auf Papier nicht; der Text im Badge tut es. */
  .badge { border: 1px solid #999; background: none !important; color: #000 !important; }

  a { text-decoration: none; color: #000; }
}

/* ── Icons ───────────────────────────────────────────────────────────── */
/* Die Grösse ist an die Schrift GEBUNDEN, nicht fix: ein Icon neben Text soll so
   gross sein wie dieser Text. Vorher war sie das auch — nur unkontrolliert, weil
   jedes Glyph aus einer anderen Fallback-Schrift kam und dort eine andere
   Zeichenhöhe je em hatte; dasselbe ✕ erschien deshalb in drei Grössen. Mit 1em
   auf einer gemeinsamen viewBox misst ein Icon in einer 13px-Zeile 13px und in
   einer 14px-Zeile 14px — nachvollziehbar statt zufällig.
   `flex: none` verhindert, dass ein Icon in einer Flex-Zeile gestaucht wird;
   `vertical-align` richtet es an der Schriftlinie aus, wo es neben Text steht. */
.icon { flex: none; width: 1em; height: 1em; vertical-align: -0.125em; }
/* In Knöpfen etwas kräftiger als der Text, sonst wirken die dünnen Striche
   neben halbfetter Beschriftung blass. */
.btn .icon, button .icon { stroke-width: 2.25; }
/* Im Nur-Icon-Knopf gibt es keinen Text, an den sich das Icon binden könnte —
   es IST der Inhalt. Feste 1rem in der 2rem/2.25rem-Box (das Verhältnis, das
   shadcn für Icon-Knöpfe benutzt); an die 13px-Schriftgrösse von .btn-sm
   gekoppelt wirkte es in der quadratischen Fläche verloren. */
.btn-icon .icon { width: 1rem; height: 1rem; }

/* ── Pflichtfeld-Marker ──────────────────────────────────────────────── */
/* `.lbl` klammert Beschriftung und Stern zu EINEM Flex-Kind: `label` ist eine
   Flex-Spalte, ein loses Element darin landete unter dem Eingabefeld (siehe die
   ausführliche Begründung in req.templ). Es braucht selbst keine Eigenschaften —
   die Regel steht hier trotzdem, damit die Klasse auffindbar ist und niemand sie
   beim nächsten Aufräumen für einen Tippfehler hält. */
.lbl { /* nur Gruppierung — siehe req.templ */ }

/* `required` stand vierzig Mal im Markup und war nirgends zu sehen. Der Stern
   steht am Wort, nicht am Feldrand.
   `text-decoration: none` überschreibt den Standard-Unterstrich von <abbr>;
   die Farbe ist die destruktive, weil sie in diesem System bereits „hier musst
   du etwas tun" bedeutet — eine zweite Signalfarbe wäre eine Konvention mehr. */
.req {
  color: var(--destructive);
  font-weight: 600;
  text-decoration: none;
  cursor: help;
}

/* ── Nur für Hilfsmittel sichtbar ────────────────────────────────────── */
/* Es gab kein solches Hilfsmittel; einen versteckten zugänglichen Namen zu
   vergeben war schlicht nicht möglich. `clip-path` statt des alten
   clip-rect-Hacks, `white-space: nowrap` verhindert Umbruch-Artefakte. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden; white-space: nowrap;
  clip-path: inset(50%);
  border: 0;
}

/* ── Abgelehnte Formulare ────────────────────────────────────────────── */
/* Meldung im Dialog bzw. über dem Formular, wenn eine Eingabe zurückgewiesen
   wurde. Ersetzt die frühere Vollbild-Fehlerseite, die sämtliche Eingaben
   verwarf — siehe ui/html/admin/formerr.go. */
.form-error {
  margin: 0 0 0.85rem;
  padding: 0.55rem 0.75rem;
  background: var(--error-bg);
  border: 1px solid var(--error-border);
  border-left-width: 3px;
  border-radius: var(--radius-md);
  color: var(--error-text);
  font-size: var(--text-md);
}
/* Das zurückgewiesene Feld selbst markieren — die Meldung allein sagt bei
   dreizehn Feldern nicht, welches gemeint ist. aria-invalid ist bereits gesetzt,
   die Regel hängt sich daran, statt eine zweite Quelle der Wahrheit zu schaffen. */
[aria-invalid="true"] { border-color: var(--destructive); }
[aria-invalid="true"]:focus-visible, [aria-invalid="true"]:focus {
  border-color: var(--destructive);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--destructive) 30%, transparent);
}

/* ── Helpers ─────────────────────────────────────────────────────────── */
.muted { color: var(--text-muted); }
.empty { color: var(--text-muted); padding: 0.75rem 0; }
.actions { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; }

/* ── Section spacing (cards used as form sections) ───────────────────── */
.form-section { margin-bottom: var(--space-5); }
.form-section h2 { font-size: var(--text-lg); font-weight: 600; margin-bottom: var(--space-3); }
/* Zweispaltiges Formular auf einer Seite (Anlass-Einstellungen, Systempost): die
   kurzen Felder nutzen die Kartenbreite, statt in einer schmalen Spalte zu
   stapeln. Gedeckelt, damit die Eingaben eine angenehme Länge behalten; das
   Formular wird auf volle Breite gezwungen, weil .card (align-items:flex-start)
   es sonst auf seine Inhaltsbreite schrumpft und das Raster zusammenfällt.
   Der Name ist bewusst generisch — die Regel hiess einmal `.anlass-edit-*` und
   war damit auf der zweiten Seite, die sie brauchte, nicht benutzbar, ohne dort
   „anlass" ins Markup zu schreiben. */
.form-2col { width: 100%; max-width: 56rem; }
/* Doppelte Klasse im Selektor (0-2-0), weil das Markup `field-grid grid-2col`
   trägt und `.field-grid` weiter unten steht: bei gleicher Spezifität gewann sonst
   dessen `auto-fit`-Raster und machte aus den zwei gewollten Spalten je nach
   Fensterbreite drei oder vier. Die Hinweistexte unter einzelnen Feldern ziehen
   die Rasterzeile auf — je schmaler die Spalte, desto mehr Zeilen, desto grösser
   die Lücke unter den Nachbarfeldern. Zwei breite Spalten halten sie klein. */
.field-grid.grid-2col {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.85rem 1.5rem;
  margin-bottom: 1.25rem;
  align-items: start;
}
.field-grid.grid-2col .field { max-width: none; margin-bottom: 0; }
@media (max-width: 40rem) { .field-grid.grid-2col { grid-template-columns: 1fr; } }
.field { margin-bottom: 0.85rem; max-width: 28rem; }
/* Gestapelte Karten brauchen einen Abstand zueinander. In waagrechten
   Kartencontainern (Flex/Grid) stiftet dieselbe Regel dagegen einen Versatz:
   jede Karte ausser der ersten rutschte um 1.25rem nach unten, weil `gap`
   den Abstand schon setzt und `margin-top` obendrauf kommt. Deshalb wird der
   gestapelte Abstand in genau diesen Containern zurückgenommen.
   ACHTUNG: Ein neuer waagrechter Kartencontainer muss hier eingetragen werden. */
.card + .card { margin-top: 1.25rem; }
.cards > .card + .card,
.card-grid > .card + .card,
.theme-editor > .card + .card,
.composer > .card + .card,
.form-builder-grid > .card + .card { margin-top: 0; }
/* Toolbar-Zeile (z. B. „+ Neuer Anlass", „+ Neuer Gast") über dem Seiteninhalt:
   .actions bringt selbst keinen Abstand mit — was folgt (Karte oder Zahlenkarten)
   bekommt denselben Abstand wie gestapelte Karten. */
.actions + .card,
.actions + .stat-cards { margin-top: 1.25rem; }
.card h2 { font-size: var(--text-lg); font-weight: 600; margin-bottom: var(--space-3); }
.card h3 { font-size: var(--text-base); font-weight: 600; margin: 1rem 0 0.5rem; }

/* ── DNS verification instructions ───────────────────────────────────── */
.dns-instructions { margin: 0.25rem 0; }
.dns-instructions > summary {
  display: inline-flex;
  cursor: pointer;
  padding: 0.3rem 0.7rem;
  font-size: var(--text-sm);
  border-radius: var(--radius);
  background: var(--surface-alt);
  border: 1px solid var(--border);
  color: var(--text);
  list-style: none;
}
.dns-instructions > summary::-webkit-details-marker { display: none; }
.dns-detail {
  margin-top: var(--space-2);
  padding: 0.75rem 1rem;
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: var(--text-md);
}
.dns-detail dl { display: grid; grid-template-columns: auto 1fr; gap: 0.25rem 0.75rem; margin: 0.5rem 0; }
.dns-detail dt { font-weight: 600; color: var(--text-muted); }
.dns-detail code { font-family: ui-monospace, "Cascadia Code", Consolas, monospace; font-size: var(--text-sm); word-break: break-all; }
.dns-detail .hint { color: var(--text-muted); margin-top: var(--space-2); }

/* ── Table niceties for the domains/routes lists ─────────────────────── */
.data-table td .actions { justify-content: flex-start; }
.data-table a { font-weight: 500; }

/* ════════════════════════════════════════════════════════════════════════
   Components for classes used in templates but previously undefined.
   Grouped by feature; all consume the :root tokens above. (Phase 1)
   ════════════════════════════════════════════════════════════════════════ */

/* ── Helper additions ────────────────────────────────────────────────── */
.text-muted { color: var(--text-muted); }
.hint { font-size: var(--text-sm); color: var(--text-muted); margin-top: 0.35rem; }
/* A hint that reports a blocking condition (e.g. no SMTP mailbox resolves, so
   nothing can be sent) — same size and rhythm as .hint, but it must not read as
   background information. */
.hint-warn { color: var(--destructive); }
.card-warn { border-color: var(--destructive); }
.card-warn p + p { margin-top: var(--space-2); }
.form-actions { display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: center; margin-top: 1.1rem; }

/* ── Button additions ────────────────────────────────────────────────── */
/* shadcn ghost: transparent, neutral hover fill (no more dashed border) */
.btn-ghost { background: transparent; color: var(--foreground); border-color: transparent; }
.btn-ghost:hover { background: var(--shadcn-accent); border-color: transparent; }
/* "danger" modifier (e.g. the block delete ✕): neutral until hover */
button.danger, .btn.danger { background: var(--background); color: var(--destructive); border: 1px solid var(--border); }
button.danger:hover, .btn.danger:hover { background: var(--error-bg); border-color: var(--error-border); }

/* ── CMS block editor ────────────────────────────────────────────────── */
.editor { display: flex; flex-direction: column; gap: 1.25rem; }
/* Title row sits above the tab bar (like every other Teil tab); match the standard
   page-title size + spacing so the tab bar lines up consistently. */
.editor-head { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; margin-bottom: var(--space-5); }
.editor-head h1 { margin-bottom: 0; font-size: var(--text-2xl); flex: 1; min-width: 12rem; }
.publish-controls { display: inline-flex; align-items: center; gap: 0.6rem; }
.palette {
  display: flex; flex-wrap: wrap; gap: var(--space-2); padding: 0.85rem;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow-sm);
}
.blocks { display: flex; flex-direction: column; gap: var(--space-4); }
.block-edit {
  display: flex; flex-direction: column; gap: 0.6rem; padding: 1rem 1.1rem;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow-sm);
}
.block-edit > label { max-width: 32rem; }
.block-edit > .btn-sm { align-self: flex-start; }
.block-bar { display: flex; align-items: center; gap: 0.35rem; padding-bottom: 0.6rem; border-bottom: 1px solid var(--border); }
.block-bar strong { flex: 1; font-size: var(--text-base); }
.block-bar .btn-sm {
  background: var(--surface-alt); color: var(--text); border: 1px solid var(--border);
  padding: 0.2rem 0.55rem; min-width: 2rem;
}
.block-bar .btn-sm:hover { background: var(--border); }
.block-bar .btn-sm.danger { color: var(--destructive); }
/* 88% matches the original #b91c1c hover and stays consistent with .btn-danger;
   adapts automatically once --destructive brightens in dark mode. */
.block-bar .btn-sm.danger:hover { background: var(--error-bg); border-color: var(--error-border); color: color-mix(in oklch, var(--destructive) 88%, black); }
.editor-thumb { max-width: 12rem; max-height: 8rem; border-radius: var(--radius); border: 1px solid var(--border); }

/* ── Repeatable rows (Programm / FAQ editors) ────────────────────────── */
.row-list { display: flex; flex-direction: column; gap: var(--space-2); }
.row-edit {
  display: grid; gap: 0.4rem 0.5rem; align-items: start;
  padding: 0.6rem 0.7rem; background: var(--surface-alt);
  border: 1px solid var(--border); border-radius: var(--radius);
}
.row-agenda { grid-template-columns: 6rem 1fr auto; }
.row-faq    { grid-template-columns: 1fr auto; }
.row-edit .row-desc { grid-column: 1 / -1; min-height: 2.8rem; resize: vertical; }
.row-edit input, .row-edit textarea { width: 100%; }
/* Also used by the RSVP field list (.fb-field): small neutral icon-ish buttons in
   their own grid column. `<form>` children (the ↑/↓ one-button posts) are flex
   items here, so they line up with the plain buttons. */
.row-actions { display: inline-flex; flex-wrap: wrap; gap: var(--space-1); align-self: start; }
.row-actions .btn-sm {
  background: var(--surface); color: var(--text); border: 1px solid var(--border);
  padding: 0.2rem 0.5rem; min-width: 1.9rem;
}
.row-actions .btn-sm:hover { background: var(--border); }
.row-actions .btn-sm.danger { color: var(--destructive); }

/* ── Per-element style dialog (segmented controls + swatches + live preview) ── */
.style-controls { margin-top: var(--space-1); }
.style-trigger { display: inline-flex; align-items: center; gap: 0.4rem; }

.style-dialog {
  width: min(36rem, calc(100vw - 2rem));
  max-height: calc(100vh - 4rem); overflow-y: auto;
  display: flex; flex-direction: column; gap: var(--space-2);
}
/* Umbenannt von h4: die Ebene war eine Schriftgrösse, keine Gliederung. */
.style-dialog-group { margin: 0 0 0.25rem; font-size: var(--text-base); font-weight: 600; }
.style-section { padding: 0.85rem; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface-alt); display: flex; flex-direction: column; gap: 0.6rem; margin-bottom: var(--space-2); }
.style-group { display: flex; flex-direction: column; gap: 0.3rem; }
.style-label { font-size: var(--text-sm); color: var(--text-muted); }
/* Alias auf .hint. Es gab drei Klassen für Hilfstext in drei Größen (.hint
   0.8125rem, .dialog-hint 0.78rem, .muted geerbt) — ohne dass ein Unterschied
   dahinterstand. Der Selektor bleibt, damit die Templates nicht angefasst werden
   müssen; die Größe ist jetzt dieselbe. Neuer Hilfstext nimmt .hint. */
.dialog-hint { font-size: var(--text-sm); color: var(--text-muted); margin: 0.35rem 0 0; }

/* segmented control */
.seg { display: inline-flex; flex-wrap: wrap; gap: var(--space-1); }
.seg button {
  font: inherit; font-size: var(--text-sm); cursor: pointer;
  padding: 0.32rem 0.7rem; border-radius: 999px;
  background: var(--surface); color: var(--text); border: 1px solid var(--border);
  transition: background var(--dur-fast, .15s), border-color var(--dur-fast, .15s);
}
.seg button:hover { background: var(--border); }
.seg button.seg-active { background: var(--brand); color: var(--brand-foreground); border-color: var(--brand); }

/* Farbpunkte im Stil-Dialog des Inhalts-Editors.
   AUF .editor GESCOPT: die Seite „Darstellung" definiert `.swatches`/`.swatch`
   ein zweites Mal (Akzent-Vorgaben, ~250 Zeilen weiter unten). Bei gleicher
   Spezifität gewann die spätere Definition, sodass diese als 1.75rem-Kreise mit
   2px-Rand gebauten Punkte tatsächlich als 2rem-Rechtecke mit 1px-Rand
   erschienen. Die beiden Zwecke sind verschieden (Textfarbe wählen vs.
   Akzentfarbe wählen) — deshalb getrennt statt zusammengeführt. */
.editor .swatches { display: inline-flex; flex-wrap: wrap; gap: 0.4rem; }
.editor .swatch {
  width: 1.75rem; height: 1.75rem; border-radius: 999px; cursor: pointer; padding: 0;
  border: 2px solid var(--border); background: var(--surface);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--text-muted); font-size: var(--text-sm); line-height: 1;
  min-width: 0;
}
/* „—" = keine Überschreibung, erbt die Themefarbe; neutral wie die Grundfläche. */
.editor .swatch-std { background: var(--surface); }
.editor .swatch.swatch-active { outline: 2px solid var(--brand); outline-offset: 2px; border-color: var(--surface); }
.sw-text { background: var(--color-text); } .sw-muted { background: var(--color-muted); }
.sw-primary { background: var(--color-primary); } .sw-accent { background: var(--color-accent); }
.sw-link { background: var(--color-link); }

/* live preview — reuses the public fs-/fw-/txt- class names, scoped here with
   dialog-appropriate (smaller, non-vw) sizes; weight + colour are exact. The
   --color-*/--font-* come from the injected microsite theme (scoped to .swatches
   /.style-preview). The subtitle variant mirrors the public <p> default (body
   font, weight 400) instead of the heading default (heading font, weight 700). */
.style-preview {
  border: 1px dashed var(--border); border-radius: var(--radius);
  padding: 0.9rem 1rem; background: var(--surface); min-height: 3.5rem;
  display: flex; align-items: center; overflow: hidden;
}
.style-preview-text {
  font-family: var(--font-heading, inherit);
  font-size: var(--blk-fs, 1.6rem); font-weight: var(--blk-weight, 700);
  color: var(--blk-color, var(--color-text, var(--text)));
  line-height: 1.2; word-break: break-word;
}
.style-preview.preview-subtitle .style-preview-text {
  font-family: var(--font-body, inherit); font-weight: var(--blk-weight, 400);
}
.style-preview .fs-s { --blk-fs: 1rem; } .style-preview .fs-m { --blk-fs: 1.35rem; }
.style-preview .fs-l { --blk-fs: 1.75rem; } .style-preview .fs-xl { --blk-fs: 2.15rem; }
.style-preview .fs-xxl { --blk-fs: 2.6rem; }
.style-preview .fw-light { --blk-weight: 300; } .style-preview .fw-normal { --blk-weight: 400; }
.style-preview .fw-medium { --blk-weight: 500; } .style-preview .fw-semibold { --blk-weight: 600; }
.style-preview .fw-bold { --blk-weight: 700; } .style-preview .fw-extrabold { --blk-weight: 800; }
.style-preview .txt-text { --blk-color: var(--color-text); } .style-preview .txt-muted { --blk-color: var(--color-muted); }
.style-preview .txt-primary { --blk-color: var(--color-primary); } .style-preview .txt-accent { --blk-color: var(--color-accent); }
.style-preview .txt-link { --blk-color: var(--color-link); }

/* ── Theme editor: form + live preview ───────────────────────────────── */
/* Compact form (left) beside a sticky live preview (right) that re-renders via
   SSE as colours/fonts change. Collapses to a stack below 64rem. */
.theme-editor {
  display: grid;
  grid-template-columns: minmax(0, 34rem) minmax(0, 1fr);
  gap: var(--space-5);
  align-items: start;
  /* This <div> replaced a .card.form-section root; restore the inter-card gap to
     the following card (Events / „Event löschen") which the removed card carried. */
  margin-bottom: var(--space-5);
}
/* The .card is a flex column with align-items:flex-start, so its <form> shrinks
   to content width and the responsive .theme-grid/.field-grid below collapse to
   one column. Force the form to the full card width so the grids can flow. */
.theme-form > form { width: 100%; }
.theme-preview-card { position: sticky; top: 1rem; align-self: start; }
.theme-preview-card > #theme-preview { width: 100%; }
@media (max-width: 64rem) {
  .theme-editor { grid-template-columns: 1fr; }
  .theme-preview-card { position: static; }
}

/* RSVP form builder: field list (left) beside a sticky guest-view preview
   (right, a sandboxed iframe rendered server-side). Re-renders on every field
   add/edit/delete/move (all reload the page). Stacks below 64rem.
   Column order is deliberately the inverse of .theme-editor: the list is the
   flexible column and the preview gets the fixed width. The old
   `minmax(0,40rem) minmax(0,1fr)` pinned the editor at 640px and starved the
   preview, and the field table then overflowed its card *under* the sticky
   preview — which is how the row action buttons became unreachable. */
.form-builder-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(20rem, 32rem);
  gap: var(--space-5);
  align-items: start;
  margin-bottom: var(--space-5);
}
.fb-preview-card { position: sticky; top: 1rem; align-self: start; }
.rsvp-preview {
  width: 100%;
  height: min(44rem, calc(100vh - 7rem));
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: #fff;
}
@media (max-width: 64rem) {
  .form-builder-grid { grid-template-columns: 1fr; }
  .fb-preview-card { position: static; }
}

/* ── Inhalts-Editor: Blockliste + Vorschau ───────────────────────────── */
/* Dieselbe Aufteilung wie im Formularbaukasten — bewusst, denn es ist dieselbe
   Aufgabe: links bearbeiten, rechts das Ergebnis. Zwei verschiedene Rasterungen
   für dasselbe Muster wären eine dritte Sache zum Merken. */
.editor-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(20rem, 32rem);
  gap: var(--space-5);
  align-items: start;
}
.editor-preview {
  position: sticky; top: 1rem; align-self: start;
  display: flex; flex-direction: column; gap: var(--space-2);
}
.editor-preview-head { display: flex; flex-direction: column; gap: var(--space-1); }
.editor-preview-head h2 { font-size: var(--text-lg); font-weight: 600; margin: 0; }
.editor-preview-frame {
  width: 100%;
  height: min(46rem, calc(100vh - 9rem));
  border: 1px solid var(--border);
  border-radius: var(--radius);
  /* Weiss statt transparent: die Gästeseite bringt ihre eigene Hintergrundfarbe
     mit, aber bis das Theme-CSS greift, würde das Admin-Grau durchscheinen. */
  background: #fff;
}
@media (max-width: 64rem) {
  .editor-grid { grid-template-columns: 1fr; }
  .editor-preview { position: static; }
}

/* ── CSV-Import: Schrittleiste, Spaltenzuordnung, Sackgasse ──────────── */
/* Schritt 2 und 3 zeigten nicht, dass sie Schritt 2 und 3 sind: man stand
   mitten in einem dreistufigen Vorgang, ohne zu wissen, wie viel noch kommt. */
.import-steps {
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  list-style: none; margin-bottom: var(--space-5);
  font-size: var(--text-sm);
}
.import-step {
  padding: 0.25rem 0.7rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  color: var(--text-muted);
}
.import-step.active { background: var(--foreground); color: var(--background); border-color: transparent; font-weight: 600; }
.import-step.done { color: var(--text); }

.import-columns {
  list-style: none;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  gap: var(--space-2);
  font-size: var(--text-md);
}
.import-columns li { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
.import-columns code { flex: none; }
.import-columns .icon { color: var(--text-muted); }
.import-columns span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Zeilennummern sind zum Abgleichen mit der Datei da — untereinander lesbar
   heisst gleich breite Ziffern. */
.row-num { font-variant-numeric: tabular-nums; color: var(--text-muted); }

/* Erklärter Leerzustand statt Sackgasse: „0 importierbar" zeigte bisher eine
   Tabelle voller roter Zeilen, keinen Knopf und keinen Grund. */
.empty-state {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-alt);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
  display: flex; flex-direction: column; gap: var(--space-2);
}
.empty-state p { margin: 0; }

/* ── Bildfeld mit Medien-Picker ──────────────────────────────────────── */
.image-input { display: flex; gap: var(--space-2); align-items: center; }
.image-input input[type="text"] { flex: 1; min-width: 0; }
.image-input .btn-sm { flex: none; }

/* ── Medien-Picker-Dialog ────────────────────────────────────────────── */
.dialog-media { width: min(52rem, 92vw); }
.media-picker-grid {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: var(--space-3);
  /* Die Bibliothek kann gross werden; der Dialog darf deswegen nicht aus dem
     Bild wachsen. Gescrollt wird die Liste, nicht der Dialog. */
  max-height: min(28rem, 60vh);
  overflow-y: auto;
  margin: var(--space-3) 0;
}
.media-pick {
  display: flex; flex-direction: column; gap: var(--space-1);
  width: 100%; height: auto; padding: var(--space-2);
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius);
  text-align: left; cursor: pointer;
}
.media-pick:hover { background: var(--shadcn-accent); border-color: var(--brand); }
.media-pick img {
  width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
  border-radius: calc(var(--radius) - 2px);
  /* Karierter Grund, damit ein freigestelltes PNG nicht wie ein leerer Rahmen
     aussieht. */
  background:
    repeating-conic-gradient(var(--surface-alt) 0% 25%, transparent 0% 50%) 50% / 12px 12px;
}
.media-pick-name {
  font-size: var(--text-xs); color: var(--text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Field list. A row is a 2-column grid — content | actions — instead of the
   former 6-column table: a table sizes to its min-content width and pushes the
   action column out of the card, a grid row cannot. `.fb-field-main{min-width:0}`
   is the brake that lets long keys/labels wrap rather than widen the track. */
.fb-fields { list-style: none; display: flex; flex-direction: column; gap: var(--space-2); width: 100%; }
.fb-field {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 0.5rem 1rem;
  align-items: start; padding: 0.7rem 0.8rem; background: var(--surface-alt);
  border: 1px solid var(--border); border-radius: var(--radius);
}
.fb-field-main { min-width: 0; }
.fb-field-title { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2); }
.fb-field-title code { font-size: var(--text-sm); color: var(--text-muted); word-break: break-all; }
.fb-field-meta { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-top: 0.35rem; }
.fb-field-opts { font-size: var(--text-sm); margin-top: 0.3rem; }
/* Below 48rem the actions move under the content instead of squeezing it. */
@media (max-width: 48rem) { .fb-field { grid-template-columns: 1fr; } }

/* Live preview sample block. The theme CSS vars are injected (scoped) onto
   .theme-pv by ThemePreview; everything here consumes them so the block mirrors
   the published microsite. Vars stay scoped to .theme-pv so the theme's --radius
   etc. never shadow admin chrome. */
.theme-pv {
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: var(--base-size);
  /* Frame derived from the theme (not admin --border) so it tracks the previewed
     palette and stays correct under admin dark mode. Matches .theme-pv-surface. */
  border: 1px solid color-mix(in oklch, var(--color-text) 12%, transparent);
  border-radius: var(--radius);
  padding: 1.25rem;
  display: flex;
  flex-direction: column;
  /* Density (--section-rhythm) is a page-level section gap; scaled down here so
     the „Abstände"-control visibly affects the compact sample without dwarfing it. */
  gap: calc(var(--section-rhythm, 3rem) * 0.28);
}
.theme-pv-h { font-family: var(--font-heading); color: var(--color-text); font-size: 1.4em; line-height: 1.2; margin: 0; }
.theme-pv-body { margin: 0; line-height: 1.5; }
.theme-pv-link { color: var(--color-link); text-decoration: underline; cursor: pointer; }
.theme-pv-muted { color: var(--color-muted); font-size: 0.9em; margin: 0; }
.theme-pv-surface {
  background: var(--color-surface);
  border: 1px solid color-mix(in oklch, var(--color-text) 12%, transparent);
  border-radius: var(--radius);
  padding: 0.85rem 1rem;
}
.theme-pv-btn {
  align-self: flex-start;
  height: auto;
  padding: 0.55rem 1.1rem;
  background: var(--btn-bg);
  color: var(--btn-fg);
  border: 1px solid var(--btn-border);
  border-radius: var(--btn-radius);
  font-family: var(--font-body);
}
.theme-pv-btn:hover { background: var(--btn-bg); color: var(--btn-fg); filter: brightness(0.95); }
/* Accent shown as accent-coloured text+outline on the page background — how the
   microsite actually uses --color-accent — which stays legible for any accent hue
   (a solid accent fill with --color-on-primary text is not contrast-safe). */
.theme-pv-accent {
  align-self: flex-start;
  background: transparent;
  color: var(--color-accent);
  border: 1px solid var(--color-accent);
  border-radius: var(--radius);
  padding: 0.15rem 0.6rem;
  font-size: 0.85em;
}

/* ── Theme editor color grid ─────────────────────────────────────────── */
.theme-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr));
  gap: var(--space-3); margin-bottom: 1.25rem;
}
.theme-grid .field { margin-bottom: 0; max-width: none; }

/* Farbwähler und Hex-Feld sind EIN Bedienelement für einen Wert und stehen
   deshalb in einer Zeile. Der Wähler ist quadratisch (er zeigt nur die Farbe),
   das Textfeld nimmt den Rest — dort steht die Information, die man kopiert. */
.color-input { display: flex; gap: var(--space-2); align-items: center; }
.color-input input[type="color"] {
  flex: none; width: 2.25rem; height: 2.25rem; padding: 0.15rem; cursor: pointer;
}
.color-input .hex-input {
  flex: 1; min-width: 0;
  /* Tabellenziffern und Monospace: Hex-Werte vergleicht man Zeichen für
     Zeichen, und in einer proportionalen Schrift sehen 8 und B ähnlich aus. */
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--text-sm);
  text-transform: lowercase;
}

/* ── Kontrastbericht (Theme-Editor) ───────────────────────────────────── */
.contrast-report { margin-top: var(--space-5); border-top: 1px solid var(--border); padding-top: var(--space-4); }
.contrast-report h3 { font-size: var(--text-base); font-weight: 600; margin-bottom: var(--space-2); }
.contrast-ok { color: var(--text-muted); font-size: var(--text-md); }
.contrast-list { list-style: none; display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-3); }
.contrast-bad { display: flex; align-items: baseline; gap: var(--space-2); flex-wrap: wrap; font-size: var(--text-md); }
/* Das Verhältnis ist die Zahl, um die es geht — sie steht vorn und in
   Tabellenziffern, damit mehrere Zeilen untereinander vergleichbar sind. */
.contrast-bad .badge { font-variant-numeric: tabular-nums; }
.contrast-where { color: var(--text-muted); font-size: var(--text-sm); }

/* multi-column layout for the theme selects + brand URL fields (wider cells) */
.field-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); gap: .5rem 1.25rem; margin-bottom: 1.25rem; }
.field-grid .field { margin-bottom: 0; max-width: none; }

/* ── Media library ───────────────────────────────────────────────────── */
.media-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr)); gap: var(--space-4); }
.media-card {
  display: flex; flex-direction: column; background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius);
  overflow: hidden; box-shadow: var(--shadow-sm);
}
.media-card img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; background: var(--surface-alt); display: block; }
.media-card figcaption { display: flex; flex-direction: column; gap: 0.4rem; padding: 0.6rem 0.7rem; }
.media-url { font-family: ui-monospace, "Cascadia Code", Consolas, monospace; font-size: var(--text-xs); padding: 0.3rem 0.45rem; }
.media-context { align-self: flex-start; font-size: var(--text-xs); font-weight: 600; padding: 0.1rem 0.45rem; border-radius: 999px; color: var(--color-primary, var(--brand, #2563eb)); border: 1px solid color-mix(in oklab, currentColor 35%, transparent); background: color-mix(in oklab, currentColor 8%, transparent); }

/* ── Per-Teil tab bar (unified sub-navigation) ───────────────────────── */
/* Note: when the bar wraps to a 2nd row (narrow/mobile) the active tab's underline
   has no full-width divider beneath it — accepted cosmetic edge case, the trade for
   killing the phantom scrollbar (see below). */
.tabbar {
  display: flex;
  flex-wrap: wrap;             /* wrap on narrow viewports instead of scrolling: */
  gap: var(--space-1);                /* overflow-x:auto would force overflow-y:auto and the */
  margin: -0.5rem 0 1.5rem;    /* .tab margin-bottom:-1px overlap then shows a phantom */
  border-bottom: 1px solid var(--border); /* vertical scrollbar (notably on Windows). */
}
/* Pushes the trailing tab group (Einstellungen/Erscheinungsbild) to the right on
   wide viewports; once .tabbar wraps it is simply a line break's worth of gap. */
.tabbar-gap { flex: 1 1 auto; min-width: 1rem; }
.tabbar .tab {
  flex-shrink: 0;
  padding: 0.55rem 0.9rem;
  color: var(--text-muted);
  font-size: var(--text-base);
  font-weight: 500;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;         /* overlap the bar's bottom border */
  white-space: nowrap;
}
.tabbar .tab:hover { color: var(--text); text-decoration: none; }
.tabbar .tab:focus-visible {
  outline: none; border-radius: var(--radius-sm);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--brand) 35%, transparent);
}
.tabbar .tab.active {
  color: var(--brand);
  border-bottom-color: var(--brand);
  font-weight: 600;
}

/* ── Card grid (cockpit / dashboards) ────────────────────────────────── */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}
.cockpit-card { gap: 0.4rem; }
.cockpit-card .cockpit-title { font-size: var(--text-lg); font-weight: 600; color: var(--text); }
.cockpit-card .cockpit-hint  { font-size: var(--text-md); color: var(--text-muted); font-weight: 400; }

/* ── Offene-Punkte-Liste (Event-Cockpit) ─────────────────────────────── */
.todo-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.todo-item {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-3);
  padding: .75rem 0; border-bottom: 1px solid var(--border);
}
.todo-item:last-child { border-bottom: none; }
/* Der Text nimmt den Rest, damit die Aktion rechts bündig steht — bei Umbruch
   rutscht sie unter den Text statt eine dritte Spalte aufzumachen. */
.todo-text { flex: 1 1 16rem; font-size: var(--text-base); }

/* ── Page header (title + actions row) ───────────────────────────────── */
.page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}
.page-header h1 { margin-bottom: 0; }
/* Nimmt die Breite, die die Aktionen rechts übriglassen — ohne `flex` schrumpft
   der Block auf die Breite der Überschrift und der Untertitel bricht viel zu früh. */
.page-head-text { display: flex; flex-direction: column; gap: .2rem; min-width: 0; flex: 1 1 24rem; }
.page-subtitle { font-size: var(--text-md); color: var(--muted-foreground); margin: 0; max-width: 60ch; }
/* Leerer Aktions-Slot darf keine Lücke reissen. */
.page-header > .actions:empty { display: none; }

/* ── Client-side list search ─────────────────────────────────────────── */
.list-search {
  display: block;
  max-width: 22rem;
  margin-bottom: var(--space-4);
}

/* ── Serverseitige Listen: Werkzeugleiste, Sortierung, Blätterung ────── */
/* Suchfeld + Trefferzahl in einer Zeile. Die Suche ist hier ein echtes
   GET-Formular (nicht das klientseitige Ein-/Ausblenden), damit sie auch Zeilen
   findet, die gar nicht gerendert sind. */
.list-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin-bottom: var(--space-4); }
.list-toolbar .list-search { margin-bottom: 0; flex: 1 1 14rem; }
.list-count { margin-left: auto; font-size: var(--text-sm); color: var(--muted-foreground); }

/* Sortierbarer Spaltenkopf: der ganze Kopf ist der Link, damit die Trefferfläche
   nicht nur der Text ist. */
.sort-head { display: inline-flex; align-items: center; gap: .3rem; color: inherit; }
.sort-head:hover { color: var(--foreground); text-decoration: none; }
.sort-head.active { color: var(--foreground); font-weight: 600; }

.pager { display: flex; align-items: center; gap: var(--space-3); margin-top: var(--space-4); }
.pager-state { font-size: var(--text-sm); color: var(--muted-foreground); }

/* Auswahlspalte + Sammelaktionsleiste. Die Leiste erscheint erst mit einer
   Auswahl, damit sie im Normalfall keinen Platz kostet. */
.col-pick { width: 1px; white-space: nowrap; }
.col-pick input[type="checkbox"] { width: auto; margin: 0; }
.bulk-bar {
  display: flex; align-items: center; gap: var(--space-3);
  margin-bottom: var(--space-3); padding: .5rem .75rem;
  background: var(--muted); border: 1px solid var(--border);
  border-radius: var(--radius-md); font-size: var(--text-md);
}

/* ── Appearance (accent colour) swatches ─────────────────────────────── */
/* Gegenstück zu `.editor .swatch` (Stil-Dialog): grössere, abgerundete Kacheln.
   Beide teilen sich absichtlich NICHT dieselbe Regel — siehe Kommentar dort. */
.swatches { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: 0.4rem; }
.swatch {
  width: 2rem; height: 2rem; padding: 0; min-width: 0;
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  cursor: pointer;
  color: var(--muted-foreground);
}
.swatch-neutral { background: var(--background); }
.swatch.selected { box-shadow: 0 0 0 2px var(--background), 0 0 0 4px var(--foreground); }

/* Gruppenbeschriftung über einem Bedienelement, das kein einzelnes Feld ist
   (Vorgaben-Kacheln, Farbschema-Segment, Tracking-Block). Optisch wie ein
   <label>, aber bewusst ein <span> — es gibt kein einzelnes Zielelement. */
.label { display: block; font-size: var(--text-md); font-weight: 500; margin-bottom: 0.35rem; color: var(--foreground); }

/* Checkbox mit Text daneben. Nötig, weil `label` global eine Flex-SPALTE ist:
   ohne diese Regel steht das Kästchen über seiner Beschriftung. */
.check-row { flex-direction: row; align-items: center; gap: var(--space-2); }
.check-row input[type="checkbox"] { width: auto; margin: 0; flex: none; }

/* colour-scheme (Hell/Dunkel/System) segmented control on the appearance page */
.theme-modes { display: inline-flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.4rem; }
.theme-mode.selected {
  background: var(--brand); color: var(--brand-foreground); border-color: transparent;
}

/* ── Stat cards (merged Gäste page: Eingeladen/Geantwortet/…) ─────────── */
.stat-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}
.stat-card {
  display: flex; flex-direction: column; gap: 0.15rem;
  background: var(--card); color: var(--card-foreground);
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: 0.85rem 1rem;
}
.stat-card .stat-value { font-size: var(--text-2xl); font-weight: 700; line-height: 1.2; }
.stat-card .stat-label { font-size: var(--text-sm); color: var(--muted-foreground); }
/* Verlinkte Kachel (Übersicht): sie sieht aus wie die anderen, führt aber
   irgendwohin — der Zeiger und die Hebung sagen das, ohne ein zweites Aussehen
   zu erfinden. Ohne die Unterstreichungs-Rücknahme bekäme das Label eine, weil
   die ganze Kachel ein `<a>` ist. */
a.stat-card { text-decoration: none; transition: box-shadow 0.15s, border-color 0.15s; }
a.stat-card:hover { box-shadow: var(--shadow-sm); border-color: var(--brand); }

/* ── Dialog / AlertDialog (Datastar-driven, replaces window.confirm) ──── */
.confirm-form { display: inline; }
.dialog-overlay { position: fixed; inset: 0; background: oklch(0 0 0 / 0.5); z-index: var(--z-overlay); }
.dialog {
  position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: var(--z-dialog);
  width: min(28rem, calc(100vw - 2rem));
  background: var(--popover); color: var(--popover-foreground);
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: var(--shadow); padding: var(--space-5);
  text-align: left;
}
.dialog-title { font-size: var(--text-lg); font-weight: 600; margin-bottom: 0.35rem; }
.dialog-desc { font-size: var(--text-md); color: var(--muted-foreground); margin-bottom: 1.25rem; }
.dialog-footer { display: flex; justify-content: flex-end; gap: var(--space-2); }
/* Form variant: a dialog holding a full create/edit form — a bit wider, and
   scrolls internally when custom fields make the form tall. */
.dialog-form {
  width: min(34rem, calc(100vw - 2rem));
  max-height: min(80vh, 44rem);
  overflow-y: auto;
}
.dialog-form .dialog-footer { margin-top: var(--space-4); }
/* Wide variant: shows the mail preview at near-full width so the email renders
   without the horizontal scroll the narrow sidebar preview forces. */
.dialog-preview {
  width: min(56rem, calc(100vw - 3rem));
  padding: var(--space-4);
}
.dialog-preview-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3); margin-bottom: var(--space-3);
}
.dialog-preview .mail-preview { height: min(80vh, 50rem); }

/* ── Toast (self-dismissing flash) ───────────────────────────────────── */
.toaster { position: fixed; bottom: 1rem; right: 1rem; z-index: var(--z-toast); display: flex; flex-direction: column; gap: var(--space-2); }
.toast {
  display: flex; align-items: center; gap: var(--space-3); max-width: 24rem;
  background: var(--popover); color: var(--popover-foreground);
  border: 1px solid var(--border); border-radius: var(--radius-md);
  box-shadow: var(--shadow); padding: 0.7rem 1rem; font-size: var(--text-md);
}
.toast-x { height: auto; padding: 0; min-width: 0; background: transparent; color: var(--muted-foreground); border: none; }
.toast-x:hover { background: transparent; color: var(--foreground); }
/* Error toast: red accent, persists until dismissed (no auto-dismiss interval). */
.toast.toast-error {
  border-color: var(--error-border);
  background: var(--error-bg);
  color: var(--error-text);
}
.toast.toast-error .toast-x { color: var(--error-text); }

/* ── Standalone admin error page (GET errors with no page to return to) ── */
.admin.error-page { align-items: center; justify-content: center; padding: var(--space-6); }
.error-card {
  max-width: 32rem; text-align: center;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: var(--shadow); padding: 2rem 2.5rem;
}
.error-code { font-size: var(--text-4xl); font-weight: 700; color: var(--text-muted); margin-bottom: var(--space-1); }
.error-card h1 { font-size: var(--text-xl); margin-bottom: var(--space-2); }
.error-msg { color: var(--text-muted); margin-bottom: var(--space-5); }

/* ── Command palette (⌘K) ────────────────────────────────────────────── */
/* `.cmdk` (Wurzel) und `.cmdk-group` (Klammer je Anlass, role=presentation)
   tragen bewusst keine Regeln: sie sind reine DOM-Anker für den Tastatur-Handler
   bzw. für die Gruppierung. Nicht als „vergessenes CSS" nachrüsten. */
.cmdk-overlay { position: fixed; inset: 0; background: oklch(0 0 0 / 0.5); z-index: var(--z-cmdk); }
.cmdk-panel {
  position: fixed; left: 50%; top: 15%; transform: translateX(-50%); z-index: calc(var(--z-cmdk) + 1);
  width: min(34rem, calc(100vw - 2rem));
  background: var(--popover); color: var(--popover-foreground);
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: var(--shadow); overflow: hidden;
}
.cmdk-input { width: 100%; border: none; border-bottom: 1px solid var(--border); border-radius: 0; padding: 0.85rem 1rem; font-size: var(--text-base); }
/* Das Suchfeld entfernte seinen Fokusring ersatzlos — als einziges Feld der
   Anwendung. Es ist zwar das erste fokussierte Element beim Öffnen, aber sobald
   der Fokus per Tab in die Trefferliste und zurück wandert, fehlte jede Anzeige.
   Statt des Rings eine kräftigere Unterkante: die Panel-Optik bleibt ruhig. */
.cmdk-input:focus, .cmdk-input:focus-visible {
  box-shadow: none;
  border-color: var(--ring);
  border-bottom-width: 2px;
}
.cmdk-list { list-style: none; margin: 0; padding: 0.35rem; max-height: 22rem; overflow-y: auto; }
.cmdk-list li a { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); padding: 0.55rem 0.65rem; border-radius: var(--radius-sm); color: var(--popover-foreground); }
.cmdk-list li a:hover { background: var(--shadcn-accent); text-decoration: none; }
.cmdk-hint { font-size: var(--text-sm); color: var(--muted-foreground); }
/* Live anlass/event search results (patched into #cmdk-results) */
.cmdk-results { padding: 0 0.35rem 0.35rem; max-height: 18rem; overflow-y: auto; border-top: 1px solid var(--border); }
.cmdk-results:empty { border-top: none; }
.cmdk-section { font-size: var(--text-xs); text-transform: uppercase; letter-spacing: .04em; color: var(--muted-foreground); padding: 0.55rem 0.65rem 0.25rem; }
.cmdk-results a { display: flex; align-items: center; gap: 0.55rem; padding: 0.5rem 0.65rem; border-radius: var(--radius-sm); color: var(--popover-foreground); }
.cmdk-results a:hover { background: var(--shadcn-accent); text-decoration: none; }
.cmdk-results .cmdk-anlass { font-weight: 500; }
.cmdk-results .cmdk-teil { padding-left: 1.7rem; color: var(--muted-foreground); }
/* Feste Breite, damit Anlass-Symbol und Baum-Zweig der Events darunter auf
   derselben Achse stehen — sonst versetzt jedes unterschiedlich breite Zeichen
   den Text daneben. */
.cmdk-ico { flex: none; width: 1.1em; text-align: center; }
.cmdk-branch { flex: none; width: 1.1em; text-align: center; color: var(--muted-foreground); }
.cmdk-empty { padding: 0.5rem 0.65rem; color: var(--muted-foreground); font-size: var(--text-md); }
/* Keyboard-navigation highlight: ArrowUp/Down move .cmdk-active over the visible
   [data-cmdk-item] anchors. A brand-coloured left bar distinguishes the
   keyboard-active item from a plain mouse :hover (which only fills the bg). */
.cmdk-list li a.cmdk-active, .cmdk-results a.cmdk-active {
  position: relative;
  background: var(--shadcn-accent);
}
/* Straight, square-ended bar (same look as the active-tab underline). A pseudo-
   element is used instead of an inset box-shadow so the item's border-radius
   doesn't curve the bar's ends. */
.cmdk-list li a.cmdk-active::before, .cmdk-results a.cmdk-active::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--brand);
}

/* ── Sidebar user dropdown ───────────────────────────────────────────── */
.side-user { position: relative; }
.side-user-trigger {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);
  width: 100%; height: auto; padding: 0.5rem 0.6rem;
  background: transparent; color: var(--sidebar-foreground);
  border: 1px solid var(--sidebar-border); border-radius: var(--radius-md);
  font-size: var(--text-md); font-weight: 500;
}
.side-user-trigger:hover { background: var(--sidebar-accent); }
.side-user-trigger .caret { color: var(--sidebar-muted); }
.user-menu {
  position: absolute; bottom: calc(100% + 0.35rem); left: 0; right: 0; z-index: var(--z-popover);
  display: flex; flex-direction: column; gap: 0.1rem; padding: 0.35rem;
  background: var(--popover); color: var(--popover-foreground);
  border: 1px solid var(--border); border-radius: var(--radius-md); box-shadow: var(--shadow);
}
.user-menu form { margin: 0; }
.user-menu a, .user-menu .menu-item-btn {
  display: block; width: 100%; text-align: left; height: auto;
  padding: 0.45rem 0.6rem; border-radius: var(--radius-sm);
  background: transparent; color: var(--popover-foreground); border: none;
  font-size: var(--text-md); font-weight: 500;
}
.user-menu a:hover, .user-menu .menu-item-btn:hover { background: var(--shadcn-accent); text-decoration: none; }

/* ── E-Mail campaign composer ────────────────────────────────────────── */
/* Side-by-side: a compact editor form (left) next to a live preview (right)
   that stays pinned while the taller form scrolls. Fills the horizontal space
   the narrow form would otherwise leave empty. Collapses to a stack below 64rem. */
.composer {
  display: grid;
  grid-template-columns: minmax(0, 34rem) minmax(0, 1fr);
  gap: var(--space-5);
  align-items: start;
}
.composer-edit { max-width: 34rem; }
/* Pinned preview: align-self:start gives sticky room to travel within the grid. */
.composer-preview {
  position: sticky;
  top: 1rem;
  align-self: start;
}
/* The .card is a flex column with align-items:flex-start, so children shrink to
   content width. Force the preview header + iframe wrapper to the full card
   width, otherwise the iframe collapses to its intrinsic ~300px default. */
.composer-preview > #campaign-preview { width: 100%; }
.preview-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
}
.preview-head h2 { margin-bottom: 0; }
@media (max-width: 64rem) {
  .composer { grid-template-columns: 1fr; }
  .composer-preview { position: static; }
}
.composer-edit textarea {
  width: 100%;
  font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: var(--text-md);
}
.merge-palette { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.4rem; }
.chip {
  height: auto;
  border: 1px solid var(--border);
  background: var(--secondary);
  color: var(--secondary-foreground);
  border-radius: 999px;
  padding: 0.2rem 0.7rem;
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
}
.chip:hover { border-color: var(--brand); color: var(--brand); background: var(--background); }
/* Button chips (insert a themed {{…_button}}) read as solid, vs the outline link/field chips. */
.chip-button { background: var(--brand); color: var(--brand-foreground); border-color: transparent; }
.chip-button:hover { background: var(--brand-hover); color: var(--brand-foreground); }

/* In-page sub-tabs (Mail-Design ↔ Versand): the .tab buttons reuse the .tabbar
   look but need the default <button> chrome neutralised. */
.subtabbar { margin-top: 0; }
/* Neutralise the default <button> chrome (rounded corners, fixed height) so the
   sub-tabs render identically to the <a>.tab main tabs: a clean straight
   underline flush with the bar, not a rounded pill. Font/padding come from
   `.tabbar .tab`, which already outranks the base button rule. */
.tabbar button.tab { background: none; border: none; border-bottom: 2px solid transparent; border-radius: 0; height: auto; cursor: pointer; color: var(--text-muted); }
.tabbar button.tab.active { color: var(--brand); border-bottom-color: var(--brand); font-weight: 600; }

/* Recipient list filters (search + status select side by side). */
.recipient-filters { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; margin-bottom: var(--space-4); }
.recipient-filters .list-search { margin-bottom: 0; }
.recipient-filters select.list-search { max-width: 14rem; }
.recipient-action { text-align: right; white-space: nowrap; }
.mail-preview {
  width: 100%;
  /* Cap to the viewport so the sticky preview never gets clipped under the fold. */
  height: min(40rem, calc(100vh - 7rem));
  border: 1px solid var(--border);
  border-radius: var(--radius);
  /* Intentionally light, even under admin dark mode: the preview shows the
     actual email, which is a light HTML document as recipients see it. */
  background: #fff;
}
.send-failures {
  margin-top: var(--space-3);
  padding: 0.75rem 1rem;
  border: 1px solid var(--error-border);
  background: var(--error-bg);
  border-radius: var(--radius);
}
.send-failures ul { margin: 0.25rem 0 0.5rem 1.25rem; }
.send-failures li { color: var(--error-text); }

/* ── Dashboard quick-access (Schnellzugriff) ─────────────────────────── */
.quick-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: center;
}
.quick-links {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
  gap: 0.6rem;
}
.quick-link {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  padding: 0.7rem 0.9rem;
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  transition: border-color 0.15s, background 0.15s;
}
.quick-link:hover { border-color: var(--accent); background: var(--surface); text-decoration: none; }
.quick-link-name { font-weight: 600; }
.quick-link-meta { font-size: var(--text-sm); color: var(--text-muted); }

/* ── Member access control (users page) ──────────────────────────────── */
tr.access-panel td { background: var(--surface-alt); padding-top: 0; }
tr.access-panel:hover td { background: var(--surface-alt); }
.access-panel summary { cursor: pointer; padding: 0.4rem 0; font-size: var(--text-md); font-weight: 600; color: var(--text-muted); }
.access-grid { display: flex; flex-direction: column; gap: var(--space-2); padding: 0.5rem 0 0.75rem; }
.access-row {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-3);
  padding: 0.5rem 0.75rem; background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius);
}
.access-row > span:first-of-type { flex: 1; min-width: 9rem; font-weight: 500; }
.access-row label { flex-direction: row; align-items: center; gap: 0.3rem; font-weight: 400; }
