/* ============================================================
   Oberflaechen-Standard – Fassung 2.21.1, 19.10.2026
   components.css – Basiskomponenten des Standards
   Setzt tokens.css voraus. Enthaelt bewusst keine festen
   Farb- oder Abstandswerte.
   ============================================================ */

/* ------------------------------------------------------------
   Sinnbildvorrat
   Muss eine Klasse sein, kein style-Attribut: jede
   Inhaltsrichtlinie mit default-src 'self' wirft
   style="display:none" weg, und der Vorrat steht dann als
   unsichtbarer 300x150-Kasten im Fluss.
   ------------------------------------------------------------ */

.iconsprite {
  display: none;
}

/* ------------------------------------------------------------
   Layout: Sidebar, Kopfzeile, Inhalt
   ------------------------------------------------------------ */

/* Gibt es eine Seitenleiste, ruecken die festen Leisten mit.
   Steht hier und nicht in tokens.css als fester Wert: eine
   Oberflaeche ohne Seitenleiste soll nichts einruecken, und
   niemand soll daran denken muessen. */
:root:has(.app) {
  --bar-left: var(--sidebar-width);
}

.app {
  display: grid;
  grid-template-columns: var(--sidebar-width) 1fr;
  grid-template-rows: var(--header-height) 1fr;
  grid-template-areas:
    "sidebar header"
    "sidebar main";
  min-height: 100vh;
}

/* Stufe 2: Icon-Leiste bei mittleren Breiten, sowie manuell
   eingeklappt ueber .app--collapsed */
.app--collapsed {
  grid-template-columns: var(--sidebar-width-icons) 1fr;
}

.main {
  grid-area: main;
  /* Gedaempfter Grund: die Panels darauf sind die helle
     Flaeche. Ohne das haette eine weisse Panel auf weissem
     Grund nur ihren Rahmen. */
  background: var(--color-bg-subtle);
  padding: var(--space-5);
  /* Platz fuer eine eingeblendete untere Leiste freihalten */
  padding-bottom: calc(var(--space-5) + var(--bottombar-height));
  max-width: var(--content-max-width);
  /* Ohne min-width:0 waechst die Grid-Spalte mit breiten
     Tabellen mit, statt sie scrollen zu lassen – die Seite
     laeuft dann seitlich ueber. */
  min-width: 0;
}

/* --- Sidebar --------------------------------------------- */

.sidebar {
  grid-area: sidebar;
  background: var(--color-surface);
  border-right: 1px solid var(--color-border);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3) var(--space-2);
  overflow-y: auto;
  transition: width var(--transition-base);
}

.sidebar__item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--touch-target);
  padding: 0 var(--space-3);
  border-radius: var(--radius-md);
  color: var(--color-text);
  text-decoration: none;
  white-space: nowrap;
}

.sidebar__item:hover {
  background: var(--color-bg-subtle);
}

.sidebar__item[aria-current="page"] {
  background: var(--color-primary-soft);
  color: var(--color-primary);
  font-weight: var(--weight-bold);
}

.sidebar__icon {
  flex: 0 0 20px;
  width: 20px;
  height: 20px;
}

/* Eingeklappt: nur Icons, Beschriftung verschwindet */
.app--collapsed .sidebar__label {
  display: none;
}

/* --- Bereich in der Leiste ---------------------------------
   Hat ein Menuepunkt viele Unterpunkte, schaltet die Leiste in
   ihn hinein: oben der Rueckweg „Alle Bereiche“, darunter der
   Bereich als Ueberschrift und seine Unterpunkte, bei Bedarf
   mit Zwischenueberschriften. Die Reiter im Inhalt entfallen
   dann – dieselben Wege zweimal waeren zwei Antworten auf eine
   Frage. Verhalten: initSidebarArea. */

/* Die beiden Ebenen tragen keinen eigenen Kasten: ihre
   Eintraege stehen im gap der Leiste wie sonst auch. */
.sidebar__level {
  display: contents;
}

/* Der Rueckweg ist kein .sidebar__item: initSidebar schloesse
   sonst am Telefon die Schublade, obwohl es in ihr weitergeht. */
.sidebar__back {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--touch-target);
  padding: 0 var(--space-3);
  border: 0;
  border-radius: var(--radius-md);
  background: none;
  color: var(--color-text-muted);
  font: inherit;
  text-align: left;
  white-space: nowrap;
  cursor: pointer;
}

.sidebar__back:hover {
  background: var(--color-bg-subtle);
  color: var(--color-text);
}

/* Liegt anderswo etwas an, traegt der Rueckweg einen Punkt.
   Das Wort steht fuer Vorlesehilfen im Element (.sr-only). */
.sidebar__back[data-pending]::after {
  content: "";
  width: var(--space-2);
  height: var(--space-2);
  margin-left: auto;
  border-radius: 50%;
  background: var(--color-danger);
}

.sidebar__area {
  margin: var(--space-2) 0 0;
  padding: 0 var(--space-3);
  font-size: var(--font-lg);
  line-height: var(--line-heading);
  font-weight: var(--weight-bold);
}

.sidebar__group {
  margin: var(--space-3) 0 0;
  padding: 0 var(--space-3);
  font-size: var(--font-xs);
  font-weight: var(--weight-bold);
  color: var(--color-text-muted);
}

/* Schmale Stufe: der Rueckweg wird ein Pfeil wie die anderen
   Sinnbilder, die Ueberschrift faellt weg (das Wort steht im
   title), eine Zwischenueberschrift wird ein Strich. */
.app--collapsed .sidebar__area {
  display: none;
}

.app--collapsed .sidebar__group {
  height: 0;
  margin: var(--space-2) var(--space-2);
  padding: 0;
  overflow: hidden;
  border-top: 1px solid var(--color-border);
}

/* --- Kopfzeile ------------------------------------------- */

.header {
  grid-area: header;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 0 var(--space-5);
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  /* Wie bei .main: ohne min-width:0 zieht die Kopfzeile die
     ganze Grid-Spalte auf ihre min-content-Breite auf. */
  min-width: 0;
}

.header__title {
  font-size: var(--font-lg);
  font-weight: var(--weight-bold);
  margin: 0;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Burger und Nutzermenue stehen beide rechts, nebeneinander:
   Burger links neben dem Nutzermenue. */
.header__actions {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: 0 0 auto;
}

/* Spezifitaet noetig, sonst gewinnt .btn weiter unten */
.header .header__burger {
  display: none; /* nur in der Handy-Stufe */
}

.usermenu__trigger {
  width: var(--touch-target);
  height: var(--touch-target);
  border-radius: 50%;
  border: 1px solid var(--color-border);
  background: var(--color-primary-soft);
  color: var(--color-primary);
  font-size: var(--font-sm);
  font-weight: var(--weight-bold);
  cursor: pointer;
}

/* --- Nutzermenue ----------------------------------------- */

.usermenu {
  position: relative;
}

.usermenu__menu {
  position: absolute;
  right: 0;
  top: calc(100% + var(--space-2));
  min-width: 220px;
  display: none;
  flex-direction: column;
  padding: var(--space-1);
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  z-index: var(--z-header);
}

.usermenu:has(> .usermenu__trigger[aria-expanded="true"]) .usermenu__menu {
  display: flex;
}

.usermenu__name {
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--color-border);
  color: var(--color-text-muted);
  font-size: var(--font-sm);
}

/* Ein Eintrag ist Verweis oder Knopf: „Hell oder dunkel“ handelt sofort,
   „Abmelden“ schickt ein Formular (POST). Der Knopf verliert hier alles,
   was der Browser ihm mitgibt – Rand, graue Flaeche, Systemschrift,
   Mittelachse –, sonst steht er als Fremdkoerper zwischen den Verweisen. */
.usermenu__item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  min-height: var(--touch-target);
  padding: 0 var(--space-3);
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--color-text);
  font: inherit;
  text-align: start;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}

/* Jeder Eintrag beginnt mit seinem Sinnbild, 20 px wie in der
   Seitenleiste: die Eintraege stehen in derselben 40-px-Zeile und
   werden ebenso ueberflogen. Ohne Groesse rechnete der Browser mit
   300 x 150. */
.usermenu__item svg {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
}

/* Das Formular um „Abmelden“ ist fuer die Anordnung nicht da: der Knopf
   steht direkt in der Spalte wie die anderen Eintraege. */
.usermenu__menu form {
  display: contents;
}

.usermenu__item:hover {
  background: var(--color-bg-subtle);
}

/* --- Menueknopf --------------------------------------------
   Eine Handlung mit mehreren Wegen („Annehmen …“ → „und
   Nachricht senden“ / „und Nachricht bearbeiten“). Die Punkte
   kuendigen die Wahl an. Ein Knopf, ein Ziel, ein Tab-Halt –
   kein geteilter Knopf, dessen zweite Haelfte viele nie finden.

   Das Menue steht fixed und wird von positionPopover gesetzt:
   .panel traegt overflow: clip und schnitte es sonst ab.
   Verhalten: initMenuButton. */
.menu {
  position: fixed;
  z-index: var(--z-header);
  min-width: 220px;
  display: flex;
  flex-direction: column;
  padding: var(--space-1);
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}

.menu__item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-target);
  padding: 0 var(--space-3);
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--color-text);
  font: inherit;
  text-align: left;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}

.menu__item:hover,
.menu__item:focus-visible {
  background: var(--color-bg-subtle);
}

.menu__item:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: -2px;
}

/* ------------------------------------------------------------
   Buttons
   Primary und Secondary sind gleich gross. Der Unterschied
   liegt nur in der Farbe.
   ------------------------------------------------------------ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--touch-target);
  padding: 0 var(--space-4);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  font-family: inherit;
  font-size: var(--font-sm);
  font-weight: var(--weight-bold);
  line-height: 1;
  /* Ein Link, der wie ein Knopf aussieht (Herunterladen,
     „Neu anlegen" in der Kopfzone), erbt sonst die
     Unterstreichung der Verweise. */
  text-decoration: none;
  cursor: pointer;
  transition: background var(--transition-fast);
}

.btn--primary {
  background: var(--color-primary);
  color: var(--color-primary-text);
}

.btn--primary:hover:not(:disabled) {
  background: var(--color-primary-hover);
}

.btn--secondary {
  background: var(--color-surface);
  color: var(--color-text);
  border-color: var(--color-border-strong);
}

.btn--secondary:hover:not(:disabled) {
  background: var(--color-bg-subtle);
}

.btn--danger {
  background: var(--color-danger);
  color: var(--color-text-inverse);
}

.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Laufende Aktion: Spinner im Knopf, Schutz gegen Doppelklick
   uebernimmt zusaetzlich das JavaScript. Der Spinner wird aus
   aria-busy erzeugt – so kann kein Knopf "beschaeftigt" sein
   und trotzdem still stehen. */
.btn[aria-busy="true"] {
  pointer-events: none;
}

.btn[aria-busy="true"] .btn__label {
  opacity: 0.6;
}

.btn[aria-busy="true"]::before {
  content: "";
  width: 1em;
  height: 1em;
  flex: 0 0 auto;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: btn-spin 0.6s linear infinite;
}

@keyframes btn-spin {
  to { transform: rotate(360deg); }
}

/* Ohne Bewegung bleibt der Ring stehen und markiert den
   Zustand allein durch seine Anwesenheit. */
@media (prefers-reduced-motion: reduce) {
  .btn[aria-busy="true"]::before {
    animation: none;
    border-right-color: currentColor;
    opacity: 0.4;
  }
}

/* Ohne Groesse rechnet der Browser mit 300x150 – im
   40-Pixel-Knopf. Gilt fuer jedes Sinnbild im Knopf, nicht
   nur fuer die Icon-only-Form. */
.btn svg {
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
}

/* Die Haupthandlung einer Ansicht: derselbe Knopf wie ein
   Primary, nur deutlich groesser. Gedacht fuer die eine
   Handlung, um die es auf dieser Seite geht – "Nummer
   aufrufen", "Lauf starten".

   Ersetzt .btn--primary, wird nicht damit kombiniert: sonst
   entstehen zwei Primary-Knoepfe in einer Ansicht, und die
   Regel "nur einer" ist dahin.

   Die Hoehe haengt an --main-action-height. In der Variante
   "Theke" steht die Variable auf 88px, sonst auf 64px – die
   Komponente selbst bleibt dieselbe. */
.btn--main {
  min-height: var(--main-action-height);
  padding: 0 var(--space-7);
  font-size: var(--font-lg);
  border-radius: var(--radius-lg);
  background: var(--color-primary);
  color: var(--color-primary-text);
}

.btn--main:hover:not(:disabled) {
  background: var(--color-primary-hover);
}

/* Icon-only nur in Tabellenzeilen und Karten. Braucht immer
   aria-label und Tooltip. */
.btn--icon {
  width: var(--touch-target);
  min-height: var(--touch-target);
  padding: 0;
  background: transparent;
  color: var(--color-text-muted);
}

.btn--icon:hover {
  background: var(--color-bg-subtle);
  color: var(--color-text);
}

/* Ein Weg, der keine Handlung ist: Filter zuruecksetzen,
   "Mehr anzeigen", Abbrechen neben einem Ghost-Paar. Traegt
   keine Flaeche und keinen Rand, damit er nicht mit einer
   Handlung verwechselt wird – aber dieselbe Groesse, damit die
   Zeile ruhig bleibt. */
.btn--ghost {
  background: transparent;
  color: var(--color-primary);
}

.btn--ghost:hover:not(:disabled) {
  background: var(--color-primary-soft);
}

/* Handgriffe in einer Tabellenzeile oder Karte, wo die volle
   Knopfhoehe die Zeile sprengen wuerde. Nur dort – in
   Formularen und Dialogen bleibt die volle Groesse. */
.btn--small {
  min-height: calc(var(--touch-target) - var(--space-2));
  padding: 0 var(--space-3);
  font-size: var(--font-xs);
}

/* Hoechstens drei Buttons; darueber zwei plus Ueberlauf-Menue. */
.btn-group {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  flex-wrap: wrap;
}

/* ------------------------------------------------------------
   Formulare
   ------------------------------------------------------------ */

/* Eine Zeile fasst hoechstens drei Felder. Darueber stehen die
   Verhaeltnisse gegeneinander und jedes Feld verliert; ausserdem
   findet das Auge die Zusammengehoerigkeit nicht mehr. Wer neun
   Felder in eine Zeile legt, bekommt neun zu schmale Felder. */
.form-row {
  display: flex;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
  flex-wrap: wrap;
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  flex: 1 1 auto;
  /* Verankert die Schriftgroesse, an der ch gerechnet wird.
     Alle Feldbreiten stehen in ch, und ch gilt an der Schrift
     DES KASTENS, der die Breite traegt – nicht an der des
     Bedienelements darin. Ohne diese Zeile macht eine kleinere
     Schrift am Kasten jedes Feld schmaler, als die Tabelle
     sagt, und zwar unbemerkt. Die kleine Schrift der
     Beschriftung gehoert an .field__label, nicht hierher. */
  font-size: var(--font-base);
}

/* Feldbreiten folgen dem erwarteten Inhalt.

   Jede Breite ist ein Verhaeltnis MIT Boden. Ein reines
   Verhaeltnis (flex-basis 0, keine min-width) schrumpft in
   einer vollen Zeile unter jede brauchbare Groesse: die PLZ
   zeigt dann zwei von fuenf Stellen und die Anrede gar nichts.
   Der Boden steht in ch, haengt also am erwarteten Inhalt und
   nicht an einer Pixelzahl. */
.field--street     { flex: 3 1 0; min-width: 16ch; }
.field--houseno    { flex: 1 1 0; min-width: 6ch; }
.field--zip        { flex: 1 1 0; min-width: 8ch; }
.field--city       { flex: 3 1 0; min-width: 14ch; }
.field--salutation { flex: 1 1 0; min-width: 9ch; }
.field--firstname  { flex: 2 1 0; min-width: 12ch; }
.field--lastname   { flex: 2 1 0; min-width: 12ch; }

/* Felder mit natuerlicher Obergrenze: der Inhalt hat eine
   bekannte Laenge, also waechst das Feld nicht mit. Ein
   Datumsfeld ueber die halbe Zeile zu ziehen sieht falsch aus,
   weil dahinter immer zehn Zeichen stehen. */
/* Datum und Uhrzeit zeichnet der Browser selbst: Ziffern UND
   ein Sinnbild. Gemessen in Chromium bei 16px Feldschrift
   braucht ein Datumsfeld 176px, ein Uhrzeitfeld 138px.

   Liegt der Boden darunter, quetscht der Browser das Feld und
   schiebt das Sinnbild ueber die Ziffern – das Uhr-Zeichen
   verdeckt dann die Minuten. Was man sieht, ist nicht ein zu
   schmales Feld, sondern ein kaputtes. Die Boeden liegen
   deshalb ueber der Eigenbreite. */
.field--date       { flex: 0 1 auto; min-width: 20ch; max-width: 22ch; }
.field--time       { flex: 0 1 auto; min-width: 16ch; max-width: 18ch; }
.field--amount     { flex: 0 1 auto; min-width: 12ch; max-width: 16ch; }

/* Mengen und Betraege – etwas, das man rechnet. */
.field--number     { flex: 0 1 auto; min-width: 12ch; max-width: 20ch; }

/* Kennungen – etwas, das man abliest und weitergibt. Getrennt
   von .field--number, weil es eine andere Sache ist, nicht nur
   eine kuerzere: man rechnet nicht mit ihr.

   Kennungen stehen in der Kennungsschrift, und die ist breiter
   als ch, das an der Textschrift von .field misst. Gemessen in
   Chromium bei 16px: ein Zeichen in Source Code Pro braucht im
   Feld 10px, ch sind 8px; dazu kommen 26px Innenabstand und
   Rand. Ein Feld fuer n Zeichen braucht also 1,25 × n + 3,25 ch.
   Die alten Boeden in ch der Textschrift zeigten „K-10428“
   nicht mehr ganz.

   Mitglieds-, Kunden-, Belegnummer: Boden 8, Deckel 10 Zeichen. */
.field--identifier    { flex: 0 1 auto; min-width: 14ch; max-width: 16ch; }

/* Bankkennungen. BIC: 8 oder 11 Zeichen. Referenz:
   Mandatsreferenz (bis 35 Zeichen) und Glaeubiger-ID (18) –
   der Boden fasst die Glaeubiger-ID, der Deckel die laengste
   Mandatsreferenz. Die Referenz waechst (flex-grow 1): mit 0
   bliebe sie bei der Eigenbreite des Eingabeelements stehen,
   gemessen 226px, und zeigte nie mehr als 20 Zeichen. */
.field--bic        { flex: 0 1 auto; min-width: 17ch; max-width: 19ch; }
.field--reference  { flex: 1 1 auto; min-width: 26ch; max-width: 47ch; }

/* Bezeichnung eines Objekts: "Beitragslauf 2026 Q1",
   Vereinsname, Titel einer Vorlage. Nicht fuer Personennamen –
   dafuer gibt es .field--firstname und .field--lastname. */
.field--title { flex: 2 1 0; min-width: 12ch; max-width: 32ch; }

/* Kontaktangaben mit bekannter Laenge */
.field--phone      { flex: 0 1 auto; min-width: 14ch; max-width: 20ch; }
.field--email      { flex: 3 1 0;    min-width: 18ch; max-width: 40ch; }

/* Eine deutsche IBAN hat 22 Zeichen, mit Gruppentrennung 27.
   Der BODEN bestimmt hier die Breite, nicht der Deckel: bei
   flex-basis auto ist das Feld sonst so breit wie das Eingabe-
   element von Haus aus ist (20 Zeichen), und der Deckel aendert
   daran nichts. In Kennungsschrift (siehe .field--identifier)
   braucht die gruppierte Schreibweise 37ch; die frueheren 30ch
   reichten nur in der Textschrift (gemessen: 240 statt 296px). */
.field--iban       { flex: 0 1 auto; min-width: 37ch; max-width: 40ch; }

/* Was man abliest und weitergibt, steht in Kennungsschrift:
   Ziffern gleicher Breite, eine Null, die kein O ist. Im Feld
   ebenso wie dort, wo derselbe Wert nur gezeigt wird
   (.identifier). */
.field--identifier .field__input,
.field--iban .field__input,
.field--bic .field__input,
.field--reference .field__input,
.identifier {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums slashed-zero;
}

/* Vor- und Nachname in EINEM Feld ("Maike Ahrens" oder
   "Ahrens, Maike"). Breite entspricht firstname + lastname,
   der Boden deckt beide Teile plus Leerzeichen ab. */
.field--fullname { flex: 4 1 0; min-width: 20ch; max-width: 36ch; }

/* Strasse und Hausnummer in EINEM Feld ("Feldweg 53").
   Breiter als .field--street, weil die Nummer mit hineinmuss. */
.field--streetline { flex: 4 1 0; min-width: 20ch; }

/* Nimmt die ganze Zeile fuer sich – Notizen, lange Bezeichnungen. */
.field--full       { flex: 1 1 100%; }

/* Auswahlfelder richten sich nach ihrer laengsten Option, nicht
   nach der Zeile. Ein Auswahlfeld mit "ja/nein" ueber die volle
   Breite sieht aus, als fehlte etwas – und der Nutzer sucht den
   Inhalt am falschen Ende. */
.field--choice {
  flex: 0 1 auto;
  min-width: 9ch;
  max-width: 100%;
}

select.field__input {
  width: auto;
  /* Platz fuer den Pfeil rechts */
  padding-right: var(--space-6);
}

.field__label {
  font-size: var(--font-sm);
  color: var(--color-text-muted);
  /* Sicherheitsnetz, kein Freibrief: bei einem schmalen,
     gedeckelten Feld bricht eine zu lange Beschriftung um,
     statt in den Nachbarn zu laufen. Schoen ist das nicht –
     ohne Trennwoerterbuch bricht es mitten im Wort. Die
     Loesung ist die kurze Beschriftung ("Nummer" statt
     "Mitgliedsnummer"), nicht der Umbruch. */
  overflow-wrap: break-word;
  hyphens: auto;
  max-width: 100%;
}

/* Pflichtfelder mit Stern, optionale bleiben unmarkiert */
.field__label--required::after {
  content: " *";
  color: var(--color-danger);
}

.field__input {
  min-height: var(--touch-target);
  padding: 0 var(--space-3);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text);
  font-family: inherit;
  /* Nicht kleiner als 16px, sonst zoomt iOS beim Fokus */
  font-size: var(--font-base);
  /* Ein input hat voreingestellt min-width: auto, also seine
     eigene Mindestbreite, und width: 100% aendert daran nichts.
     Ohne min-width: 0 laeuft es aus seinem Kasten heraus und
     ueberlappt den Nachbarn.

     Das allein genuegt nicht: aus dem Ueberlauf wird dann eine
     Quetschung. Felder, die der Browser zeichnet, brauchen
     zusaetzlich einen Boden oberhalb ihrer Eigenbreite – siehe
     .field--date und .field--time. */
  min-width: 0;
}

/* Mehrzeilige Textfelder teilen sich die Gestaltung mit den
   einzeiligen, brauchen aber Innenabstand oben und unten und
   eine Standardhoehe. Nur senkrecht groesser ziehbar: waagerecht
   wuerde das Feld aus dem Zeilenraster laufen. */
textarea.field__input {
  min-height: calc(4 * var(--font-base) * var(--line-body));
  padding: var(--space-2) var(--space-3);
  line-height: var(--line-body);
  resize: vertical;
}

.field--invalid .field__input {
  border-color: var(--color-danger);
}

/* Erklaerender Satz unter dem Label, vor dem Fehler. Steht
   dauerhaft da, im Gegensatz zur Fehlermeldung. Wird ueber
   aria-describedby mit dem Feld verbunden. */
.field__hint {
  font-size: var(--font-sm);
  color: var(--color-text-muted);
}

/* Fehlermeldung direkt unter dem Feld, nicht gesammelt oben */
.field__error {
  font-size: var(--font-sm);
  color: var(--color-danger);
}

.field:not(.field--invalid) .field__error {
  display: none;
}

/* Feste Speichern-Leiste in langen Formularen. Setzt zur
   Laufzeit --bottombar-height, damit Toasts darueber liegen. */
/* Die Leisten beginnen NEBEN der Seitenleiste, nicht unter
   ihr. Mit left: 0 liegen sie ueber deren Fuss – dort stehen
   Einstellungen, Hilfe und das Abmelden. Der z-index regelt
   nur, wer den Klick bekommt; was zu SEHEN ist, regelt die
   Kante. --bar-left folgt den drei Stufen der Seitenleiste. */
.savebar,
.actionbar {
  position: fixed;
  left: var(--bar-left);
  right: 0;
  bottom: 0;
  z-index: var(--z-bottombar);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-5);
  background: var(--color-surface-raised);
  border-top: 1px solid var(--color-border);
  box-shadow: var(--shadow-md);
  flex-wrap: wrap;
}

.savebar .btn-group {
  margin-left: auto;
}

/* Aktionsleiste der Mehrfachauswahl faehrt ein.

   visibility gehoert dazu: transform schiebt die Leiste nur aus
   dem Bild, ihre Knoepfe bleiben im Tabulatorlauf. Wer tabbt,
   geht sonst durch unsichtbare Bedienelemente. Die Verzoegerung
   sorgt dafuer, dass die Leiste erst nach dem Ausfahren
   verschwindet. */
.actionbar {
  transform: translateY(100%);
  visibility: hidden;
  transition: transform var(--transition-base),
              visibility 0s linear var(--transition-base);
}

.actionbar--visible {
  transform: translateY(0);
  visibility: visible;
  transition: transform var(--transition-base),
              visibility 0s;
}

.actionbar__count {
  font-weight: var(--weight-bold);
  white-space: nowrap;
}

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

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--font-sm);
  table-layout: fixed;
}

.table th,
.table td {
  height: var(--row-height);  /* immer gleiche Zeilenhoehe */
  padding: 0 var(--space-3);
  text-align: left;
  /* Alle Zellen mittig. Bei zweizeiligen Zeilen faellt eine
     oben haengende Aktion sofort auf. */
  vertical-align: middle;
  border-bottom: 1px solid var(--color-border);
  /* Langer Text wird abgeschnitten, nicht umgebrochen.
     Voller Inhalt steht im title-Attribut. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.table th {
  color: var(--color-text-muted);
  font-weight: var(--weight-bold);
  background: var(--color-bg-subtle);
  /* Normal geschrieben, nicht in Versalien: Hervorhebung
     laeuft ueber Gewicht und Farbe. Versalien lesen sich
     langsamer und nehmen den Woertern die Umrissform. */
  text-transform: none;
  letter-spacing: normal;
}

/* Nur der Kopf klebt, nicht ein Zeilenkopf im Rumpf
   (<th scope="row">). Der rutschte sonst unter den Kopf, und
   als positionierte Zelle fing er das ::after von .row-link
   ein: geklickt werden konnte dann nur noch diese Zelle. */
.table thead th {
  position: sticky;
  /* Standardfall: das DOKUMENT rollt. Der Kopf klebt unter
     Kopfzeile und Filterzeile – ohne den Zuschlag verdeckt er
     beim Scrollen die Filter.

     Rollt stattdessen ein Kasten (.table-scroll), gilt der
     Zuschlag nicht; die Ausnahme steht bei .table-scroll. Es
     kann immer nur eines von beidem rollen. */
  top: calc(var(--header-height) + var(--filterbar-height));
  z-index: 1;
}

/* Zahlen und Betraege rechtsbuendig, damit die Kommastellen
   untereinander stehen */
.table .num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Sortierung: ein Klick auf, zwei ab, drei zurueck zum
   Standard. Nur eine Spalte gleichzeitig. */
.table th[aria-sort] {
  cursor: pointer;
}

.table th[aria-sort] .sort-icon {
  opacity: 0.3;
  margin-left: var(--space-1);
}

.table th[aria-sort="ascending"] .sort-icon,
.table th[aria-sort="descending"] .sort-icon {
  opacity: 1;
}

.table tbody tr:hover {
  background: var(--color-bg-subtle);
}

.table tbody tr[aria-selected="true"] {
  background: var(--color-primary-soft);
}

/* Rollt der Kasten statt des Dokuments, ist er selbst der
   Bezugspunkt: der Zuschlag fuer Kopf- und Filterzeile ist
   dann falsch und schoebe den Kopf in die Tabelle hinein. */
.table-scroll .table thead th {
  top: 0;
}

/* Zweizeilige Zeilen: Titel plus eine Kennzeile. Die Hoehe ist
   wieder fest – nur eben die zweizeilige. Eine dritte Zeile
   gibt es nicht; was dann noch fehlt, gehoert in die
   Detailansicht.

   Der Kopf behaelt die einzeilige Hoehe: er traegt eine Zeile
   Text, und in einem 64px hohen Band schwimmt die Beschriftung
   ohne Bezug zu den Spalten darunter. */
.table--two-line tbody td {
  height: var(--row-height-2);
}

.cell__title {
  display: block;
  font-weight: var(--weight-bold);
  overflow: hidden;
  text-overflow: ellipsis;
}

.cell__meta {
  display: block;
  font-size: var(--font-xs);
  color: var(--color-text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Verweise in Listen sind Text, nicht Verweisblau, und kennen
   kein „besucht". Eine Liste schlaegt man immer wieder auf;
   nach ein paar Tagen waere jede Zeile violett, und die Farbe
   saehe nach einem Zustand des Datensatzes aus, den es nicht
   gibt. Dass der Titel oeffnet, zeigen Zeiger und
   Unterstreichung beim Ueberfahren. */
a.cell__title {
  color: var(--color-text);
  text-decoration: none;
}

a.cell__title:hover {
  text-decoration: underline;
}

/* Die ganze Zeile oeffnet den Datensatz: .row-link am Titel.
   Es bleibt ein echtes <a> – Strg-Klick, Mittelklick, „Link
   kopieren" und die Tastatur mit einem Tab-Halt je Zeile. Ein
   Klick-Handler am <tr> koennte nichts davon.

   Gestreckt wird ueber ::after bis an die Zeile. Dass td
   overflow: hidden traegt, schneidet nichts ab: Bezug ist die
   Zeile, nicht die Zelle. z-index 0 macht jede Zeile zu einem
   eigenen Stapel, sonst rollten die angehobenen Knoepfe unten
   ueber den klebenden Tabellenkopf.

   Die Zelle des Titels darf nicht positioniert sein, sonst ist
   SIE der Bezug und nur sie oeffnet. Deshalb nie in der festen
   ersten Spalte von .table--sticky-first. */
.table tbody tr:has(.row-link) {
  position: relative;
  z-index: 0;
  cursor: pointer;
}

.row-link::after {
  content: "";
  position: absolute;
  inset: 0;
}

/* Alles andere Bedienbare der Zeile liegt ueber der Flaeche,
   sonst oeffnet auch der Klick auf Auswahl oder Aktion den
   Datensatz. */
.table tbody tr:has(.row-link) :is(a, button, input, select, textarea, label, summary):not(.row-link) {
  position: relative;
  z-index: 1;
}

.table tbody tr:has(.row-link):hover .row-link {
  text-decoration: underline;
}

/* Der Fokus umrandet die ganze Zeile, denn die ganze Zeile ist
   das Ziel. Ersetzt, nicht entfernt. */
.row-link:focus-visible {
  outline: none;
  box-shadow: none;
}

.row-link:focus-visible::after {
  outline: 2px solid var(--color-focus);
  outline-offset: -2px;
}

/* Aktionsspalte: ganz rechts, feste Breite, rechtsbuendig.
   Die Breite richtet sich nach der Anzahl der Knoepfe, damit
   die Spalte nicht mit dem Inhalt springt. */
.table .col-actions {
  width: calc(var(--touch-target) + 2 * var(--space-3));
  text-align: right;
  white-space: nowrap;
}

.table .col-actions--2 {
  width: calc(2 * var(--touch-target) + 2 * var(--space-3));
}

/* Auswahlspalte ganz links, ebenfalls fest */
.table .col-select {
  width: calc(var(--touch-target) + var(--space-2));
}

/* Das Kaestchen selbst: grosszuegige Trefferflaeche, in Zeile
   wie in Karte dieselbe. */
.row-check {
  width: var(--space-5);
  height: var(--space-5);
  cursor: pointer;
}

/* ------------------------------------------------------------
   Panel
   Jeder Inhalt steht in einem Panel – Listen, Formulare,
   Kennzahlen, Texte. Das Panel hebt sich vom gedaempften Grund
   des Inhaltsbereichs ab; ohne sie schwimmt der Inhalt.

   Das Panel selbst hat KEINEN Innenabstand: eine Tabelle soll
   bis an den Rand laufen. Den Abstand bringen .panel__header und
   .panel__body mit.
   ------------------------------------------------------------ */

.panel {
  /* Jede Klasse, die einen Blockkasten meint, nennt ihre
     Anzeigeart. An einem <div> faellt das nicht auf, an einem
     eigenen Element (<cl-liste class="panel">) faellt alles
     aus, was einen Blockkasten voraussetzt – eigene Elemente
     sind von Haus aus inline. */
  display: block;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);

  /* clip, NICHT hidden. Beides schneidet an der Rundung ab,
     aber hidden macht aus dem Panel einen Rollbereich – und
     ein sticky darin klebt dann an einem Kasten, der selbst
     mitrollt. Der Spaltenkopf wandert damit aus dem Bild
     (gemessen: -149 statt 50). clip erzeugt keinen
     Rollbereich und kostet nichts: Dialoge und Toasts haengen
     ohnehin am Rumpf. */
  overflow: clip;
}

/* Mehrere Panels untereinander. Der Abstand haengt am Panel
   selbst, nicht an einem Umschlag – ein Element, das nichts
   traegt als einen Abstand, ist eines zu viel, und ohne es
   stehen zwei Panels sonst mit null Abstand aneinander. */
/* Panels stehen in .panels, und der Abstand kommt aus gap.
   gap ist egal, was zwischen zwei Panels steht; ein Abstand
   ueber .panel + .panel faellt dagegen still auf null, sobald
   eine Huelle dazwischen steht (gemessen: 0 statt 24 px). Die
   Nachbarregel bleibt nur als Rueckfall fuer Panels ausserhalb
   von .panels. */
.panel + .panel {
  margin-top: var(--space-5);
}

.panels {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.panels > .panel + .panel {
  margin-top: 0;
}

/* Platz fuer ein Panel, das nachgeladen wird oder ausbleibt.
   display: contents nimmt den Kasten aus dem Fluss: das Panel
   darin steht im gap wie seine Nachbarn, und ein leerer Platz
   erzeugt keine doppelte Luecke. */
.slot {
  display: contents;
}

/* Kopf eines Panels: Titel links, rechts eine Angabe – bei
   Listen die Anzahl der Treffer, sonst etwa ein Zeitraum. */
.panel__header {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-4) var(--space-3);
}

.panel__title {
  font-size: var(--font-lg);
  font-weight: var(--weight-bold);
  margin: 0;
}

.panel__count {
  margin-left: auto;
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

/* Fuer alles ausser Tabellen: bringt den Innenabstand mit. */
.panel__body {
  padding: 0 var(--space-4) var(--space-4);
}

/* Abschluss eines Panels, etwa mit den Aktionen des Formulars */
.panel__footer {
  display: flex;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--color-border);
  background: var(--color-bg-subtle);
}

/* ------------------------------------------------------------
   Tabellenfuss
   Fuer Summen und Zwischenergebnisse. Er gehoert in <tfoot>,
   nicht als letzte Zeile in den Rumpf: dort wuerde er
   mitsortiert und bei Mehrfachauswahl mitgezaehlt.
   ------------------------------------------------------------ */

.table tfoot td {
  height: var(--row-height);
  font-weight: var(--weight-bold);
  background: var(--color-bg-subtle);
  border-top: 2px solid var(--color-border-strong);
  border-bottom: none;
  /* Klebt am unteren Rand des Rollbereichs, damit die Summe
     auch bei langen Listen sichtbar bleibt. */
  position: sticky;
  bottom: 0;
}

/* Beschriftung der Summenzeile, meist ueber mehrere Spalten */
.table tfoot .table__total-label {
  font-weight: var(--weight-normal);
  color: var(--color-text-muted);
  text-align: right;
}

/* --- Positionstabelle -------------------------------------
   Eine Tabelle, die man ausfuellt: Rechnungspositionen,
   Stuecklisten. Keine Formularreihe – die Spaltenkoepfe sind
   die Beschriftungen, jedes Feld traegt zusaetzlich aria-label.
   Darum gilt die Grenze von drei Feldern je .form-row hier nicht.

   Die Zeile waechst mit dem Feld; feste Hoehe und Ellipse der
   Lesetabelle wuerden die Felder abschneiden. */
.table--entry th,
.table--entry td {
  height: auto;
  overflow: visible;
  white-space: normal;
}

.table--entry td {
  padding-top: var(--space-1);
  padding-bottom: var(--space-1);
}

.table--entry td .field__input {
  width: 100%;
}

/* Mengen und Preise rechtsbuendig wie in jeder Zahlenspalte */
.table--entry td.num .field__input {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Am Telefon wird jede Position eine Karte: die Leistung oben
   ueber die Breite, Entfernen daneben, die uebrigen Felder zu
   zweit darunter. Die Spaltenkoepfe stehen dann nicht mehr
   ueber den Feldern; ihr Wort kommt aus data-label an der Zelle. */
@media (max-width: 700px) {
  .table--entry,
  .table--entry tbody,
  .table--entry tfoot,
  .table--entry td {
    display: block;
  }

  .table--entry thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
  }

  .table--entry tr {
    display: flex;
    flex-wrap: wrap;
    align-items: end;
    gap: var(--space-2) var(--space-3);
    padding: var(--space-3);
    border-bottom: 1px solid var(--color-border);
  }

  .table--entry td {
    flex: 1 1 calc(50% - var(--space-3));
    min-width: 0;
    width: auto;
    padding: 0;
    border: 0;
  }

  /* Das Wort steht links, auch ueber Zahlenfeldern; nur deren
     Wert bleibt rechtsbuendig. */
  .table--entry td[data-label]::before {
    content: attr(data-label);
    display: block;
    text-align: left;
    margin-bottom: var(--space-1);
    font-size: var(--font-sm);
    color: var(--color-text-muted);
  }

  .table--entry tbody td:first-child {
    order: -2;
    flex-basis: calc(100% - var(--touch-target) - var(--space-3));
  }

  /* Genau so breit wie der Knopf, sonst passt er nicht neben
     die Leistung (die Aktionsspalte ist sonst 64px breit). */
  .table--entry tbody td.col-actions {
    order: -1;
    flex: 0 0 var(--touch-target);
    width: var(--touch-target);
  }

  /* Die Summe ist eine Zeile, nicht eine Reihe grauer Kaesten:
     Flaeche und Linie wandern von den Zellen an die Zeile. */
  .table--entry tfoot tr {
    justify-content: space-between;
    background: var(--color-bg-subtle);
    border-top: 2px solid var(--color-border-strong);
    border-bottom: 0;
  }

  .table--entry tfoot td {
    flex: 0 1 auto;
    height: auto;
    background: none;
    border: 0;
  }

  .table--entry tfoot td:empty {
    display: none;
  }
}

/* Vergleichstabellen: horizontal scrollbar, erste Spalte fix.

   ACHTUNG: overflow-x: auto zwingt die Senkrechte ebenfalls auf
   auto – der Kasten wird damit zum Rollbereich, und ein sticky
   darin misst ab SEINER Oberkante, nicht ab der des Fensters.
   Deshalb bekommt der Tabellenkopf hier top: 0 statt des
   Zuschlags fuer Kopfzeile und Filterzeile. Siehe
   .table-scroll .table thead th weiter unten. */
.table-scroll {
  overflow-x: auto;
  max-width: 100%;
}

.table--sticky-first th:first-child,
.table--sticky-first td:first-child {
  position: sticky;
  left: 0;
  background: var(--color-surface);
}

/* --- Filterzeile ----------------------------------------- */

.filterbar {
  display: flex;
  gap: var(--space-3);
  align-items: center;
  flex-wrap: wrap;
  padding: var(--space-3) 0;
  position: sticky;
  top: var(--header-height);
  z-index: 2;
  background: var(--color-bg);
}

/* --- Filterarten ------------------------------------------
   Vier, nicht zwei. Text und Auswahl decken „Nur
   Ehrenmitglieder" und „Eintritt zwischen" nicht ab; wer sie
   als Auswahl mit zwei Optionen baut, kostet zwei Klicks fuer
   eine Ja-Nein-Frage.
   ------------------------------------------------------------ */

/* Schalter: eine Ja-Nein-Einschraenkung. Traegt immer die
   einschraenkende Formulierung ("Nur Ehrenmitglieder"), nie die
   neutrale ("Ehrenmitglieder") – sonst ist unklar, was der
   ausgeschaltete Zustand bedeutet. */
.filter-check {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-target);
  padding: 0 var(--space-3);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  font-size: var(--font-sm);
  white-space: nowrap;
  cursor: pointer;
}

.filter-check:has(input:checked) {
  background: var(--color-primary-soft);
  border-color: var(--color-primary);
  color: var(--color-primary);
  font-weight: var(--weight-bold);
}

/* Zeitraum: zwei Datumsfelder als ein Filter. "bis" steht
   zwischen ihnen, nicht als eigenes Label darueber. */
.filter-range {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-sm);
  color: var(--color-text-muted);
}

.filter-range .field__input {
  min-width: 14ch;
  max-width: 18ch;
}

/* --- Karten (Handy-Darstellung von Datensatzlisten) ------- */

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

.card {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}

/* Eine Karte, die den Datensatz oeffnet, ist selbst der Link –
   in Textfarbe, ohne „besucht", aus demselben Grund wie
   a.cell__title. */
a.card {
  color: var(--color-text);
  text-decoration: none;
}

a.card:hover {
  border-color: var(--color-border-strong);
}

.card__check {
  width: var(--touch-target);
  height: var(--touch-target);
  flex: 0 0 auto;
}

.card__title {
  font-weight: var(--weight-bold);
}

/* Nur eine identifizierende Zeile, alles Weitere im Detail */
.card__meta {
  font-size: var(--font-sm);
  color: var(--color-text-muted);
}

/* ------------------------------------------------------------
   Lade- und Leerzustaende
   ------------------------------------------------------------ */

/* Eigene Variable statt --row-height: ein Projekt mit einer
   Dichteschicht setzt die Zeilenhoehe je nach Bedienart, und
   der Skelettbalken soll davon unberuehrt bleiben. Wer ihn in
   Zeilenhoehe braucht, setzt --skeleton-height darauf. */
.skeleton {
  height: var(--skeleton-height);
  border-radius: var(--radius-sm);
  background: linear-gradient(
    90deg,
    var(--color-bg-subtle) 25%,
    var(--color-border) 37%,
    var(--color-bg-subtle) 63%
  );
  background-size: 400% 100%;
  animation: skeleton-shimmer 1.4s ease infinite;
}

@keyframes skeleton-shimmer {
  from { background-position: 100% 50%; }
  to   { background-position: 0 50%; }
}

/* Nachladen: alte Daten bleiben abgedimmt stehen */
.is-refreshing {
  opacity: 0.5;
  pointer-events: none;
}

/* Der Balken zeigt, DASS etwas laeuft, nicht wie weit. Eine
   Anfrage kennt ihren Fortschritt nicht; ein wachsender Balken
   behauptet eine Auskunft, die es nicht gibt. Deshalb laeuft
   eine kurze Strecke durch. */
.progressbar {
  position: fixed;
  top: 0;
  left: 0;
  height: 3px;
  width: 100%;
  background: linear-gradient(90deg,
    transparent 0%, var(--color-primary) 50%, transparent 100%);
  background-size: 40% 100%;
  background-repeat: no-repeat;
  z-index: var(--z-toast);
  animation: progress-run 1.2s ease-in-out infinite;
}

@keyframes progress-run {
  from { background-position: -40% 0; }
  to   { background-position: 140% 0; }
}

/* Kennt der Vorgang seinen Fortschritt wirklich – Hochladen,
   Import einer Datei –, dann und nur dann ein Balken, der
   waechst. */
.progressbar--determinate {
  animation: none;
  background: var(--color-primary);
  background-size: auto;
  width: 0;
  transition: width var(--transition-base);
}

.empty {
  text-align: center;
  padding: var(--space-7) var(--space-4);
  color: var(--color-text-muted);
}

.empty__icon {
  width: 24px;
  height: 24px;
  margin-bottom: var(--space-3);
}

/* ------------------------------------------------------------
   Toasts
   Desktop oben rechts, Handy unten. Bei sichtbarer unterer
   Leiste rasten sie darueber ein.
   ------------------------------------------------------------ */

.toast-region {
  position: fixed;
  top: calc(var(--header-height) + var(--space-3));
  right: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  z-index: var(--z-toast);
}

.toast {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  min-width: 280px;
  max-width: 420px;
  padding: var(--space-3);
  border-radius: var(--radius-md);
  border-left: 4px solid var(--color-border-strong);
  background: var(--color-surface-raised);
  box-shadow: var(--shadow-md);
}

/* Erfolg verschwindet von selbst */
.toast--success {
  border-left-color: var(--color-success);
  background: var(--color-success-soft);
}

/* Fehler bleibt stehen, bis der Nutzer schliesst */
.toast--error {
  border-left-color: var(--color-danger);
  background: var(--color-danger-soft);
}

.toast--warning {
  border-left-color: var(--color-warning);
  background: var(--color-warning-soft);
}

.toast__close {
  margin-left: auto;
}

/* ------------------------------------------------------------
   Modal
   Kleine Datensaetze im Modal, grosse als eigene Seite.
   ------------------------------------------------------------ */

.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(16, 24, 40, 0.5);
  z-index: var(--z-modal);
  display: grid;
  place-items: center;
  padding: var(--space-4);
}

.modal {
  width: min(560px, 100%);
  max-height: 85vh;
  overflow: auto;
  background: var(--color-surface-raised);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--space-5);
}

.modal__title {
  font-size: var(--font-lg);
  margin: 0 0 var(--space-4);
}

.modal__footer {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-top: var(--space-5);
}

/* ------------------------------------------------------------
   Statuskennzeichnung
   Farbe traegt nie allein die Information – immer mit Text
   oder Icon.
   ------------------------------------------------------------ */

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  font-size: var(--font-xs);
  font-weight: var(--weight-bold);
}

.badge--success { background: var(--color-success-soft); color: var(--color-success); }
.badge--warning { background: var(--color-warning-soft); color: var(--color-warning); }
.badge--danger  { background: var(--color-danger-soft);  color: var(--color-danger); }
.badge--neutral { background: var(--color-bg-subtle);    color: var(--color-text-muted); }

/* ------------------------------------------------------------
   Handy
   ------------------------------------------------------------ */

/* Stufe 2: Icon-Leiste bei mittleren Breiten */
@media (max-width: 1024px) {
  .app {
    grid-template-columns: var(--sidebar-width-icons) 1fr;
  }
  .sidebar__label {
    display: none;
  }
  /* Dieselbe Form wie oben, sonst gewinnt :root:has(.app)
     gegen ein blosses :root und die Stufe greift nicht. */
  :root:has(.app) {
    --bar-left: var(--sidebar-width-icons);
  }
}

/* Stufe 3: Burger */
@media (max-width: 700px) {
  /* Am Telefon gibt es keine Seitenleiste im Fluss – die
     Leisten laufen ueber die volle Breite. */
  :root:has(.app) {
    --bar-left: 0px;
  }

  .app {
    grid-template-columns: 1fr;
    grid-template-areas:
      "header"
      "main";
  }

  .header .header__burger {
    display: inline-flex;
  }

  /* Dieselbe Komponente, nur als Overlay eingeblendet */
  .sidebar {
    position: fixed;
    inset: var(--header-height) auto 0 0;
    width: var(--sidebar-width);
    transform: translateX(-100%);
    transition: transform var(--transition-base);
    z-index: var(--z-sidebar);
    box-shadow: var(--shadow-lg);
  }

  .sidebar--open {
    transform: translateX(0);
  }

  .sidebar__label {
    display: inline;
  }

  .main {
    padding: var(--space-4);
    padding-bottom: calc(var(--space-4) + var(--bottombar-height));
  }

  /* Formularzeilen gestapelt, jedes Feld volle Breite –
     auch die Auswahlfelder, die am Desktop mitwachsen. */
  .form-row {
    flex-direction: column;
  }

  select.field__input,
  .field--choice {
    width: 100%;
    max-width: none;
  }

  /* Toasts unten, oberhalb der eingeblendeten Leiste */
  .toast-region {
    top: auto;
    left: var(--space-3);
    right: var(--space-3);
    bottom: calc(var(--bottombar-height) + var(--space-3));
  }

  .toast {
    min-width: 0;
    max-width: none;
  }

  /* Modals bildschirmfuellend */
  .modal-backdrop {
    padding: 0;
  }

  .modal {
    width: 100%;
    height: 100%;
    max-height: none;
    border-radius: 0;
  }
}

/* ------------------------------------------------------------
   Horizontales Menue im Inhalt (Priority-Plus)
   ------------------------------------------------------------ */

.nav {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  border-bottom: 1px solid var(--color-border);
  margin-bottom: var(--space-5);
  min-width: 0;
  /* KEIN overflow: hidden. Das Ueberlaufmenue haengt mit
     position: absolute unter dem Kasten und wuerde
     abgeschnitten – gefuellt und nie sichtbar, der erste
     ausgelagerte Reiter damit unerreichbar. Gegen den
     Ueberlauf vor der ersten Messung hilft stattdessen
     flex-wrap: nowrap plus die Messung im ResizeObserver. */
  flex-wrap: nowrap;
}

.nav__item {
  padding: var(--space-3) var(--space-4);
  color: var(--color-text-muted);
  text-decoration: none;
  white-space: nowrap;
  border-bottom: 2px solid transparent;
}

.nav__item[aria-current="page"] {
  color: var(--color-primary);
  font-weight: var(--weight-bold);
  border-bottom-color: var(--color-primary);
}

.nav__overflow {
  position: relative;
  margin-left: auto;
}

.nav__overflow-menu {
  position: absolute;
  right: 0;
  top: 100%;
  min-width: 200px;
  display: none;
  flex-direction: column;
  padding: var(--space-1);
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  z-index: var(--z-header);
}

/* aria-expanded steht am ausloesenden Knopf, nicht am
   umgebenden Kasten – sonst sagt die Vorlesehilfe beim Fokus
   auf den Knopf nichts ueber den Zustand. */
.nav__overflow:has(> button[aria-expanded="true"]) .nav__overflow-menu {
  display: flex;
}

/* Traegt die Gestaltung selbst: die Kopie im Menue verliert
   bewusst die Klasse .nav__item, damit sie bei spaeteren
   Messungen nicht mitgezaehlt wird. */
.nav__overflow-item {
  display: flex;
  align-items: center;
  min-height: var(--touch-target);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  border-bottom: none;
  color: var(--color-text);
  text-decoration: none;
  white-space: nowrap;
}

.nav__overflow-item[aria-current="page"] {
  color: var(--color-primary);
  font-weight: var(--weight-bold);
}

.nav__overflow-item:hover {
  background: var(--color-bg-subtle);
}

/* ------------------------------------------------------------
   Tastenkuerzel-Uebersicht
   ------------------------------------------------------------ */

kbd {
  display: inline-block;
  padding: 2px var(--space-2);
  border: 1px solid var(--color-border-strong);
  border-bottom-width: 2px;
  border-radius: var(--radius-sm);
  background: var(--color-bg-subtle);
  font-family: var(--font-mono);
  font-size: var(--font-xs);
}

/* --- Codeblock ---------------------------------------------
   Vorformatierter Text, der abgelesen und nicht gelesen wird:
   Aufrufstapel, Rohantwort, Konfigurationszeile.

   Er bricht nicht um – ein Stapel mit umbrechenden Zeilen sieht
   aus wie doppelt so viele Rahmen – und rollt deshalb fuer sich.
   Das Panel darum traegt overflow: clip; ohne eigene Rollflaeche
   waere das Ende nicht abgeschnitten, sondern weg. Die Hoehe ist
   gedeckelt, sonst schiebt ein langer Stapel alles darunter aus
   dem Bild. tabindex="0" am Element, damit die Rollflaeche auch
   mit der Tastatur erreichbar ist. */
.codeblock {
  display: block;
  margin: 0;
  padding: var(--space-3);
  max-height: calc(var(--codeblock-lines) * 1lh + 2 * var(--space-3));
  overflow: auto;
  white-space: pre;
  font-family: var(--font-mono);
  font-size: var(--font-xs);
  line-height: var(--line-body);
  font-variant-numeric: tabular-nums slashed-zero;
  background: var(--color-bg-subtle);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  color: var(--color-text);
}

.codeblock:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}

/* Nur fuer Screenreader */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  /* Kein margin: -1px. Der gehoert zur aelteren Fassung des
     Kniffs ohne position: absolute; hier verschoebe er das
     Element nur um einen Pixel. */
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Fehlerseiten nutzen .empty, brauchen aber mehr Luft */
.empty h1 {
  font-size: var(--font-xl);
  color: var(--color-text);
  margin-bottom: var(--space-3);
}

.empty p {
  max-width: 52ch;
  margin: 0 auto var(--space-5);
}

/* ------------------------------------------------------------
   Schalter mit Vererbung
   Drei Stellungen: an / aus / geerbt. Ueberall dort, wo eine
   Ebene den Wert einer hoeheren uebernehmen kann
   (Mandant → Standort, Vorlage → Einzelfall).
   ------------------------------------------------------------ */

.inherit-switch {
  display: inline-flex;
  flex-direction: column;
  gap: var(--space-1);
  /* Sonst streckt der umgebende Flex-Container den Schalter
     auf die volle Breite. */
  align-items: flex-start;
  align-self: flex-start;
  max-width: 100%;
}

.inherit-switch__options {
  /* Gleich breite Stellungen: sonst springt der Schalter beim
     Umschalten und zwei Schalter nebeneinander stehen nicht in
     einer Flucht.

     minmax(6ch, 1fr) statt einer festen Zahl: nach oben waechst
     der Schalter mit dem laengsten Wort mit – andere Sprachen
     und kuenftige Zustandsnamen brauchen keine Anpassung. Nach
     unten haelt der Boden kurze Beschriftungen ("Ja/Nein") auf
     einer vernuenftigen Trefferflaeche. In ch, damit der Boden
     an der Schriftgroesse haengt und nicht an einer Pixelzahl,
     die spaeter niemand mehr nachprueft. */
  display: inline-grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(6ch, 1fr);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--color-surface);
}

.inherit-switch__option {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--touch-target);
  padding: 0 var(--space-4);
  font-size: var(--font-sm);
  font-weight: var(--weight-bold);
  color: var(--color-text-muted);
  cursor: pointer;
  white-space: nowrap;
  border-right: 1px solid var(--color-border);
}

.inherit-switch__option:last-child {
  border-right: none;
}

.inherit-switch__option:hover {
  background: var(--color-bg-subtle);
}

.inherit-switch__option input {
  /* Die Bedienung laeuft ueber echte Radios, sichtbar ist das
     Segment. Fokus wird auf das Segment gespiegelt. */
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.inherit-switch__option:has(input:checked) {
  background: var(--color-primary);
  color: var(--color-primary-text);
}

.inherit-switch__option:has(input:focus-visible) {
  outline: 2px solid var(--color-focus);
  outline-offset: -2px;
}

/* Die geerbte Stellung ist zurueckhaltender gefaerbt: sie ist
   keine eigene Entscheidung, sondern eine Uebernahme. */
.inherit-switch__option--inherited:has(input:checked) {
  background: var(--color-primary-soft);
  color: var(--color-primary);
}

/* Nennt den geerbten Wert und seine Herkunft. Ohne diese
   Zeile ist die Stellung "geerbt" eine Leerstelle – der
   Betreiber muesste eine Ebene hoeher nachsehen.

   Sie traegt die eigentliche Auskunft und ist die einzige
   Zeile am Schalter, die man nicht erraten kann. Fuer eine
   Ellipse taugt sie deshalb nicht: sie bricht um, und die
   Hoehe fuer zwei Zeilen ist reserviert, damit beim
   Umschalten nichts springt. */
.inherit-switch__origin {
  font-size: var(--font-sm);
  color: var(--color-text-muted);
  white-space: normal;
  overflow-wrap: break-word;
  max-width: 42ch;
  min-height: calc(2 * var(--font-sm) * var(--line-body));
}

.inherit-switch__origin strong {
  color: var(--color-text);
  font-weight: var(--weight-bold);
}

/* ============================================================
   Kopfzone, Plaettchenfeld, Suchauswahl, Dateifeld
   ============================================================ */

/* ------------------------------------------------------------
   Kopfzone
   Titel, Kennzahl, Erklaersatz und die Handlungen der Ansicht.
   Sie steht UEBER den Panels und in keinem – sie gehoert zur
   Ansicht, nicht zu einem ihrer Teile.

   Feste Mindesthoehe, damit der Titel beim Seitenwechsel nicht
   springt: ohne sie wandert die ganze Seite, sobald eine
   Ansicht keinen Erklaersatz hat.
   ------------------------------------------------------------ */

.page-head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
  min-height: 72px;
  margin-bottom: var(--space-5);
}

.page-head__text {
  min-width: 0;
}

.page-head__title {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  font-size: var(--font-xl);
  font-weight: var(--weight-bold);
  margin: 0;
}

.page-head__count {
  font-size: var(--font-base);
  font-weight: var(--weight-normal);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

/* Eigene Zeile unter dem Titel, nicht daneben – und mit eigener
   Breite, damit ein langer Satz nicht die Kopfzone aufblaeht. */
.page-head__hint {
  margin: var(--space-1) 0 0;
  color: var(--color-text-muted);
  font-size: var(--font-sm);
  max-width: 70ch;
}

.page-head__actions {
  margin-left: auto;
  flex: 0 0 auto;
}

/* ------------------------------------------------------------
   Plaettchenfeld
   Mehrere Werte in einem Feld, jeder einzeln entfernbar. Ein
   Kommafeld ist keine Alternative: wer den dritten von fuenf
   Werten wegnehmen will, zaehlt Kommas.

   Zusammengesetztes Bedienelement: .field__input traegt das
   Eingabefeld darin, nicht der Rahmen.
   ------------------------------------------------------------ */

.tagfield {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-target);
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}

.tagfield:focus-within {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}

.tag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--color-primary-soft);
  color: var(--color-primary);
  font-size: var(--font-sm);
  white-space: nowrap;
}

.tag__remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--space-5);
  height: var(--space-5);
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: inherit;
  cursor: pointer;
}

.tag__remove:hover {
  background: var(--color-surface);
}

/* Den Fokus zeigt der Rahmen ringsum. Zeigte ihn auch das
   Eingabefeld, staenden zwei Ringe ineinander. */
.tagfield .field__input:focus-visible {
  outline: none;
  box-shadow: none;
}

/* Im Fehlerfall faerbt sich der Rahmen ringsum – der innere
   Rahmen, den die Regel sonst meint, gibt es hier nicht. */
.field--invalid .tagfield {
  border-color: var(--color-danger);
}

/* Das Eingabefeld im Plaettchenfeld traegt keinen eigenen
   Rahmen – den bringt der Rahmen ringsum mit. */
.tagfield .field__input {
  flex: 1 1 8ch;
  min-width: 8ch;
  border: none;
  outline: none;
  min-height: calc(var(--touch-target) - var(--space-2));
  padding: 0 var(--space-1);
  background: transparent;
}

/* ------------------------------------------------------------
   Suchauswahl
   Auswahl aus einem Vorrat, den niemand durchliest – Mitglieder,
   Konten, Orte. Ein select mit fuenfzehnhundert Eintraegen ist
   eine Liste, keine Auswahl.
   ------------------------------------------------------------ */

.combobox {
  position: relative;
  display: block;
}

/* position: fixed, nicht absolute. Ein Panel traegt
   overflow: clip – ein Ausklapper darin waere an dessen Rand
   abgeschnitten und der abgeschnittene Teil nicht einmal
   anklickbar. Fixed haengt am Fenster und entkommt dem
   Beschnitt; die Lage setzt positionPopover(). */
.combobox__list {
  position: fixed;
  left: 0;
  top: 0;
  max-height: calc(6 * var(--row-height));
  overflow-y: auto;
  display: none;
  flex-direction: column;
  padding: var(--space-1);
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  z-index: var(--z-header);
}

.combobox:has(> .field__input[aria-expanded="true"]) .combobox__list {
  display: flex;
}

.combobox__option {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-height: var(--touch-target);
  justify-content: center;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.combobox__option:hover,
.combobox__option[aria-selected="true"] {
  background: var(--color-primary-soft);
}

/* Zweite Zeile zur Unterscheidung von Namensvettern –
   dieselbe Rolle wie .cell__meta in der Tabelle. */
.combobox__meta {
  font-size: var(--font-xs);
  color: var(--color-text-muted);
}

.combobox__empty {
  padding: var(--space-3);
  color: var(--color-text-muted);
  font-size: var(--font-sm);
}

/* ------------------------------------------------------------
   Dateifeld
   Ein input[type="file"] zeichnet der Browser selbst – mit
   seinem eigenen Knopf, in der Sprache des Browsers und nicht
   in der der Anwendung. "Durchsuchen… / Keine Datei
   ausgewaehlt" ist deshalb kein Gestaltungsversaeumnis,
   sondern das, was ohne Bauteil herauskommt.

   Das native Element bleibt erhalten und bedienbar, wird aber
   verdeckt: die Beschriftung traegt den sichtbaren Knopf, und
   ein Textstueck daneben nennt die gewaehlte Datei. Weil label
   und input verbunden sind, funktionieren Tastatur und
   Vorlesehilfe unveraendert.
   ------------------------------------------------------------ */

.filefield {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--touch-target);
}

/* Sichtbar verdeckt, nicht entfernt: display:none naehme dem
   Element den Fokus und damit die Tastaturbedienung. */
.filefield__input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  /* Kein margin: -1px. Der gehoert zur aelteren Fassung des
     Kniffs ohne position: absolute; hier verschoebe er das
     Element nur um einen Pixel. */
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Die Beschriftung ist der Knopf. Dieselben Masse wie
   .btn--secondary, damit die Zeile ruhig bleibt. */
.filefield__button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--touch-target);
  padding: 0 var(--space-4);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text);
  font-size: var(--font-sm);
  font-weight: var(--weight-bold);
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
}

.filefield__button:hover {
  background: var(--color-bg-subtle);
}

/* Der Fokus liegt am verdeckten Element und wird auf den
   sichtbaren Knopf gespiegelt. */
.filefield:has(.filefield__input:focus-visible) .filefield__button {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  box-shadow: var(--focus-ring);
}

.filefield__input:disabled + .filefield__button {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Nennt die gewaehlte Datei oder sagt, dass keine gewaehlt ist
   – nie leer, sonst sieht das Feld kaputt aus. */
.filefield__name {
  min-width: 0;
  font-size: var(--font-sm);
  color: var(--color-text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.filefield__name--gewaehlt {
  color: var(--color-text);
}

/* Ablagefeld: nur dort, wo mehrere Dateien oder Bilder
   erwartet werden. Das Ablegen ist nicht auffindbar und mit
   der Tastatur nicht bedienbar – der Knopf bleibt deshalb
   immer daneben stehen, nie als Ersatz. */
.filefield--drop {
  flex-direction: column;
  justify-content: center;
  gap: var(--space-3);
  min-height: calc(6 * var(--space-6));
  padding: var(--space-5);
  border: 2px dashed var(--color-border-strong);
  border-radius: var(--radius-md);
  background: var(--color-bg-subtle);
  text-align: center;
}

.filefield--drop.is-over {
  border-color: var(--color-primary);
  background: var(--color-primary-soft);
}

/* Bereits gewaehlte oder abgelegte Dateien */
.filelist {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

.filelist__item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--touch-target);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}

.filelist__name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.filelist__meta {
  margin-left: auto;
  color: var(--color-text-muted);
  font-size: var(--font-sm);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ------------------------------------------------------------
   Baumtabelle mit Umsortieren
   Eine Tabelle, deren Zeilen eine Hierarchie tragen
   (Abteilung → Gruppe), mit Auf- und Zuklappen und Umsortieren
   innerhalb derselben uebergeordneten Zeile.

   Die Einruecken kommt aus --level an der ersten Zelle, nicht
   aus einer Klasse je Stufe – die Tiefe ist nicht begrenzt.
   ------------------------------------------------------------ */

/* Bezugspunkt fuer die Einfuegemarke */
.tree {
  position: relative;
  display: block;
}

.tree-cell {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  min-width: 0;
  padding-left: calc(var(--level, 0) * var(--space-5));
}

.tree-toggle,
.drag-handle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: var(--space-5);
  height: var(--space-5);
  padding: 0;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-text-muted);
  cursor: pointer;
}

.tree-toggle:hover,
.drag-handle:hover {
  background: var(--color-bg-subtle);
  color: var(--color-text);
}

.tree-toggle svg,
.drag-handle svg {
  width: 16px;
  height: 16px;
}

.tree-toggle {
  transition: transform var(--transition-fast);
}

.tree-toggle[aria-expanded="true"] {
  transform: rotate(90deg);
}

/* Eine Zeile ohne Kinder behaelt den Platz des Schalters, damit
   die Namen gleicher Stufe auf einer Flucht stehen. */
.tree-toggle--leaf {
  visibility: hidden;
}

/* touch-action: none – sonst rollt die Seite, statt dass die
   Zeile gezogen wird. */
.drag-handle {
  cursor: grab;
  touch-action: none;
}

/* Mit der Tastatur aufgenommen */
.drag-handle[aria-pressed="true"] {
  background: var(--color-primary-soft);
  color: var(--color-primary);
}

/* Die gezogene Zeile samt allen Unterzeilen tritt zurueck. So
   ist sichtbar, dass ein ganzer Ast wandert, nicht eine Zeile. */
.table--tree tr.is-dragging > td {
  opacity: 0.4;
}

.table--tree tr.is-grabbed > td {
  background: var(--color-primary-soft);
}

.is-reordering,
.is-reordering * {
  cursor: grabbing !important;
  user-select: none;
}

/* Kein erlaubtes Ziel unter dem Zeiger: keine Marke, und der
   Zeiger sagt, dass hier nichts abgelegt wird. */
.is-reordering.is-invalid,
.is-reordering.is-invalid * {
  cursor: not-allowed !important;
}

/* Die Einfuegemarke. Eine Linie ZWISCHEN zwei Zeilen, nicht die
   Hervorhebung einer Zeile – hervorgehoben waere "hierhinein",
   gemeint ist "hierhin". Sie beginnt auf der Einrueckung der
   Stufe, in die abgelegt wird; der Punkt am Anfang macht diese
   Stufe auch dann ablesbar, wenn die Linie ueber Zeilen einer
   tieferen Stufe laeuft. */
.drop-indicator {
  position: absolute;
  right: var(--space-3);
  height: 2px;
  margin-top: -1px;
  background: var(--color-primary);
  border-radius: 1px;
  pointer-events: none;
  z-index: 3;
}

.drop-indicator::before {
  content: "";
  position: absolute;
  left: -4px;
  top: -3px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--color-primary);
}

/* ------------------------------------------------------------
   Suchauswahl: Aufklapper und eigener Wert
   Ergaenzt .combobox um den Pfeil, der die Liste oeffnet, und
   um den Eintrag, mit dem ein Wert uebernommen wird, der nicht
   in der Liste steht (initCombobox mit allowCustom).
   ------------------------------------------------------------ */

.combobox > .field__input {
  width: 100%;
  /* Platz fuer den Pfeil rechts */
  padding-right: calc(var(--touch-target) + var(--space-1));
}

/* Der Pfeil gehoert zum Feld, nicht in den Tabulatorlauf: wer
   tabbt, will ins naechste Feld, nicht auf einen Aufklapper. */
.combobox__toggle {
  position: absolute;
  top: 0;
  right: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--touch-target);
  height: var(--touch-target);
  padding: 0;
  border: none;
  background: transparent;
  color: var(--color-text-muted);
  cursor: pointer;
}

.combobox__toggle svg {
  width: 16px;
  height: 16px;
  transition: transform var(--transition-fast);
}

.combobox:has(> .field__input[aria-expanded="true"]) .combobox__toggle svg {
  transform: rotate(180deg);
}

/* Mit der Tastatur angesteuert – aria-activedescendant zeigt
   darauf, der Fokus bleibt im Eingabefeld. */
.combobox__option.is-active {
  background: var(--color-primary-soft);
  outline: 2px solid var(--color-focus);
  outline-offset: -2px;
}

/* Der eigene Wert ist ein eigener Eintrag, abgesetzt vom Rest
   der Liste. Er wird nie still uebernommen: wer "Festwiese am
   Bach" tippt, soll sehen, dass das kein Wert aus dem Bestand
   ist, bevor er ihn waehlt. */
.combobox__option--custom {
  flex-direction: row;
  align-items: center;
  justify-content: flex-start;
  gap: var(--space-2);
  border-top: 1px solid var(--color-border);
  margin-top: var(--space-1);
  padding-top: var(--space-2);
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  color: var(--color-primary);
}

.combobox__option--custom:first-child {
  border-top: none;
  margin-top: 0;
}

.combobox__option--custom strong {
  font-weight: var(--weight-bold);
}

/* Sagt nach der Wahl, dass ein eigener Wert gilt. Leer, solange
   ein Listenwert gewaehlt ist – dann gibt es nichts zu sagen. */
.combobox__status {
  font-size: var(--font-sm);
  color: var(--color-text-muted);
  min-height: calc(var(--font-sm) * var(--line-body));
}

/* ------------------------------------------------------------
   Baumtabelle: Einfuegen an Ort und Stelle
   An jeder Luecke zwischen zwei Zeilen erscheinen beim
   Ueberfahren kleine Plus – eines je Stufe, in die dort
   eingefuegt werden kann. Zwischen dem letzten Kind eines
   Astes und der naechsten Zeile der oberen Stufe sind das zwei:
   "am Ende des Astes" und "danach auf oberer Stufe". Ein
   einzelnes Plus wuerde dort eine der beiden Bedeutungen
   verschweigen.
   ------------------------------------------------------------ */

.tree__slots {
  position: absolute;
  left: 0;
  right: 0;
  height: 0;
  pointer-events: none;
  z-index: 4;
}

/* Sitzt mitten ueber der Schalterspalte seiner Stufe, genau auf
   der Grenzlinie – dort beruehrt es den mittig stehenden
   Schalter nur am Rand. */
.tree__insert {
  position: absolute;
  top: 0;
  transform: translate(-50%, -50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border: 1.5px solid var(--color-primary);
  border-radius: 50%;
  background: var(--color-surface);
  color: var(--color-primary);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  pointer-events: auto;
}

.tree__insert svg {
  width: 12px;
  height: 12px;
}

.tree__insert:hover {
  background: var(--color-primary);
  color: var(--color-primary-text);
}

/* Die Linie zum Plus: gestrichelt, damit sie sich von der
   Einfuegemarke beim Verschieben unterscheidet – dort wandert
   etwas, hier entsteht etwas. */
.drop-indicator--insert {
  background: repeating-linear-gradient(90deg,
    var(--color-primary) 0 6px, transparent 6px 10px);
}

/* Der Punkt am Anfang entfaellt – das Plus steht an seiner
   Stelle und traegt dieselbe Auskunft. */
.drop-indicator--insert::before {
  display: none;
}

/* Ohne Zeiger, der schweben kann – am Telefon, am Tablet –
   gibt es kein Ueberfahren und damit keine Plus. Dort gilt der
   Weg ueber "Anlegen" in der Kopfzone. */
@media (hover: none) {
  .tree__slots {
    display: none;
  }
}

/* Die neue Zeile, solange sie noch keinen Namen hat */
.table--tree tr.is-draft > td {
  background: var(--color-primary-soft);
}

.table--tree tr.is-draft .field__input {
  flex: 1 1 auto;
  min-width: 12ch;
  min-height: calc(var(--touch-target) - var(--space-2));
}

/* ------------------------------------------------------------
   Farbfeld
   input[type="color"] oeffnet den Dialog des Browsers – in
   jedem Browser ein anderer, nicht in der Sprache der
   Anwendung, und ohne jede Auskunft darueber, ob die Farbe
   brauchbar ist. Dasselbe Muster wie beim Dateifeld.

   Das Farbfeld zeigt stattdessen den Vorrat des Hauses, nimmt
   einen eigenen Wert als Hex entgegen und rechnet den Kontrast
   mit: eine Vereinsfarbe, auf der weisse Schrift nicht lesbar
   ist, faellt beim Auswaehlen auf und nicht erst im Prueflauf.
   ------------------------------------------------------------ */

.colorfield {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

/* Zeigt die Farbe und oeffnet den Vorrat. Das Karomuster
   darunter macht sichtbar, wenn keine Farbe gesetzt ist. */
.colorfield__swatch {
  width: var(--touch-target);
  height: var(--touch-target);
  flex: 0 0 auto;
  padding: 0;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  background-color: var(--farbe, transparent);
  background-image: linear-gradient(45deg, var(--color-border) 25%, transparent 25%),
                    linear-gradient(-45deg, var(--color-border) 25%, transparent 25%),
                    linear-gradient(45deg, transparent 75%, var(--color-border) 75%),
                    linear-gradient(-45deg, transparent 75%, var(--color-border) 75%);
  background-size: 10px 10px;
  background-position: 0 0, 0 5px, 5px -5px, -5px 0;
  background-blend-mode: normal;
  cursor: pointer;
}

/* Ist eine Farbe gesetzt, deckt sie das Karomuster zu. */
.colorfield__swatch[data-gesetzt="true"] {
  background-image: none;
}

/* Steht neben dem Hex-Wert, sobald die Deckkraft unter 100
   liegt: sonst sieht das zugeklappte Feld aus wie eine volle
   Farbe, und die Zeichenkette sagt nichts darueber. */
.colorfield__deckung {
  font-size: var(--font-sm);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.colorfield__hex {
  width: 11ch;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}

/* Wie bei der Suchauswahl: fixed, sonst schneidet das Panel
   den Ausklapper ab. */
.colorfield__panel {
  position: fixed;
  top: 0;
  left: 0;
  z-index: var(--z-header);
  display: none;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 260px;
  padding: var(--space-3);
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}

.colorfield:has(.colorfield__swatch[aria-expanded="true"]) .colorfield__panel {
  display: flex;
}

.colorfield__gruppe {
  font-size: var(--font-xs);
  color: var(--color-text-muted);
}

.colorfield__palette {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  gap: var(--space-2);
}

.colorfield__chip {
  width: var(--space-6);
  height: var(--space-6);
  padding: 0;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--farbe, transparent);
  cursor: pointer;
}

/* Die gewaehlte Farbe traegt einen Ring, keinen Haken: ein
   Haken in der Farbe selbst waere auf hellen Feldern
   unsichtbar. */
.colorfield__chip[aria-pressed="true"] {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}

/* Kontrastauskunft: Zahl und Urteil, nie nur Farbe. */
.colorfield__kontrast {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--font-sm);
}

.colorfield__zeile {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.colorfield__probe {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: calc(var(--space-8) + var(--space-2));
  padding: 2px var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-size: var(--font-sm);
}

.colorfield__wert {
  margin-left: auto;
  font-variant-numeric: tabular-nums;
  color: var(--color-text-muted);
}

/* --- Farbfeld: freie Wahl ---------------------------------
   Flaeche fuer Saettigung und Helligkeit, Regler fuer Farbton
   und wahlweise Deckkraft. Der Vorrat bleibt daneben: die
   meisten Faelle sind Hausfarben, die freie Wahl ist fuer den
   Rest.
   ------------------------------------------------------------ */

.colorfield__spektrum {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.colorfield__sv {
  position: relative;
  height: 132px;
  border-radius: var(--radius-md);
  cursor: crosshair;
  /* touch-action: none – sonst rollt die Seite, statt dass die
     Farbe gewaehlt wird. */
  touch-action: none;
  background-color: hsl(var(--ton, 250) 100% 50%);
  background-image: linear-gradient(to top, #000, transparent),
                    linear-gradient(to right, #fff, transparent);
  background-blend-mode: multiply;
}

.colorfield__svpunkt {
  position: absolute;
  width: 14px;
  height: 14px;
  border: 2px solid #fff;
  border-radius: 50%;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.45);
  transform: translate(-50%, -50%);
  pointer-events: none;
}

/* Farbton und Deckkraft sind echte Regler. Ein eigener Schieber
   aus Kaesten muesste Tastatur, Fokus und Vorlesehilfe
   nachbauen – der Regler bringt das mit. */
.colorfield__regler {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 12px;
  margin: 0;
  border-radius: 9999px;
  background: transparent;
  cursor: pointer;
}

.colorfield__regler:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 3px;
}

.colorfield__ton {
  background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%,
    #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
}

.colorfield__deckkraft {
  background-image: linear-gradient(to right, transparent, var(--farbe, #000)),
    repeating-conic-gradient(var(--color-border) 0% 25%, var(--color-surface) 0% 50%);
  background-size: auto, 8px 8px;
}

.colorfield__regler::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  box-shadow: var(--shadow-sm);
}

.colorfield__regler::-moz-range-thumb {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  box-shadow: var(--shadow-sm);
}

.colorfield__werkzeuge {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-sm);
  color: var(--color-text-muted);
}

.colorfield__deckkraftwert {
  margin-left: auto;
  font-variant-numeric: tabular-nums;
}

/* ============================================================
   Zweispaltiges Layout, Kalenderblatt, Terminkarte, Hinweisleiste
   ============================================================ */

/* ------------------------------------------------------------
   Split: Inhalt und Nebenleiste
   Umbricht ueber den tatsaechlich verfuegbaren Platz, nicht
   ueber einen festen Breakpoint – die Breite haengt daran, ob
   die Seitenleiste offen ist.
   ------------------------------------------------------------ */

.split {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
  align-items: flex-start;
}

.split__main {
  flex: 100 1 620px;
  min-width: 0;
}

.split__side {
  flex: 1 1 300px;
  min-width: 0;
}

/* ------------------------------------------------------------
   Kalenderblatt
   Sechs Tagesspalten nebeneinander. Auf dem Handy wird daraus
   eine Liste untereinander – sechs Spalten seitlich zu rollen
   ist keine Woche mehr.
   ------------------------------------------------------------ */

.weekgrid {
  display: grid;
  grid-template-columns: repeat(var(--tage, 6), minmax(0, 1fr));
  gap: var(--space-3);
}

.weekgrid__day {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}

/* Zwei Zeilen, immer: Name oben, Datum und "heute" darunter.
   Nebeneinander passt das in einer von sechs Spalten nicht –
   der Kopf lief sonst in die Nachbarspalte. Zwei feste Zeilen
   halten ausserdem alle Tagesspalten auf einer Flucht. */
.weekgrid__dayhead {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--color-border);
}

.weekgrid__dayline {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  min-width: 0;
}

.weekgrid__dayname {
  font-weight: var(--weight-bold);
}

.weekgrid__daydate {
  font-size: var(--font-sm);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

/* "heute" steht als Wort da. Eine Farbe allein sagt nicht,
   was sie bedeutet. */
.weekgrid__today {
  font-size: var(--font-xs);
  font-weight: var(--weight-bold);
  color: var(--color-primary);
}

.weekgrid__day--today .weekgrid__dayhead {
  border-bottom-color: var(--color-primary);
}

/* Ein leerer Tag sagt, dass er leer ist. */
.weekgrid__empty {
  padding: var(--space-4) var(--space-2);
  text-align: center;
  font-size: var(--font-sm);
  color: var(--color-text-muted);
}

/* ------------------------------------------------------------
   Terminkarte
   ------------------------------------------------------------ */

.eventcard {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 100%;
  min-height: var(--touch-target);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  text-align: left;
  font: inherit;
  cursor: pointer;
}

.eventcard:hover {
  background: var(--color-bg-subtle);
}

.eventcard__time {
  font-size: var(--font-sm);
  font-weight: var(--weight-bold);
  font-variant-numeric: tabular-nums;
}

/* Eine Stufe kleiner als der Fliesstext: in einer von sechs
   Spalten entscheidet jeder Millimeter darueber, ob ein Titel
   in einer Zeile steht oder umbricht. */
.eventcard__title {
  font-size: var(--font-sm);
  /* Silbentrennung, wo der Browser ein Trennwoerterbuch hat –
     dafuer muss am <html> lang="de" stehen. Hat er keines,
     greift das Netz darunter und bricht mitten im Wort. Das ist
     haesslich, aber besser als ein Titel, der aus der Karte
     laeuft. Die eigentliche Abhilfe ist ein kuerzerer Titel. */
  hyphens: auto;
  overflow-wrap: break-word;
}

.eventcard__meta {
  font-size: var(--font-xs);
  color: var(--color-text-muted);
}

.eventcard__flag {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  margin-bottom: 2px;
}

/* Gewaehlt: Flaeche und kraeftiger Rand, nicht Farbe allein –
   die zugehoerige Karte in der Leiste ist dann aufgeklappt. */
.eventcard[aria-pressed="true"],
.eventcard--selected {
  background: var(--color-primary-soft);
  border-color: var(--color-primary);
  border-left-color: var(--color-primary);
}

.eventcard--blocks { border-left-color: var(--color-danger); }
.eventcard--reason { border-left-color: var(--color-warning); }
.eventcard--hint   { border-left-color: var(--color-success); }

/* ------------------------------------------------------------
   Hinweisleiste "Was im Weg steht"
   Eine Liste von Stellen, die Aufmerksamkeit brauchen, mit je
   zwei Wegen heraus. Allgemein gehalten: dieselbe Bauart traegt
   Konflikte im Kalender, Rueckläufe im Beitragslauf oder
   Pruefungen vor einem Import.
   ------------------------------------------------------------ */

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

.issues__count {
  font-size: var(--font-sm);
  color: var(--color-text-muted);
}

.issue {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  overflow: hidden;
}

.issue--selected {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 1px var(--color-primary);
}

/* Der ganze Kopf ist der Schalter – eine Zeile weit zu treffen
   ist leichter als ein kleines Dreieck. */
.issue__summary {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  width: 100%;
  padding: var(--space-3);
  border: none;
  background: transparent;
  text-align: left;
  font: inherit;
  cursor: pointer;
}

.issue__summary:hover {
  background: var(--color-bg-subtle);
}

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

.issue__what {
  font-weight: var(--weight-bold);
}

.issue__when {
  font-size: var(--font-sm);
  color: var(--color-text-muted);
}

.issue__why {
  font-size: var(--font-sm);
}

.issue__body {
  padding: 0 var(--space-3) var(--space-3);
  border-top: 1px solid var(--color-border);
  padding-top: var(--space-3);
}

/* Zwei Wege, beide gleich gross. Der Primary der Ansicht steht
   in der Kopfzone – hier waere ein zweiter einer zu viel. */
.issue__ways {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

.issues__note {
  font-size: var(--font-sm);
  color: var(--color-text-muted);
}

/* Steht anstelle der Leiste, wenn nichts mehr offen ist. */
.issues__clear {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-success-soft);
  color: var(--color-success);
  font-size: var(--font-sm);
}

/* Kleines Kennzeichen fuer die Terminkarte */
.badge--small {
  padding: 1px var(--space-1);
  font-size: var(--font-xs);
}

.badge svg {
  width: 12px;
  height: 12px;
  flex: 0 0 auto;
}

/* ------------------------------------------------------------
   Handy: Tagesliste statt sechs Spalten
   ------------------------------------------------------------ */

@media (max-width: 700px) {
  .weekgrid {
    grid-template-columns: 1fr;
    gap: var(--space-5);
  }

  /* Der Tageskopf klebt, solange man in seinem Tag ist. Ueber
     drei Bildschirme Liste ist "welcher Tag ist das gerade"
     sonst eine staendige Frage. Nebeneinander stellt sie sich
     nicht, deshalb nur hier. */
  .weekgrid__dayhead {
    position: sticky;
    top: var(--header-height, 0px);
    z-index: 2;
    background: var(--color-surface);
  }

  /* Ein leerer Tag steht in einer Zeile: "Mittwoch 18.03. ·
     keine Termine". Ganz ausblenden wuerde die Woche
     verfaelschen – dass an einem Tag nichts ist, ist eine
     Auskunft, gerade wenn man etwas unterbringen will. */
  .weekgrid__day--empty {
    flex-direction: row;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--space-2);
  }

  .weekgrid__day--empty .weekgrid__dayhead {
    flex-direction: row;
    align-items: baseline;
    gap: var(--space-2);
    padding-bottom: 0;
    border-bottom: none;
    position: static;
  }

  .weekgrid__day--empty .weekgrid__empty {
    /* Der Absatz bringt seinen Aussenabstand mit – der macht
       aus der einen Zeile wieder zwei. */
    margin: 0;
    padding: 0;
    text-align: left;
  }

  .weekgrid__day--empty .weekgrid__empty::before {
    content: "· ";
  }

  /* Die Leiste steht ueber der Liste und ist zugeklappt, damit
     der Kalender sichtbar bleibt. */
  .split {
    flex-direction: column;
  }

  /* flex-basis gilt in der Spalte fuer die HOEHE: aus
     "1 1 300px" wuerden 300 Pixel Leerraum unter der
     zugeklappten Leiste. */
  .split__main,
  .split__side {
    flex: 0 0 auto;
    width: 100%;
  }

  .split__side {
    order: -1;
  }

  /* Titel und Handlung untereinander – nebeneinander draengt
     der Knopf den Titel in den Umbruch. */
  .page-head {
    flex-wrap: wrap;
  }

  .page-head__actions {
    margin-left: 0;
    width: 100%;
  }

  .page-head__actions .btn {
    flex: 1 1 auto;
  }

  .issue__ways .btn {
    flex: 1 1 100%;
  }
}

/* Auf dem Handy steht die Leiste zugeklappt ueber der
   Tagesliste – sonst drueckt sie den Kalender aus dem Bild.
   Der Schalter nennt dabei, was drinsteht. */
.issues__handle {
  display: none;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  min-height: var(--touch-target);
  padding: var(--space-2) var(--space-4);
  border: none;
  background: transparent;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.issues__handle svg {
  width: 16px;
  height: 16px;
  margin-left: auto;
  transition: transform var(--transition-fast);
}

.issues__handle[aria-expanded="true"] svg {
  transform: rotate(180deg);
}

@media (max-width: 700px) {
  .issues__handle {
    display: flex;
  }
}

/* --- Zustaende des Kalenderblatts --------------------------
   Skelett, leere Woche und Ladefehler. Alle drei sind haeufiger
   zu sehen als der Normalfall und werden deshalb genauso
   gebaut, nicht nachtraeglich.
   ------------------------------------------------------------ */

/* Das Skelett hat die Form dessen, was kommt: Tageskopf und
   Kartenplaetze. Ein Balkenhaufen ohne Form laesst das Layout
   springen, sobald die Daten da sind. */
.skeleton--head {
  height: calc(var(--font-base) * var(--line-body));
  width: 60%;
}

.skeleton--line {
  height: var(--skeleton-height);
  width: 40%;
}

/* Hoehe aus den Zeilen gerechnet, die die Karte traegt –
   Uhrzeit, Titel, Ort – plus Innenabstand. Geraten waere sie
   halb so hoch, und das Blatt spraenge beim Eintreffen der
   Daten. Genau treffen kann das Skelett nicht: eine Karte mit
   Kennzeichen oder zweizeiligem Titel ist hoeher. Es geht um
   die Groessenordnung, nicht um den Pixel. */
.skeleton--card {
  /* Gemessen an den echten Karten: die niedrigste ist 103px,
     die hoechste 145. Das Skelett nimmt die niedrigste – dann
     waechst das Blatt beim Eintreffen der Daten, statt zu
     schrumpfen. Wachsen faellt weniger auf als ein Sprung nach
     oben, bei dem der Inhalt unter dem Zeiger wegrutscht. */
  height: calc(4 * var(--font-base) * var(--line-body) + var(--space-2));
  border-radius: var(--radius-md);
}

/* Eine Tagesspalte im Skelett behaelt Kopf und Trennlinie –
   so steht das Geruest schon, bevor der Inhalt kommt. */
.weekgrid__day--skeleton .weekgrid__dayhead {
  gap: var(--space-2);
}

/* Fehler in einem Panel, nicht auf einer eigenen Seite: was
   nicht geladen hat, ist dieser eine Block – der Rest der
   Ansicht steht und bleibt bedienbar. */
.empty--error .empty__icon {
  color: var(--color-danger);
}

/* ------------------------------------------------------------
   Mitwachsende Maske
   Zeigt hinter der Eingabe, was noch kommen koennte – wie das
   Datumsfeld des Browsers, aber ohne feste Laenge: man hoert
   auf, wo man fertig ist.
   ------------------------------------------------------------ */

.masked {
  position: relative;
  display: flex;
  flex: 1 1 12ch;
  min-width: 12ch;
}

.masked > .field__input {
  position: relative;
  z-index: 1;
  width: 100%;
  background: transparent;
}

/* Liegt genau unter der Eingabe und traegt dieselbe Schrift –
   sonst stehen Getipptes und Schablone nicht uebereinander. */
.masked__ghost {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  padding: 0 var(--space-1);
  font: inherit;
  font-size: var(--font-base);
  color: var(--color-text-muted);
  white-space: pre;
  pointer-events: none;
  overflow: hidden;
}

/* Das Getippte schiebt nur – zu sehen ist es im Feld darueber. */
.masked__getippt {
  visibility: hidden;
}

/* Der Rest der Schablone. Erbt Schrift und Farbe vom Umschlag;
   die Regel steht trotzdem hier, damit der Name im Stilblatt
   vorkommt und nicht nur im Markup. */
.masked__rest {
  color: inherit;
}

/* ------------------------------------------------------------
   Monatsblatt
   Sieben Spalten, fuenf bis sechs Zeilen. Eine Zelle ist rund
   150 Pixel breit – darin passt je Termin eine Zeile, mehr
   nicht. Die Terminkarte des Kalenderblatts waere hier falsch.
   ------------------------------------------------------------ */

.monthgrid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 1px;
  background: var(--color-border);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: clip;
}

.monthgrid__weekday {
  padding: var(--space-2) var(--space-3);
  background: var(--color-bg-subtle);
  font-size: var(--font-sm);
  font-weight: var(--weight-bold);
}

.monthgrid__day {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-height: calc(5 * var(--space-6));
  padding: var(--space-2);
  background: var(--color-surface);
}

/* Tage der Nachbarmonate stehen blass da: sie gehoeren zum
   Raster, nicht zum Monat. */
.monthgrid__day--outside {
  background: var(--color-bg-subtle);
}

.monthgrid__day--outside .monthday__num {
  color: var(--color-text-muted);
}

.monthday__kopf {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
}

.monthday__num {
  font-weight: var(--weight-bold);
  font-variant-numeric: tabular-nums;
}

.monthday__today {
  font-size: var(--font-xs);
  font-weight: var(--weight-bold);
  color: var(--color-primary);
}

.monthgrid__day--today {
  box-shadow: inset 0 2px 0 var(--color-primary);
}

/* Ein Termin als eine Zeile: Uhrzeit und Titel, sonst nichts. */
.monthentry {
  display: flex;
  gap: var(--space-2);
  width: 100%;
  min-height: var(--space-6);
  padding: 2px var(--space-2);
  border: none;
  border-left: 3px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-bg-subtle);
  font: inherit;
  font-size: var(--font-sm);
  text-align: left;
  cursor: pointer;
}

.monthentry:hover {
  background: var(--color-primary-soft);
}

.monthentry__time {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
  color: var(--color-text-muted);
}

.monthentry__title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Die Haerte traegt hier ein Strich – fuer das Wort ist kein
   Platz. Der Strich steht deshalb NIE allein: der Titel des
   Elements nennt sie beim Namen, und ein Wort fuer
   Vorleseprogramme steht im Element. Ein Tooltip allein
   erreicht weder Finger noch Tastatur. */
.monthentry--blocks { border-left-color: var(--color-danger); }
.monthentry--reason { border-left-color: var(--color-warning); }
.monthentry--hint   { border-left-color: var(--color-success); }

.monthday__more {
  align-self: flex-start;
  padding: 0 var(--space-1);
  border: none;
  background: transparent;
  color: var(--color-text-muted);
  font: inherit;
  font-size: var(--font-xs);
  text-decoration: underline;
  cursor: pointer;
}

/* ------------------------------------------------------------
   Monatsblatt auf dem Handy: eine lange Tagesliste
   Sieben Spalten zu je 50 Pixel sind unlesbar.
   ------------------------------------------------------------ */

/* Nennt den Wochentag – im Raster sagt ihn die Spalte, in der
   Tagesliste auf dem Handy muss er am Tag stehen. */
.monthday__weekday {
  display: none;
  font-size: var(--font-sm);
  color: var(--color-text-muted);
}

@media (max-width: 700px) {
  .monthday__weekday {
    display: inline;
    order: 1;
  }

  .monthgrid {
    display: block;
    border: none;
    background: transparent;
    border-radius: 0;
  }

  /* Die Spaltenkoepfe entfallen – jeder Tag nennt sich selbst. */
  .monthgrid__weekday {
    display: none;
  }

  /* Tage der Nachbarmonate ebenso: in einer Liste sind sie
     nur Beiwerk. */
  .monthgrid__day--outside {
    display: none;
  }

  .monthgrid__day {
    min-height: 0;
    padding: 0 0 var(--space-4);
    background: transparent;
  }

  .monthday__kopf {
    position: sticky;
    top: var(--header-height, 0px);
    z-index: 2;
    padding: var(--space-2) 0;
    border-bottom: 1px solid var(--color-border);
    background: var(--color-surface);
  }

  /* In der Liste nennt sich der Tag beim Namen, und der steht
     voran: "Montag 2." liest sich, "2 Montag" nicht. */
  .monthday__num {
    order: 2;
  }

  .monthday__num::after {
    content: ".";
  }

  .monthday__today {
    order: 3;
  }

  /* Ein leerer Tag bleibt eine Zeile, ohne Trennlinie und ohne
     den Abstand, den ein Tag mit Terminen darunter braucht. */
  .monthgrid__day--empty {
    padding-bottom: 0;
  }

  .monthgrid__day--empty .monthday__kopf {
    position: static;
    padding: var(--space-1) 0;
    border-bottom: none;
  }

  .monthentry {
    min-height: var(--touch-target);
    align-items: center;
    font-size: var(--font-base);
  }
}
