/* GENERIERT aus /Users/jeremy/.claude/standards/design/components/ui.css — nicht von Hand ändern.
   Neu holen: node /Users/jeremy/.claude/standards/design/scripts/install.mjs <ordner>
   Global gültig.
   Lokale Abweichungen gehören NICHT hierher, sondern in eine eigene Datei
   daneben, die nach diesem Generat geladen wird. */
/* KOMPONENTEN — die kanonische Quelle. Kein rohes Hex, kein roher px-Wert in
 * Schrift, Abstand oder Form, kein Griff in die Primitives.
 *
 * Die .md-Dateien daneben dokumentieren Wann/Verhalten/Anpassen und zeigen das
 * Markup. Die Gallery wird aus beidem generiert — es gibt keine dritte Kopie.
 *
 * Prüfbar mit: node scripts/audit.mjs components/ui.css */

/* ═══════════════════════════════════════════════════════════════════════════
   BASIS
   ═══════════════════════════════════════════════════════════════════════════ */

.ui {
  font-family: var(--font-sans);
  font-size: var(--text-2);
  line-height: var(--leading-2);
  font-weight: var(--weight-regular);
  color: var(--foreground);
}

/* Nur für die Sprachausgabe. Wo eine Grafik die Aussage trägt — das Messgerät
   in der Rechte-Matrix, ein Balken, ein Punkt — steht die Wortfassung daneben
   in dieser Klasse. `display: none` ginge nicht: die Sprachausgabe überspringt
   es dann ebenfalls.

   Stand bis heute in der Doku und NICHT im CSS: das Markup der Rechte-Matrix
   benutzt sie an acht Stellen, und die Zeilen standen dort sichtbar. Ein
   fehlender Selektor bricht nichts, er zeigt zu viel — deshalb fiel es nicht auf. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;   /* optisch: die Klammer um ein Pixel, kein Abstand */
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ═══════════════════════════════════════════════════════════════════════════
   MARKE
   Zwei Dateien, nicht eine eingefaerbte: das Zeichen hat zwei Toene — den
   Vollton und die hellere Diagonale dazwischen. Eine Maske oder currentColor
   wuerde die Diagonale verschlucken und aus dem Zeichen ein Klotz machen.
   Gruen steht auf Hell, Weiss auf Dunkel. Die Quelle bleibt am Element als
   src, damit das Bild auch ohne unser CSS erscheint.
   ═══════════════════════════════════════════════════════════════════════════ */
.logo { display: block; height: var(--space-7); width: auto; }
.logo--sm { height: var(--control-sm); }

/* Polster zaehlt zur Breite. Ohne diesen Satz haengt jedes Bauteil mit
   `width: 100%` und eigenem Polster davon ab, dass die SEITE einen Reset
   mitbringt — und wo sie keinen hat, steht die halbe Navigationsleiste
   ueber ihrer eigenen Kante. Ein Baukasten darf das nicht voraussetzen. */
.ui, .ui *, .ui *::before, .ui *::after { box-sizing: border-box; }

/* Scrollbalken sind unsichtbar — gescrollt wird per Rad, Trackpad oder
   Tastatur. Aus intelli-os, wo es app-weit gilt. Ein Balken, der auftaucht
   und verschwindet, verschiebt den Inhalt um seine Breite; auf einer Fläche
   mit vielen scrollenden Kästen zuckt dann bei jeder Bewegung etwas.
   Der Preis ist benannt: ohne Balken sieht man nicht, WIE VIEL noch kommt.
   Wo das zählt (lange Listen, Belege), gehört ein anderer Hinweis hin —
   eine Zahl, kein Balken. */
.ui, .ui * {
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.ui::-webkit-scrollbar,
.ui *::-webkit-scrollbar { width: 0; height: 0; display: none; }

:where(.ui *):focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
  border-radius: var(--radius-1);
}

@media (prefers-reduced-motion: reduce) {
  .ui *, .ui *::before, .ui *::after {
    animation-duration: var(--motion-off) !important;
    animation-iteration-count: 1 !important;
    transition-duration: var(--motion-off) !important;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   MATTE OBERFLÄCHE
   Das Papier-Gefühl. Zwei Schichten über der Fläche: ein radialer Schimmer
   unten rechts, darüber ein feines Korn. Ohne die beiden wirkt jede Fläche
   wie ein flacher Farbblock — das ist der Unterschied zwischen „dunkles UI"
   und „mattes Papier".

   WANN: Die matte Fläche ist ein BLATT PAPIER, das auf der Seite liegt.
     ja   Panel, Karte, Stream-Eintrag, Board-Spalte, Pane, Modal, Menü,
          Toast, Sheet, Auth-Karte
     nein Seitengrund · Regionen wie Sidebar und Toolbar · Bedienelemente
          (Button, Input — die bedient man, die liegen nicht) · Inline-Dinge
          (Badge, Chip) · Zeilen IN einem Panel (die liegen auf dem Blatt,
          sie sind nicht selbst eins) · Stat-Kacheln (eingelassen, nicht erhoben)

   MAXIMAL ZWEI EBENEN: Arbeitsfläche und Blatt darauf. Nie Blatt auf Blatt —
   sonst verdoppelt sich das Korn und die Tiefe wird unlesbar.

   ALLE DREI LAGEN SIND BILDEBENEN, auch das Korn. Das ist keine Stilfrage,
   sondern die Bedingung dafür, dass eine matte Fläche intern scrollen darf:
   ein `::after` mit `inset: 0` ist zwar containergross, scrollt in einem
   Scroll-Container aber mit dem Inhalt weg — nach der ersten Bildschirmhöhe
   ist die Fläche glatt. Genau in dieser Lage standen `.sheet` und
   `.shell__nav` (beide `overflow-y: auto`), und `.l-board__col` kommt als
   dritte dazu. Als Bildebene klebt das Korn am Kasten und bleibt stehen.
   intelli-os geht denselben Weg und schreibt den Grund an ihre scrollende
   Board-Spalte (`ui/board.css:57`).

   Die Reihenfolge ist die Ableitungsrichtung: Korn zuoberst, darunter der
   Sheen aus der unteren Ecke, darunter die matte Aufhellung. Der eigentliche
   Grundton bleibt `background-color` am Bauteil — so behält jede Fläche einen
   soliden, prüfbaren Kontrastwert. Ihre Kurzform überschreibt ihn und lässt
   die Fläche vom Untergrund abhängen.

   Wird nicht einzeln angewendet, sondern von den Flächen selbst geerbt.
   ═══════════════════════════════════════════════════════════════════════════ */

.surface,
.panel,
.kcard,
.modal,
.menu,
.toast,
.add-inline__field,
.l-stream__entry,
.l-auth__card,
.l-board__col,
.l-swipe__col,
.pane,
.sheet,
.composer,
.shell__nav {
  position: relative;
  background-image:
    var(--mat-grain-layer),
    radial-gradient(ellipse at 85% 95%, var(--mat-sheen) 0%, transparent 50%),
    linear-gradient(var(--mat-base), var(--mat-base));
}

/* Der Inhalt muss nur noch dort gehoben werden, wo wirklich ein Pseudo-Element
   über der Fläche liegt. Seit das Korn eine Bildebene ist, bleibt genau eine
   solche Stelle: das Raster der Seitenleiste (`.shell__nav::before`). Ohne die
   Hebung deckt es die Navigationseinträge zu — nicht positionierte Kinder
   malen unter einem positionierten `z-index: 0`. */
.shell__nav > * {
  position: relative;
  z-index: 1;
}

/* Getönte Fläche: Karte trägt die Kategoriefarbe. Der Ton kommt von aussen
   über --tint, damit die Komponente die Kategorie nicht kennen muss.
   Beispiel: style="--tint: var(--cat-blue-card)" */
.tint { background-color: var(--tint, var(--surface)); }

/* ═══════════════════════════════════════════════════════════════════════════
   BUTTON — genau eine Primäraktion pro View
   ═══════════════════════════════════════════════════════════════════════════ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-stack);
  height: var(--control-lg);
  padding: 0 var(--space-group);
  /* Randlos. Der Button ist eine FLÄCHE, kein Umriss — er hebt sich durch
     seinen Ton vom Untergrund ab, nicht durch eine Linie. */
  border: 0;
  border-radius: var(--radius-2);
  background: var(--surface-hover);
  color: var(--foreground);
  font: inherit;
  font-size: var(--text-2);
  line-height: var(--leading-2);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--motion-fast), color var(--motion-fast), transform var(--motion-instant);
}
.btn:hover { background: var(--border); }
.modal .btn:hover, .menu .btn:hover, .drawer .btn:hover { background: var(--surface-active); }
.btn:active:not(:disabled):not([aria-disabled="true"]) { transform: translateY(1px); }

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

/* Zerstörend ist zurückhaltend, bis man drübergeht — sonst zieht der
   gefährlichste Knopf den Blick auf sich. */
.btn--danger { background: transparent; color: var(--danger-foreground); }
.btn--danger:hover { background: var(--surface-active); }

.btn--ghost { background: transparent; color: var(--muted-foreground); }
.btn--ghost:hover { background: var(--surface-hover); color: var(--foreground); }

.btn--sm { height: var(--control-md); padding: 0 var(--space-inset); }
.btn--pill { border-radius: var(--radius-pill); }

.btn[disabled], .btn[aria-disabled="true"] {
  opacity: 0.55;
  cursor: default;
}

/* Icon-only — braucht immer aria-label */
.btn-icon {
  display: grid;
  place-items: center;
  width: var(--control-md);
  height: var(--control-md);
  border: 0;
  border-radius: var(--radius-2);
  background: transparent;
  color: var(--muted-foreground);
  cursor: pointer;
  transition: background var(--motion-fast), color var(--motion-fast);
}
.btn-icon:hover { background: var(--surface-hover); color: var(--foreground); }
.btn-icon--circle { border-radius: var(--radius-circle); }
.btn-icon--sm { width: var(--control-sm); height: var(--control-sm); }
.btn-icon--lg { width: var(--control-lg); height: var(--control-lg); }
/* Gefüllt: wenn der Icon-Button allein steht und gefunden werden muss. */
.btn-icon--solid { background: var(--surface-hover); color: var(--foreground); }
.btn-icon--solid:hover { background: var(--surface-active); }
.btn-icon--accent { background: var(--accent); color: var(--accent-foreground); }
.btn-icon--accent:hover { background: var(--accent-hover); }
/* Gefährliches färbt die SCHRIFT, nicht die Fläche. Ein roter Block zieht den
   Blick auf die Aktion, die man am seltensten will. */
.btn-icon--danger:hover { background: var(--surface-active); color: var(--danger-foreground); }
/* Eingeschaltet heisst gefüllt, nicht bunt — sonst konkurriert der Schalter
   mit dem Akzent, der der Aktion gehört. Gilt für Zustände OHNE Folge für die
   Daten (Sortierrichtung, Seitenleiste auf/zu). Blendet der Zustand Daten aus,
   ist es ein .filter-toggle und der trägt den Akzent. */
.btn-icon[aria-pressed="true"] { background: var(--surface-active); color: var(--foreground); }

/* ═══════════════════════════════════════════════════════════════════════════
   FORMULAR
   ═══════════════════════════════════════════════════════════════════════════ */

.field { display: flex; flex-direction: column; gap: var(--space-stack); }

.field__label {
  font-size: var(--text-0);
  line-height: var(--leading-0);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-0);
  text-transform: uppercase;
  color: var(--muted-foreground);
}

.field__hint,
.field__error {
  font-size: var(--text-1);
  line-height: var(--leading-1);
}
.field__hint { color: var(--muted-foreground); }
.field__error { color: var(--danger-foreground); }

/* Gedämpfter Kleintext, der etwas erklärt statt etwas zu benennen: die Folge
   einer Aktion, ein Beisatz in einer Kopfzeile, die zweite Zeile eines
   Listeneintrags. `.field__hint` deckt nur den Satz unter einem Feld ab — für
   alles andere stand bisher `style="color:var(--muted-foreground)"` im Markup,
   und dann hat jede Stelle ihre eigene Stufe von „grau und klein".
   NICHT für Fehler oder Warnungen — die tragen Farbe und stehen in feedback.md.
   Aus intelli-os (`.portal-hint`, sieben Verwendungen in vier Rollen). */
.hint {
  font-size: var(--text-1);
  line-height: var(--leading-1);
  color: var(--muted-foreground);
}
/* Als eigener Absatz braucht der Hinweis Luft nach oben, als Beisatz in einer
   Zeile nicht. Deshalb am Absatz und nicht an der Klasse — ein <span> im Fluss
   bleibt im Fluss. */
p.hint { margin: var(--space-stack) 0 0; }

.input, .select, .textarea {
  width: 100%;
  padding: 0 var(--space-inset);
  /* Das Feld wird durch seine FÜLLUNG erkennbar, nicht durch einen Rand.
     Der transparente Rand hält den Platz, damit Fokus und Fehler ihn setzen
     können, ohne dass etwas springt. */
  background: var(--surface-hover);
  box-shadow: var(--inset-shadow);
  border: 1px solid transparent;
  border-radius: var(--radius-2);
  color: var(--foreground);
  font-family: inherit;
  font-size: var(--text-2);
  line-height: var(--leading-2);
  transition: border-color var(--motion-fast), box-shadow var(--motion-fast);
}
.input, .select { height: var(--control-lg); }
.textarea { padding: var(--space-stack) var(--space-inset); min-height: 76px; resize: vertical; }

.input::placeholder, .textarea::placeholder { color: var(--muted-foreground); }

.input:focus, .select:focus, .textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: var(--inset-shadow), 0 0 0 3px var(--accent-surface);
}

.input[aria-invalid="true"], .textarea[aria-invalid="true"] { border-color: var(--danger-border); }
.input[aria-invalid="true"]:focus { box-shadow: 0 0 0 3px var(--danger-surface); }

.input[disabled], .select[disabled], .textarea[disabled] {
  background: var(--disabled-surface);
  color: var(--disabled-foreground);
  cursor: not-allowed;
}

.select {
  appearance: none;
  padding-right: var(--space-section);
  cursor: pointer;
}

/* Der Pfeil ist unser Icon, nicht das des Betriebssystems — sonst sieht das
   Feld auf jeder Plattform anders aus. */
.select-wrap { position: relative; display: block; }
.select-wrap__icon {
  position: absolute;
  right: var(--space-inset);
  top: 50%;
  transform: translateY(-50%);
  color: var(--muted-foreground);
  pointer-events: none;
}

/* Suche: Lupe links, Feld darum. Rund als einziges Feld (inputs.md) — die
   Form unterscheidet die Suche von jedem Feld, hinter dem ein Speichern
   steht. Stand bis 12.08.2026 nur in der Doku, nicht im CSS. */
.search { position: relative; display: flex; align-items: center; }
.search .input { padding-left: var(--space-page); border-radius: var(--radius-pill); }
.search__icon {
  position: absolute;
  left: var(--space-inset);
  top: 50%;
  transform: translateY(-50%);
  color: var(--muted-foreground);
  pointer-events: none;
}

/* Aktionen IM Feld, rechts. Für Suchen, die durch Treffer BLÄTTERN — eine
   Filtersuche, die nur die Liste darunter kürzt, braucht sie nicht.
   Das Leeren steht ganz aussen: es beendet die Suche und gehört deshalb nicht
   zwischen die Schritte.

   Der Platz wird GERECHNET: --search-actions ist die Anzahl Knöpfe, Standard
   drei. Eine geschätzte Zahl stimmt nach der ersten Token-Änderung nicht mehr,
   und dann läuft der getippte Text unter die Knöpfe. */
.search--actions .input {
  padding-right: calc(
    var(--control-sm) * var(--search-actions, 3) +
    var(--space-inline) * (var(--search-actions, 3) + 1)
  );
}
.search__actions {
  position: absolute;
  top: 50%;
  right: var(--space-inline);
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  gap: var(--space-inline);
}
/* Der leere Fall steht IM Feld, an der Stelle der Pfeile — nicht in einem
   Kästchen darunter, das wie ein abgerissener Tooltip aussieht. Ein Wort und
   keine Farbe: «Farbe ist nie die einzige Information», und `aria-invalid`
   wäre falsch, denn ein Suchwort ohne Treffer ist nicht ungültig.
   Eine Trefferzahl gibt es NICHT: dass es Treffer gibt, zeigen die Treffer. */
.search__lage {
  padding: 0 var(--space-inline);
  color: var(--muted-foreground);
  font-size: var(--text-1);
  line-height: var(--leading-1);
  white-space: nowrap;
}
.search__lage[hidden] { display: none; }
/* Ohne Treffer verschwinden die zwei Pfeile, das Wort tritt an ihre Stelle.
   Es ist breiter als sie, also braucht das Feld rechts mehr Platz. Die 5.5rem
   sind an «kein Treffer» gemessen — ein anderes Wort heisst ein anderer Wert,
   deshalb steht er hier neben der Regel und nicht in einem Token. */
.search--leer .input {
  padding-right: calc(var(--control-sm) + var(--space-inline) * 3 + 5.5rem);
}
.search--leer .search__actions button[data-r] { display: none; }
/* Das Kreuz des Browsers sitzt an einer anderen Stelle, hat eine andere Farbe
   und kommt je nach Browser gar nicht. Zwei Kreuze nebeneinander sind
   schlimmer als eines an der falschen Stelle. */
.search .input::-webkit-search-cancel-button,
.search .input::-webkit-search-decoration {
  -webkit-appearance: none;
  appearance: none;
}

/* Checkbox und Toggle: eigene Optik, echtes Input darunter */
.check { display: inline-flex; align-items: center; gap: var(--space-stack); cursor: pointer; }
.check input { position: absolute; opacity: 0; width: 0; height: 0; }
.check__box {
  width: 18px; height: 18px; flex: none;
  border: 1px solid var(--border-control);
  border-radius: var(--radius-1);
  background: transparent;
  display: grid; place-items: center;
  color: var(--accent-foreground);
  transition: background var(--motion-fast), border-color var(--motion-fast);
}
.check input:checked + .check__box { background: var(--accent); border-color: var(--accent); }
.check input:focus-visible + .check__box { outline: 2px solid var(--ring); outline-offset: 2px; }
.check__box svg { opacity: 0; transition: opacity var(--motion-fast); }
.check input:checked + .check__box svg { opacity: 1; }
/* Teilauswahl (Kopf-Kaestchen einer Tabelle): Strich statt Haken. Ohne das
   Bild ist ein per JS gesetztes `indeterminate` ein unsichtbarer Zustand. */
.check input:indeterminate + .check__box { background: var(--accent); border-color: var(--accent); }
.check input:indeterminate + .check__box::after {
  content: "";
  width: 10px; height: 2px;
  border-radius: var(--radius-pill);
  background: currentColor;
}
.check input:indeterminate + .check__box svg { display: none; }

.toggle { display: inline-flex; align-items: center; gap: var(--space-stack); cursor: pointer; }
.toggle input { position: absolute; opacity: 0; width: 0; height: 0; }
.toggle__track {
  width: 38px; height: 22px; flex: none;
  border-radius: var(--radius-pill);
  background: var(--surface-active);
  border: 1px solid var(--border-control);
  padding: 2px; /* optisch: Einfassung des Thumbs, kein Abstand */
  transition: background var(--motion-fast), border-color var(--motion-fast);
}
.toggle__thumb {
  width: 16px; height: 16px;
  border-radius: var(--radius-circle);
  background: var(--muted-foreground);
  transition: transform var(--motion-fast), background var(--motion-fast);
}
.toggle input:checked + .toggle__track { background: var(--accent); border-color: var(--accent); }
.toggle input:checked + .toggle__track .toggle__thumb { transform: translateX(16px); background: var(--accent-foreground); }
.toggle input:focus-visible + .toggle__track { outline: 2px solid var(--ring); outline-offset: 2px; }

/* ═══════════════════════════════════════════════════════════════════════════
   BADGE — Bedeutung, nicht Dekoration. Zuordnung in standards/01-color.md
   ═══════════════════════════════════════════════════════════════════════════ */

.badge {
  display: inline-block;
  padding: var(--space-inline) var(--space-stack);
  border-radius: var(--radius-pill);
  border: 0;
  font-size: var(--text-1);
  line-height: var(--leading-1);
  font-weight: var(--weight-medium);
  white-space: nowrap;
}
/* Gedämpfte Fläche, farbige Schrift — in BEIDEN Modi dieselbe Richtung.
   Die Umkehr auf Dark (helle Fläche, dunkle Schrift) stach heraus, wirkte aber
   als greller Fremdkörper in einer ruhigen Zeile. Wer laut braucht, nimmt den
   Solid-Ton; --badge--soft ist damit die gleiche Optik und bleibt nur als
   Alias bestehen, bis alle Aufrufer umgestellt sind. */
.badge--info      { background: var(--info-badge);    color: var(--info-badge-foreground); }
.badge--warning   { background: var(--warning-badge); color: var(--warning-badge-foreground); }
.badge--success   { background: var(--success-badge); color: var(--success-badge-foreground); }
.badge--danger    { background: var(--danger-badge);  color: var(--danger-badge-foreground); }
.badge--neutral   { background: var(--neutral-badge); color: var(--neutral-badge-foreground); }

/* Leise Variante, wenn viele Badges untereinander stehen und die Zeile sonst
   flimmert — dann trägt die Farbe, nicht die Helligkeit. */
.badge--soft.badge--info    { background: var(--info-surface);    color: var(--info-foreground); }
.badge--soft.badge--warning { background: var(--warning-surface); color: var(--warning-foreground); }
.badge--soft.badge--success { background: var(--success-surface); color: var(--success-foreground); }
.badge--soft.badge--danger  { background: var(--danger-surface);  color: var(--danger-foreground); }
.badge--soft.badge--neutral { background: var(--neutral-surface); color: var(--neutral-foreground); }

/* ── Abzeichen mit Kategorieton ────────────────────────────────────────
   Die fünf Badge-Töne oben sind BEWERTUNGEN (neu, in Arbeit, gut, schlecht,
   erledigt). Ein Profil, ein Modul oder eine Rolle ist keine Bewertung,
   sondern eine Zuordnung — wer sie in Statusfarben malt, behauptet ein Urteil,
   das niemand gemeint hat.

   Der Ton kommt von aussen über `data-tone` (siehe Rechte-Matrix weiter
   unten), das Bauteil kennt keinen Farbnamen. Damit trägt dasselbe Modul auf
   jedem Screen denselben Ton, ohne dass ein Screen eine Farbe nennt.
   Der Rückfall ist der neutrale Badge: ohne data-tone ist das Abzeichen
   leise, nicht unsichtbar.
   Aus intelli-os .wm-profile und .wm-modchip samt deren pc-*-Palette. */
.badge--tone {
  background: var(--tone-fill, var(--neutral-badge));
  color: var(--tone-text, var(--neutral-badge-foreground));
  font-weight: var(--weight-semibold);
}
/* Marke für ein Ding, nicht für eine Person: eckiger als die Pille. Die Pille
   ist rund, weil sie an einem Namen hängt; eine Modulmarke benennt einen
   Bereich und liest sich als Schild. Aus intelli-os .wm-modchip (Radius 7,
   effektiv 7.35 — unsere kleinste Stufe). */
.badge--mark { border-radius: var(--radius-1); }

/* Status-Punkt für dichte Zeilen, wo ein Badge zu laut wäre */
.dot { display: inline-block; width: 8px; height: 8px; border-radius: var(--radius-circle); }
.dot--info { background: var(--info-solid); }
.dot--warning { background: var(--warning-solid); }
.dot--success { background: var(--success-solid); }
.dot--danger { background: var(--danger-solid); }
.dot--neutral { background: var(--muted-foreground); }

/* ═══════════════════════════════════════════════════════════════════════════
   INLINE-ALERT — Meldung im Fluss, nicht als Overlay
   ═══════════════════════════════════════════════════════════════════════════ */

.alert {
  display: flex;
  gap: var(--space-inset);
  padding: var(--space-inset);
  border: 1px solid;
  border-radius: var(--radius-2);
  font-size: var(--text-2);
  line-height: var(--leading-2);
}
.alert__title { font-weight: var(--weight-medium); }
.alert__body { display: flex; flex-direction: column; gap: var(--space-inline); }
.alert svg { flex: none; margin-top: 2px; } /* optisch: Icon auf erste Textzeile */
.alert--info    { background: var(--info-surface);    color: var(--info-foreground);    border-color: var(--info-border); }
.alert--warning { background: var(--warning-surface); color: var(--warning-foreground); border-color: var(--warning-border); }
.alert--success { background: var(--success-surface); color: var(--success-foreground); border-color: var(--success-border); }
.alert--danger  { background: var(--danger-surface);  color: var(--danger-foreground);  border-color: var(--danger-border); }

/* ═══════════════════════════════════════════════════════════════════════════
   PANEL & TABELLE
   ═══════════════════════════════════════════════════════════════════════════ */

/* Randlos: Tonwechsel plus weicher Schatten tragen die Erhebung.
   Der transparente Rand hält den Platz frei, damit Hover oder ein aktiver
   Zustand einen Rand setzen kann, ohne dass das Layout springt. */
/* Matt-Papier: Grundflaeche + matte Aufhellung + Sheen-Radial aus der unteren
   Ecke, darueber ein feines Korn. Alle drei Lagen stehen EINMAL, in der
   Matt-Liste weiter oben — hier stand die ganze Anatomie ein zweites Mal, mit
   denselben Werten. Zwei Fassungen derselben Sache driften irgendwann.
   Randlos: die Erhebung traegt der Schatten. */
.panel {
  position: relative;
  overflow: hidden;
  background-color: var(--surface);
  border: 0;
  border-radius: var(--radius-3);
  box-shadow: var(--shadow-raised);
}

/* Auf farbigem Grund (Board-Spalte, getoenter Bereich): OPAKE Base. Eine
   durchscheinende Flaeche wuerde sich einfaerben und waere kein Papier mehr. */
.panel--opaque { --mat-base: var(--surface); }
.panel__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-inset);
  padding: var(--space-inset) var(--space-group);
  border-bottom: 1px solid var(--border-subtle);
}
.panel__title {
  font-size: var(--text-3);
  line-height: var(--leading-3);
  letter-spacing: var(--tracking-3);
  font-weight: var(--weight-medium);
}
.panel__body { padding: var(--space-group); }

.table { width: 100%; border-collapse: collapse; }
.table th {
  text-align: left;
  padding: var(--space-inset) var(--space-group);
  font-size: var(--text-0);
  line-height: var(--leading-0);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-0);
  text-transform: uppercase;
  color: var(--muted-foreground);
  border-bottom: 1px solid var(--border-subtle);
  background: var(--surface);
}
.table td {
  padding: var(--space-group);
  border-bottom: 1px solid var(--border-subtle);
  vertical-align: middle;
}
.table tbody tr:last-child td { border-bottom: none; }
.table tbody tr { transition: background var(--motion-fast); }
.table--selectable tbody tr { cursor: pointer; }
.table tbody tr:hover { background: var(--surface-hover); }
.table tbody tr[aria-selected="true"] { background: var(--surface-active); }
.table .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.table .mono { font-family: var(--font-mono); font-size: var(--text-1); line-height: var(--leading-1); }
/* Eine Anzahl, die in der Zeile steht statt in einer Betragsspalte: Mono, weil
   sie sich von Zeile zu Zeile vergleicht, aber links wie der übrige Text, weil
   sie keine Spalte mit Nachkommastellen ist. Fiel bisher zwischen .num
   (rechtsbündige Betragsspalte) und .mono (Kennung ohne Gewicht).
   Aus intelli-os .wm-keycount. */
.table .count {
  font-family: var(--font-mono);
  font-size: var(--text-1);
  line-height: var(--leading-1);
  font-weight: var(--weight-semibold);
}
/* Eine Null trägt keine Information — gedämpft, damit das Auge die Spalte nach
   echten Beträgen absucht statt nach Nullen. Nicht ausblenden: die Zelle muss
   belegt bleiben, sonst wirkt die Zeile unvollständig. Aus intelli-os
   (`.cust-table td.num.zero`), aber ohne deren zusätzliche Deckkraft — die
   drückt gedämpften Text unter jede Lesbarkeitsschwelle. */
.table .num[data-zero] { color: var(--muted-foreground); }

/* Dichte Variante: gleiche Struktur, knapperer Innenabstand */
.table--dense td { padding: var(--space-stack) var(--space-inset); }
.table--dense th { padding: var(--space-inline) var(--space-inset); }

/* ═══════════════════════════════════════════════════════════════════════════
   STAT — Kennzahl in einer Karte
   ═══════════════════════════════════════════════════════════════════════════ */

.stat {
  display: flex;
  flex-direction: column;
  gap: var(--space-inline);
  padding: var(--space-group);
  background: var(--surface-hover);
  border-radius: var(--radius-2);
}
.stat__label {
  font-size: var(--text-0);
  line-height: var(--leading-0);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-0);
  text-transform: uppercase;
  color: var(--muted-foreground);
}
.stat__value {
  font-family: var(--font-mono);
  font-size: var(--text-5);
  line-height: var(--leading-5);
  letter-spacing: var(--tracking-5);
  font-weight: var(--weight-semibold);
}

/* ═══════════════════════════════════════════════════════════════════════════
   PROGRESS
   ═══════════════════════════════════════════════════════════════════════════ */

.progress {
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--surface-active);
  overflow: hidden;
}
.progress__bar { height: 100%; border-radius: var(--radius-pill); background: var(--accent); transition: width var(--motion-slow); }
.progress__bar--warning { background: var(--warning-solid); }
.progress__bar--danger { background: var(--danger-solid); }

/* ═══════════════════════════════════════════════════════════════════════════
   DIE VIER ZUSTÄNDE — jede Datenansicht behandelt alle vier
   ═══════════════════════════════════════════════════════════════════════════ */

.skeleton {
  background: var(--surface-active);
  border-radius: var(--radius-1);
  animation: skeleton-pulse 1.4s ease-in-out infinite;
}
@keyframes skeleton-pulse { 0%,100% { opacity: 1 } 50% { opacity: .55 } }
.skeleton--line { height: 12px; }
.skeleton--title { height: 18px; width: 40%; }

.state {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-stack);
  padding: var(--space-page) var(--space-group);
  color: var(--muted-foreground);
}
.state__icon { color: var(--muted-foreground); margin-bottom: var(--space-inline); }
.state__title {
  font-size: var(--text-3);
  line-height: var(--leading-3);
  font-weight: var(--weight-medium);
  color: var(--foreground);
}
.state__text { max-width: 44ch; }
.state__action { margin-top: var(--space-inset); }
.state--error .state__icon { color: var(--danger-foreground); }
/* Füllt seinen Platz, statt an seinem Text zu hängen. Ein Leerzustand in einer
   Spalte neben einer langen Tabelle muss deren Höhe mitgehen — sonst steht die
   Aussage oben in der Ecke und die Spalte sieht kaputt aus. */
.state--fill { flex: 1; justify-content: center; }

/* ═══════════════════════════════════════════════════════════════════════════
   OVERLAYS — z-index-Leiter: Dropdown 10 · Modal 100 · Toast 1000
   ═══════════════════════════════════════════════════════════════════════════ */

.scrim {
  position: fixed;
  inset: 0;
  background: var(--overlay-scrim);
  z-index: 90;
}
/* Derselbe Schleier, aber an die Pane gebunden statt ans Fenster: `absolute`
   innerhalb von `.pane__overlays` statt `fixed`. Nur so bleibt die Nachbar-Pane
   bedienbar, während hier ein Dialog offen steht. Aus intelli-os
   (`.pane-scrim`). */
.scrim--local { position: absolute; }
/* Der Verdunkler einer verdeckten Ebene ist überflüssig: der obere deckt
   dieselbe Fläche schon ab, und zwei übereinander machen den Dialog darunter
   doppelt dunkel. Gesetzt wird das Attribut in `behaviour/overlay.js`, an
   derselben Stelle, die auch entscheidet, wer verdeckt ist. */
.scrim[data-verdeckt] { display: none; }

/* ── Zustand der Overlays ─────────────────────────────────────────────────
   Das Verhalten liegt in `behaviour/` und schreibt genau ein Attribut:
   `data-open` am Overlay. Hier liest das CSS dieselbe Stelle — keine zweite
   Wahrheit, kein Klassenschalter daneben.

   Die Regeln hängen an `[data-overlay]`, nicht an `.modal` oder `.drawer`: nur
   Markup, das ein Verhalten angemeldet hat, wird zugeklappt. Die Beispiele in
   der Dokumentation tragen das Attribut nicht und bleiben offen zu sehen.

   `[hidden]` braucht die zwei Attribute im Selektor, damit es `display: flex`
   von `.modal--tall` und `.sheet` schlägt. Das Browser-Stylesheet tut das
   nicht — genau so standen in diesem Repo schon Elemente sichtbar da, an denen
   `hidden` stand. Verborgen wird erst NACH der Ausfahrt; die Zeitmessung macht
   `behaviour/overlay.js` am Element, nicht mit einer eigenen Zahl. */
[data-overlay][hidden] { display: none; }

.modal {
  position: fixed;
  z-index: 100;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 90%;
  max-width: 441px;
  background-color: var(--popover);
  border-radius: var(--radius-3);
  box-shadow: var(--shadow-float);
  animation: modal-in var(--motion-base) ease;
}
@keyframes modal-in {
  from { opacity: 0; transform: translate(-50%, calc(-50% + 10px)); }
  to   { opacity: 1; transform: translate(-50%, -50%); }
}
/* Angemeldetes Verhalten: dieselbe Bewegung, aber als Übergang am Zustand.
   Wie beim PushScreen (`layouts.css`) ist das kein Geschmack — eine Animation
   läuft nur beim Erscheinen. Beim Schliessen verschwand das Modal hart, und
   `behaviour/overlay.js` mass eine Übergangsdauer von 0: der ganze Apparat aus
   „Bewegung abwarten" war für Modal und Menü toter Code. Ein Übergang läuft in
   beide Richtungen und ist messbar. Das Beispiel ohne `data-overlay` behält
   die Einblendung oben. */
[data-overlay="modal"] {
  animation: none;
  opacity: 0;
  transform: translate(-50%, calc(-50% + 10px));
  transition: opacity var(--motion-base) ease, transform var(--motion-base) ease;
}
[data-overlay="modal"][data-open] { opacity: 1; transform: translate(-50%, -50%); }
/* Der Kopf ist eine Zeile: links die Überschrift, rechts der Schliessen-Knopf.
   Titel und Untertitel stehen zusammen in `.modal__heading` — ohne diese Gruppe
   ist der Untertitel ein Geschwister des Knopfes und schiebt ihn aus der Zeile.
   Genau das machte unser eigenes dokumentiertes Markup bisher falsch. */
.modal__head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-inset);
  padding: var(--space-group) var(--space-section) var(--space-inline);
}
/* Die Überschrift nimmt die Zeile, die Knöpfe stehen rechts nebeneinander.
   Ohne `flex: 1` verteilt die Zeile den Rest zwischen Titel und Knöpfen und
   ein zweiter Knopf rutscht in die Mitte. */
.modal__heading { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--space-inline); }
.modal__title {
  font-size: var(--text-3);
  line-height: var(--leading-3);
  font-weight: var(--weight-semibold);
}
.modal__body { padding: var(--space-inset) var(--space-section) var(--space-inline); }
.modal__foot {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-inset);
  padding: var(--space-group) var(--space-section) var(--space-group);
}
/* Ein Satz unter dem Titel, der die Aufgabe des Dialogs erklärt. Bei einer
   Rückfrage überflüssig, bei einem Formular mit acht Feldern der Unterschied
   zwischen „ausfüllen" und „raten". */
.modal__sub {
  font-size: var(--text-1);
  line-height: var(--leading-1);
  color: var(--muted-foreground);
}

/* Modal für ein langes Formular: der Dialog steht fest in der Höhe, nur die
   Mitte scrollt. Ohne das läuft ein Formular mit zehn Feldern aus dem Fenster
   und der Speichern-Knopf ist unerreichbar. Bei ihnen heisst das
   `.wizard-modal`, obwohl drei ihrer fünf Verwendungen gar keine Schritte
   haben — der Schritt ist die Schrittleiste, nicht der Dialog. */
.modal--tall {
  display: flex;
  flex-direction: column;
  width: min(588px, 100% - var(--space-section));
  max-width: none;                 /* die 441 des Standard-Modals gilt hier nicht */
  max-height: calc(100% - var(--space-page));
  overflow: hidden;                /* der Körper scrollt, nicht der Dialog */
}
/* Ein Formulardialog trägt einen grösseren Titel als eine Rückfrage — er ist
   die Überschrift einer Aufgabe, nicht der Text einer Frage. */
.modal--tall .modal__title {
  font-size: var(--text-4);
  line-height: var(--leading-4);
}
/* Kopf und Fuss stehen fest — der Speichern-Knopf soll nicht wegscrollen. */
.modal--tall > .modal__head,
.modal--tall > .modal__foot { flex: none; }
/* Der Fuss bekommt hier eine Trennlinie, weil unter ihm Inhalt wegscrollt:
   ohne Kante sieht man nicht, wo die Scrollfläche endet. Am Standard-Modal
   scrollt nichts, dort wäre die Linie nur Dekoration. */
.modal--tall > .modal__foot { border-top: 1px solid var(--border-subtle); }
/* Der Körper nimmt, was Kopf und Fuss übriglassen. min-height:0 ist Pflicht:
   ohne das wächst ein Flex-Kind über seinen Container hinaus und scrollt nie. */
.modal__body--scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

.menu {
  position: absolute;
  z-index: 200;
  min-width: 200px;
  padding: var(--space-inline);
  background-color: var(--popover);
  border-radius: var(--radius-2);
  box-shadow: var(--shadow-float);
  animation: menu-in var(--motion-instant) var(--ease-out);
}
@keyframes menu-in {
  from { opacity: 0; transform: scale(0.97); }
  to   { opacity: 1; transform: none; }
}
/* Angemeldetes Verhalten: Übergang statt Animation, aus demselben Grund wie
   beim Modal — sonst gibt es keine Ausfahrt und nichts zu messen.
   OHNE das Skalieren, und das ist der Preis: `behaviour/menu.js` misst das
   Menü, um es an der Kante zu klemmen, und ein skaliertes Element meldet ein
   skaliertes Rechteck. Entweder es blendet auf oder es steht auf dem Pixel —
   und am Bildschirmrand überstehen ist der schlimmere Fehler. */
[data-overlay="menu"] {
  animation: none;
  opacity: 0;
  transition: opacity var(--motion-instant) var(--ease-out);
}
[data-overlay="menu"][data-open] { opacity: 1; }
.menu__item {
  display: flex;
  align-items: center;
  gap: var(--space-stack);
  width: 100%;
  height: var(--control-md);
  padding: 0 var(--space-inset);
  border: none;
  border-radius: var(--radius-2);
  background: none;
  color: var(--foreground);
  font: inherit;
  font-size: var(--text-2);
  font-weight: var(--weight-medium);
  text-align: left;
  cursor: pointer;
}
.menu__item:hover { background: var(--surface-hover); }
.menu__item--danger { color: var(--danger-foreground); }
/* Das führende Icon ordnet ein, der Text sagt die Sache. Auf voller Textfarbe
   konkurrieren beide, und das Häkchen rechts verliert seine Ausnahmestellung.
   Als Selektorregel statt als Klasse im Markup: eine Regel kann ein Bauteil
   nicht vergessen, eine Klasse im siebten Beispiel schon (DECISIONS,
   „Matt-Papier: eine Anatomie, zwei Zuständigkeiten"). */
.menu__item > .icon:first-child { color: var(--muted-foreground); }
/* Beim Löschen-Eintrag gehört das Icon zur Warnung, nicht zur Einordnung. */
.menu__item--danger > .icon:first-child { color: var(--danger-foreground); }
.menu__sep { height: 1px; background: var(--border-subtle); margin: var(--space-inline) var(--space-stack); }

/* Reaktionen stehen in EINER Zeile, nicht als Einträge untereinander: vier
   Emojis wären sonst vier Menüzeilen für eine einzige Handlung. Sie füllen die
   Menübreite gleichmässig, damit die Reihe als Griff gelesen wird, nicht als
   Liste. Aus intelli-os (`.cmenu-react`). */
.menu__react { display: flex; gap: 2px; } /* optisch: Fuge, kein Abstand */
.menu__emoji {
  display: flex;
  flex: 1;
  align-items: center;
  justify-content: center;
  height: var(--control-md);
  border: none;
  border-radius: var(--radius-2);
  background: none;
  font-size: var(--text-4);
  line-height: 1;
  cursor: pointer;
  transition: background var(--motion-fast) var(--ease-out),
              transform var(--motion-fast) var(--ease-out);
}
/* Randlose Nachbarn ohne Trennung: die Fläche allein sagt bei einem Emoji auf
   --text-4 zu leise, welches gerade unter dem Zeiger liegt. Das Wachsen sagt es. */
.menu__emoji:hover { background: var(--surface-hover); transform: scale(1.14); }
/* Schon von mir gesetzt. Gefüllt plus Ring, nicht Akzentfläche: den Akzent
   trägt ein Zustand nur, wenn er Daten ausblendet — eine eigene Reaktion
   blendet nichts aus, sie ist eine Markierung. */
.menu__emoji[aria-pressed="true"] {
  background: var(--surface-active);
  box-shadow: inset 0 0 0 1px var(--accent);
}

.toast {
  z-index: 1000;
  display: flex;
  align-items: center;
  gap: var(--space-inset);
  min-width: 280px;
  padding: var(--space-inset);
  background-color: var(--popover);
  color: var(--popover-foreground);
  border: 1px solid transparent;
  border-radius: var(--radius-2);
  box-shadow: var(--shadow-float);
  font-size: var(--text-2);
  line-height: var(--leading-2);
}
/* Der Toast trägt seine Bedeutung als Tönung — neutral wäre er nur eine
   Meldung, getönt ist er sofort einzuordnen. */
.toast--success { background: var(--success-surface); color: var(--success-foreground); }
.toast--danger  { background: var(--danger-surface);  color: var(--danger-foreground); }
.toast--warning { background: var(--warning-surface); color: var(--warning-foreground); }
.toast__close { margin-left: auto; }

.tooltip {
  z-index: 10;
  padding: var(--space-inline) var(--space-stack);
  background: var(--popover);
  color: var(--popover-foreground);
  border: 1px solid transparent;
  border-radius: var(--radius-1);
  font-size: var(--text-1);
  line-height: var(--leading-1);
  box-shadow: var(--shadow-float);
  white-space: nowrap;
}

/* ═══════════════════════════════════════════════════════════════════════════
   NAVIGATION
   ═══════════════════════════════════════════════════════════════════════════ */

/* Der Icon-Platz ist PFLICHT, nicht Schmuck. Eingeklappt verschwindet die
   Beschriftung und das Icon ist alles, was vom Eintrag übrig bleibt — ohne es
   ist die Leiste dann eine Spalte leerer Flächen. Aus intelli-os (.nav-ico). */
.nav-item__icon {
  display: grid;
  place-items: center;
  flex: none;
}
.nav-item__icon .icon { width: 16px; height: 16px; }   /* ihre 15 mal 1.05 */

.nav-item {
  display: flex;
  align-items: center;
  gap: var(--space-stack);
  width: 100%;
  height: var(--control-lg);
  padding: 0 var(--space-inset);
  border: none;
  border-radius: var(--radius-2);
  background: transparent;
  color: var(--foreground);
  font: inherit;
  font-size: var(--text-2);
  font-weight: var(--weight-medium);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--motion-fast) var(--ease-out);
  animation: nav-item-in var(--motion-base) var(--ease-out);
}
/* Eine Zeile, die erscheint, schiebt sich von unten herein statt aufzublitzen.
   Bei einer Liste, in die man Zeilen HINZUFÜGT (Projekt anlegen, Ordner), sagt
   die Bewegung, welche Zeile die neue ist. Aus intelli-os (`rowIn`). */
@keyframes nav-item-in {
  from { opacity: 0; transform: translateY(var(--space-inline)); }
  to   { opacity: 1; transform: none; }
}
.nav-item:hover { background: var(--surface-hover); }
/* Ablageziel: die Zeile nimmt die Akzentflaeche und einen INNEN liegenden
   Ring — ein aussen liegender würde die Nachbarzeilen verschieben. */
.nav-item[data-drop] {
  background: var(--accent-surface);
  box-shadow: inset 0 0 0 2px var(--accent);
}
.nav-item[aria-current] {
  background: var(--accent-surface);
  color: var(--accent-text);
}

/* Der Name nimmt den Platz und kürzt — nicht der Zähler. Ein Zähler, der
   verschwindet, ist schlimmer als ein gekürzter Name. */
.nav-item__label {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Zähler in Mono, damit die Ziffern untereinander stehen und die Spalte beim
   Wechsel von 9 auf 10 nicht wackelt. */
.nav-item__count {
  flex: none;
  padding: 0 var(--space-stack);
  border-radius: var(--radius-pill);
  background: var(--accent-surface);
  color: var(--accent-text);
  font-family: var(--font-mono);
  font-size: var(--text-1);
  line-height: var(--leading-1);
}
/* Zeilen-Aktion (anheften, entfernen). Erscheint bei Hover und Tastaturfokus.
   ACHTUNG: auf Touch gibt es kein Hover — dort muss sie dauerhaft sichtbar
   sein, sonst existiert die Funktion nicht. In intelli-os fehlt genau das. */
.nav-item__action {
  flex: none;
  display: grid; place-items: center;
  width: var(--control-sm); height: var(--control-sm);
  border: none; background: none; cursor: pointer;
  border-radius: var(--radius-1);
  color: var(--muted-foreground);
  transition: color var(--motion-fast);
}
.nav-item__action:hover { color: var(--foreground); }
/* Verschachtelung: eine Ebene, mehr nicht. Wer eine zweite braucht, hat einen
   Baum und keine Navigation — dafür ist die Gruppenüberschrift da. */
.nav-item--sub { padding-left: var(--space-group); }

/* Anlegen und Umbenennen passieren IN der Zeile, nicht in einem Dialog daneben.
   Deshalb hat das Feld keine eigene Fläche und keinen eigenen Rand: die Zeile
   ist der Rahmen. Ein Feld mit eigenem Kasten würde die Leiste an dieser Stelle
   aufreissen und die Liste optisch zerteilen. Aus intelli-os (`.addinput`) —
   dass dieselbe Zeile auch das Umbenennen trägt, steht dort nur in der .tsx. */
.nav-item__input {
  flex: 1;
  min-width: 0;
  border: 0;
  padding: 0;
  background: transparent;
  color: var(--foreground);
  font: inherit;
  font-size: var(--text-2);
  line-height: var(--leading-2);
  font-weight: var(--weight-medium);
  /* Kein Ring am Feld — der Zustand steht an der Zeile. Zwei Ringe ineinander
     sagen nichts Zusätzliches. */
  outline: none;
}
.nav-item__input::placeholder { color: var(--muted-foreground); }
/* Solange getippt wird, ist die Zeile keine Schaltfläche: sie hebt sich ab und
   der Zeiger hört auf, eine Navigation zu versprechen. Der Hover wird dabei
   stillgelegt — bei ihnen flackert der Zeilengrund beim Tippen unter dem
   Cursor. */
.nav-item[data-editing] {
  background: var(--surface-hover);
  cursor: default;
}
.nav-item[data-editing]:hover { background: var(--surface-hover); }

.nav-label {
  font-size: var(--text-0);
  line-height: var(--leading-0);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-0);
  text-transform: uppercase;
  color: var(--muted-foreground);
  /* Dieselbe Kante wie `.nav-item`. Der Wert ist keine Randbreite für sich,
     sondern die Fluchtlinie der Leiste — mit einer engeren Stufe stünde die
     Gruppenüberschrift vier Pixel links neben ihren eigenen Einträgen. */
  padding: 0 var(--space-inset);
}

.tabs { display: flex; gap: var(--space-inline); border-bottom: 1px solid var(--border-subtle); }
.tab {
  height: var(--control-lg);
  padding: 0 var(--space-inset);
  border: none;
  background: none;
  color: var(--muted-foreground);
  font: inherit;
  font-size: var(--text-2);
  font-weight: var(--weight-medium);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px; /* optisch: hebt die 1px-Border auf */
  transition: color var(--motion-fast), border-color var(--motion-fast);
}
.tab:hover { color: var(--foreground); }
.tab[aria-selected="true"] { color: var(--accent-text); border-bottom-color: var(--accent); }

/* ═══════════════════════════════════════════════════════════════════════════
   AVATAR · SEGMENTED · CHIP
   ═══════════════════════════════════════════════════════════════════════════ */

.avatar {
  display: grid;
  place-items: center;
  box-sizing: border-box;
  width: var(--control-sm);
  height: var(--control-sm);
  flex: none;
  border: 2px solid var(--surface);
  border-radius: var(--radius-circle);
  background: var(--accent-surface);
  color: var(--accent-text);
  font-size: var(--text-0);
  line-height: var(--leading-1);
  font-weight: var(--weight-semibold);
  margin-left: calc(var(--space-stack) * -1);
}
.avatar:first-child { margin-left: 0; }
.avatar--brand { background: var(--primary); color: var(--primary-foreground); }
/* Mittlere Stufe: Tabellenzeile und Panelkopf. Zwischen der Marke in einer
   Chip-Reihe (Grundstufe) und dem Kopfbild eines Leerzustands (--lg) klaffte
   bisher nichts — und genau dort sitzt der Avatar am häufigsten. */
.avatar--md {
  width: var(--control-md); height: var(--control-md);
  font-size: var(--text-1); line-height: var(--leading-1);
}

/* Segmented: zwei bis vier gleichrangige Zustände. Nicht für Aktionen. */
.segmented {
  display: flex;
  flex: none;
  gap: var(--space-inline);
  padding: var(--space-inline);
  background: var(--surface-hover);
  border-radius: var(--radius-2);
}
.segmented__item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-inline);
  height: var(--control-sm);
  padding: var(--space-inline) var(--space-inset);
  border: none;
  border-radius: var(--radius-1);
  background: none;
  color: var(--muted-foreground);
  font: inherit;
  font-size: var(--text-1);
  font-weight: var(--weight-medium);
  cursor: pointer;
  transition: all var(--motion-fast) var(--ease-out);
}
.segmented__item[aria-pressed="true"],
.segmented__item[aria-selected="true"] {
  background: var(--background);
  color: var(--foreground);
  box-shadow: var(--shadow-raised);
}

/* Chip: entfernbare Auswahl. Das X ist Teil des Chips, kein Nachbar. */
/* ── Screen-Kopf ───────────────────────────────────────────────────────────
   Die Kopf-Norm JEDES Screens, geerntet aus intelli-os. Zwei Zeilen:
   oben der Titel, darunter die Kontroll-Zeile — links Filter und Umschalter,
   rechts die Ansicht und die EINE Primaeraktion.

   Warum als eigenes Bauteil und nicht "einfach ein Flex-Container": die
   Kontroll-Zeile NORMIERT, was in ihr sitzt. Knoepfe, Umschalter und Oeffner
   bekommen hier dieselbe Hoehe, denselben Radius und dieselbe gedaempfte
   Ruhefarbe — nur die Primaeraktion bleibt gefuellt. Ohne diese Normierung
   steht in jedem Kopf eine andere Mischung aus 32er und 38er Controls, und
   genau daran erkennt man selbstgebaute Screens. */
.screen-head { display: flex; align-items: center; gap: var(--space-stack); min-width: 0; }
.screen-head h1 {
  font-size: var(--text-3);
  line-height: var(--leading-3);
  letter-spacing: calc(var(--tracking-1) * -1);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Die Anzahl gehoert zum Titel, nicht in einen Badge: sie zaehlt, was unten
   steht, und aendert sich mit dem Filter. */
.screen-head .count { font-size: var(--text-1); color: var(--muted-foreground); white-space: nowrap; }

.topbar { display: flex; align-items: center; gap: var(--space-stack); }
.topbar h1 { margin-right: var(--space-inset); }
.topbar-left {
  display: flex; align-items: center; gap: var(--space-stack);
  flex-wrap: wrap; min-width: 0;
}
.topbar-right {
  margin-left: auto;
  display: flex; align-items: center; gap: var(--space-stack);
  flex: none;
}
/* EINE Hoehe fuer alles in der Zeile. */
.topbar .btn,
.topbar .btn-icon,
.topbar .filter-toggle,
.topbar .filter-btn,
.topbar .segmented { height: var(--control-md); border-radius: var(--radius-2); }
.topbar .segmented { align-items: center; }
.topbar .segmented__item { border-radius: var(--radius-2); }
/* Sekundaer ist gedaempft: gleiche Flaeche, gleiche Schrift, kein Rand.
   Die Primaeraktion ist davon ausgenommen — sie soll herausstehen. */
.topbar .filter-toggle,
.topbar .filter-btn {
  background: var(--surface-hover);
  color: var(--muted-foreground);
  padding: 0 var(--space-inset);
  font-size: var(--text-2);
  font-weight: var(--weight-semibold);
}
.topbar .filter-toggle:hover,
.topbar .filter-btn:hover { background: var(--border); color: var(--foreground); }
.topbar .filter-toggle[aria-pressed="true"],
.topbar .filter-btn[data-active] {
  background: var(--accent-surface);
  color: var(--accent-text-strong);
}

/* ── Filter-Toggle ─────────────────────────────────────────────────────────
   An/aus, alleinstehend, mehrere gleichzeitig — UND-verknüpft. NICHT der Chip
   (der zeigt eine Auswahl und hat ein X) und NICHT das Segment (das ist
   exklusiv). Trägt als einziger Schalter den Akzent, weil sein Zustand die
   sichtbare Datenmenge ändert: wer ihn übersieht, sucht einen Datensatz, den
   der Filter versteckt. */
.filter-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-stack);
  height: var(--control-md);
  padding: 0 var(--space-inset);
  /* Gleiche Geometrie wie .btn — randlos, gleicher Radius. Der Umschalter ist
     dieselbe Art Griff wie eine Aktion, nur mit An/Aus. Eine eigene Form
     (Pille) wuerde ein eigenes Bauteil behaupten, das er nicht ist. */
  border: 0;
  border-radius: var(--radius-2);
  background: var(--surface-hover);
  color: var(--muted-foreground);
  font: inherit;
  font-size: var(--text-1);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--motion-fast), color var(--motion-fast), border-color var(--motion-fast);
}
.filter-toggle:hover { background: var(--surface-active); color: var(--foreground); }
.filter-toggle[aria-pressed="true"] {
  background: var(--accent-surface);
  color: var(--accent-text-strong);
}
/* Aktiv + Hover: der Akzent bleibt, nur die Schrift zieht an — eine zweite
   Akzentfläche gibt es nicht, und heller würde den Aktiv-Zustand schwächen. */
.filter-toggle[aria-pressed="true"]:hover { color: var(--accent-text-strong); filter: brightness(1.12); }
.filter-toggle[disabled] {
  background: var(--disabled-surface);
  color: var(--disabled-foreground);
  cursor: not-allowed;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-inline);
  height: var(--control-sm);
  padding: 0 var(--space-stack);
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--surface-hover);
  color: var(--foreground);
  font-size: var(--text-1);
  line-height: var(--leading-1);
  white-space: nowrap;
}
.chip--accent { background: var(--accent-surface); color: var(--accent-text); }
.chip__remove {
  display: inline-grid;
  place-items: center;
  border: none;
  background: none;
  color: inherit;
  cursor: pointer;
  opacity: .7;
  padding: 0;
}
.chip__remove:hover { opacity: 1; }

/* ── Reaktionen ────────────────────────────────────────────────────────────
   Reaktionen unter einem Beitrag. Ein Knopf, kein Chip: der Klick setzt die
   eigene Reaktion und nimmt sie wieder zurück. Die Geometrie ist die des
   Chips — dieselbe Pille, damit die Zeile nicht wie ein zweites System wirkt.
   Aus intelli-os (`.reactrow`/`.react` in notes.css). */
.reaction-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-inline);
  margin-top: var(--space-stack);
}
.reaction {
  display: inline-flex;
  align-items: center;
  gap: var(--space-inline);
  height: var(--control-sm);
  padding: 0 var(--space-stack);
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--surface-hover);
  color: var(--foreground);
  font-size: var(--text-1);
  line-height: var(--leading-1);
  cursor: pointer;
  transition: background var(--motion-fast) var(--ease-out),
              color var(--motion-fast) var(--ease-out);
}
.reaction:hover { background: var(--surface-active); }
/* „Ich habe reagiert" trägt die Farbe, nicht eine eigene Fläche: die Fläche
   ist schon der Träger für Überfahren, und den Akzent als Fläche hat allein
   der Filter-Schalter, weil nur er Daten ausblendet. Ihre Fassung setzt für
   Hover und für „meine" denselben Randwechsel — dort sind beide Zustände
   nicht auseinanderzuhalten. */
.reaction[aria-pressed="true"],
.reaction[aria-pressed="true"] .reaction__count { color: var(--accent-text-strong); }
.reaction__icon {
  font-size: var(--text-1);
  line-height: 1;
}
/* Die Zahl steht in Ziffernbreite, damit eine Reihe von Reaktionen beim
   Hochzählen nicht wandert. */
.reaction__count {
  font-family: var(--font-mono);
  font-size: var(--text-0);
  line-height: var(--leading-0);
  color: var(--muted-foreground);
}

/* Filter: ein Schalter, der zeigt, wie viele Bedingungen aktiv sind.
   Ohne Zahl weiss niemand, ob sich das Öffnen lohnt. */
.filter-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-stack);
  height: var(--control-md);
  padding: 0 var(--space-inset);
  border: 0;
  border-radius: var(--radius-2);
  background: var(--surface-hover);
  color: var(--foreground);
  font: inherit;
  font-size: var(--text-2);
  font-weight: var(--weight-medium);
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--motion-fast), color var(--motion-fast), border-color var(--motion-fast);
}
.filter-btn:hover { background: var(--surface-active); }
/* Geöffnet und „trägt einen Wert" sind DERSELBE An-Zustand — so steht es in
   intelli-os begründet, und unser eigenes Zustands-Modell sagt dasselbe: der
   Akzent gehört an einen Zustand, der die sichtbaren Daten ändert. Genau das
   tut ein gesetzter Filter, auch wenn sein Popover gerade zu ist.
   Vorher trug die Basisregel nur hellere Schrift und nur die Topbar-Fassung
   den Akzent — zwei Wahrheiten für denselben Knopf. */
.filter-btn[aria-expanded="true"],
.filter-btn[data-active] {
  background: var(--accent-surface);
  color: var(--accent-text-strong);
}
.filter-btn__count {
  display: inline-grid;
  place-items: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px; /* optisch: Zahlpolster, kein Abstand */
  border-radius: var(--radius-pill);
  background: var(--accent);
  color: var(--accent-foreground);
  font-size: var(--text-1);
  font-weight: var(--weight-semibold);
}

/* ═══════════════════════════════════════════════════════════════════════════
   NAVIGATION — Breadcrumb · Pagination
   ═══════════════════════════════════════════════════════════════════════════ */

.breadcrumb {
  display: flex;
  align-items: center;
  gap: var(--space-stack);
  font-size: var(--text-1);
  line-height: var(--leading-1);
  color: var(--muted-foreground);
}
.breadcrumb a { color: var(--muted-foreground); text-decoration: none; }
.breadcrumb a:hover { color: var(--foreground); }
.breadcrumb [aria-current] { color: var(--foreground); }
.breadcrumb__sep { opacity: .5; }

.pagination { display: flex; align-items: center; gap: var(--space-stack); }
.pagination__info { font-family: var(--font-mono); font-size: var(--text-1); color: var(--muted-foreground); }

/* ═══════════════════════════════════════════════════════════════════════════
   TABELLE — sortierbar, mehrfach auswählbar, aufklappbar
   ═══════════════════════════════════════════════════════════════════════════ */

.th-sort {
  display: inline-flex;
  align-items: center;
  gap: var(--space-inline);
  border: none;
  background: none;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
  padding: 0;
}
.th-sort svg { opacity: 0; transition: opacity var(--motion-fast), transform var(--motion-fast); }
.th-sort:hover svg { opacity: .45; }
.th-sort[aria-sort] { color: var(--foreground); }
.th-sort[aria-sort] svg { opacity: 1; }
.th-sort[aria-sort="descending"] svg { transform: rotate(180deg); }

.table__select { width: 1%; white-space: nowrap; }

/* Row-Expand: die Zeile klappt auf, statt auf eine Detailseite zu springen.
   Für 1:N-Beziehungen — Positionen, Ereignisse, Abhängigkeiten. */
.row-detail > td { padding: 0; border-bottom: 1px solid var(--border-subtle); }
.row-detail__inner {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--motion-base) var(--ease-out);
}
.row-detail[data-open] .row-detail__inner { grid-template-rows: 1fr; }
.row-detail__body { overflow: hidden; }
.row-detail__pad { padding: var(--space-inset) var(--space-group) var(--space-group); background: var(--surface-hover); }

.row-toggle svg { transition: transform var(--motion-base) var(--ease-out); }
.row-toggle[aria-expanded="true"] svg { transform: rotate(90deg); }

/* ═══════════════════════════════════════════════════════════════════════════
   ZEITLICHES — Ereigniskette · Uptime · Sparkline
   ═══════════════════════════════════════════════════════════════════════════ */

.timeline { display: flex; flex-direction: column; gap: var(--space-inset); }
.timeline__item { display: grid; grid-template-columns: auto 1fr; gap: var(--space-inset); }
.timeline__rail { display: flex; flex-direction: column; align-items: center; gap: var(--space-inline); }
.timeline__dot { width: 9px; height: 9px; border-radius: var(--radius-circle); background: var(--muted-foreground); }
.timeline__line { flex: 1; width: 1px; background: var(--border-subtle); min-height: 12px; }
.timeline__item:last-child .timeline__line { display: none; }
.timeline__body { display: flex; flex-direction: column; gap: var(--space-inline); padding-bottom: var(--space-stack); }
.timeline__time { font-family: var(--font-mono); font-size: var(--text-1); color: var(--muted-foreground); }

.uptime { display: flex; gap: 2px; align-items: flex-end; } /* optisch: Balkenfuge, kein Abstand */
.uptime__tick { width: 5px; height: 22px; border-radius: 2px; background: var(--success-solid); } /* optisch: Balkenradius */
.uptime__tick--warning { background: var(--warning-solid); }
.uptime__tick--danger { background: var(--danger-solid); }
.uptime__tick--none { background: var(--surface-active); }

.sparkline { display: block; width: 100%; height: 34px; overflow: visible; }
.sparkline path { fill: none; stroke: var(--accent); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.sparkline .area { fill: var(--accent-surface); stroke: none; }

/* Status-Light: für Betriebszustände, die man dauernd im Blick hat. */
.status-light { display: inline-flex; align-items: center; gap: var(--space-stack); font-size: var(--text-2); }
.status-light__lamp { width: 8px; height: 8px; border-radius: var(--radius-circle); flex: none; }
.status-light--ok .status-light__lamp { background: var(--success-solid); box-shadow: 0 0 0 3px var(--success-surface); }
.status-light--warn .status-light__lamp { background: var(--warning-solid); box-shadow: 0 0 0 3px var(--warning-surface); }
.status-light--down .status-light__lamp { background: var(--danger-solid); box-shadow: 0 0 0 3px var(--danger-surface); }
.status-light--off .status-light__lamp { background: var(--muted-foreground); }

/* ═══════════════════════════════════════════════════════════════════════════
   TILE-GRID — Kacheln statt Zeilen, wenn das Element kompakt ist
   ═══════════════════════════════════════════════════════════════════════════ */

/* Fuss der Kachel: Herkunft links, Entscheidung rechts. Er UMBRICHT — eine
   Kachel steht in einem Raster und wird schmal, und zwei Knoepfe plus ein
   Etikett passen dann nicht in eine Zeile. Ohne Umbruch schneidet die Kachel
   ihre eigene Antwort ab, und die abgeschnittene ist immer die rechte:
   „Abgelehnt". */
.tile__foot {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-stack);
}

.tile-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: var(--space-inset); }
.tile {
  display: flex;
  flex-direction: column;
  gap: var(--space-stack);
  padding: var(--space-inset);
  /* Kachel = Fläche wie die Karte. Die Schriftfarbe MUSS gesetzt sein:
     als <button> erbt sie sonst das Schwarz des Betriebssystems. */
  background: var(--surface-hover);
  color: var(--foreground);
  font: inherit;
  font-size: var(--text-2);
  border: 0;
  border-radius: var(--radius-2);
  cursor: pointer;
  text-align: left;
  transition: background var(--motion-fast);
}
.tile:hover { background: var(--surface-active); }
/* Ausgewählt heisst heller, nicht bunt — die Kachel bleibt neutral, sonst
   konkurriert die Auswahl mit den Statusfarben darin. */
.tile[aria-selected="true"] { background: var(--surface-active); }
.tile__head { display: flex; align-items: center; gap: var(--space-stack); }
.tile__title { font-weight: var(--weight-medium); color: var(--foreground); }
.tile__meta { font-size: var(--text-1); color: var(--muted-foreground); }

/* ═══════════════════════════════════════════════════════════════════════════
   DRAWER — Slide-out von rechts, für Gelegentliches
   ═══════════════════════════════════════════════════════════════════════════ */

.drawer {
  display: flex;
  flex-direction: column;
  width: min(400px, 100%);
  background: var(--popover);
  border-left: 1px solid var(--border);
  box-shadow: var(--shadow-float);
}
.drawer__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-inset);
  padding: var(--space-inset) var(--space-group);
  border-bottom: 1px solid var(--border-subtle);
}
.drawer__body { flex: 1; overflow-y: auto; padding: var(--space-group); }

/* Erst die Anmeldung eines Verhaltens macht aus der Fläche ein Overlay. Ohne
   diese Trennung müsste jede Filterspalte, die fest im Screen steht, plötzlich
   `fixed` sein — und die Spalte im Beispiel oben läge über der Seite.
   Eingefahren ist die Ruhelage, offen ist der Zustand: `data-open` ist die
   eine Wahrheit, `behaviour/drawer.js` setzt sie. */
[data-overlay="drawer"] {
  position: fixed;
  top: 0;
  bottom: 0;
  z-index: 100;
  transition: transform var(--motion-slow) var(--ease-out);
}
/* Rechts ist die Vorgabe: dort liegt der Daumen, und der Inhalt links bleibt
   an seinem Platz. Links nur, wenn der Drawer zur Navigation gehört — die
   steht in diesem System links, und ein Navigationsdrawer, der von rechts
   kommt, widerspricht dem Rest der Anwendung. */
[data-overlay="drawer"] { right: 0; transform: translateX(100%); }
[data-overlay="drawer"][data-seite="links"] {
  right: auto;
  left: 0;
  border-left: none;
  border-right: 1px solid var(--border);
  transform: translateX(-100%);
}
[data-overlay="drawer"][data-open] { transform: none; }

/* ── Verdeckt: eine Sprosse tiefer ────────────────────────────────────────
   Zwei Dialoge übereinander. Der zweite bringt seinen eigenen Verdunkler mit,
   und der muss über den ersten Dialog — sonst stünde der hell vor dem
   Schleier, der ihn meint.

   NICHT der obere Schleier wird angehoben. Das war die naheliegende Lösung und
   sie war falsch: ein angehobener Schleier liegt über BEIDEN Dialogen, also
   auch über dem, zu dem er gehört. Gemessen war der obere Dialog dann mit der
   Maus tot — jeder Klick darin galt als „daneben" und warf ihn weg. Stattdessen
   tritt der VERDECKTE Dialog hinter die Schleier-Sprosse (90) zurück. Das
   stimmt für zwei Ebenen, für drei und für jede Mischung aus Modal, Drawer und
   Sheet, ohne dass die Leiter neue Sprossen bekommt.

   Steht hier unten und nicht oben beim Schleier, weil `[data-overlay="drawer"]`
   dieselbe Spezifität hat: bei Gleichstand gewinnt die spätere Regel. */
[data-overlay][data-verdeckt] { z-index: 80; }

/* ═══════════════════════════════════════════════════════════════════════════
   BANNER — systemweite Mitteilung, ganz oben, nicht wegklickbar
   ═══════════════════════════════════════════════════════════════════════════ */

.banner {
  display: flex;
  align-items: center;
  gap: var(--space-inset);
  padding: var(--space-stack) var(--space-page);
  background: var(--warning-surface);
  color: var(--warning-foreground);
  border-bottom: 1px solid var(--warning-border);
  font-size: var(--text-2);
}
.banner--danger { background: var(--danger-surface); color: var(--danger-foreground); border-bottom-color: var(--danger-border); }
.banner__actions { margin-left: auto; display: flex; gap: var(--space-stack); }

/* ═══════════════════════════════════════════════════════════════════════════
   RADIO — bei drei bis fünf Optionen, die man vergleichen will
   ═══════════════════════════════════════════════════════════════════════════ */

.radio { display: inline-flex; align-items: center; gap: var(--space-stack); cursor: pointer; }
.radio input { position: absolute; opacity: 0; width: 0; height: 0; }
.radio__mark {
  width: 18px; height: 18px; flex: none;
  border: 1px solid var(--border-control);
  border-radius: var(--radius-circle);
  background: var(--surface);
  display: grid; place-items: center;
  transition: border-color var(--motion-fast);
}
.radio__mark::after {
  content: "";
  width: 8px; height: 8px;
  border-radius: var(--radius-circle);
  background: var(--accent);
  transform: scale(0);
  transition: transform var(--motion-fast);
}
.radio input:checked + .radio__mark { border-color: var(--accent); }
.radio input:checked + .radio__mark::after { transform: scale(1); }
.radio input:focus-visible + .radio__mark { outline: 2px solid var(--ring); outline-offset: 2px; }

/* ═══════════════════════════════════════════════════════════════════════════
   DATUM — eigener Kalender statt Systemwidget
   Das native type="date" sieht auf jeder Plattform anders aus und lässt sich
   nicht in die Tokens holen. Struktur aus intelli-os übernommen.
   ═══════════════════════════════════════════════════════════════════════════ */

.datefield { position: relative; display: block; }
.datefield__trigger {
  width: 100%;
  height: var(--control-lg);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-stack);
  padding: 0 var(--space-inset);
  background: var(--surface-hover);
  box-shadow: var(--inset-shadow);
  border: 1px solid transparent;
  border-radius: var(--radius-2);
  color: var(--foreground);
  font: inherit;
  font-size: var(--text-2);
  cursor: pointer;
  transition: border-color var(--motion-fast), box-shadow var(--motion-fast);
}
/* Überfahren zeigt nur die Grenze, nicht den Fokusring — sonst ist im
   Tastaturbetrieb nicht mehr erkennbar, wo der Fokus steht. Gleich behandelt
   wie .dropdown__trigger, wo derselbe Mangel schon behoben ist. */
.datefield__trigger:hover { border-color: var(--border-control); }
.datefield__trigger:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: var(--inset-shadow), 0 0 0 3px var(--accent-surface);
}
.datefield__trigger--empty { color: var(--muted-foreground); }
.datefield__trigger svg { color: var(--muted-foreground); flex: none; }

/* Schwebt wie das Auswahl-Popover: fixe Position, weil ein Formular scrollt und
   ein Modal-Overflow den Kalender sonst abschneidet. Dieselbe Sprosse wie
   .dropdown__pop — beide sind Popover über allem ausser Toast. */
.cal {
  position: fixed;
  z-index: 1000;
  width: 273px;
  padding: var(--space-inset);
  background: var(--popover);
  border-radius: var(--radius-2);
  box-shadow: var(--shadow-float);
}
.cal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-inset);
}
.cal__title { font-size: var(--text-2); font-weight: var(--weight-semibold); }
/* Sechs Wochenzeilen, immer — auch wenn der Monat mit fünf auskommt. Sonst
   ändert der Kalender beim Blättern seine Höhe, der Tag unter dem Zeiger
   wandert weg und man klickt daneben. Erste Zeile sind die Wochentage. */
.cal__grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  grid-template-rows: auto repeat(6, auto);
  gap: 2px; /* optisch: Zellfuge, kein Abstand */
}
.cal__wd {
  display: grid;
  place-items: center;
  height: var(--control-sm);
  font-size: var(--text-0);
  font-weight: var(--weight-semibold);
  color: var(--muted-foreground);
}
.cal__day {
  display: grid;
  place-items: center;
  aspect-ratio: 1;
  border: 0;
  border-radius: var(--radius-2);
  background: none;
  color: var(--foreground);
  font: inherit;
  font-size: var(--text-2);
  cursor: pointer;
  transition: background var(--motion-fast), color var(--motion-fast);
}
.cal__day:hover { background: var(--surface-hover); }
.cal__day--muted { color: var(--disabled-foreground); }
/* Heute ist ein Ring, Gewählt ist eine Fläche. Zwei Zustände, die auf
   derselben Zelle gleichzeitig gelten können — als Farbe allein verschwindet
   Heute unter der gewählten Fläche. */
.cal__day--today {
  box-shadow: inset 0 0 0 1px var(--accent);
  color: var(--accent-text);
  font-weight: var(--weight-semibold);
}
.cal__day[aria-selected="true"] {
  background: var(--accent);
  color: var(--accent-foreground);
  font-weight: var(--weight-semibold);
}
.cal__day[aria-selected="true"]:hover { background: var(--accent); }
.cal__day[aria-selected="true"].cal__day--today { color: var(--accent-foreground); }
.cal__foot {
  display: flex;
  justify-content: space-between;
  gap: var(--space-stack);
  margin-top: var(--space-inset);
  padding-top: var(--space-inset);
  border-top: 1px solid var(--border-subtle);
}

/* ═══════════════════════════════════════════════════════════════════════════
   BARRIEREFREIE VARIANTE — Umriss für Bedienelemente
   Die randlose Optik erkennt ein Feld an seiner Füllung. Der Tonunterschied
   liegt bei ~1.08:1 und erfüllt WCAG 1.4.11 (3:1 für die Grenze eines
   Bedienelements) NICHT. Das ist eine bewusste Gestaltungsentscheidung und
   Branchenpraxis — Radix, shadcn, Linear und intelli-os machen es genauso.

   Für Kontexte, in denen das nicht reicht (öffentliche Angebote, regulierte
   Anwendungen, Formulare für Ungeübte): html.outlined einschalten. Dann tragen
   Felder wieder einen sichtbaren Rand mit 3:1, alles andere bleibt.
   ═══════════════════════════════════════════════════════════════════════════ */

html.outlined .input,
html.outlined .select,
html.outlined .textarea,
html.outlined .datefield__trigger,
html.outlined .dropdown__trigger,
html.outlined .btn:not(.btn--primary):not(.btn--ghost):not(.btn--danger) {
  border-color: var(--border-control);
}

/* ═══════════════════════════════════════════════════════════════════════════
   DROPDOWN — eigenes Auswahlfeld statt Systemwidget
   Das native <select> öffnet eine Liste, die das Betriebssystem malt: fremde
   Schrift, fremde Farben, fremde Höhe. Struktur aus intelli-os.
   ═══════════════════════════════════════════════════════════════════════════ */

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

.dropdown__trigger {
  width: 100%;
  height: var(--control-lg);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-stack);
  padding: 0 var(--space-inset);
  background: var(--surface-hover);
  box-shadow: var(--inset-shadow);
  border: 1px solid transparent;
  border-radius: var(--radius-2);
  color: var(--foreground);
  font: inherit;
  font-size: var(--text-2);
  cursor: pointer;
  transition: border-color var(--motion-fast), box-shadow var(--motion-fast);
}
.dropdown__trigger:hover { border-color: var(--border-control); }
.dropdown__trigger:focus-visible,
.dropdown__trigger[aria-expanded="true"] {
  outline: none;
  border-color: var(--accent);
  box-shadow: var(--inset-shadow), 0 0 0 3px var(--accent-surface);
}
.dropdown__trigger--empty { color: var(--muted-foreground); }
.dropdown__trigger[disabled] {
  background: var(--disabled-surface);
  color: var(--disabled-foreground);
  cursor: not-allowed;
}
.dropdown__trigger svg { color: var(--muted-foreground); flex: none; }

.dropdown__pop {
  position: fixed;
  z-index: 1000;
  min-width: 240px;
  max-height: 294px;
  overflow-y: auto;
  padding: var(--space-inline);
  background: var(--popover);
  border: 0;
  border-radius: var(--radius-2);
  box-shadow: var(--shadow-float);
}
.dropdown__section {
  padding: var(--space-stack) var(--space-stack) var(--space-inline);
  font-size: var(--text-0);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-0);
  text-transform: uppercase;
  color: var(--muted-foreground);
}
.dropdown__opt {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-stack);
  width: 100%;
  padding: var(--space-stack) var(--space-inset);
  border: 0;
  border-radius: var(--radius-1);
  background: none;
  color: var(--foreground);
  font: inherit;
  font-size: var(--text-2);
  text-align: left;
  cursor: pointer;
  transition: background var(--motion-instant);
}
.dropdown__opt:hover { background: var(--surface-hover); }
.dropdown__opt svg { opacity: 0; color: var(--accent-text); flex: none; }
.dropdown__opt[aria-selected="true"] { color: var(--accent-text); font-weight: var(--weight-semibold); }
.dropdown__opt[aria-selected="true"] svg { opacity: 1; }

/* ═══════════════════════════════════════════════════════════════════════════
   FILTER-POPOVER — Suche plus Mehrfachauswahl
   Der Filter-Knopf sagt WIE VIELE, dieses Popover sagt WELCHE.
   Struktur aus intelli-os (FilterSelect).
   ═══════════════════════════════════════════════════════════════════════════ */

/* Anker für das Popover — ohne ihn hat `position: absolute` keinen Bezug. */
.filter { position: relative; display: inline-flex; }

.filter-pop {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 60;
  margin-top: var(--space-stack);
  min-width: 284px;
  background: var(--popover);
  border: 0;
  border-radius: var(--radius-2);
  box-shadow: var(--shadow-float);
  overflow: hidden;
  animation: filter-pop-in var(--motion-fast) ease;
}
@keyframes filter-pop-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}
.filter-pop__search {
  display: flex;
  align-items: center;
  gap: var(--space-stack);
  padding: var(--space-inset);
  border-bottom: 1px solid var(--border-subtle);
  color: var(--muted-foreground);
}
.filter-pop__search input {
  width: 100%;
  border: 0;
  background: none;
  color: var(--foreground);
  font: inherit;
  font-size: var(--text-2);
  outline: none;
}
.filter-pop__list { max-height: 336px; overflow-y: auto; padding: var(--space-stack); }
/* Überschrift einer Sektion in der Filterliste. Ohne sie stehen zwei Arten von
   Werten — Kunden und Personen — ununterschieden untereinander, und die Suche
   liefert Treffer, deren Herkunft man raten muss.
   Kein `position: sticky`: die Liste ist kurz, und ein klebender Kopf in einem
   Popover verdeckt bei drei sichtbaren Zeilen ein Drittel des Inhalts.
   Unten weniger Luft als oben, damit die Überschrift zu ihrer Gruppe gehört
   und nicht zwischen zwei Gruppen schwebt. */
.filter-pop__sec {
  padding: var(--space-stack) var(--space-stack) var(--space-inline);
  font-size: var(--text-0);
  line-height: var(--leading-0);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-0);
  text-transform: uppercase;
  color: var(--muted-foreground);
}
.filter-pop__row {
  display: flex;
  align-items: center;
  gap: var(--space-stack);
  width: 100%;
  padding: var(--space-stack);
  border: 0;
  border-radius: var(--radius-2);
  background: none;
  color: var(--foreground);
  font: inherit;
  font-size: var(--text-2);
  text-align: left;
  cursor: pointer;
  transition: background var(--motion-fast);
}
.filter-pop__row:hover { background: var(--surface-hover); }
/* Der Ring liegt INNEN: die Zeile sitzt in einer scrollenden Liste mit
   overflow, ein aussen liegender Umriss wird an deren Kante beschnitten. */
.filter-pop__row:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--ring);
}
/* Die Zeile trägt den Zustand selbst — sie ist ein Schalter, kein Label um
   ein verstecktes Feld. Deshalb aria-checked statt input:checked. */
.filter-pop__row .check__box svg { opacity: 0; }
.filter-pop__row[aria-checked="true"] .check__box { background: var(--accent); border-color: var(--accent); }
.filter-pop__row[aria-checked="true"] .check__box svg { opacity: 1; }
.filter-pop__count { margin-left: auto; font-family: var(--font-mono); font-size: var(--text-1); color: var(--muted-foreground); }
.filter-pop__foot {
  display: flex;
  justify-content: space-between;
  gap: var(--space-stack);
  padding: var(--space-stack) var(--space-inset);
  border-top: 1px solid var(--border-subtle);
}

/* ═══════════════════════════════════════════════════════════════════════════
   IDENTITÄT — Mandant · Konto · Avatar-Gruppe
   ═══════════════════════════════════════════════════════════════════════════ */

/* Wer bin ich und wo bin ich — gehört ans untere Ende der Sidebar, weil man
   es selten braucht, aber immer finden muss. */
.identity {
  display: flex;
  align-items: center;
  gap: var(--space-stack);
  width: 100%;
  padding: var(--space-stack);
  border: 0;
  border-radius: var(--radius-2);
  background: none;
  color: var(--foreground);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background var(--motion-fast);
}
.identity:hover { background: var(--surface-hover); }
.identity__text { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.identity__name {
  font-size: var(--text-2);
  font-weight: var(--weight-semibold);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.identity__sub { font-size: var(--text-1); color: var(--muted-foreground); }
.identity__switch { color: var(--muted-foreground); flex: none; }

/* Gestapelte Avatare: wer beteiligt ist, ohne eine Liste aufzumachen. */
.avatars { display: flex; }
.avatars__more { background: var(--surface-active); color: var(--muted-foreground); }

/* ═══════════════════════════════════════════════════════════════════════════
   MENÜ — Untermenü
   ═══════════════════════════════════════════════════════════════════════════ */

/* Hüllt Elterneintrag und Untermenü. Zwei Aufgaben, die beide nicht an den
   Eintrag können: der Eintrag ist ein <button> und darf das Untermenü nicht
   enthalten — ohne diese Hülle rechnet das absolut gesetzte Untermenü gegen
   das ganze Menü und steht immer an dessen Oberkante statt an seiner Zeile.
   Und die Hover-Zone muss Eintrag UND Untermenü umfassen, sonst schliesst sich
   das Untermenü auf dem Weg dorthin. Aus intelli-os (`.cmenu-subwrap`). */
.menu__subwrap { position: relative; }
/* Der Chevron wird über die Farbe gedämpft, nicht über die Deckkraft: Deckkraft
   senkt den Kontrast unkontrolliert mit — derselbe Mangel, den wir an ihrem
   gedämpften Kalendertag benannt haben. */
.menu__item--parent > svg:last-child { margin-left: auto; color: var(--muted-foreground); }
.menu__sub {
  position: absolute;
  left: calc(100% - var(--space-inline));
  top: calc(var(--space-stack) * -1);
  max-height: 336px;
  overflow-y: auto;
  /* Gleiche Mindestbreite wie das Elternmenü. Ein Untermenü, das schmaler ist
     als sein Elternteil, sieht abgeschnitten aus. */
  min-width: 200px;
  padding: var(--space-inline);
  background: var(--popover);
  border-radius: var(--radius-2);
  box-shadow: var(--shadow-float);
  /* Dieselbe Einblendung wie das Menü. Bei ihnen kommt sie über die zweite
     Klasse (`class="cmenu cmenu-sub"`), bei uns steht sie hier. */
  animation: menu-in var(--motion-instant) var(--ease-out);
}
.menu__check { margin-left: auto; color: var(--accent-text); opacity: 0; }
.menu__item[aria-checked="true"] .menu__check { opacity: 1; }

/* Zu- und aufgeklappt, gelesen an EINER Stelle: `aria-expanded` am
   Elterneintrag. In intelli-os lebt dieser Zustand in `useState openSub` — das
   CSS kann ihn dort nicht lesen, und deshalb entscheidet dort React, ob das
   Untermenü überhaupt im DOM steht. Bei uns entscheidet das Attribut.
   Wie überall in `behaviour/` hängt die Regel am angemeldeten Verhalten und
   nicht an `.menu__sub`: das Beispiel in `components/overlays.md` soll offen
   zu sehen bleiben. */
[data-overlay="menu"] .menu__sub { display: none; }
[data-overlay="menu"] .menu__item--parent[aria-expanded="true"] + .menu__sub { display: block; }
/* Kein Platz mehr nach rechts — dann nach links, statt aus dem Bild zu laufen.
   Gesetzt wird das Attribut von `behaviour/menu.js`, nachdem es gemessen hat;
   die Ruhelage bleibt hier, sonst stünde die Position zweimal. */
[data-overlay="menu"] .menu__sub[data-seite="links"] {
  left: auto;
  right: calc(100% - var(--space-inline));
}

/* ═══════════════════════════════════════════════════════════════════════════
   TEXTAREA, DIE MITWÄCHST
   Ein Feld, das beim Tippen wächst, statt intern zu scrollen. Der Trick ohne
   JavaScript: ein unsichtbarer Zwilling im selben Raster gibt die Höhe vor.
   ═══════════════════════════════════════════════════════════════════════════ */

.grow-textarea { display: grid; }
.grow-textarea::after {
  content: attr(data-value) " ";
  visibility: hidden;
  white-space: pre-wrap;
}
.grow-textarea > textarea {
  resize: none;
  overflow: hidden;
  min-height: var(--control-lg);
  background: var(--surface-hover);
  box-shadow: var(--inset-shadow);
  color: var(--foreground);
}
.grow-textarea > textarea,
.grow-textarea::after {
  grid-area: 1 / 1;
  padding: var(--space-inset);
  font-family: inherit;
  font-size: var(--text-2);
  line-height: var(--leading-2);
  border: 1px solid transparent;
  border-radius: var(--radius-2);
}

/* ═══════════════════════════════════════════════════════════════════════════
   SPEICHER-ANZEIGE
   Automatisches Speichern braucht eine Rückmeldung, sonst weiss niemand, ob
   es passiert ist. Leise, an derselben Stelle, ohne Toast.
   ═══════════════════════════════════════════════════════════════════════════ */

.saved {
  display: inline-flex;
  align-items: center;
  gap: var(--space-inline);
  font-size: var(--text-1);
  line-height: var(--leading-1);
  color: var(--muted-foreground);
}
/* Gerade Kantenlängen. Bei ungeraden Massen liegt der Drehpunkt auf einem
   halben Pixel und der Ring eiert sichtbar. */
.spinner {
  width: 14px;
  height: 14px;
  flex: none;
  border-radius: var(--radius-circle);
  border: 2px solid var(--border-control);
  border-top-color: var(--accent);
  animation: saved-spin .8s linear infinite;
  will-change: transform;
}
.spinner--lg { width: 20px; height: 20px; }
.saved--done { color: var(--success-foreground); }
.saved--error { color: var(--danger-foreground); }
@keyframes saved-spin { to { transform: rotate(360deg); } }

/* ═══════════════════════════════════════════════════════════════════════════
   KOMMANDOSUCHE — alles erreichbar über eine Zeile
   ═══════════════════════════════════════════════════════════════════════════ */

.cmdk {
  width: min(560px, 100%);
  background: var(--popover);
  border: 0;
  border-radius: var(--radius-3);
  box-shadow: var(--shadow-float);
  overflow: hidden;
}
.cmdk__search {
  display: flex;
  align-items: center;
  gap: var(--space-inset);
  padding: var(--space-inset) var(--space-group);
  border-bottom: 1px solid var(--border-subtle);
  color: var(--muted-foreground);
}
.cmdk__search input {
  width: 100%;
  border: 0;
  background: none;
  color: var(--foreground);
  font: inherit;
  font-size: var(--text-3);
  outline: none;
}
.cmdk__list { max-height: 320px; overflow-y: auto; padding: var(--space-inline); }
.cmdk__group {
  padding: var(--space-stack) var(--space-stack) var(--space-inline);
  font-size: var(--text-0);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-0);
  text-transform: uppercase;
  color: var(--muted-foreground);
}
.cmdk__item {
  display: flex;
  align-items: center;
  gap: var(--space-inset);
  width: 100%;
  height: var(--control-lg);
  padding: 0 var(--space-stack);
  border: 0;
  border-radius: var(--radius-1);
  background: none;
  color: var(--foreground);
  font: inherit;
  font-size: var(--text-2);
  text-align: left;
  cursor: pointer;
}
.cmdk__item:hover,
.cmdk__item[aria-selected="true"] { background: var(--surface-hover); }
.cmdk__hint { margin-left: auto; }
.kbd {
  display: inline-grid;
  place-items: center;
  min-width: 20px;
  height: 20px;
  padding: 0 6px; /* optisch: Tastenpolster, kein Abstand */
  border-radius: var(--radius-1);
  background: var(--surface-active);
  color: var(--muted-foreground);
  font-family: var(--font-mono);
  font-size: var(--text-1);
}

/* ═══════════════════════════════════════════════════════════════════════════
   ZIEHEN UND FALLENLASSEN
   Zwei Dinge müssen sichtbar sein: was am Zeiger hängt und wo es hin darf.
   Ohne beides ist Ziehen Raten.
   ═══════════════════════════════════════════════════════════════════════════ */

.drag-ghost {
  display: inline-flex;
  align-items: center;
  gap: var(--space-stack);
  padding: var(--space-stack) var(--space-inset);
  border-radius: var(--radius-2);
  background: var(--popover);
  color: var(--foreground);
  font-size: var(--text-2);
  box-shadow: var(--shadow-float);
  transform: rotate(-2deg);
}
.drag-ghost__count {
  display: inline-grid;
  place-items: center;
  min-width: 20px; height: 20px;
  border-radius: var(--radius-pill);
  background: var(--accent);
  color: var(--accent-foreground);
  font-family: var(--font-mono);
  font-size: var(--text-1);
}

.dropzone {
  display: grid;
  place-items: center;
  min-height: 72px;
  padding: var(--space-inset);
  border-radius: var(--radius-2);
  background: var(--surface-hover);
  color: var(--muted-foreground);
  font-size: var(--text-1);
  outline: 2px dashed var(--border-control);
  outline-offset: -4px;
  transition: background var(--motion-fast), color var(--motion-fast), outline-color var(--motion-fast);
}
.dropzone--over {
  background: var(--accent-surface);
  color: var(--accent-text);
  outline-color: var(--accent);
}

/* ═══════════════════════════════════════════════════════════════════════════
   NOTIZKARTE — Beitrag in einem Strom, direkt am Ort bearbeitbar

   Aus intelli-os geerntet (`.note` in notes.css), dort im Betrieb gewachsen.
   Der Unterschied zur gewöhnlichen Karte: der Text IST das Bedienelement.
   Man klickt hinein und schreibt, es gibt keinen Bearbeiten-Modus mit
   eigener Maske. Daraus folgt die ganze Anatomie.
   ═══════════════════════════════════════════════════════════════════════════ */

.note {
  position: relative;
  padding: var(--space-inset) var(--space-group);
  border: 1px solid transparent;
  border-radius: var(--radius-3);
  background: var(--surface);
  box-shadow: var(--shadow-raised);
  transition: box-shadow var(--motion-base) var(--ease-out),
              transform var(--motion-base) var(--ease-out);
}
/* Anfassbar: ein Pixel Anheben plus mehr Schatten. Mehr Weg würde bei einem
   Strom aus vielen Karten unruhig wirken. */
.note:hover { transform: translateY(-1px); box-shadow: var(--shadow-hover); }
/* In Bearbeitung: farbiger Schein statt Rahmen. Ein Rahmen würde das Papier
   um seine eigene Breite verschieben und der Text springt beim Hineinklicken. */
.note[data-editing] { transform: translateY(-1px); box-shadow: var(--shadow-edit); }
/* Im Zug: die Notiz wird durchscheinend, damit man sieht, wo sie landet.
   Aus intelli-os (.note.dragging). */
.note[data-dragging] { opacity: 0.35; }
/* Eingefärbt: der Ton kommt als --tint von aussen, damit die Karte die Farbe
   ihrer Zuordnung tragen kann (Kunde, Projekt). Nur die Kartenstufe der
   kategorialen Skala — die Fülltöne wären zu laut für eine Textfläche. */
.note--tint { background: var(--tint); }

/* Der Kopf hält rechts Platz frei für Zeit und Aktionen. Ohne diese Reserve
   läuft ein langer Kundenname unter die Uhrzeit. */
.note__head {
  display: flex; align-items: center; gap: var(--space-stack);
  min-height: var(--control-sm);
  margin-bottom: var(--space-stack);
  padding-right: var(--space-page);
}
/* Zuordnung: wohin die Notiz gehört. Akzentfarbe, weil es ein Sprungziel ist. */
.note__ctx {
  display: flex; align-items: center; gap: var(--space-inline);
  min-width: 0; overflow: hidden; white-space: nowrap;
  color: var(--accent-text);
  font-size: var(--text-1);
  line-height: var(--leading-1);
  font-weight: var(--weight-medium);
}
.note__ctx-sub { color: var(--muted-foreground); font-weight: var(--weight-regular); }

/* Zeit und Verfasser schweben oben rechts und reservieren KEINE Höhe — sonst
   springt der Text, sobald eine Karte einen Bearbeitet-Vermerk bekommt.
   Nicht anklickbar, damit sie die Aktionen darunter nicht abfangen. */
.note__meta {
  position: absolute;
  top: var(--space-inset); right: var(--space-group);
  display: flex; align-items: center; gap: var(--space-stack);
  pointer-events: none;
  color: var(--muted-foreground);
  font-size: var(--text-1);
  line-height: var(--leading-1);
  transition: opacity var(--motion-base) var(--ease-out);
}
.note__time { font-family: var(--font-mono); }
.note__edited { font-style: italic; }

/* Die Aktionen sitzen dort, wo die Zeit steht, und tauschen sie beim Hover
   aus. Zwei Dinge am selben Ort, nie gleichzeitig — das spart die Zeile.
   Über der Textebene, sonst landet der Klick im Text statt auf dem Knopf. */
.note__actions {
  position: absolute;
  top: var(--space-stack); right: var(--space-inset);
  z-index: 1;
  display: flex; gap: var(--space-inline);
  padding: var(--space-inline);
  border-radius: var(--radius-1);
  background: var(--surface);
  pointer-events: none;
}
.note[data-reveal]:hover .note__actions,
.note[data-reveal]:focus-within .note__actions { pointer-events: auto; }
.note:hover .note__meta { opacity: 0; }
/* Ohne Hover — Telefon — sind die Aktionen dauerhaft da und die Zeit weicht. */
@media (hover: none) {
  .note__actions { pointer-events: auto; }
  .note__meta { display: none; }
}

/* Gespeichert-Vermerk: erscheint kurz und geht wieder. Ein Toast wäre zu
   laut für etwas, das bei jedem Tastendruck passiert. */
.note__saved {
  display: flex; align-items: center; gap: var(--space-inline);
  color: var(--accent-text);
  font-size: var(--text-1);
  line-height: var(--leading-1);
  font-weight: var(--weight-medium);
  opacity: 0;
  transition: opacity var(--motion-base) var(--ease-out);
}
.note__saved[data-show] { opacity: 1; }

/* Der Fliesstext. Zeigt den Textcursor nur, wo man wirklich schreiben darf —
   fremde Notizen sind lesbar, nicht bearbeitbar. */
.note__body { font-size: var(--text-2); line-height: var(--leading-2); outline: none; }
.note__body[contenteditable] { cursor: text; }
.note__body p { margin: 0 0 var(--space-inline); }
.note__body p:last-child { margin-bottom: 0; }
.note__body ul, .note__body ol { margin: 0 0 var(--space-stack); padding-inline-start: var(--space-group); }
/* Checklisten stehen bündig: die Box ersetzt den Aufzählungspunkt, also fällt
   der Listeneinzug weg. Der Platz für die Box kommt aus der Zeile selbst. */
.note__body ul:has(> .check-line) { padding-inline-start: 0; }

/* ═══════════════════════════════════════════════════════════════════════════
   CHECKLISTE IM FLIESSTEXT — nicht zu verwechseln mit .check

   `.check` ist das Formularfeld mit echtem <input>. Das hier lebt IN einem
   bearbeitbaren Text, und dort geht kein echtes Eingabefeld: es bricht Caret
   und Textmarkierung. Deshalb eine absolut gesetzte Fläche, die aus dem
   Textfluss herausgenommen ist und sich nicht mitmarkieren lässt.

   Aus intelli-os, samt der Begründung im dortigen Kommentar.
   ═══════════════════════════════════════════════════════════════════════════ */

.check-line {
  list-style: none;
  position: relative;
  padding-left: var(--space-section);
  /* Eine Stufe mehr Zeile als der Fliesstext: das Kaestchen ist 17px hoch und
     sitzt auf 4px — auf einer 20px-Zeile stossen zwei Kaestchen aneinander.
     Bei ihnen traegt der Notizkoerper 1.55 (rund 22px) und schafft die Luft;
     unsere Leiter kennt zwischen 20 und 24 nichts. */
  line-height: var(--leading-3);
}
.check-line__box {
  position: absolute;
  left: var(--space-inline); top: var(--space-inline);
  width: 17px; height: 17px;
  border: 1px solid var(--border-control);
  border-radius: var(--radius-1);
  background: var(--surface);
  cursor: pointer;
  user-select: none; -webkit-user-select: none;
  display: grid; place-items: center;
  color: var(--accent-foreground);
  transition: background var(--motion-fast), border-color var(--motion-fast);
}
.check-line__box:hover { border-color: var(--accent); }
.check-line__box:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
.check-line__box svg { opacity: 0; transition: opacity var(--motion-fast); }
.check-line[data-done] .check-line__box { background: var(--accent); border-color: var(--accent); }
.check-line[data-done] .check-line__box svg { opacity: 1; }
/* Erledigt wird durchgestrichen UND leiser — Farbe allein trägt keinen
   Zustand, und ein Strich allein liest sich als Korrektur. */
.check-line[data-done] {
  color: var(--muted-foreground);
  text-decoration: line-through;
  text-decoration-color: var(--border);
}

/* ═══════════════════════════════════════════════════════════════════════════
   FORMATIERLEISTE — erscheint am Text, den man gerade bearbeitet

   Nicht die Werkzeugleiste des Screens (.toolbar), sondern die des Feldes.
   Sie fährt auf, wenn der Text den Fokus hat, und wieder zu, wenn nicht.

   WICHTIG für Touch: Ihre Knöpfe müssen `pointerdown` abfangen, sonst ist die
   Textmarkierung weg, bevor die Formatierung greift — und wo Verlassen des
   Feldes speichert, schreibt jeder Knopfdruck eine leere Fassung. Steht in
   standards/10-mobile.md als Tastatur-Falle.
   ═══════════════════════════════════════════════════════════════════════════ */

.format-bar {
  display: flex; align-items: center; gap: var(--space-inline);
  max-height: 0; overflow: hidden; opacity: 0;
  transition: max-height var(--motion-base) var(--ease-out),
              margin var(--motion-base) var(--ease-out),
              opacity var(--motion-base) var(--ease-out);
}
.format-bar[data-open] {
  max-height: var(--control-lg);
  margin-top: var(--space-inset);
  opacity: 1;
}
.format-bar button {
  display: flex; align-items: center; justify-content: center;
  min-width: var(--control-sm); height: var(--control-sm);
  padding: 0 var(--space-inline);
  border: none; background: none; cursor: pointer;
  border-radius: var(--radius-1);
  color: var(--muted-foreground);
  transition: color var(--motion-fast), background var(--motion-fast);
}
.format-bar button:hover { background: var(--surface-hover); color: var(--foreground); }
.format-bar button[aria-pressed="true"] { background: var(--surface-active); color: var(--foreground); }
/* Trenner zwischen Gruppen: Auszeichnung, Listen, Einzug. */
.format-bar__sep {
  width: 1px; height: var(--space-group);
  flex: none;
  margin: 0 var(--space-inline);
  background: var(--border-subtle);
}
/* Fertig steht rechts aussen und trägt ein Wort — es beendet das Bearbeiten,
   es formatiert nicht. */
.format-bar__done {
  margin-left: auto;
  gap: var(--space-inline);
  /* Der einzige Knopf der Leiste mit einem WORT statt einem Icon — er braucht
     die Polsterung eines Knopfes, nicht die einer Icon-Zelle. */
  padding: 0 var(--space-inset);
  color: var(--accent-text);
  font-size: var(--text-1);
  line-height: var(--leading-1);
  font-weight: var(--weight-semibold);
}
.format-bar__done:hover { background: var(--accent-surface); color: var(--accent-text); }

/* ═══════════════════════════════════════════════════════════════════════════
   REVEAL — Aktion, die erst am Element erscheint, zu dem sie gehört

   Eine Zeile, eine Karte, ein Block trägt oft eine Aktion, die dauerhaft nur
   Rauschen wäre. Drei Bedingungen, sonst ist sie für jemanden nicht da:
   Überfahren, Fokus irgendwo im Träger, und dauerhaft ohne Zeiger.

   Der Träger trägt `data-reveal`, die Aktion die Klasse. Vorher stand diese
   Mechanik dreimal einzeln im System — und in intelli-os fehlt die dritte
   Bedingung überall, weshalb dort mehrere Funktionen am Telefon nicht
   existieren.
   ═══════════════════════════════════════════════════════════════════════════ */

.reveal { opacity: 0; transition: opacity var(--motion-fast); }
[data-reveal]:hover .reveal,
[data-reveal]:focus-within .reveal,
.reveal:focus-visible { opacity: 1; }
@media (hover: none) { .reveal { opacity: 1; } }

/* ═══════════════════════════════════════════════════════════════════════════
   ZUWACHS AUS DEM BETRIEB — Zustände und Auswege, die intelli-os gelernt hat

   Kein neues Bauteil in diesem Block. Alles hier erweitert etwas Bestehendes
   um einen Zustand, den erst der Betrieb erzwungen hat: einen leeren Fall,
   einen zu langen Wert, einen Ausweg aus einer Liste ohne passenden Eintrag.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Aktion am Fuss einer Auswahlliste („Kunde anlegen"). Dass ein Eintrag fehlt,
   merkt man beim Suchen — dort muss der Ausweg stehen. Ein Knopf neben dem
   Feld wird erst gesehen, wenn die Liste schon zu ist.
   Abgesetzt durch eine Trennlinie: sie wählt nichts aus, sie führt woanders
   hin, und ohne die Linie liest man sie als weiteren Wert. */
.dropdown__action {
  display: flex; align-items: center; gap: var(--space-stack);
  width: 100%;
  margin-top: var(--space-inline);
  padding: var(--space-stack) var(--space-inset);
  border: 0;
  border-top: 1px solid var(--border-subtle);
  border-radius: 0 0 var(--radius-1) var(--radius-1);
  background: transparent;
  color: var(--accent-text);
  font: inherit;
  font-size: var(--text-2);
  font-weight: var(--weight-semibold);
  text-align: left;
  cursor: pointer;
}
.dropdown__action:hover { background: var(--accent-surface); }
.dropdown__action:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }

/* Keine Optionen, oder Suche ohne Treffer. Ein leeres Kästchen liest sich als
   kaputt — und ein Popover ist eine Datenansicht, also gelten die vier
   Zustände auch hier. */
.dropdown__empty,
.filter-pop__empty {
  padding: var(--space-inset);
  color: var(--muted-foreground);
  font-size: var(--text-2);
  line-height: var(--leading-2);
  text-align: center;
}

/* Geöffnet ist ein Zustand des FELDES, nicht nur des Popovers — sonst sieht
   man dem Auslöser nicht an, wozu die Liste darunter gehört. Gleiche Optik
   wie Fokus, weil es derselbe Sachverhalt ist: hier bin ich gerade. */
.dropdown__trigger[aria-expanded="true"] {
  border-color: var(--accent);
  box-shadow: var(--inset-shadow), 0 0 0 3px var(--accent-surface);
}
.dropdown__trigger[disabled] {
  background: var(--disabled-surface);
  color: var(--disabled-foreground);
  cursor: not-allowed;
}
.dropdown__trigger[disabled]:hover { border-color: transparent; }

/* Der Wert kürzt sich, der Chevron bleibt stehen. `min-width: 0` steht hier
   ausgeschrieben, obwohl `overflow: hidden` es einem Flex-Kind ohnehin
   verschafft — nimmt jemand später das overflow weg, fällt sonst still auch
   die Kürzung weg. Ihr `.sel-value` kommt aus demselben Grund ohne aus. */
.dropdown__value {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  text-align: left;
}
.dropdown__lead { display: inline-flex; flex: none; color: var(--muted-foreground); }
.dropdown__opt-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Ein Fuss mit nur einer Aktion. Zurücksetzen erscheint erst, wenn es etwas
   zurückzusetzen gibt, und füllt dann die Breite. */
.filter-pop__foot--single { justify-content: stretch; }
.filter-pop__foot--single .btn { width: 100%; }

/* Führendes Icon IM Feld — ein Umschlag, keine Feldvariante: Rahmen, Höhe und
   Fokus bleiben beim .input. Nicht mit .search verwechseln; die ist rund, weil
   sie sofort filtert, dieses Feld wird abgeschickt. */
.input-lead { position: relative; display: flex; align-items: center; }
.input-lead__icon,
.input-lead__unit {
  position: absolute;
  color: var(--muted-foreground);
  pointer-events: none;
}
.input-lead__icon { left: var(--space-inset); }
.input-lead__unit { right: var(--space-inset); font-size: var(--text-2); }
.input-lead .input { padding-left: var(--space-page); }
.input-lead:has(.input-lead__unit) .input { padding-right: var(--space-page); }

/* Lange Beschriftung kürzt sich, statt das Menü breitzuziehen. */
.menu__label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Ein Segment, das es gibt, aber gerade nicht geht. Weglassen wäre schlechter:
   dann rechnet niemand damit, dass die Ansicht überhaupt existiert. */
.segmented__item[disabled] { opacity: 0.45; cursor: default; }

/* ═══════════════════════════════════════════════════════════════════════════
   INLINE-ANLEGEN — Geisterzeile am Ende einer Liste
   Aus intelli-os. Nicht für Datensätze mit Pflichtfeldern: dort ist ein
   halb angelegter Satz teurer als ein Klick mehr.
   ═══════════════════════════════════════════════════════════════════════════ */

.add-inline {
  padding: var(--space-inset);
  border: 2px dashed var(--border-control);
  border-radius: var(--radius-2);
  background: transparent;
  color: var(--muted-foreground);
  font-family: inherit;
  font-size: var(--text-1);
  font-weight: var(--weight-semibold);
  text-align: center;
  cursor: pointer;
  transition: color var(--motion-fast), border-color var(--motion-fast);
}
.add-inline:hover { color: var(--accent-text); border-color: var(--accent); }
.add-inline:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }

/* Auf einer gefüllten Fläche (Board-Spalte, Swimlane-Zelle): randlos und links
   ausgerichtet. Ein gestrichelter Rahmen auf getöntem Grund ist ein Kasten im
   Kasten und erschlägt die Karten daneben. */
.add-inline--plain {
  border: 0;
  text-align: left;
  padding: var(--space-stack) var(--space-inset);
}
.add-inline--plain:hover { background: var(--surface-hover); color: var(--foreground); }

/* Aufgeklappt: dieselbe Anatomie wie die Karte, die gleich entsteht. */
.add-inline__field {
  border-radius: var(--radius-2);
  /* Keine eigene Grundfarbe: das Feld steht in der Matt-Liste oben und
     bekommt Sheen und Korn von dort — dieselbe Anatomie wie die Karte, die
     gleich daraus entsteht. */
  box-shadow: var(--shadow-raised);
}
/* Der Fokusring sitzt am Rahmen, nicht am nackten Feld — sonst hätte man einen
   Ring um einen unsichtbaren Kasten inmitten der Karte. */
.add-inline__field:focus-within {
  box-shadow: 0 0 0 2px var(--ring), var(--shadow-raised);
}
.add-inline__field input {
  width: 100%;
  padding: var(--space-inset);
  border: 0;
  background: transparent;
  outline: none;              /* Ersatz: der Ring am Rahmen darüber */
  font: inherit;
  font-size: var(--text-2);
  line-height: var(--leading-2);
  font-weight: var(--weight-semibold);
  color: var(--foreground);
}
.add-inline__field input::placeholder {
  color: var(--muted-foreground);
  font-weight: var(--weight-regular);
}

/* ═══════════════════════════════════════════════════════════════════════════
   BLOCK — Abschnitt, den der Nutzer selbst anlegt
   Für Inhalt, den der Nutzer zusammenstellt statt in feste Felder füllt.
   Nicht für einen Datensatz mit bekannten Feldern.
   ═══════════════════════════════════════════════════════════════════════════ */

.block { position: relative; padding: var(--space-inline) 0; }

/* Wohin der Block fällt. Ohne diese Linie zieht man ins Blaue: der Block
   verschwindet unter dem Zeiger und taucht irgendwo wieder auf. Gleiche
   Anatomie wie am Karten-Slot, nur enger — Bloecke stehen dichter. */
.block[data-insert]::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 2px;              /* Strichstärke, kein Abstand — deshalb px. */
  border-radius: var(--radius-pill);   /* 1px auf 2px sind runde Enden */
  background: var(--accent);
}
.block[data-insert="before"]::after { top: -1px; }    /* optisch: auf der Fuge */
.block[data-insert="after"]::after  { bottom: -1px; } /* optisch: auf der Fuge */

.block__head {
  display: flex;
  align-items: center;
  margin: var(--space-inline) 0;
  gap: var(--space-stack);
  min-height: var(--control-sm);
}

/* Der Griff steht LINKS AUSSERHALB. Der negative Aussenabstand zieht ihn genau
   um seine eigene Breite hinaus — damit bleibt der Titel bündig mit dem Text
   darunter, statt um die Griffbreite eingerückt zu sein. */
.block__grip {
  flex: none;
  display: grid;
  place-items: center;
  width: var(--control-sm);
  height: var(--control-sm);
  margin-left: calc(var(--control-sm) * -1);
  color: var(--muted-foreground);
  opacity: 0;
  cursor: grab;
  transition: opacity var(--motion-fast) ease;
}
/* focus-within, nicht nur hover: sonst ist der Griff mit der Tastatur unsichtbar
   und damit nicht vorhanden. */
.block:hover .block__grip { opacity: 0.7; }
.block:focus-within .block__grip,
.block__grip:hover { opacity: 1; }
/* Ohne Zeiger gibt es kein Überfahren — dort stehen die Griffe dauerhaft. */
@media (hover: none) { .block__grip { opacity: 1; } }

/* Titel und Text: randlose Bearbeitungsflächen. Der negative Aussenabstand
   gleicht das eigene Innenpolster aus, damit der Text im Ruhezustand bündig
   mit dem Rest steht und beim Überfahren nicht springt. */
.block__title,
.block__body {
  margin: 0 calc(var(--space-stack) * -1);
  padding: var(--space-inline) var(--space-stack);
  cursor: text;
  outline: none;                       /* Ersatz: der Ring bei :focus unten */
  transition: background var(--motion-fast) ease;
}
.block__title:hover,
.block__body:hover { background: var(--surface-hover); }
.block__title:focus,
.block__body:focus {
  background: var(--surface-hover);
  box-shadow: 0 0 0 2px var(--ring);
}

.block__title {
  min-width: var(--control-lg);        /* leerer Titel bleibt anklickbar */
  max-width: 100%;
  border-radius: var(--radius-1);
  font-size: var(--text-1);
  line-height: var(--leading-1);
  font-weight: var(--weight-semibold);
  color: var(--foreground);
}

.block__body {
  border-radius: var(--radius-2);
  font-size: var(--text-2);
  line-height: var(--leading-2);
  /* Drei Zeilen Grundhöhe: ein leerer Textblock soll wie ein Absatz aussehen
     und nicht wie ein Schlitz. Layout-Mass, deshalb px. */
  min-height: 61px;
}

/* Der Platzhalter ist ein Pseudo-Element, kein Textknoten — sonst stünde er
   nach dem ersten Tastendruck mitten im Inhalt. */
.block__title:empty::before,
.block__body:empty::before {
  content: attr(data-placeholder);
  color: var(--muted-foreground);
  /* Leiser als der Text, den er vertritt — sonst liest sich der leere Block
     wie ein gefüllter. Aus intelli-os. */
  opacity: 0.7;
}

/* Listen im Textkörper: eigener Einzug, damit die Punkte nicht am Rand kleben. */
.block__body ul,
.block__body ol {
  margin: var(--space-inline) 0;
  padding-left: var(--space-section);
}

/* Verschachtelte Checklisten bündig: der Einzug pro Ebene kommt schon aus dem
   Platz für die Box im übergeordneten Punkt. Ein eigenes Listen-Padding
   addierte sich dazu und jede Ebene rückte doppelt ein. */
.block__body li ul:has(> li.check-line) { padding-left: 0; }

.block__count {
  flex: none;
  padding: 0 var(--space-stack);
  border-radius: var(--radius-pill);
  background: var(--surface-hover);
  color: var(--muted-foreground);
  font-family: var(--font-mono);       /* sonst wackelt es von 9 auf 10 */
  font-size: var(--text-1);
  line-height: var(--leading-1);
}

/* Löschen sitzt rechts aussen und erscheint mit dem Block. */
.block__remove { flex: none; margin-left: auto; }


/* ═══════════════════════════════════════════════════════════════════════════
   MODAL, GETEILT — Hauptspalte und Seitenschiene
   Variante des Modals fuer ein Detail, das nebenher eine zweite Spur hat
   (Notizen, Verlauf). Grundplatte kommt von .modal.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Variante unseres Modals: zwei Spalten, im Bereich statt im Fenster.
   Die Grundplatte (Fläche, Schatten, Radius) kommt von .modal. */
.modal--split {
  /* Im Bereich, nicht im Fenster: der Dialog sitzt in der Overlay-Ebene seines
     Bereichs und ist über dessen Mitte zentriert, nicht über der Fenstermitte.
     Mit `fixed` stünde er neben einem schmalen Bereich falsch. */
  position: absolute;
  display: flex;
  flex-direction: column;
  width: min(924px, calc(100% - var(--space-section)));
  height: min(861px, calc(100% - var(--space-section)));
  /* Grund als background-COLOR, nicht als Kurzform: die Kurzform würde die
     Textur-Lagen aus der Matt-Liste oben zurücksetzen. */
  background-color: var(--surface);
  /* Zwei Schatten: der weite trägt die Höhe, der enge setzt die Kante ab.
     Allein wirkt der weite wie ein Nebel ohne Objekt. */
  box-shadow: var(--shadow-float), var(--shadow-raised);
  animation: modal-split-in var(--motion-base) ease;
}
@keyframes modal-split-in {
  from { opacity: 0; transform: translate(-50%, calc(-50% + 10px)); }
  to   { opacity: 1; transform: translate(-50%, -50%); }
}

.modal__crumb {
  flex: 1;
  min-width: 0;
  font-size: var(--text-1);
  line-height: var(--leading-1);
  font-weight: var(--weight-medium);
  color: var(--muted-foreground);
  /* Einzeilig gekürzt: die Pfadzeile darf den Kopf nie zweizeilig machen,
     sonst springt der Dialog beim Wechsel auf ein Element mit langem Pfad. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.modal__cols {
  display: flex;
  flex: 1;
  /* Ohne min-height:0 wächst ein Flex-Kind über seinen Container hinaus und
     die inneren overflow-Bereiche scrollen nie. Der häufigste Fehler in
     dieser Bauart. */
  min-height: 0;
}

.modal__main {
  flex: 1;
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
  padding: 0 var(--space-section) var(--space-section);
}

/* Eigene Scroll-Fläche. Die Trennlinie ist die eine Stelle, an der hier ein
   Rand steht: er trennt zwei Bereiche, er hebt nichts an. */
.modal__side {
  flex: none;
  width: 315px;
  display: flex;
  flex-direction: column;
  min-height: 0;
  border-left: 1px solid var(--border-subtle);
  padding: var(--space-inline) var(--space-group) var(--space-group);
  /* Die Schiene beginnt eine Stufe tiefer als die Hauptspalte — sonst steht ihre
     Überschrift auf gleicher Höhe wie der Titel und konkurriert mit ihm. */
  margin-top: var(--space-stack);
}
/* Kopf und Fuss der Schiene stehen fest, nur die Mitte scrollt — die
   Anlegezeile soll nicht weglaufen. */
.modal__side > * { flex: none; }
.modal__side-scroll { flex: 1; min-height: 0; overflow-y: auto; }

/* ── Rechte-Matrix ── (Ernte rechte) ────────────────────────────────────── */

/* ── Kategoriale Tonbindung ────────────────────────────────────────────
   Ein Modul ist eine Kategorie, kein Status. Die Komponente kennt keinen
   Farbnamen — sie liest --tone-*. Zyklisch vergeben wie mc0–mc3 bei
   intelli-os: gleiche Modul-Reihenfolge, gleicher Ton, über alle Screens. */
[data-tone="a"] { --tone-text: var(--cat-teal-brand-text); --tone-fill: var(--cat-teal-brand-fill); }
[data-tone="b"] { --tone-text: var(--cat-amber-text);      --tone-fill: var(--cat-amber-fill); }
[data-tone="c"] { --tone-text: var(--cat-navy-text);       --tone-fill: var(--cat-navy-fill); }
[data-tone="d"] { --tone-text: var(--cat-teal-text);       --tone-fill: var(--cat-teal-fill); }
/* Die Stufenleiter selbst ist kein Modul und bekommt einen festen Ton.
   Ihr Kommentar hält den Grund fest, den wir sonst neu lernen müssten:
   „fixe Palette wie mc0 — var(--primary) wäre im Dark Mode das helle
   Salbei". Ein Token, das den Modus wechselt, taugt nicht als Füllung,
   die über beide Modi dieselbe Sache bedeuten soll. */
[data-tone="stufe"] { --tone-text: var(--cat-teal-brand-text); --tone-fill: var(--cat-teal-brand-fill); }
/* „Weicht von jeder Vorlage ab" — kein Modul, sondern die Aussage, dass hier
   keine Vorlage greift und jemand von Hand zugeschnitten hat. Sie braucht
   einen eigenen Ton, weil sie sonst wie ein Profilname aussieht.

   Bei ihnen ist das metallisches Gold (Verlauf plus zwei rgba-Innenschatten),
   dasselbe Rezept trägt ihr Rollen-Abzeichen in Smaragd. Wir übernehmen die
   BEDEUTUNG, nicht das Metall: ein Verlauf mit Lichtkante und Fase ist eine
   zweite Materialsprache neben dem Matt-Papier (DECISIONS.md), und die
   rgba-Innenschatten wirken je nach Untergrund verschieden stark — derselbe
   Grund, aus dem unsere Ränder solide Stufen sind. Gelb ist die kategoriale
   Entsprechung des Goldtons, gelöst gegen seine Fläche. NICHT Bernstein: das
   trägt schon Modul b, und zwei Abzeichen nebeneinander in derselben Farbe
   behaupten dieselbe Zuordnung.

   Die Rolle bekommt KEINEN eigenen Ton: sie trägt `data-tone="stufe"` und
   damit denselben Ton wie der Stufenbalken darunter, den sie beschriftet.
   Bei ihnen ist auch das schon so (.mc-role = feste Palette wie mc0), nur das
   Abzeichen daneben tanzt aus der Reihe. */
[data-tone="individuell"] { --tone-text: var(--cat-yellow-text); --tone-fill: var(--cat-yellow-fill); }

/* ── Messgerät: Stufe als gefüllte Segmente ────────────────────────────
   Lesen 1 · Schreiben 2 · Verwalten 3. Dieselbe Form wie der Slider im
   Pass, nur klein und nicht bedienbar. */
.meter {
  display: inline-flex;
  gap: var(--space-inline);
  /* optisch: 14px — Zellenmarke, kein Ziel. Unter --control-sm, weil das
     Ziel die ganze Zeile ist und nicht die Marke. */
  --meter-square: 14px;
}
.meter__seg {
  width: var(--meter-square);
  height: var(--meter-square);
  border-radius: var(--radius-1);
  background: var(--surface-active);
}
/* erreichte Stufen gemischt, damit sie solide bleiben — keine Opazität */
.meter__seg[data-fill="reached"] {
  background: color-mix(in srgb, var(--tone-text) 55%, var(--surface-active));
}
.meter__seg[data-fill="top"] { background: var(--tone-text); }
/* Abweichung von der Standardstufe der Person. Farbe trägt das NICHT —
   die Zelle hat zusätzlich Text im .sr-only, siehe Markup. */
.meter[data-override="true"] .meter__seg[data-fill] {
  outline: 2px solid var(--tone-text);
  outline-offset: -2px;
}

/* ── Die Matrix ────────────────────────────────────────────────────────
   Erbt .table. Nur was eine Matrix zusätzlich braucht, steht hier. */
.rights-matrix {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-2);
  line-height: var(--leading-2);
}
.rights-matrix th {
  font-size: var(--text-0);
  line-height: var(--leading-0);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-0);
  text-transform: uppercase;
  color: var(--muted-foreground);
  text-align: left;
  padding: var(--space-stack) var(--space-inset);
  border-bottom: 1px solid var(--border);
}
.rights-matrix td {
  padding: var(--space-stack) var(--space-inset);
  border-bottom: 1px solid var(--border-subtle);
  vertical-align: middle;
}
.rights-matrix tbody tr:last-child td { border-bottom: none; }

/* Der Identitätsblock bleibt stehen, wenn die Modulspalten wegscrollen.
   Ohne das weiss man ab Spalte vier nicht mehr, wessen Zeile man liest. */
.rights-matrix__who {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--surface);
  min-width: 220px;
}
.rights-matrix thead th { position: sticky; top: 0; background: var(--surface); z-index: 2; }
/* Senkrechte Grenze zwischen „wer" und „was" — ein Rand bedeutet Grenze,
   und das ist hier eine. */
.rights-matrix__who { border-right: 1px solid var(--border); }

/* Modulspalten schmal und zentriert: das Messgerät ist eine Marke, kein Text */
.rights-matrix__cell { text-align: center; width: 1%; white-space: nowrap; }

/* Zeile als Ziel. Der Knopf steckt in der ersten Zelle und trägt den
   Zustand — die Zeile selbst ist kein Control und bekommt auch keins. */
.rights-matrix__pick {
  display: flex;
  align-items: center;
  gap: var(--space-inset);
  width: 100%;
  min-height: var(--control-lg);
  padding: 0;
  background: none;
  border: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.rights-matrix__pick:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }

.rights-matrix tbody tr:hover td { background: var(--surface-hover); }
.rights-matrix tbody tr:hover .rights-matrix__who { background: var(--surface-hover); }
/* Kreuzung: die Spalte, über der die Maus steht, wird mitgehoben. Ohne
   das trifft man in einer breiten Matrix regelmässig die falsche Spalte.
   Das Attribut setzt JS auf die Tabelle, siehe Verhalten. */
.rights-matrix[data-col-hover] td[data-col]:not([data-col=""]) { transition: background var(--motion-fast); }

.rights-matrix tr[aria-selected="true"] td,
.rights-matrix tr[aria-selected="true"]:hover td { background: var(--surface-active); }
.rights-matrix tr[aria-selected="true"] .rights-matrix__who { background: var(--surface-active); }


/* ── Stufen-Slider ─────────────────────────────────────────────────────
   Radiogruppe in Slider-Gestalt. Höhe: ihre 34px sind unter zoom 1.05
   effektiv 35.7 — nächster Wert auf unserer Leiter ist --control-md. */
.level {
  position: relative;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: var(--space-inline);
  height: var(--control-md);
  padding: 0;
  border-radius: var(--radius-2);
  background: transparent;
  user-select: none;
}
/* Die Füllung ist EIN Balken, der bis zur gewählten Stufe wächst — nicht eine
   Einfärbung je erreichter Stufe. Der Unterschied ist die Aussage: eine
   wachsende Länge sagt „bis hierhin", einzeln gefärbte Kästchen sagen nur
   „diese und diese". Beim Umstellen bewegt sich die Kante, statt dass Flächen
   an- und ausgehen.

   Der Aufrufer sagt, WELCHE Stufe gilt — nicht, wie breit der Balken ist:
   --level-count = Anzahl Stopps, --level = Index der gewählten (ab 0),
   --level-base = erster füllbarer Stopp (1, wenn „kein Zugriff" links steht).
   Das sind die Zahlen, die die Fachlogik ohnehin hat; eine Prozentzahl wäre
   eine Darstellungsgrösse, die der Aufrufer bei jedem neuen Stopp neu
   ausrechnen müsste. Ein falscher Index fällt auf, eine falsche Prozentzahl
   nicht. Die Stufenzahl steht in der Variablen, nicht im CSS — dieses Bauteil
   trägt auch fünf oder sieben Stopps.

   Die Breite ist (Index − Anfang + 1) Stopps weit. Ist keine Stufe gesetzt
   oder steht die Wahl auf „kein Zugriff", wird das null und der Balken
   verschwindet von selbst — dafür braucht es keine Sonderregel, und ein
   unbekannter Wert zeigt lieber nichts als etwas Falsches.

   Warum (100% + Lücke) / Anzahl: bei n Spalten mit Lücke g ist eine Spalte
   plus ihre Lücke genau (Breite + g) / n. Damit endet die Füllung auf der
   Spaltenkante statt daneben. intelli-os zieht stattdessen pauschal eine
   halbe Lücke ab, weil dort React rechnet und die Lücke nicht kennt.

   Aus intelli-os .wm-cover, OHNE dessen Metall: Verlauf, Lichtkante und
   Fase sind dort eine zweite Materialsprache neben dem Matt-Papier. Die
   Aussage trägt die Länge, nicht die Politur. */
.level__fill {
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(var(--level-base, 0) * (100% + var(--space-inline)) / var(--level-count, 1));
  width: max(0px, (var(--level, -1) - var(--level-base, 0) + 1)
                  * (100% + var(--space-inline)) / var(--level-count, 1)
                  - var(--space-inline));
  border-radius: var(--radius-2);
  background: var(--tone-text);
  /* Die Kante wandert, sie blinkt nicht — deshalb die lange Dauer. */
  transition: left var(--motion-slow) var(--ease-out),
              width var(--motion-slow) var(--ease-out);
  pointer-events: none;
}

.level__stop {
  position: relative;
  z-index: 1;
  border: 0;
  border-radius: var(--radius-2);
  background: transparent;
  color: var(--muted-foreground);
  font: inherit;
  font-size: var(--text-0);
  font-weight: var(--weight-semibold);
  /* Eine Stufenbeschriftung bricht nie um: umgebrochen wächst der Stopp in die
     Höhe, und der Balken hat plötzlich zwei Zeilen. Lieber ein kürzeres Wort
     wählen — der Balken sagt die Ordnung, nicht der Satz. */
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--motion-base) var(--ease-out);
}
/* Alles unter der Füllung schweigt — die überdeckten Stufen UND die gewählte.
   Sonst liest sich der Balken als Reihe beschrifteter Knöpfe statt als eine
   Skala, und genau das war der Mangel unserer ersten Fassung. Die Füllung
   macht die Aussage schon; ihr Ende IST die gewählte Stufe. Als Wort steht
   der Wert in der Zeile darüber — die ist deshalb Pflicht, nicht Zierrat.
   `transparent` statt Ausblenden: der Text bleibt für die Sprachausgabe da,
   nur das Auge sieht ihn nicht mehr. Wo der Fokus steht, zeigt der Fokusring.
   Aus intelli-os .wm-stop.cov und .wm-stop.in. */
.level__stop[data-fill="covered"],
.level__stop[aria-checked="true"] { color: transparent; }
/* „kein Zugriff" ist keine Stufe: erhabene neutrale Fläche, nie Tonfüllung.
   Es liegt nie unter der Füllung und schweigt deshalb auch gewählt nicht —
   die Abwesenheit eines Zugangs hat keine Länge, die für sie sprechen könnte. */
.level__stop[data-off] { background: transparent; }
.level__stop[data-off][aria-checked="true"] {
  background: var(--surface);
  color: var(--foreground);
  box-shadow: var(--shadow-raised);
}
.level__stop:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
.level__stop[disabled] { cursor: not-allowed; }
.level__stop[aria-disabled="true"] {
  cursor: default;
  background: var(--disabled-surface);
  color: var(--disabled-foreground);
}

/* ── Sektionskopf mit Wert rechts ──────────────────────────────────────
   Versalien tragen die Hierarchie, nicht die Grösse. Ihre 9.5px liegen
   unter unserer kleinsten Stufe — siehe Konflikte. */
.section-label {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-stack);
  margin: var(--space-group) 0 var(--space-stack);
  font-size: var(--text-0);
  line-height: var(--leading-0);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-0);
  text-transform: uppercase;
  color: var(--muted-foreground);
}
.section-label__value { color: var(--accent-text); }

/* ── Beschriftungszeile über einem Bedienelement ───────────────────────
   Marke links, aktueller Wert rechts, darunter das Element. Sieht dem
   Sektionskopf ähnlich und ist etwas anderes: der Sektionskopf gliedert eine
   Seite und steht in Versalien, diese Zeile gehört zu GENAU EINEM Element und
   nennt dessen Wert. Wer beide zusammenlegt, schreibt Modulnamen in Versalien
   und verliert die Stufe darüber.

   Der Wert gehört hierher und nicht ins Element: ein Stufenbalken zeigt eine
   Länge, kein Wort — ohne diese Zeile müsste man die Skala ablesen.
   Aus intelli-os .wm-modlab. */
.control-label {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-stack);
  margin-bottom: var(--space-stack);
  font-size: var(--text-1);
  line-height: var(--leading-1);
}
/* Der Wert trägt die Versalien-Typografie, nicht die Zeile: die Marke links
   ist ein Name (gemischt gesetzt), der Wert rechts ist eine Zustandsangabe. */
.control-label__value {
  flex: none;
  font-size: var(--text-0);
  line-height: var(--leading-0);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-0);
  text-transform: uppercase;
  color: var(--accent-text);
}


/* ── Sonderrecht mit Befristung ── (Ernte rechte) ────────────────────────────────────── */

/* ── Sonderrecht ───────────────────────────────────────────────────────
   Zeile: Schlüssel links (Mono, technisch), Steuerung rechts. */
.grant-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-inset);
  padding: var(--space-inline) 0;
}
.grant-row__key {
  font-family: var(--font-mono);
  font-size: var(--text-1);
  line-height: var(--leading-1);
  color: var(--foreground);
  white-space: nowrap;
}
.grant-row__ctl { display: inline-flex; align-items: center; gap: var(--space-stack); }

/* Der Schalter selbst. Ihre 34px sind effektiv 35.7 → --control-md. */
.grant {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-inline);
  height: var(--control-md);
  min-width: 113px;               /* ihre 108 unter zoom 1.05 */
  padding: 0 var(--space-inset);
  border: 0;
  border-radius: var(--radius-2);
  background: var(--surface-hover);
  color: var(--muted-foreground);
  font: inherit;
  font-size: var(--text-1);
  font-weight: var(--weight-semibold);
  cursor: pointer;
  transition: background var(--motion-base) var(--ease-out),
              color var(--motion-base) var(--ease-out);
}
.grant:hover { background: var(--surface-active); color: var(--foreground); }
.grant:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
.grant[disabled] { cursor: not-allowed; }

/* Vergeben wächst von links ein, statt umzuschalten: das Wachsen sagt „wird
   erteilt", ein Umschalten sagt nur „ist anders". Aus intelli-os .wm-keyfill,
   ohne dessen Metall-Lagen. Die Fläche liegt VOR dem Grund, damit die
   Zustands-Töne unten weiter greifen; die Beschriftung liegt darüber. */
.grant::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--grant-fill, var(--accent-surface));
  opacity: 0;
  transform: scaleX(0.6);
  transform-origin: left center;
  transition: opacity var(--motion-base) var(--ease-out),
              transform var(--motion-slow) var(--ease-out);
}
.grant[aria-checked="true"]::before { opacity: 1; transform: scaleX(1); }
.grant__label { position: relative; z-index: 1; }

/* vergeben, unbefristet */
.grant[aria-checked="true"] {
  color: var(--accent-text-strong);
  font-weight: var(--weight-semibold);
}
/* vergeben, befristet — läuft noch, also Amber. Das Datum steht im Chip. */
.grant[aria-checked="true"][data-until] {
  --grant-fill: var(--warning-surface);
  color: var(--warning-foreground);
}
/* abgelaufen: erledigt und nicht mehr wirksam → neutral, aber sichtbar */
.grant[data-state="expired"] {
  background: var(--neutral-surface);
  color: var(--neutral-badge-foreground);
}
/* über die Stufe geerbt: kein Besitz, also keine Füllung und nicht schaltbar */
.grant[data-source="stufe"] {
  background: transparent;
  color: var(--muted-foreground);
  cursor: default;
}
/* Das Recht WIRKT, also steht `aria-checked="true"` zu Recht — und genau das
   hat die Füllung angeschaltet und den geerbten Chip wie einen vergebenen
   aussehen lassen. Der Unterschied ist der Punkt der ganzen Darstellung
   (access-control.md: die Vergabe soll eine sichtbare Einzelentscheidung sein,
   keine Nebenwirkung der Rolle). Die Ansage bleibt, die Fläche geht weg. */
.grant[data-source="stufe"]::before { opacity: 0; }
.grant[aria-disabled="true"] { cursor: default; }
.grant .icon { width: 14px; height: 14px; flex: none; }  /* optisch: Icon zu --text-1 */


/* ── Leerzustand als Übersicht ── (Ernte rechte) ────────────────────────────────────── */

.pass--empty {
  display: grid;
  gap: var(--space-stack);
  text-align: center;
  padding: var(--space-7) var(--space-inset);
  color: var(--muted-foreground);
  font-size: var(--text-2);
  line-height: var(--leading-2);
}
.pass--empty__title {
  font-size: var(--text-3);
  line-height: var(--leading-3);
  font-weight: var(--weight-semibold);
}
.pass--empty__hint {
  margin-top: var(--space-group);
  font-size: var(--text-1);
  line-height: var(--leading-1);
  color: var(--muted-foreground);
}


/* ── Farbwahl ── (Ernte rechte) ────────────────────────────────────── */

/* ── Tupfer-Radiogruppe ────────────────────────────────────────────────
   Eine von wenigen Farben wählen. NICHT der Statuspunkt (.dot): der zeigt
   an, dieser hier wird bedient — gleiche Form, andere Gattung.

   Die Farbe ist hier der Inhalt, also gibt es keine Beschriftung, die man
   lesen könnte. Deshalb ist die Trefferfläche der ganze Knopf und nicht der
   sichtbare Tupfer: ihre 18px liegen unter der WCAG-2.2-Untergrenze von 24px,
   und ein Ziel, das man nur sieht und nicht trifft, ist keins.
   Aus intelli-os .wm-colors / .wm-dot. */
.swatches { display: inline-flex; gap: var(--space-inline); align-items: center; }

.swatch {
  width: var(--control-sm);
  height: var(--control-sm);
  flex: none;
  padding: 0;
  border: 0;
  border-radius: var(--radius-circle);
  background: var(--tone-fill, var(--surface-active));
  /* currentColor ist der Ring unten — er muss der Fläche gehören, nicht dem
     Text daneben. */
  color: var(--tone-text, var(--muted-foreground));
  cursor: pointer;
  transition: box-shadow var(--motion-fast) var(--ease-out);
}
/* Doppelter Ring: erst eine Lücke in der Flächenfarbe, dann der Ring im Ton
   des Tupfers. Ein einfacher Ring direkt am Rand liest sich als Rahmen und
   verschwindet über der eigenen Farbe — die Lücke macht ihn zum Ring.
   Flächenfarbe, weil die Gruppe im Panel steht; gleiche Machart wie der Ring
   um überlappende Avatare. */
.swatch[aria-checked="true"] {
  box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px currentColor;
}
.swatch:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }


/* ── Bearbeitbarer Titel ── (Ernte rechte) ────────────────────────────────────── */

/* Der Name IST die Überschrift und wird an Ort und Stelle geändert — kein
   Dialog, kein Stift-Knopf. Nur sinnvoll, wo der Name das einzige freie Feld
   des Objekts ist; alles Weitere gehört in ein Formular.

   Ohne Ruhe-Rahmen, sonst steht in jedem Panelkopf ein Eingabefeld. Dass es
   eins ist, zeigt sich beim Überfahren — und beim Fokus liegt ein Ring an,
   den ihre Fassung (`outline: none` ohne Ersatz) nicht hat: dort ist mit der
   Tastatur nicht zu sehen, wo man steht.
   Aus intelli-os .wm-pname. */
.edit-title {
  flex: 1;
  min-width: 0;
  /* Der negative Aussenabstand gleicht das eigene Innenpolster aus: im
     Ruhezustand fluchtet der Titel mit dem Text darunter und springt beim
     Überfahren nicht. Gleiche Machart wie .block__title. */
  margin: 0 calc(var(--space-stack) * -1);
  padding: var(--space-inline) var(--space-stack);
  border: 0;
  border-radius: var(--radius-1);
  background: transparent;
  outline: none;                       /* Ersatz: der Ring bei :focus unten */
  font: inherit;
  font-size: var(--text-2);
  line-height: var(--leading-2);
  font-weight: var(--weight-semibold);
  color: var(--foreground);
  cursor: text;
  transition: background var(--motion-fast) var(--ease-out);
}
.edit-title:hover { background: var(--surface-hover); }
.edit-title:focus {
  background: var(--surface-hover);
  box-shadow: 0 0 0 2px var(--ring);
}


/* ── Inline-Anhang ── (Ernte anhaenge) ────────────────────────────────────── */

/* ═══════════════════════════════════════════════════════════════════════════
   INLINE-ANHANG — eine Datei mitten im Fliesstext

   Gespeichert wird NICHT dieses Markup, sondern ein Textmarker `[[file:<id>]]`
   (optional `|w=320`). Die Vorschau lebt nur im DOM, contenteditable="false",
   und wird vor dem Speichern zurück zum Marker serialisiert. Reiner Text
   überlebt jeden Sanitizer und jeden URL-Wechsel — ein gespeichertes <img>
   nicht. Aus intelli-os, samt Verfahren (app/notefiles.ts).
   ═══════════════════════════════════════════════════════════════════════════ */

.attach {
  display: block;
  margin: var(--space-stack) 0;
}

/* Bild: feste Vorschaugrösse, damit ein Text mit mehreren Bildern eine ruhige
   Spalte bleibt. Ohne Deckel bestimmt die Kamera das Layout. */
.attach--image {
  --attach-preview-w: 252px;
  --attach-preview-h: 273px;
  display: inline-block;
  position: relative;
  max-width: var(--attach-preview-w);
  line-height: 0;   /* optisch: sonst sitzt unter dem Bild eine Zeilenlücke */
}
.attach--image img {
  display: block;
  max-width: 100%;
  max-height: var(--attach-preview-h);
  border-radius: var(--radius-2);
  box-shadow: var(--shadow-raised);
  cursor: zoom-in;
}
/* Vom Nutzer gezogene Breite: die Breite steht am Container (data-width, in
   den Marker serialisiert), die Höhe folgt dem Seitenverhältnis. Der Deckel
   fällt weg — wer selbst zieht, hat entschieden. */
.attach--image[data-width] { max-width: 100%; }
.attach--image[data-width] img { width: 100%; max-height: none; }

/* Der Caret nimmt die Zeilenhöhe an. Neben einem 260px hohen Bild ist er ein
   260px hoher blinkender Strich. Nur in der Bildzeile ausblenden — überall
   sonst braucht man ihn. */
[contenteditable="true"] :is(div, p):has(> .attach--image) {
  caret-color: transparent;
}

/* Werkzeug erscheint am AUSGEWÄHLTEN Bild, nicht an jedem. In intelli-os hing
   der Griff zuerst dauerhaft an jedem Bild — bei fünf Bildern fünfmal Werkzeug
   und keinmal Inhalt (Commit ff54090). */
.attach__resize,
.attach__full { display: none; }

.attach--image[aria-selected="true"] {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
  border-radius: var(--radius-3);
}
/* Ziehgriff rechts. Trefffläche --control-sm breit (WCAG 2.2), der sichtbare
   Balken darin ist schmal — sonst wirkt der Griff wie ein zweiter Rand. */
.attach--image[aria-selected="true"] .attach__resize {
  display: block;
  position: absolute;
  inset-block: 0;
  right: calc(-1 * var(--space-inset));
  width: var(--control-sm);
  cursor: ew-resize;
}
.attach--image[aria-selected="true"] .attach__resize::after {
  content: "";
  position: absolute;
  top: 50%;
  right: var(--space-stack);
  transform: translateY(-50%);
  width: 6px;                        /* optisch: schmaler Balken, kein Rand */
  height: var(--space-page);
  border-radius: var(--radius-1);
  background: var(--accent);
  box-shadow: 0 0 0 2px var(--background);  /* hebt ihn von der Bildkante ab */
}
.attach__resize:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }

.attach--image[aria-selected="true"] .attach__full {
  display: grid;
  place-items: center;
  position: absolute;
  top: var(--space-inline);
  right: var(--space-inline);
  width: var(--control-sm);
  height: var(--control-sm);
  padding: 0;
  border: none;
  border-radius: var(--radius-1);
  background: var(--surface);
  color: var(--foreground);
  box-shadow: var(--shadow-raised);
  cursor: pointer;
}

/* Dokument: kein Bild, also ein Chip in Zeilenhöhe. Er trägt den Dateinamen —
   „Anhang" wäre keine Information. */
.attach--doc {
  display: inline-flex;
  align-items: center;
  gap: var(--space-inline);
  max-width: 100%;
  height: var(--control-lg);
  padding: 0 var(--space-inset);
  border-radius: var(--radius-2);
  background: var(--surface);
  color: var(--foreground);
  font-size: var(--text-2);
  line-height: var(--leading-2);
  font-weight: var(--weight-medium);
  cursor: pointer;
}
.attach--doc:hover { background: var(--surface-hover); }
.attach__name {
  min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}


/* ── Datei-Betrachter ── (Ernte anhaenge) ────────────────────────────────────── */

/* ═══════════════════════════════════════════════════════════════════════════
   DATEI-BETRACHTER — ein Anhang wird IN der Anwendung angesehen

   Die Haltung ist Teil des Bauteils, nicht des Screens: kein Download, kein
   neuer Tab, beim PDF keine native Werkzeugleiste. Fremdes HTML läuft nur in
   einem Sandbox-iframe ohne allow-same-origin, aus einer Blob-URL. Begründung
   in components/overlays.md und in standards/security.md.

   MUSS per Portal ans Ende von <body>: position:fixed verliert das
   Ansichtsfenster als Bezug, sobald ein Vorfahre transform/filter trägt — in
   intelli-os landete der Betrachter dadurch oben links.

   Das Blatt (.file-viewer__frame) gehört in die Matt-Papier-Liste oben.
   ═══════════════════════════════════════════════════════════════════════════ */

.file-viewer {
  position: fixed;
  inset: 0;
  z-index: 100;                    /* Modal-Sprosse. Toast bleibt darüber. */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-section);
  background: var(--overlay-scrim);
}

/* Bild: direkt auf dem Scrim, ohne Blatt darunter. Ein Rahmen um ein Foto ist
   ein zweiter Rahmen — das Bild bringt seinen eigenen mit. */
.file-viewer > img {
  max-width: 100%;
  max-height: 100%;
  border-radius: var(--radius-2);
  box-shadow: var(--shadow-float);
  cursor: zoom-out;
}

/* Dokument: eigenes Blatt, weil PDF und Text eine Fläche brauchen, auf der sie
   stehen. Höhe gedeckelt, damit auf einem grossen Schirm kein Plakat entsteht. */
.file-viewer__frame {
  display: flex;
  flex-direction: column;
  width: min(1000px, 96vw);
  height: min(88vh, 900px);
  overflow: hidden;
  border-radius: var(--radius-3);
  background: var(--surface);
  box-shadow: var(--shadow-float);
}
.file-viewer__frame iframe {
  flex: 1;
  width: 100%;
  border: 0;
  background: var(--surface);
}

/* Schliessen liegt am Rand des Ansichtsfensters, nicht am Blatt: beim Bild gibt
   es kein Blatt, an dem der Knopf hängen könnte. */
.file-viewer__close {
  position: fixed;
  top: calc(var(--space-inset) + env(safe-area-inset-top));
  right: var(--space-inset);
  z-index: 1;
}


/* ── Anhangsleiste ── (Ernte anhaenge) ────────────────────────────────────── */

/* ═══════════════════════════════════════════════════════════════════════════
   ANHANGSLEISTE — Dateien an einem Datensatz, und der Puffer vor dem Senden

   Beides ist dieselbe Leiste: Bilder als Miniatur, alles andere als Chip mit
   Namen. Der Unterschied ist nur, ob die Datei schon hochgeladen ist.
   ═══════════════════════════════════════════════════════════════════════════ */

.attach-tray {
  display: flex;
  flex-wrap: wrap;
  margin-top: var(--space-inset);
  align-items: center;
  gap: var(--space-stack);
}

.attach-tray__thumb {
  position: relative;
  flex: none;
  height: var(--space-7);          /* eine Zeile Bildhöhe, 48 */
  padding: 0;
  overflow: hidden;
  border: none;
  border-radius: var(--radius-2);
  background: var(--surface);
  cursor: zoom-in;
}
.attach-tray__thumb img {
  display: block;
  height: 100%;
  width: auto;
  max-width: 170px;                /* breite Panoramen kappen, nicht dehnen */
  object-fit: cover;
}

/* Läuft ein Upload, steht der Platz schon da. Sonst springt die Leiste in dem
   Moment auf, in dem die Datei ankommt. Aus intelli-os .askel. */
.attach-tray__skeleton {
  display: block;
  flex: none;
  width: var(--space-7);
  height: var(--space-7);
  border-radius: var(--radius-2);
}

.attach-tray__chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--space-stack);
  max-width: 242px;
  height: var(--control-md);
  padding: 0 var(--space-inset);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-2);
  background: var(--surface-hover);
  color: var(--foreground);
  font: inherit;
  font-size: var(--text-2);
  font-weight: var(--weight-medium);
  cursor: pointer;
  transition: background var(--motion-fast);
}
.attach-tray__chip:hover { background: var(--border); }
.attach-tray__chip .icon { color: var(--muted-foreground); }

/* Entfernen sitzt IM klickbaren Element — deshalb span mit role="button".
   Ein <button> im <button> ist ungültig und der äussere Klick feuert mit. */
.attach-tray__remove {
  position: absolute;
  top: var(--space-inline);
  right: var(--space-inline);
  display: grid;
  place-items: center;
  width: var(--control-sm);
  height: var(--control-sm);
  border-radius: var(--radius-circle);
  background: var(--surface);
  color: var(--muted-foreground);
  box-shadow: var(--shadow-raised);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--motion-fast);
}
.attach-tray__chip .attach-tray__remove { top: 50%; transform: translateY(-50%); }
.attach-tray__thumb:hover .attach-tray__remove,
.attach-tray__chip:hover .attach-tray__remove,
.attach-tray__remove:focus-visible { opacity: 1; }
/* Ohne Hover ist eine Aktion, die nur bei Hover erscheint, nicht vorhanden. */
@media (hover: none) {
  .attach-tray__remove { opacity: 1; }
}


/* ── Composer ── (Ernte anhaenge) ────────────────────────────────────── */

/* ═══════════════════════════════════════════════════════════════════════════
   COMPOSER — die andockende Eingabe am Fuss eines Stroms

   Wächst beim Fokus, ohne JS-Zustand: :focus-within bleibt wahr, wenn der
   Fokus auf einen Knopf INNERHALB des Composers wandert. Die Blur-Akrobatik
   aus intelli-os (setTimeout 120) entfällt fürs Aussehen — fürs SPEICHERN
   bleibt sie nötig, siehe standards/10-mobile.md.

   Werkzeugleiste = .format-bar, nur an anderer Andockstelle. Keine zweite.
   .composer gehört in die Matt-Papier-Liste oben.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Andockzone: hält den Composer unten fest und blendet den Strom darunter aus.
   Eine harte Kante würde behaupten, der Text sei dort zu Ende. */
.composer-dock {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  z-index: 5;   /* über dem Strom (1), unter der Dropdown-Sprosse (10) */
  padding: var(--space-page) var(--space-section) var(--space-section);
  background: linear-gradient(to top,
              var(--background) 0%,
              var(--background) calc(100% - var(--space-page)),
              transparent 100%);
  pointer-events: none;   /* der Strom bleibt durch den Fade hindurch bedienbar */
}
.composer-dock > * { pointer-events: auto; }

.composer {
  max-width: 714px;       /* Lesebreite des Stroms — der Composer ist so breit wie das, was er erzeugt */
  margin: 0 auto;
  display: flex;
  align-items: flex-end;
  gap: var(--space-inset);
  padding: var(--space-stack) var(--space-stack) var(--space-stack) var(--space-group);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-3);
  /* Opaker Grund unter den transluzenten Lagen der Matt-Liste, sonst scheint
     der Strom durch den Composer hindurch. Aus intelli-os (`.composer.mat`). */
  background-color: var(--surface);
  transition: box-shadow var(--motion-base) var(--ease-out),
              border-color var(--motion-base) var(--ease-out);
}
/* Angefasst: hebt sich vom Strom ab, ohne die Farbe zu wechseln. */
.composer:focus-within {
  border-color: var(--border-control);
  box-shadow: var(--shadow-float);
}

/* Ein Hauch Luft über und unter dem Text: die Schrift sitzt sonst auf der
   Oberkante des Feldes und die erste Zeile klebt am Rand. */
.composer__body { flex: 1; min-width: 0; padding: var(--space-inline) 0; }

/* Drei Zeilen in Ruhe, fünf im Fokus, dann intern scrollen. Als Zeilen
   gerechnet, damit die Absicht ablesbar bleibt. */
.composer__input {
  min-height: calc(3 * var(--leading-3));
  max-height: calc(5 * var(--leading-3));
  overflow-y: auto;
  outline: none;
  font-size: var(--text-2);
  line-height: var(--leading-3);
  transition: min-height var(--motion-base) var(--ease-out);
}
.composer:focus-within .composer__input { min-height: calc(5 * var(--leading-3)); }
/* Der Platzhalter nennt das ZIEL, nicht die Tätigkeit: „Notiz in Kneuss
   Güggeli…" statt „Schreiben…". Er ersetzt kein Label — das steht als
   aria-label am Feld. */
.composer__input:empty::before {
  content: attr(data-placeholder);
  color: var(--muted-foreground);
}

/* Werkzeugleiste: unsere .format-bar, hier an :focus-within statt [data-open]. */
.composer:focus-within .format-bar {
  max-height: var(--control-lg);
  margin-top: var(--space-stack);
  opacity: 1;
}

/* Senden steht auf der Grundlinie, nicht mittig — bei fünf Zeilen Text sonst
   irgendwo in der Mitte des Blocks. */
.composer__send {
  align-self: flex-end;
  opacity: .45;
  transition: opacity var(--motion-fast) var(--ease-out);
}
/* Voll deckend, sobald es etwas zu senden gibt oder der Composer angefasst ist.
   `[data-has-content]` ersetzt ihr `.hascontent` — derselbe Zustand, als
   Attribut statt als Klasse. Es sitzt am COMPOSER, nicht am Knopf: gefüllt ist
   das Eingabefeld, und der Knopf kann davon nichts wissen. Aus intelli-os
   (`.composer.hascontent .sendbtn`). */
.composer:focus-within .composer__send,
.composer[data-has-content] .composer__send { opacity: 1; }


/* ── Auswahlleiste ── (Ernte anhaenge) ────────────────────────────────────── */

/* ═══════════════════════════════════════════════════════════════════════════
   AUSWAHLLEISTE — schwebt über dem markierten Text

   Popover-Sprosse (10), nicht Modal: sie sperrt nichts und fängt keinen Fokus.
   intelli-os stand auf 60 — erfunden.

   Sie darf die Markierung nicht kosten: pointerdown abfangen und den Standard
   verhindern, sonst ist markiert nichts mehr, wenn der Klick ankommt.
   ═══════════════════════════════════════════════════════════════════════════ */

.selection-bar {
  position: absolute;
  z-index: 10;
  display: flex;
  align-items: center;
  gap: var(--space-inline);
  padding: var(--space-inline) var(--space-stack);
  border-radius: var(--radius-pill);
  background: var(--inverse-surface);
  box-shadow: var(--inverse-shadow);
  opacity: 0;
  pointer-events: none;
  transform: translateY(var(--space-inline));   /* optisch: kommt von unten hoch */
  transition: opacity var(--motion-fast) var(--ease-out),
              transform var(--motion-fast) var(--ease-out);
}
.selection-bar[data-visible] {
  opacity: 1;
  pointer-events: auto;
  transform: none;
}
.selection-bar button {
  display: grid;
  place-items: center;
  width: var(--control-sm);
  height: var(--control-sm);
  border: none;
  background: none;
  border-radius: var(--radius-pill);
  color: var(--inverse-muted-foreground);
  cursor: pointer;
  transition: color var(--motion-fast) var(--ease-out), background var(--motion-fast) var(--ease-out);
}
.selection-bar button:hover { background: var(--inverse-surface-hover); color: var(--inverse-foreground); }
.selection-bar button[aria-pressed="true"] { background: var(--inverse-surface-hover); color: var(--inverse-foreground); }


/* ── Vorschlagszeile ── (Ernte anhaenge) ────────────────────────────────────── */

/* ═══════════════════════════════════════════════════════════════════════════
   VORSCHLAGSZEILE — was das System vorschlägt, bevor jemand zustimmt

   Der gestrichelte Rand ist die ganze Aussage: noch nicht wahr. Ein
   durchgezogener Chip behauptet, es sei schon zugeordnet — dann klickt
   niemand mehr auf Annehmen. Einzige gestrichelte Kante im System.
   ═══════════════════════════════════════════════════════════════════════════ */

.suggest-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-stack);
  margin-top: var(--space-inset);
}
.chip--suggest {
  padding: 0 var(--space-inset);
  border: 1px dashed var(--accent-border-strong);
  background: var(--accent-surface);
  color: var(--accent-text);
  font-weight: var(--weight-medium);
}


/* ── Beleg ── (Ernte business) ────────────────────────────────────── */

/* ═══ BELEG — das druckechte Blatt ═════════════════════════════════════════ */

.doc {
  box-sizing: border-box;
  width: var(--paper-width);
  max-width: 100%;
  min-height: var(--paper-height);
  margin: 0 auto;
  padding: var(--paper-margin-y) var(--paper-margin-x);
  background: var(--paper);
  color: var(--paper-ink);
  font-family: var(--font-sans);
  font-size: var(--paper-text-body);
  line-height: var(--paper-leading);
  box-shadow: var(--shadow-float);
  /* Der Beleg steht ausserhalb des Oberflächen-Massstabs: eine App darf zoomen,
     ein Blatt nicht — sonst passt es im Druck nicht mehr auf A4. Die App setzt
     --ui-zoom, hier wird der Kehrwert genommen; der Rückfallwert steht IM var(),
     nicht als eigene Deklaration — die würde den geerbten Wert verdecken und den
     Rückdreh still abschalten. */
  zoom: calc(1 / var(--ui-zoom, 1));
}

/* Fensterkuvert: der Kopf rutscht nach unten, damit die Adresse ins Sichtfenster fällt. */
.doc--window { padding-top: var(--paper-margin-window); }
.doc--window .doc__head { margin-bottom: var(--paper-margin-window); }

.doc__head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--space-section);
  margin-bottom: 12mm;
}
.doc__sender {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--space-stack);
  text-align: right;
  white-space: nowrap;
}
.doc__sender b { display: block; }

/* Die Marke als Block, nicht als Bilddatei: druckt auch ohne „Hintergrundgrafiken". */
.doc__logo {
  display: grid;
  place-items: center;
  width: 14mm;
  height: 14mm;
  border-radius: var(--radius-1);
  background: var(--paper-line-strong);
  color: var(--paper);
  font-size: var(--paper-text-lead);
  font-weight: var(--weight-semibold);
}

.doc__title  { font-size: var(--paper-text-lead); font-weight: var(--weight-semibold); margin-bottom: var(--space-stack); }
.doc__subject {
  font-size: var(--paper-text-lead);
  font-weight: var(--weight-semibold);
  padding-bottom: var(--space-stack);
  border-bottom: 1.5px solid var(--paper-line-strong);
}

/* Meta: zwei Spalten, Label links leise, Wert rechts. */
.doc__meta {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-inline) var(--space-page);
  padding: var(--space-stack) 0 var(--space-inset);
  margin-bottom: var(--space-group);
  border-bottom: 1.5px solid var(--paper-line-strong);
}
.doc__meta-col { display: flex; flex-direction: column; gap: var(--space-inline); }
.doc__meta-row { display: flex; justify-content: space-between; gap: var(--space-group); }
.doc__meta-row > span:first-child { color: var(--paper-muted); }

.doc__intro { margin: 0 0 var(--space-group); }

/* Positionen: getrennt durch Linien, nie durch Flächen — eine graue Zeile ist
   auf Papier Tonerverbrauch und im Schwarzweissdruck matschig. */
.doc__table { width: 100%; border-collapse: collapse; font-size: var(--paper-text-table); }
.doc__table th {
  text-align: left;
  font-weight: var(--weight-semibold);
  padding: 0 var(--space-inline) var(--space-stack);
  border-bottom: 1.5px solid var(--paper-line-strong);
}
.doc__table td {
  padding: var(--space-inset) var(--space-inline);
  vertical-align: top;
  border-bottom: 1px solid var(--paper-line);
}
.doc__table .num { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.doc__pos-title { font-weight: var(--weight-semibold); }
.doc__pos-list { margin: var(--space-inline) 0 0; padding-left: var(--space-group); }

/* Summen rechtsbündig als eigener Block: die Spaltenbreiten der Positions-
   tabelle passen nicht zu drei Zeilen mit zwei Werten. */
.doc__totals { width: 68mm; margin: 0 0 0 auto; }
.doc__total-row {
  display: flex;
  justify-content: space-between;
  padding: var(--space-stack) var(--space-inline);
  border-bottom: 1px solid var(--paper-line);
  font-variant-numeric: tabular-nums;
}
.doc__total-row:first-child { font-weight: var(--weight-semibold); }
.doc__total-row--gross { font-weight: var(--weight-semibold); border-bottom: 1.5px solid var(--paper-line-strong); }

.doc__terms { margin-top: var(--space-page); }
.doc__terms h3 {
  margin: 0 0 var(--space-stack);
  padding-bottom: var(--space-stack);
  border-bottom: 1.5px solid var(--paper-line-strong);
  font-size: var(--paper-text-lead);
  font-weight: var(--weight-semibold);
}
.doc__foot {
  margin-top: var(--space-page);
  padding-top: var(--space-inset);
  border-top: 1px solid var(--paper-line);
  font-size: var(--paper-text-foot);
  color: var(--paper-muted);
  text-align: center;
  line-height: 1.7;
}
.doc__foot b { color: var(--paper-ink); }

/* ── Inline-Bearbeiten ────────────────────────────────────────────────────
   Das Feld sitzt an der Stelle des Textes. Der Hover-Hinweis wird mit
   box-shadow gemalt, NICHT mit border: ein Rand verschiebt das Wort um seine
   eigene Breite und die Zeile springt genau beim Hineinklicken. */
.doc[data-editable] .doc__edit {
  cursor: text;
  border-radius: var(--radius-1);
  transition: background var(--motion-fast), box-shadow var(--motion-fast);
}
.doc[data-editable] .doc__edit:hover,
.doc[data-editable] .doc__edit:focus-visible {
  background: var(--paper-edit);
  box-shadow: 0 0 0 3px var(--paper-edit);
}
.doc__field {
  box-sizing: border-box;
  width: 100%;
  padding: 0 var(--space-inline);
  border: 1px solid var(--paper-edit-ring);
  border-radius: var(--radius-1);
  background: var(--paper);
  color: var(--paper-ink);
  font: inherit;
  outline: none;
}
.doc__field--num { width: 24mm; text-align: right; font-variant-numeric: tabular-nums; }
.doc__field[type="number"] { appearance: textfield; }
.doc__field::-webkit-inner-spin-button,
.doc__field::-webkit-outer-spin-button { appearance: none; margin: 0; }
.doc__placeholder { color: var(--paper-muted); font-style: italic; }

/* ── Rahmen: der Beleg in der App ─────────────────────────────────────────
   Das Blatt ist breiter als jedes Panel. Statt es zu beschneiden, wird es
   massstäblich verkleinert — aus der verfügbaren Breite gerechnet, nicht mit
   einer geratenen Zahl. 794 = 210mm in CSS-Pixeln bei 96dpi. */
.doc-view {
  --doc-view-width: 460px;
  padding: var(--space-group);
  background: var(--surface-subtle);
  overflow: auto;
}
.doc-view .doc {
  zoom: calc(var(--doc-view-width) / 794);
  margin: 0 auto;
}

/* Werkzeugleiste über dem Blatt: klebt oben, damit sie beim Scrollen des
   Blattes erreichbar bleibt. Nicht im Druck. */
.doc-view__bar {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  justify-content: flex-end;
  gap: var(--space-stack);
  margin-bottom: var(--space-inset);
}

/* ── Druck ────────────────────────────────────────────────────────────────
   Nur das Blatt. `visibility` statt `display`, weil display:none die Vorfahren
   des Belegs mit entfernt und er dann nichts mehr hat, worin er sitzt. */
@media print {
  @page { size: A4; margin: 0; }
  body * { visibility: hidden !important; }
  .doc, .doc * { visibility: visible !important; }
  .doc-view__bar { display: none !important; }
  .doc-view { padding: 0 !important; overflow: visible !important; }
  .doc-view .doc, .doc {
    position: absolute;
    left: 0;
    top: 0;
    zoom: 1;                       /* Vorschau-Massstab weg */
    width: var(--paper-width);
    max-width: none !important;    /* nicht auf die Druckfläche klemmen → kein Squish */
    margin: 0 !important;
    box-shadow: none !important;
    background: var(--paper) !important;  /* kein Theme-Grund im Druck */
    print-color-adjust: exact;     /* Logo-Fläche druckt auch ohne „Hintergrundgrafiken" */
    -webkit-print-color-adjust: exact;
  }
  /* Eine Position wird nie über zwei Seiten gerissen, Summen und Bedingungen
     bleiben zusammen, der Tabellenkopf wiederholt sich. */
  .doc__table tr, .doc__totals, .doc__terms { break-inside: avoid; }
  .doc__table thead { display: table-header-group; }
}


/* ── Tabelle — drei Deltas ── (Ernte business) ────────────────────────────────────── */

/* Die Trennlinie gehört zur Ruhelage. In der hervorgehobenen Zeile zerschneidet
   sie die Fläche — deshalb verschwindet sie dort, statt mitgefärbt zu werden. */
.table tbody tr:hover td,
.table tbody tr[aria-selected="true"] td { border-bottom-color: transparent; }

/* Anklickbare Zeile: der Fokusring liegt INNEN, sonst schneidet ihn die
   überlaufende Tabellenfläche ab. box-shadow ist hier keine Option — unter
   border-collapse malt der Browser ihn auf <tr> nicht. */
.table--selectable tbody tr:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: -2px;
}

/* Kein Wert ist kein Wert. Gedämpft, damit die Spalte nur die echten Zahlen zeigt. */
.table td[data-empty] { color: var(--muted-foreground); }

/* Logo statt Initialen. Nicht rund und nicht beschnitten — ein Firmenlogo mit
   abgeschnittenen Rändern ist keine Marke mehr. */
.avatar--logo { border-radius: var(--radius-1); background: var(--surface); }
.avatar--logo img { width: 100%; height: 100%; object-fit: contain; }

/* Mehrere <tbody> gruppieren zusammengehörende Zeilen. Getrennt wird nur
   zwischen den Gruppen — innerhalb hängen die Zeilen aneinander. */
.table--grouped tbody td { border-bottom: none; }
.table--grouped tbody + tbody > tr:first-child > td { border-top: 1px solid var(--border-subtle); }
.table--grouped tr[data-sub] > td { padding-top: 0; color: var(--muted-foreground); }
.table--grouped tr[data-sub] > td:first-child { padding-left: var(--space-page); }


/* ── Abschnittsmarke ── (Ernte business) ────────────────────────────────────── */

/* Abschnittsmarke: die leise Überschrift IM Inhalt. Nicht zu verwechseln mit
   .panel__title — das ist der Kopf einer Fläche und trägt eine Leiste. */
.caption {
  display: flex;
  align-items: center;
  gap: var(--space-stack);
  padding: var(--space-inline) 0 var(--space-stack);
  font-size: var(--text-0);
  line-height: var(--leading-0);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-0);
  text-transform: uppercase;
  color: var(--muted-foreground);
}
/* Der Zähler trägt den Akzent — er ist der Grund, warum man auf die Marke schaut. */
.caption__count { color: var(--accent-text); }
/* Die Summe rechts: gedämpft und in Mono, damit sie über mehrere Abschnitte
   hinweg untereinander steht und vergleichbar bleibt. */
.caption__sum { margin-left: auto; font-family: var(--font-mono); }

/* Als Deckel einer Fläche: eigene Polsterung, leicht abgesetzter Grund. */
.caption--head {
  padding: var(--space-inset) var(--space-group);
  background: var(--surface-hover);
}


/* ── Kennwert-Zeile ── (Ernte business) ────────────────────────────────────── */

/* Kennwert-Liste: Bezeichnung links, Wert rechts in Mono. */
.kv { display: flex; flex-direction: column; }
.kv__row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;   /* nicht center: die zwei Schriftgrade sitzen sonst versetzt */
  gap: var(--space-inset);
  padding: var(--space-inset) var(--space-group);
  border-top: 1px solid var(--border-subtle);
}
/* Linie oben statt unten: die Liste endet linienfrei, ohne :last-child-Ausnahme. */
.kv__row:first-child { border-top: none; }
.kv__k { color: var(--muted-foreground); }
.kv__v {
  font-family: var(--font-mono);
  font-size: var(--text-2);
  line-height: var(--leading-2);
  text-align: right;
  white-space: nowrap;
}
/* Ein Wert bekommt Farbe nur, wenn er etwas bedeutet — nie zur Auflockerung. */
.kv__v--warning { color: var(--warning-foreground); }
.kv__v--danger  { color: var(--danger-foreground); }


/* ── Stat — Hinweiszeile, Töne, Hero, Band ── (Ernte business) ────────────────────────────────────── */

/* Die Definition unter der Zahl: was heisst „Umsatz" hier genau? Ohne diese
   Zeile ist die Kachel eine Behauptung. */
.stat__hint {
  font-size: var(--text-1);
  line-height: var(--leading-1);
  color: var(--muted-foreground);
}

/* Töne. Nur wo der Wert selbst eine Bewertung trägt. */
.stat--accent  .stat__value { color: var(--accent-text); }
.stat--warning .stat__value { color: var(--warning-foreground); }
.stat--muted   .stat__value { color: var(--muted-foreground); }

/* Hero: die Leitzahl einer Fläche, rechts daneben ihr Anteil am Ganzen.
   Der Anteil ist eine getönte Fläche und kein Badge — er ist ein Wert,
   kein Zustand. */
.stat--hero {
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-inset);
  background: none;
  padding: var(--space-group);
}
.stat--hero .stat__value {
  font-size: var(--text-6);
  line-height: var(--leading-6);
  letter-spacing: var(--tracking-6);
}
.stat__share {
  flex: none;
  padding: var(--space-inline) var(--space-inset);
  border-radius: var(--radius-2);
  background: var(--accent-surface);
  color: var(--accent-text);
  font-family: var(--font-mono);
  font-size: var(--text-3);
  line-height: var(--leading-3);
  font-weight: var(--weight-semibold);
}

/* Band: die Kacheln füllen die Breite und brechen von selbst um. */
.stat-band {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--space-inset);
}


/* ── Balkenliste ── (Ernte business) ────────────────────────────────────── */

/* Balkenliste: benannte Grössen im Vergleich. Kein Diagramm — eine Liste mit
   je einem Balken. Der Balken IST .progress, kein Nachbau. */
.barlist { display: flex; flex-direction: column; gap: var(--space-stack); }

.barlist__row {
  display: flex;
  align-items: center;
  gap: var(--space-inset);
  font-size: var(--text-1);
  line-height: var(--leading-1);
}
/* Feste Breiten links und rechts: sonst tanzt die Balkenkolonne mit der Länge
   der Namen. In ch, damit sie mit dem Schriftgrad mitwachsen. */
.barlist__label {
  flex: none;
  width: 16ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: var(--weight-medium);
}
/* Zweiter Wert ohne dritte Spalte: er hängt unter der Bezeichnung. */
.barlist__meta {
  display: block;
  font-weight: var(--weight-regular);
  color: var(--muted-foreground);
}
.barlist__value {
  flex: none;
  min-width: 12ch;
  text-align: right;
  font-family: var(--font-mono);
  color: var(--muted-foreground);
  white-space: nowrap;
}
.barlist .progress { flex: 1; }

/* Summe: abgesetzt, in Mono, im Akzent — die Frage, die man beim Lesen
   einer Rangliste immer stellt. */
.barlist__total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-top: var(--space-inline);
  padding-top: var(--space-inset);
  border-top: 1px solid var(--border-subtle);
  font-size: var(--text-2);
  line-height: var(--leading-2);
  font-weight: var(--weight-medium);
}
.barlist__total-value {
  font-family: var(--font-mono);
  color: var(--accent-text);
  font-weight: var(--weight-semibold);
}

/* ── Rang-Variante: wo stehe ich? ────────────────────────────────────────
   Alle Zeilen gedämpft, EINE hervorgehoben. Die anderen sind Massstab,
   nicht Inhalt — deshalb tragen sie einen neutralen Balken. */
.barlist--rank .barlist__label { color: var(--muted-foreground); font-weight: var(--weight-regular); }
.barlist--rank .progress__bar  { background: var(--border); }

.barlist--rank [aria-current="true"] .barlist__label {
  color: var(--foreground);
  font-weight: var(--weight-medium);
}
.barlist--rank [aria-current="true"] .barlist__value { color: var(--foreground); }
.barlist--rank [aria-current="true"] .progress__bar  { background: var(--accent); }


/* ── Zeilenkarte ── (Ernte business) ────────────────────────────────────── */

/* Zeilenkarte: anklickbare Liste mit zweizeiliger Identität und Zahlen rechts.
   Zwischen Tabelle (Spalten, die man vergleicht) und Kachel (kompakt, quadratisch). */
.rowlist { display: flex; flex-direction: column; gap: var(--space-stack); }

.rowcard {
  display: flex;
  align-items: center;
  gap: var(--space-group);
  width: 100%;
  padding: var(--space-inset) var(--space-group);
  border: none;
  border-radius: var(--radius-2);
  background: var(--surface);
  color: var(--foreground);
  box-shadow: var(--shadow-raised);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background var(--motion-fast);
}
.rowcard:hover { background: var(--surface-hover); }

/* Auswahl: Ring nach INNEN, damit die Karte nicht wächst und die Nachbarn
   nicht springen. */
.rowcard[aria-selected="true"] {
  background: var(--surface-active);
  box-shadow: inset 0 0 0 2px var(--accent-border), var(--shadow-raised);
}

.rowcard__id {
  display: flex;
  flex-direction: column;
  gap: var(--space-inline);
  flex: 1;
  min-width: 0;
}
.rowcard__title {
  font-weight: var(--weight-medium);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rowcard__sub {
  font-size: var(--text-1);
  line-height: var(--leading-1);
  color: var(--muted-foreground);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rowcard__date {
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--text-1);
  line-height: var(--leading-1);
  color: var(--muted-foreground);
  white-space: nowrap;
}
.rowcard__amount {
  flex: none;
  min-width: 14ch;
  text-align: right;
  font-family: var(--font-mono);
  font-weight: var(--weight-medium);
  white-space: nowrap;
}
/* Zustandsmarke mit fester Mindestbreite: sonst rutscht der Titel je nach
   Wortlänge hin und her und die Liste wirkt zerfranst. */
.rowcard .badge { flex: none; min-width: 11ch; justify-content: center; }

/* Ruhige Variante: nachschlagen statt bearbeiten. Flach, ohne Schatten,
   knapper — hier arbeitet niemand mehr. */
.rowcard--quiet {
  gap: var(--space-inset);
  padding: var(--space-inset);
  background: var(--surface);
  box-shadow: none;
}
.rowcard--quiet:hover { background: var(--surface-hover); box-shadow: none; }
/* Der Kunde steht rechts und leise: im Archiv sucht man den Vorgang, nicht den
   Kunden — sein Name ist Bestätigung, nicht Einstieg. */
.rowcard__meta { margin-left: auto; flex: none; color: var(--muted-foreground); }


/* ── Anteil ── (Ernte business) ────────────────────────────────────── */

/* Anteil: ein Teil von einem Ganzen, mit BEIDEN Zahlen. Der Balken ordnet ein,
   die Zahlen sagen, wieviel noch fehlt — das kann ein Balken nicht. */
.ratio { display: flex; align-items: center; gap: var(--space-inset); }
.ratio .progress { flex: 1; height: 4px; }

.ratio__figure {
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--text-1);
  line-height: var(--leading-1);
  font-weight: var(--weight-medium);
  white-space: nowrap;
}
/* Das Ganze steht gedämpft hinter dem Teil: es ist Bezug, nicht Ergebnis. */
.ratio__of { color: var(--muted-foreground); font-weight: var(--weight-regular); }

/* Fertig ist ein anderer Zustand als unterwegs — und trägt neben der Farbe
   immer noch das Wort daneben. */
.ratio[data-done] .progress__bar { background: var(--success-solid); }

/* Grosse Variante: der Betrag über dem Balken, Balken über die volle Breite.
   Für den Kopf eines Detailbereichs. */
.ratio--lg { flex-direction: column; align-items: stretch; gap: var(--space-stack); }
.ratio--lg .progress { height: 6px; }
.ratio--lg .ratio__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-stack);
}
.ratio--lg .ratio__figure {
  font-size: var(--text-4);
  line-height: var(--leading-4);
  letter-spacing: var(--tracking-4);
}
.ratio__note {
  font-size: var(--text-1);
  line-height: var(--leading-1);
  color: var(--muted-foreground);
}


/* ── Entscheidungspaar ── (Ernte business) ────────────────────────────────────── */

/* Entscheidungspaar: zwei Antworten am Fuss einer Karte, die selbst schon
   anklickbar ist. Deshalb beide leise — zwei gefüllte Knöpfe pro Karte
   ergäben in einer Liste eine Wand aus Farbe. */
.decide { display: inline-flex; gap: var(--space-inline); flex: none; margin-left: auto; }

.decide__btn {
  height: var(--control-sm);
  padding: var(--space-inline) var(--space-stack);
  border: none;
  border-radius: var(--radius-1);
  background: var(--surface-hover);
  color: var(--muted-foreground);
  font: inherit;
  font-size: var(--text-1);
  font-weight: var(--weight-semibold);
  cursor: pointer;
  transition: background var(--motion-fast), color var(--motion-fast);
}
/* Die zustimmende Antwort trägt die Akzentschrift — sie ist die Aktion mit
   Folgen. Gefüllt wird sie trotzdem nicht. */
.decide__btn--yes { color: var(--accent-text); }
.decide__btn--yes:hover { background: var(--accent-surface); color: var(--accent-text-strong); }

/* Ablehnen ist im Ruhezustand neutral und wird erst beim Überfahren rot: ein
   roter Block würde den Blick auf die Aktion ziehen, die man am seltensten will. */
.decide__btn--no:hover { background: var(--danger-surface); color: var(--danger-foreground); }


/* ── Zeitraum-Schieber ── (Ernte business) ────────────────────────────────────── */

/* Zeitraum-Schieber: zwei Griffe über EINER Achse. Zwei echte <input>, damit
   Pfeiltasten, Pos1/Ende und ARIA ohne Nachbau funktionieren. */
.range2 { position: relative; width: 200px; height: var(--control-lg); flex: none; }

.range2__track,
.range2__fill { position: absolute; top: 50%; height: 3px; border-radius: var(--radius-pill); }
.range2__track { left: 0; right: 0; background: var(--surface-active); }
.range2__fill  { background: var(--accent); }   /* die gewählte Spanne */

/* Das Input selbst fängt keine Klicks — sonst liegt das obere über dem
   unteren und dessen Griff ist nicht erreichbar. Nur der Griff nimmt Zeiger an. */
.range2 input[type="range"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  background: none;
  appearance: none;
  pointer-events: none;
}
.range2 input[type="range"]::-webkit-slider-thumb {
  appearance: none;
  pointer-events: auto;
  width: 14px;
  height: 14px;
  border-radius: var(--radius-pill);
  background: var(--accent);
  /* Absatz in der Grundfläche: ohne ihn verschmelzen die zwei Griffe,
     sobald sie nebeneinander stehen. */
  border: 2px solid var(--surface);
  cursor: pointer;
}
.range2 input[type="range"]::-moz-range-thumb {
  pointer-events: auto;
  width: 14px;
  height: 14px;
  border-radius: var(--radius-pill);
  background: var(--accent);
  border: 2px solid var(--surface);
  cursor: pointer;
}
.range2 input[type="range"]:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 2px var(--ring); }
.range2 input[type="range"]:focus-visible::-moz-range-thumb     { box-shadow: 0 0 0 2px var(--ring); }

.range2__label {
  font-family: var(--font-mono);
  font-size: var(--text-1);
  line-height: var(--leading-1);
  color: var(--muted-foreground);
  white-space: nowrap;
}


/* ── Nachträge: Klassen, die im Markup standen und im CSS fehlten ────────── */

/* Speichert gerade — die Ruhefarbe reicht, der Spinner trägt die Aussage.
   Kein Akzent: der Vorgang läuft noch, es ist kein Ergebnis. */
.saved--busy { color: var(--muted-foreground); }

/* Grosser Avatar, für Leerzustände und Kopfbereiche. Gegenstück zu --sm. */
.avatar--lg {
  width: var(--control-lg); height: var(--control-lg);
  font-size: var(--text-2);
}

/* Behälter einer Blockfolge. Nur der Abstand — die Blöcke bringen alles
   Weitere mit, und ein Rahmen hier machte aus dem Dokument ein Formular. */
.blocks { display: flex; flex-direction: column; gap: var(--space-inline); }

/* Notiz als Auszug in einer Seitenschiene: gekürzt auf vier Zeilen und ohne
   das Anheben beim Überfahren. Eine Vorschau ist zum Lesen da, nicht zum
   Anfassen — wer sie öffnen will, klickt sie an. */
.note--preview {
  cursor: pointer;
  padding: var(--space-stack) var(--space-inset);
}
.note--preview:hover { transform: none; box-shadow: var(--shadow-raised); }
.note--preview .note__body {
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ── Nav-Gruppe zum Aufklappen ── (Ernte intelli-os `.psec`) ─────────────── */

/* Die Gruppenüberschrift IST der Schalter — kein Pfeil-Knopf daneben. Bei
   zwanzig Gruppen wäre ein eigenes Ziel je Gruppe zwanzigmal dasselbe kleine
   Ziel; die Überschrift ist ohnehin da und breit genug.

   Sie sieht aus wie `.nav-label` und ist es auch: dieselben Versalien, dieselbe
   gedämpfte Farbe. Nur dass man sie drücken kann. */
.nav-group__head {
  display: flex; align-items: center; gap: var(--space-inline);
  width: 100%;
  padding: var(--space-stack) var(--space-stack) var(--space-inline);
  border: 0; background: none; cursor: pointer;
  color: var(--muted-foreground);
  font: inherit;
  font-size: var(--text-0);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-0);
  text-transform: uppercase;
  text-align: left;
}
.nav-group__head:hover { color: var(--foreground); }
/* Der Pfeil dreht statt zu wechseln: eine Bewegung erklärt, dass derselbe
   Abschnitt auf- und zugeht. Zwei verschiedene Icons erzählen zwei Dinge. */
.nav-group__head svg { transition: transform var(--motion-fast) var(--ease-out); }
.nav-group__head[aria-expanded="false"] svg { transform: rotate(-90deg); }
/* Zugeklappt steht der Kopf allein, also weniger Luft nach unten. */
.nav-group__head[aria-expanded="false"] { padding-bottom: var(--space-inline); }
.nav-group__head[aria-expanded="false"] + .nav-group__body { display: none; }
.nav-group__body { display: flex; flex-direction: column; gap: var(--space-inline); }
/* Die Zahl sagt, ob sich das Aufklappen lohnt. */
.nav-group__count { margin-left: auto; font-family: var(--font-mono); }
