/* GENERIERT aus /Users/jeremy/.claude/standards/design/components/layouts.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. */
/* LAYOUTS — die Gerüste, in die Komponenten gesetzt werden.
 *
 * Eigene Datei, weil Layout eine andere Frage beantwortet als Komponente:
 * nicht „womit baue ich das", sondern „wie ist der Screen aufgeteilt".
 *
 * Geerntet aus intelli-os (Board, Stream, Workspace, Mobile) und ready2talk
 * (Dashboard, Settings, Wizard, Auth). Struktur übernommen, Optik neu.
 *
 * Prüfbar mit: node scripts/audit.mjs components/layouts.css */

/* ═══════════════════════════════════════════════════════════════════════════
   APP-SHELL — der Rahmen, in dem alle Patterns sitzen
   ═══════════════════════════════════════════════════════════════════════════ */

/* Breiten exakt aus intelli-os: 216 offen, 60 eingeklappt. */
.shell { display: grid; grid-template-columns: 216px 1fr; height: 100%; min-height: 0; }
.shell--collapsed { grid-template-columns: 60px 1fr; }
/* Fokus-Modus: die Seitenleiste geht weg, das Modul füllt das Fenster. Nicht
   dasselbe wie eingeklappt — eingeklappt bleibt die Navigation erreichbar, im
   Fokus ist sie fort. Deshalb ein eigener Zustand und kein zweiter Grad von
   „schmal". Raus kommt man per Doppel-Esc (aus intelli-os, `.shell.focus`). */
.shell--focus { grid-template-columns: 1fr; }
.shell--focus .shell__nav { display: none; }

/* Die Seitenleiste ist Matt-Papier mit Raster — nicht eine Flächenfarbe.
   Anatomie und Werte exakt aus intelli-os (`.shell-nav`): Schimmer, Korn und
   darunter ein feines Gitter, das oben links kräftig ist und zur unteren Ecke
   ausläuft. Ohne die Maske wäre es eine Tapete.
   Schimmer, Korn und die Inhaltsebene kommen aus der Matt-Liste in ui.css. */
/* Die Gruppentrennung entsteht aus dem oberen Polster der Gruppenüberschrift
   und NICHT aus dem Spalt des Behälters — ihre `.shell-nav` steht auf `gap: 2`
   (effektiv 2.1, nächste Stufe 4). Mit unseren früheren 24 kamen Spalt und
   Label-Polster zusammen auf rund das Dreifache: vier Module machten die
   Leiste rund 70px länger, und die Gruppen lasen sich als getrennte Blöcke
   statt als eine Liste mit Zwischenüberschriften.
   Polster 20/12 → effektiv 21/12.6 → --space-section/--space-inset. Damit
   stimmt es auch mit dem eingeklappten Zustand überein, der 24 schon hatte. */
.shell__nav {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: var(--space-inline);
  padding: var(--space-section) var(--space-inset);
  background-color: var(--surface-subtle);
  min-height: 0;
  overflow-y: auto;
}
/* KEIN Rand rechts. Die Leiste grenzt sich durch ihre Fläche ab — Papier neben
   Seitengrund. Ein Rand daneben ist eine zweite Aussage über dieselbe Grenze
   und macht aus der Fläche einen Kasten. In intelli-os steht dort nichts. */

/* Das Raster liegt UNTER dem Korn und unter den Kindern. */
.shell__nav::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    linear-gradient(to right, var(--grid-line) var(--grid-width), transparent var(--grid-width)),
    linear-gradient(to bottom, var(--grid-line) var(--grid-width), transparent var(--grid-width));
  background-size: var(--grid-size) var(--grid-size);
  background-position: var(--grid-offset) var(--grid-offset);
  -webkit-mask-image: var(--grid-mask);
  mask-image: var(--grid-mask);
}
/* Seitenpolster wie beim `.nav-item`: der Wert ist die Fluchtlinie der Leiste,
   kein Abstand für sich. Mit einer engeren Stufe springt die Marke gegenüber
   den Einträgen darunter nach links. */
/* Kopfzeile der Leiste: Marke links, Einklapp-Knopf rechts. Eigene Zeile und
   nicht Teil der Marke, weil eingeklappt genau DIESE Zeile zur Spalte wird —
   Marke oben, Knopf darunter. Nebeneinander passen die zwei in 60px nicht.
   Der Knopf gehört an die Leiste, die er bedient, nicht in die Werkzeugleiste
   des Inhalts. Aus intelli-os (.shell-top). */
.shell__top { display: flex; align-items: center; }
.shell__top .btn-icon { margin-left: auto; }

.shell__brand { display: flex; align-items: center; gap: var(--space-stack); padding: 0 var(--space-inset); }
/* Ein Bildlogo statt des Buchstabenzeichens — der Normalfall, sobald das
   System ein Kundenprojekt trägt. Die Höhe steht am Kontext und nicht am Bild:
   dasselbe Logo sitzt in der Leiste, auf der Anmeldeseite und eingeklappt in
   drei verschiedenen Grössen, und die Datei kann davon nichts wissen.
   Eingeklappt kleiner, weil die Leiste dann 60px breit ist und ein Logo in
   voller Höhe darin wie ein Fehler aussieht.
   Bildhöhen, keine Abstände — deshalb px und keine Stufe der Leiter. Sie auf
   32 bzw. 24 zu ziehen verzerrte das Logo, ohne etwas zu gewinnen.
   Aus intelli-os (`.shell-brand img`: 30 offen, 24 eingeklappt). */
.shell__brand img { height: 32px; display: block; flex: none; }  /* ihre 30 mal 1.05 */
.shell--collapsed .shell__brand img { height: 25px; }   /* ihre 24 mal 1.05 */
/* Die Wortmarke neben dem Zeichen. Eigene Klasse, weil das Einklappen sie
   ausblenden muss — und eine Regel, die auf einen nackten Textknoten zielt,
   kann niemand nachbauen. */
.shell__name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.shell__mark {
  width: 26px; height: 26px; flex: none;
  display: grid; place-items: center;
  border-radius: var(--radius-1);
  background: var(--primary);
  color: var(--primary-foreground);
  font-size: var(--text-1);
  font-weight: var(--weight-semibold);
}
.shell__group { display: flex; flex-direction: column; gap: var(--space-inline); }
.shell__foot { margin-top: auto; }
.shell__main { display: flex; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden; }

/* Toolbar: Suche links, genau eine Primäraktion rechts aussen */
.toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-group);
  padding: var(--space-inset) var(--space-page);
  border-bottom: 1px solid var(--border);
}
.toolbar__title {
  font-size: var(--text-4);
  line-height: var(--leading-4);
  letter-spacing: var(--tracking-4);
  font-weight: var(--weight-semibold);
  margin: 0;
}
.toolbar__spacer { margin-left: auto; }
/* Die Werkzeugleiste führt Suchfeld und Primäraktion auf --control-lg. Ein
   Segment steht von sich aus auf --control-md, und dann hat die Zeile zwei
   Höhen — genau der Mischmasch, den die Kopf-Norm verhindern soll. Die
   Kontroll-Zeile (.topbar) normiert in die andere Richtung, auf --control-md;
   welche Höhe gilt, entscheidet die Zeile, nicht das Bauteil. */
.toolbar .segmented__item { height: var(--control-md); }

.content { flex: 1; min-height: 0; overflow-y: auto; padding: var(--space-group) var(--space-page); }

/* ═══════════════════════════════════════════════════════════════════════════
   MASTER-DETAIL — Liste links, Detail rechts
   ═══════════════════════════════════════════════════════════════════════════ */

/* `flex: 1` und `overflow: hidden` sind das Paar, das den Satz unten wahr
   macht. Ohne `flex: 1` misst sich das Raster an seinem Inhalt statt an der
   Fläche — dieselbe fehlende Zeile wie bei Board, Strom und Dashboard. Ohne
   `overflow: hidden` scrollt das Raster SELBST, sobald die Tabelle länger wird,
   und dann läuft das Detailpanel beim Blättern nach oben weg. Genau das
   verhindert ihr Kommentar („Grid selbst scrollt NICHT") — wir haben ihn
   zitiert und die Eigenschaft nicht gesetzt.
   `minmax(0, …)` an beiden Spalten: eine `fr`-Spalte hat als Untergrenze ihren
   Inhalt, also sprengt eine breite Tabelle die Meisterspalte und schiebt das
   Detail aus dem Bild. Aus intelli-os (`.cust-table-layout`). */
.l-master-detail {
  flex: 1;
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  gap: var(--space-group);
  min-height: 0;
  overflow: hidden;
}
.l-master-detail > * { min-width: 0; min-height: 0; }
/* Nur die Meisterspalte scrollt. Scrollte statt ihrer das Raster, liefe das
   Detailpanel beim Blättern nach oben weg — und genau dann braucht man es.
   min-height:0 ist Pflicht: eine Rasterzelle wächst sonst über ihren Anteil
   hinaus und scrollt nie. Aus intelli-os, deren Kommentar es benennt:
   „Grid selbst scrollt NICHT." */
.l-master-detail__master {
  min-height: 0;
  overflow-y: auto;
}
/* Die Detailspalte ist selbst kein Panel, sondern nur sein Platz. Das Panel
   darin füllt die volle Spaltenhöhe und schliesst unten bündig zur Tabelle ab
   — sonst hängt es an seinem Inhalt und die Spalten enden auf verschiedener
   Höhe. Das `flex: 1` steht hier und nicht am Panel: unser `.panel` ist ein
   geteiltes Bauteil und darf nicht wissen, in welcher Spalte es hängt. */
.l-master-detail__detail {
  min-height: 0;
  display: flex;
}
.l-master-detail__detail > * { flex: 1; min-height: 0; }

/* ═══════════════════════════════════════════════════════════════════════════
   BOARD — Spalten mit Karten (Kanban)
   ═══════════════════════════════════════════════════════════════════════════ */

/* `flex: 1` ist kein Schmuck, sondern das Glied, an dem die Höhenkette hängt.
   Ohne es misst sich das Board an seiner längsten Spalte statt an der Pane:
   `min-height: 0` hat dann nichts zu tun, `overflow-x` löst nie aus, und die
   Karten laufen unten aus der Pane heraus. Ihre Kette trägt es an jedem Glied
   (`.board-page` → `.board-split` → `.board`, alle `flex: 1; min-height: 0`). */
.l-board {
  flex: 1;
  display: flex;
  gap: var(--space-group);
  align-items: flex-start;
  overflow-x: auto;
  min-height: 0;
}
/* Die Spalte scrollt für sich, das Board scrollt seitlich — zwei Achsen, zwei
   Zuständigkeiten. `max-height: 100%` deckelt die Spalte an der Fläche, sonst
   wächst sie mit ihrem Inhalt und die dreissigste Karte ist unerreichbar.
   Das untere Polster ist grösser als das obere, und das ist Absicht: die
   scrollende Spalte würde den Schatten der letzten Karte sonst abschneiden.
   Der Grund steht so auch bei ihnen (`.bcol`, `ui/board.css:70`). */
.l-board__col {
  flex: 0 0 240px;
  display: flex;
  flex-direction: column;
  gap: var(--space-stack);
  max-height: 100%;
  overflow-y: auto;
  padding: var(--space-stack) var(--space-inset) var(--space-inset);
  background-color: var(--surface);
  border: 1px solid transparent;
  border-radius: var(--radius-3);
  box-shadow: var(--shadow-raised);
}
/* Kopf, Karten und die Schnellerfassung behalten ihre natürliche Höhe. Sobald
   die Spalte scrollt, quetscht der Browser die Kinder sonst in die Spaltenhöhe
   und schneidet ihren Inhalt ab. `.kcard` bringt das selbst mit — Kopf und
   Schnellerfassung nicht. Aus intelli-os (`.bcol > *`). */
.l-board__col > * { flex: none; }
/* Ihre 11.5px sind unter `zoom: 1.05` effektiv 12.075 — das ist --text-1 und
   nicht --text-0. Die Laufweite kann der Stufe hier nicht folgen: --tracking-1
   ist für Gemischtsatz gerechnet und für Versalien viel zu eng. Dafür gibt es
   --tracking-caps, und deren 0.06em sind zufällig genau ihr Wert. */
.l-board__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-stack);
  font-size: var(--text-1);
  line-height: var(--leading-1);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--muted-foreground);
}
.l-board__count { font-family: var(--font-mono); }
.l-board__col--drop { background-color: var(--accent-surface); }

.kcard {
  display: flex;
  flex-direction: column;
  /* Nie schrumpfen. In einer Flex-Spalte, die überläuft — Board-Spalte,
     Telefon-Liste — drückt der Browser die Kinder sonst unter ihre
     Inhaltshöhe, und die letzte Zeile der Karte landet auf der nächsten. */
  flex: none;
  gap: var(--space-stack);
  padding: var(--space-inset);
  background-color: var(--surface-hover);
  border: 1px solid transparent;
  border-radius: var(--radius-2);
  box-shadow: var(--shadow-raised);
  /* Zeiger, nicht Greifhand: die Karte wird angetippt und geöffnet; das Ziehen
     ist die Nebenfunktion. Aus intelli-os. */
  cursor: pointer;
  transition: transform var(--motion-base) var(--ease-out),
              box-shadow var(--motion-base) var(--ease-out);
}
/* Anfassen hebt die Karte, es färbt keinen Rahmen. Aus intelli-os — dort ist
   es dieselbe Bewegung wie bei der Notizkarte, ein Pixel und mehr Schatten. */
.kcard:hover { transform: translateY(-1px); box-shadow: var(--shadow-hover); }
.kcard__title { font-size: var(--text-2); line-height: var(--leading-2); font-weight: var(--weight-semibold); }
.kcard__meta {
  display: flex; align-items: center; gap: var(--space-stack);
  font-size: var(--text-1); line-height: var(--leading-1); color: var(--muted-foreground);
}

/* ═══════════════════════════════════════════════════════════════════════════
   STREAM — Filter links, chronologischer Strom rechts
   ═══════════════════════════════════════════════════════════════════════════ */

/* `flex: 1` wie beim Board — ihr `.notes-page` trägt es, unseres fehlte.
   Ohne das Glied misst sich der Strom an seinem Inhalt statt an der Pane. */
.l-stream { flex: 1; display: grid; grid-template-columns: 220px 1fr; gap: var(--space-group); min-height: 0; }
/* Die Filterspalte scrollt eigenständig. Eine Liste mit dreissig Projekten
   wächst sonst über die Fläche hinaus und ist unten nicht mehr erreichbar —
   bei ihnen löst das `.npanel-scroll`, ein zweites Element allein für diesen
   Zweck. Wir brauchen das zweite Element nicht: unsere Breite steht am
   Rasterband und nicht am Inhalt. */
.l-stream__side { display: flex; flex-direction: column; gap: var(--space-inline); min-width: 0; min-height: 0; overflow-y: auto; }
.l-stream__main { display: flex; flex-direction: column; gap: var(--space-inset); min-width: 0; overflow-y: auto; }
/* Lesebreite. Der Deckel gehört an einen inneren Träger und nicht an die
   scrollende Spalte selbst: die Spalte muss über die volle Breite scrollen
   (sonst sitzt der Scrollbalken mitten im Bild), der Text darin aber nicht
   über die volle Breite laufen. Ein Notizsatz über 1400px ist unlesbar.
   680px zentriert, exakt aus intelli-os (`.stream-wrap`). */
.l-stream__wrap {
  width: 100%;
  max-width: 680px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-inset);
}
.l-stream__day {
  position: sticky;
  top: 0;
  background: var(--background);
  padding: var(--space-stack) 0;
  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);
}
.l-stream__entry {
  display: flex;
  flex-direction: column;
  gap: var(--space-inline);
  padding: var(--space-inset);
  background-color: var(--surface);
  border: 1px solid transparent;
  border-radius: var(--radius-2);
  box-shadow: var(--shadow-raised);
}

/* ═══════════════════════════════════════════════════════════════════════════
   DASHBOARD — Kennzahlen-Reihe über Panel-Raster
   ═══════════════════════════════════════════════════════════════════════════ */

.l-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--space-inset); }
/* Untergrenze 336 und nicht 320: ihre 320 sind unter `zoom: 1.05` effektiv 336.
   Die Zahl ist kein runder Zufall, sondern das Mass, ab dem eine Balkenzeile
   aus Beschriftung, Untertitel und Betrag lesbar bleibt. Unter 280 brach die
   Beschriftung um. Kein Leiterwert — eine Untergrenze ist eine Messung. */
.l-panels { display: grid; grid-template-columns: repeat(auto-fit, minmax(336px, 1fr)); gap: var(--space-group); }
/* Das Dashboard ist die eine Screen-Wurzel, die als GANZES scrollt: es gibt
   hier keinen stehenden Kopf, der beim Blättern bleiben müsste. Board und
   Strom scrollen dagegen in einem inneren Element. Ohne die drei Zeilen ist
   `.l-dashboard` nur ein Abstandshalter und kein Layout. Aus intelli-os
   (`.kpi-page`: `flex: 1; min-height: 0; overflow-y: auto`). */
.l-dashboard {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--space-section);
  min-height: 0;
  overflow-y: auto;
}

/* ═══════════════════════════════════════════════════════════════════════════
   FORMULAR — einspaltig, schmal, in Sektionen
   ═══════════════════════════════════════════════════════════════════════════ */

.l-form { max-width: 480px; display: flex; flex-direction: column; gap: var(--space-section); }
.l-form__section { display: flex; flex-direction: column; gap: var(--space-group); }
.l-form__legend {
  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);
  padding-bottom: var(--space-stack);
  border-bottom: 1px solid var(--border-subtle);
}
/* Zwei Felder auf einer Zeile: PLZ neben Ort, Von neben Bis. Raster statt
   Flex, damit beide Spalten gleich breit bleiben, egal wie lang die
   Beschriftung ist. Am Telefon fällt die Zeile auf eine Spalte — mit der
   Touch-Leiter passen zwei Felder plus Beschriftung dort nicht mehr
   nebeneinander. */
.l-form__row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-inset);
}
.l-mobile .l-form__row { grid-template-columns: 1fr; }
.l-form__foot { display: flex; gap: var(--space-stack); }

/* ═══════════════════════════════════════════════════════════════════════════
   WIZARD — Stepper plus genau ein Schritt
   ═══════════════════════════════════════════════════════════════════════════ */

.l-wizard { display: flex; flex-direction: column; gap: var(--space-section); max-width: 560px; }
.stepper { display: flex; align-items: center; gap: var(--space-stack); }
.step { display: flex; align-items: center; gap: var(--space-stack); color: var(--muted-foreground); font-size: var(--text-2); }
.step__dot {
  width: 24px; height: 24px; flex: none;
  display: grid; place-items: center;
  border-radius: var(--radius-circle);
  border: 1px solid var(--border-control);
  font-size: var(--text-1);
  font-family: var(--font-mono);
}
.step--done .step__dot { background: var(--accent); border-color: var(--accent); color: var(--accent-foreground); }
.step--current { color: var(--foreground); font-weight: var(--weight-medium); }
.step--current .step__dot { border-color: var(--accent); color: var(--accent-text); }
.step__line { flex: 1; height: 1px; background: var(--border-subtle); }

/* ═══════════════════════════════════════════════════════════════════════════
   AUTH — zentrierte Karte, kein Shell
   ═══════════════════════════════════════════════════════════════════════════ */

/* `min-height` und nicht `height`: die Bühne wächst mit der Karte. Sonst
   zentriert sie eine Karte, die höher ist als das Fenster (Einladungsformular
   mit Fehlermeldung), über beide Kanten hinaus und der obere Rand ist nicht
   mehr erreichbar. */
.l-auth { display: grid; place-items: center; min-height: 100%; padding: var(--space-page); background: var(--background); }
/* 400 und nicht 380: ihre 380 sind unter `zoom: 1.05` effektiv 399. Dieselbe
   Falle wie beim Wisch-Peek — der rohe Wert war abgeschrieben, nicht gerechnet.
   `--shadow-float` und nicht `--shadow-raised`: die Karte liegt auf einer
   leeren Seite und nicht in einer Liste von Flächen. Erhoben gegen nichts ist
   nicht erhoben; hier trägt der Schatten allein die ganze Aussage. Aus
   intelli-os (`.auth-card`). */
.l-auth__card {
  width: min(400px, 100%);
  display: flex;
  flex-direction: column;
  gap: var(--space-section);
  padding: var(--space-page);
  background-color: var(--surface);
  border: 1px solid transparent;
  border-radius: var(--radius-3);
  box-shadow: var(--shadow-float);
}
.l-auth__head { display: flex; flex-direction: column; gap: var(--space-inset); align-items: center; text-align: center; }
/* Bildlogo auf der Anmeldeseite — grösser als in der Leiste, weil hier nichts
   sonst steht. Höhe am Kontext, wie bei `.shell__brand img`.
   Aus intelli-os (`.auth-mark img`). */
.l-auth__head img { height: 40px; display: block; }
/* Titel und Unterzeile standen in unserem eigenen Beispiel als Inline-Stile.
   Eine Anmeldeseite ist der erste Screen, den jemand nachbaut — was dort inline
   steht, wird abgeschrieben statt benutzt. Ihre 19px sind effektiv 19.95
   (--text-4), ihre Unterzeile 13 ist 13.65 (--text-2). */
.l-auth__title {
  font-size: var(--text-4);
  line-height: var(--leading-4);
  letter-spacing: var(--tracking-4);
  font-weight: var(--weight-semibold);
}
.l-auth__sub {
  font-size: var(--text-2);
  line-height: var(--leading-2);
  color: var(--muted-foreground);
}
/* Die Primäraktion füllt die Karte. In einer 400px-Karte sieht ein Knopf in
   Inhaltsbreite aus wie ein Versehen — und es gibt hier nur eine Aktion, also
   nichts, wovon er sich absetzen müsste. Aus intelli-os (`.auth-card .btn.primary`). */
.l-auth__card .btn--primary { width: 100%; }
/* Der Ausweg: „Eingeladen? Passwort setzen", „Passwort vergessen". Genau einer,
   leise, zentriert unter der Aktion. Aus intelli-os (`.auth-switch`). */
.l-auth__foot {
  text-align: center;
  font-size: var(--text-1);
  line-height: var(--leading-1);
  color: var(--muted-foreground);
}

/* ═══════════════════════════════════════════════════════════════════════════
   WORKSPACE — mehrere Panes nebeneinander, Trenner verschiebbar
   ═══════════════════════════════════════════════════════════════════════════ */

/* Positionsbezug für die Rand-Ablagezonen — ohne ihn rechnen sie gegen das
   Fenster statt gegen die Arbeitsfläche. */
.l-workspace { position: relative; display: flex; flex: 1; gap: 0; min-width: 0; min-height: 0; height: 100%; }
/* Eine Pane teilt sich die Fläche, sie misst sich nicht am Inhalt. Ohne `flex`
   schrumpft sie in der Flex-Zeile auf ihre Inhaltsbreite — deshalb stand in
   unserem eigenen Beispiel ein Inline-`flex:1.4`. Das VERHÄLTNIS zweier Panes
   bestimmt der Anwender am Trenner und gehört als Inline-Wert ans Element, der
   Grundanteil aber hierher. `min-height: 0` aus demselben Grund wie
   `min-width: 0`: ein Flex-Kind mit scrollendem Inhalt wächst sonst über
   seinen Anteil hinaus. */
.pane {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  position: relative;
  background-color: var(--surface);
  border: 1px solid transparent;
  border-radius: var(--radius-2);
  box-shadow: var(--shadow-raised);
  overflow: hidden;
}
/* Fokussierte Pane: stärkerer Schatten statt farbigem Rand. Ein grüner Rahmen
   um ein Fenster liest sich wie ein Fehlerzustand. Ihre Fassung ist ein 2px-
   Rahmen als `::after` über dem Inhalt, mit dem Grund im Kommentar: sonst legen
   sich positionierte Kinder über den Rand. Unser Schatten liegt ausserhalb des
   Kastens, dorthin kann kein Kind — das Problem tritt bei uns nicht auf.
   Der Kopf trägt den Zustand mit. Zwischen zwei benachbarten Panes ist ein
   Schattenunterschied allein eine Nuance; die getönte Kopfzeile sagt es
   eindeutig. Aus intelli-os (`.pane.focused .pane-head`).
   Fokussiert wird NUR im Split gesetzt: bei einer einzelnen Pane gibt es
   nichts, wovon sie sich abheben müsste (`Pane.tsx`: `!solo && focused`).
   Das braucht keine eigene Regel — `.pane--solo` steht weiter unten und nimmt
   Schatten wie Kopf ohnehin zurück. */
.pane--focused { box-shadow: var(--shadow-float); }
.pane--focused > .pane__head { background: var(--surface-hover); }
/* Eine einzelne Pane ist kein Fenster, sondern die Arbeitsfläche selbst. Sie
   bekommt weder Kopf noch Rahmen: es gibt nichts, wovon sie sich abgrenzen
   müsste, und ein Schliessen-Knopf, der das Letzte schliesst, führt ins Leere.
   Aus intelli-os (Pane.tsx rendert den Kopf nur bei mehr als einer Pane). */
.pane--solo { border-radius: 0; box-shadow: none; }
.pane--solo > .pane__head { display: none; }
.pane__head {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--space-stack);
  min-height: var(--control-md);
  padding: 0 var(--space-stack) 0 var(--space-group);
  border-bottom: 1px solid var(--border-subtle);
}
/* Der Titel ist ein eigenes Element, weil er kürzen muss: er lautet
   „Modul · Screen" und ist damit beliebig lang. Steht er als nackter Text im
   Kopf, schiebt er den Schliessen-Knopf hinaus — kürzen kann nur ein Element,
   das `min-width: 0` und `overflow` tragen kann. Die Schriftrolle sitzt
   weiterhin am Kopf; hier steht nur, wie der Titel sich verhält. */
.pane__title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  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);
}
/* Die Innenluft steht als Variable, nicht als Zahl: randlose Bereiche in einer
   Pane (ein Board, eine Liste, die bis an die Kante läuft) ziehen sie mit
   `margin: 0 calc(var(--pane-pad-x) * -1)` wieder ab. Als Zahl an zwei Orten
   driften Polsterung und Gegenpolsterung auseinander, sobald eine sich ändert.
   Aus intelli-os (`--pane-pad-y` / `--pane-pad-x`).
   `overflow: hidden` und nicht `auto`: scrollen soll der Screen in der Pane,
   mit seinem eigenen Kopf, der stehen bleibt. Scrollt die Pane selbst, wandert
   der Screen-Kopf mit hinaus. */
.pane__body {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  --pane-pad-y: var(--space-section);
  --pane-pad-x: var(--space-page);
  padding: var(--pane-pad-y) var(--pane-pad-x);
  overflow: hidden;
}

/* Overlay-Ebene EINER Pane. Der Grund ist fachlich, nicht optisch: ein Modal,
   das im Fenster hängt, sperrt beide Panes. Portalt es stattdessen hierher,
   dimmt es nur seine eigene Pane — die Nachbar-Pane bleibt hell, bedienbar,
   und Inhalt lässt sich weiterhin von der einen in die andere ziehen.
   `pointer-events: none` auf der leeren Ebene, weil sie sonst als unsichtbare
   Scheibe über der ganzen Pane liegt und jeden Klick schluckt.
   Der eigene Stapelkontext ist Absicht: ein klebriger Tagesheader im Screen
   läge sonst vor dem Scrim. Aus intelli-os (`.pane-overlays`). */
.pane__overlays {
  position: absolute;
  inset: 0;
  z-index: 100;
  pointer-events: none;
}
.pane__overlays > * { pointer-events: auto; }

/* Rand-Ablagezonen: hierher zieht man einen Screen aus der Navigation, und es
   entsteht eine neue Pane an dieser Kante. Vier Zonen, keine Mitte — eine
   Mittelzone müsste beantworten, ob sie ersetzt oder teilt, und diese Frage
   kann eine Fläche nicht stellen. Aus intelli-os (Workspace.tsx).
   22 % pro Kante: gross genug, um sie ohne Zielen zu treffen, klein genug,
   dass in der Mitte Fläche bleibt, über der man ziehen kann, ohne etwas
   auszulösen. Prozent, keine Pixel — hier gibt es keine Leiter. */
.edge-zones {
  position: absolute;
  inset: 0;
  z-index: 40;
  pointer-events: none;
}
/* Die Zonen bleiben im Markup und werden nur sichtbar geschaltet. Kämen sie
   erst beim Ziehen ins DOM, startete die Einblendung auf ihrem Endwert und es
   gäbe keinen Übergang — bei ihnen ist die transition genau deshalb wirkungslos.
   Treffbar sind sie trotzdem nur während des Ziehens, sonst liegt ein
   unsichtbares Band über jedem Pane-Rand. */
.edge-zone {
  position: absolute;
  pointer-events: none;
  background: var(--accent-surface);
  border: 2px dashed var(--accent);
  opacity: 0;
  transition: opacity var(--motion-fast) var(--ease-out);
}
.l-workspace[data-dragging] .edge-zone { pointer-events: auto; }
/* Nur die Zone unter dem Zeiger leuchtet — leuchteten alle vier, sagte keine,
   wo die Pane landet. */
.l-workspace[data-dragging] .edge-zone:hover { opacity: 0.9; }
.edge-zone--left   { left: 0;   top: 0;  bottom: 0; width: 22%; }
.edge-zone--right  { right: 0;  top: 0;  bottom: 0; width: 22%; }
.edge-zone--top    { top: 0;    left: 0; right: 0;  height: 22%; }
.edge-zone--bottom { bottom: 0; left: 0; right: 0;  height: 22%; }
/* Der Trenner ist ein Griff, keine Linie. Im Ruhezustand unsichtbar — er
   erscheint erst, wenn man in seine Nähe kommt. Eine dauerhaft sichtbare Linie
   zwischen zwei Panes behauptet eine Grenze, die der Schatten schon zeigt. */
.divider { flex: none; width: var(--space-stack); cursor: col-resize; display: grid; place-items: center; }
.divider::before {
  content: "";
  width: 3px; height: 32px;
  border-radius: var(--radius-pill);
  background: transparent;
  transition: background var(--motion-fast);
}
.divider:hover::before { background: var(--border-control); }

/* ═══════════════════════════════════════════════════════════════════════════
   MOBILE — Bottom-Nav und Sheet statt Sidebar und Modal

   Die Telefon-Shell ist ein eigenes Gerüst, kein schmaler Desktop. Sie ist
   NICHT an einen Breakpoint gehängt: die Anwendung entscheidet einmal beim
   Start, welche Shell sie rendert — schmal UND (grober Zeiger ODER als App
   installiert). Ein schmales Desktop-Fenster behält die Desktop-Shell, siehe
   standards/09-responsive.md.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Der Grössenschalter, und der einzige Ort, an dem er umgelegt wird. Jede
   Komponente darin wächst mit, weil sie dieselben Token-Namen liest — kein
   zweiter Satz Klassen, keine zweite Designsprache.

   `.touch` ist derselbe Schalter ohne unser Gerüst. Für Bestandsprojekte, die
   ihre eigene Telefon-Shell behalten und nur einen Bereich auf das System
   umstellen: dort hängt der Bereich nicht in `.l-mobile`, und ohne diesen
   Schalter blieben die Controls mausgross. */
.touch,
.l-mobile {
  --control-sm: var(--control-touch-sm);
  --control-md: var(--control-touch-md);
  --control-lg: var(--control-touch-lg);
}

/* Drei Reihen als Raster, nicht als Flex-Spalte: der FAB muss in die mittlere
   Reihe gelegt werden können, ohne die untere Leiste zu überdecken. Absolut
   positioniert würde er auf ihr liegen — genau das passiert in intelli-os. */
.l-mobile {
  position: relative;
  display: grid;
  grid-template-rows: auto 1fr auto;
  height: 100%; min-height: 0;
}
/* Kopf und Fussleiste stossen an Kerbe und Home-Balken. Der Freiraum kommt vom
   Gerät, nicht von uns — deshalb aufaddiert statt geraten. */
.l-mobile__head {
  display: flex; align-items: center; gap: var(--space-stack);
  padding: var(--space-inset);
  padding-top: calc(var(--space-inset) + env(safe-area-inset-top));
  border-bottom: 1px solid var(--border);
  font-size: var(--text-3);
  font-weight: var(--weight-medium);
}
/* Der Titel ist ein eigenes Element, weil er kürzen muss. Steht er als nackter
   Text im Kopf, schiebt ein langer Kundenname die Icon-Knöpfe aus dem Bild —
   derselbe Grund wie bei `.pane__title`. Ihre 21px sind effektiv 22.05 und
   liegen damit näher an --text-5 (24) als an --text-4 (18); ihre 700 kappt
   harte Regel 4 auf 600. Aus intelli-os (`.m-head h1`). */
.l-mobile__title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--text-5);
  line-height: var(--leading-5);
  letter-spacing: var(--tracking-5);
  font-weight: var(--weight-semibold);
}
.l-mobile__body {
  grid-row: 2; grid-column: 1;
  overflow-y: auto;
  /* Schwungscrollen auf älterem iOS. Ohne die Zeile fühlt sich eine lange
     Liste dort an wie ein Desktop-Scrollfeld. Aus intelli-os (`.m-scroll`). */
  -webkit-overflow-scrolling: touch;
  padding: var(--space-inset);
}
/* Platz für den FAB. Er ist 48 hoch und hat 16 Rand — ohne diesen Boden ist
   der letzte Eintrag einer Liste dauerhaft halb verdeckt. `10-mobile.md`
   verlangt den Platz seit je, das CSS lieferte ihn nicht. Gerechnet und nicht
   geraten, damit es beim Ändern der Touch-Leiter mitgeht. */
.l-mobile:has(.fab) .l-mobile__body {
  padding-bottom: calc(var(--control-touch-lg) + var(--space-group) * 2);
}
.l-mobile__nav {
  display: grid;
  grid-auto-flow: column;
  padding-bottom: env(safe-area-inset-bottom);
  border-top: 1px solid var(--border);
  background: var(--surface-subtle);
}
.mnav {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--space-inline);
  min-height: var(--target-min);
  padding: var(--space-stack) var(--space-inset);
  color: var(--muted-foreground);
  font-size: var(--text-1);
  line-height: var(--leading-1);
  text-decoration: none;
  border: none; background: none; cursor: pointer;
  border-radius: var(--radius-2);
}
/* Farbe UND Fläche. Farbe allein trägt am Telefon nicht: das Wort unter dem
   Icon steht auf --text-1 und ist zu klein, um einen Farbwechsel zu tragen,
   und man sieht die Leiste beim Tippen nur aus dem Augenwinkel. Der Radius
   macht die Fläche erst zur Pille. Aus intelli-os (`.m-navbtn.on` setzt beides). */
.mnav[aria-current] { color: var(--accent-text); background: var(--accent-surface); }

/* ── Sheet — das Modal des Telefons ───────────────────────────────────────
   Verankert unten statt zentriert: der Daumen erreicht den unteren Rand, die
   Bildschirmmitte nicht. Sonst dieselbe Mechanik wie das Modal — derselbe
   Scrim aus ui.css, dieselbe Stufe 100 der z-index-Leiter.

   Zwei Höhen, mehr nicht: `.sheet` legt sich über den Inhalt und lässt ihn
   sichtbar, `.sheet--tall` ist für Inhalt, der scrollt. Wer eine dritte Höhe
   braucht, will eigentlich eine eigene Seite.

   Drei Zonen mit fester Zuständigkeit: Griff und Kopf stehen, der Körper
   scrollt. Vorher scrollte das Sheet als Ganzes — dann wandert der Titel, also
   die Antwort auf „worüber rede ich hier", als Erstes hinaus. Aus intelli-os
   (`.m-sheet-grab` / `.m-sheet-head` / `.m-sheet-body`, alle drei mit
   ausdrücklicher flex-Ansage). */
.sheet {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 100;
  max-height: 82%;
  border-top-left-radius: var(--radius-3);
  border-top-right-radius: var(--radius-3);
  background-color: var(--popover);
  border: 1px solid transparent;
  box-shadow: var(--shadow-float);
  padding: var(--space-inset) var(--space-group)
           calc(var(--space-group) + env(safe-area-inset-bottom));
  display: flex; flex-direction: column; gap: var(--space-inset);
  overflow: hidden;
  transform: translateY(100%);
  transition: transform var(--motion-slow) var(--ease-out);
}
.sheet--tall { height: 88%; max-height: 88%; }
/* Offen ist ein Zustand am Sheet selbst, damit das Hereinziehen sichtbar wird.
   Ohne Zustandsklasse springt es und die Bewegung erklärt nichts mehr. */
.sheet[data-open] { transform: none; }
.sheet__grab {
  /* Ihre 38 sind effektiv 39.9 — Griffbreite, kein Abstand, deshalb px. */
  width: 40px; height: 4px;
  flex: none;
  align-self: center;
  border-radius: var(--radius-pill);
  background: var(--border-control);
}
.sheet__head {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--space-stack);
}
/* Wie `.pane__title`: kürzen kann nur ein Element, das `min-width: 0` tragen
   kann. Steht der Titel als nackter Text im Kopf, schiebt ein langer Name den
   Schliessen-Knopf hinaus. */
.sheet__title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--text-4);
  line-height: var(--leading-4);
  letter-spacing: var(--tracking-4);
  font-weight: var(--weight-semibold);
}
.sheet__body { flex: 1; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; }

/* Die Zieh-Zone von `behaviour/sheet.js`: Griff und Kopf. `touch-action: none`
   ist die Bedingung dafür, dass die Geste überhaupt ankommt — sonst nimmt der
   Browser sie als Scrollen, und der Griff verspricht etwas, das nie passiert.
   Nur an angemeldeten Sheets: ein Griff ohne Verhalten bleibt Deko und gehört
   nach `standards/10-mobile.md` ohnehin entfernt. */
[data-overlay="sheet"] .sheet__grab,
[data-overlay="sheet"] .sheet__head { touch-action: none; }
/* Während der Finger zieht, folgt das Sheet ohne Übergang — sonst hinkt es
   ihm um --motion-slow hinterher und fühlt sich kaputt an. Der Zustand steht
   im DOM, damit die Dauer im CSS bleibt und nicht in JavaScript wandert. */
[data-overlay="sheet"][data-zieht] { transition: none; }

/* Action-Sheet: der sichtbare Ersatz fürs Kontextmenü. Long-Press findet
   niemand, der die Anwendung nicht schon kennt. Volle Breite, eine Aktion je
   Zeile, Abbrechen als letzte — abgesetzt, damit der Daumen sie nicht mit der
   letzten Aktion verwechselt. */
.sheet--actions { padding-left: var(--space-inset); padding-right: var(--space-inset); }
/* Die Aktion ist ein Ziel, keine Zeile. --space-group oben und unten ergibt
   mit --leading-3 genau 56 — ihr gemessener Wert (15px Polster unter
   `zoom: 1.05` sind 15.75, macht 56.7). Unsere bisherigen 40 waren eine Zeile.
   `min-height` bleibt als Boden für Aktionen ohne Textzeilenhöhe. */
.sheet__action {
  display: flex; align-items: center; gap: var(--space-stack);
  min-height: var(--target-min);
  padding: var(--space-group);
  border: none; background: none; cursor: pointer;
  border-radius: var(--radius-2);
  color: var(--foreground);
  font-size: var(--text-3);
  line-height: var(--leading-3);
  text-align: left;
}
/* Die Aktionen liegen in EINEM gerundeten Block, Abbrechen in einem zweiten
   darunter. Der Abstand zwischen den Blöcken ist die eigentliche Aussage: er
   sagt, dass Abbrechen nicht in derselben Reihe steht wie „Löschen". Ohne den
   Block ist Abbrechen nur die letzte von fünf Zeilen — und der Daumen ist
   ungenauer als die Maus. Aus intelli-os (`.m-actions-list`). */
.sheet__group {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border-radius: var(--radius-3);
  overflow: hidden;
}
/* Trenner INNERHALB des Blocks: die Aktionen sind eine Liste, keine Knopfreihe.
   Über den Nachbarn gesetzt und nicht an jede Zeile, damit die letzte keine
   Linie auf der Rundung liegen hat. */
.sheet__group .sheet__action { border-radius: 0; }
.sheet__group .sheet__action + .sheet__action { border-top: 1px solid var(--border-subtle); }
.sheet__action:active { background: var(--surface-active); }
.sheet__action--danger { color: var(--danger-foreground); }
.sheet__action--cancel {
  margin-top: var(--space-stack);
  justify-content: center;
  background: var(--surface-hover);
  font-weight: var(--weight-medium);
}

/* ── PushScreen — das Detail des Telefons ─────────────────────────────────
   Das Detail wird eine geschobene Seite, keine Spalte. Es legt sich über den
   Screen statt ihn zu ersetzen: die Liste darunter bleibt stehen, mit ihrer
   Scrollposition und ihrer Spalte. Kein Wiederherstellen nötig, weil nichts
   verloren geht.

   Es deckt AUCH die untere Leiste ab. Wer im Detail arbeitet, soll nicht mit
   einem Fehlgriff das Modul wechseln — in intelli-os blieb die Leiste stehen
   und genau das passiert dort.

   Der Weg zurück gehört an die URL, nicht in eine Zustandsvariable. Sonst
   verlässt die Zurück-Geste des Geräts die Anwendung statt das Detail zu
   schliessen. Der Knopf allein reicht nicht. */
.l-push {
  position: absolute;
  inset: 0;
  z-index: 10;
  display: flex; flex-direction: column;
  background: var(--background);
  animation: l-push-in var(--motion-base) var(--ease-out);
}
@keyframes l-push-in {
  from { transform: translateX(var(--space-group)); opacity: 0; }
}
.l-push__head {
  display: flex; align-items: center; gap: var(--space-stack);
  flex: none;
  padding: var(--space-inset);
  padding-top: calc(var(--space-inset) + env(safe-area-inset-top));
  border-bottom: 1px solid var(--border);
}
.l-push__title {
  flex: 1; min-width: 0;
  font-size: var(--text-4);
  line-height: var(--leading-4);
  letter-spacing: var(--tracking-4);
  font-weight: var(--weight-semibold);
}
.l-push__body {
  flex: 1; overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  /* Am Ende der Liste hört es auf. Ohne diese Zeile gibt der Scroll an die
     Seite darunter weiter — man scrollt im Detail und der Screen dahinter
     fährt weg, obwohl er verdeckt ist. Das ist der Mangel, den eine
     Scroll-Sperre am ganzen Dokument sonst mit Gewalt löst; hier wird er dort
     behoben, wo tatsächlich gescrollt wird. */
  overscroll-behavior: contain;
  padding: var(--space-inset);
  padding-bottom: calc(var(--space-group) + env(safe-area-inset-bottom));
}

/* Erst die Anmeldung eines Verhaltens macht aus der Fläche einen geschobenen
   Bildschirm mit Rückweg (`behaviour/push-screen.js`). `data-open` ist die
   eine Wahrheit, das CSS liest dieselbe Stelle.

   Die Einfahrt wird vom Keyframe auf einen Übergang umgestellt, und das ist
   kein Geschmack: eine Animation läuft nur beim Erscheinen. Beim Schliessen
   verschwindet der Bildschirm dann hart und die Richtung geht verloren —
   genau das passiert in intelli-os, das nur `m-push-in` kennt. Ein Übergang
   läuft in beide Richtungen, und `behaviour/overlay.js` misst seine Dauer am
   Element, statt eine eigene zu führen. */
[data-overlay="push"] {
  animation: none;
  transform: translateX(100%);
  transition: transform var(--motion-base) var(--ease-out);
  /* Waagrecht führt die Zurück-Geste, senkrecht scrollt der Inhalt. Ohne die
     Ansage nimmt der Browser den Zug von der Kante als Scrollversuch und die
     Geste kommt nie an. `pinch-zoom` bleibt drin: das Vergrössern gehört
     niemals einem Bauteil. */
  touch-action: pan-y pinch-zoom;
}
[data-overlay="push"][data-open] { transform: none; }
/* Solange der Finger führt, kein Übergang — sonst hinkt der Bildschirm ihm
   um --motion-base hinterher. Dieselbe Lösung wie am Sheet. */
[data-overlay="push"][data-zieht] { transition: none; }

/* ── Kartenliste — die Tabelle des Telefons ───────────────────────────────
   Eine Tabelle, die waagrecht scrollt, ist am Telefon die Notlösung, nicht die
   Antwort. Jede Zeile wird eine Karte: dieselbe .kcard wie am Board, nur
   antippbar statt ziehbar. */
.kcard--tap {
  min-height: var(--target-min);
  cursor: pointer;
}
.kcard--tap:active { background: var(--surface-active); }

/* ── Wisch-Board — Spalten nebeneinander, eine nach der anderen ───────────
   Reines Scroll-Snap, kein Gesten-JS. Die nächste Spalte guckt heraus — das
   ist die ganze Erklärung, warum man wischen soll. Ohne diesen Spalt sieht
   der Screen aus, als gäbe es nur eine Liste.

   Karten verschiebt man hier NICHT durch Ziehen. Ziehen und Scrollen sind am
   Finger dieselbe Geste; eine von beiden verliert immer. Stattdessen zwei
   sichtbare Ziele auf der Karte: Statuspille für eine beliebige Spalte,
   Pfeil für die nächste. */
/* Das Polster gehört an die Zeile und nicht an die Spalte: es ist der Rand des
   Boards zum Bildschirm. Der Snap-Wert MUSS derselbe sein, sonst rastet die
   Spalte neben ihrem eigenen Rand ein und ein Streifen der Vorgängerspalte
   bleibt stehen — bei uns standen 12 Snap gegen gar kein Polster.
   `overflow-y: hidden`, damit nur zwei Ebenen scrollen: die Zeile waagrecht,
   die Spalte senkrecht. Ohne die Ansage scrollt bei einer hohen Spalte auch
   die Zeile, und dann fängt keine Geste mehr das, was man meint.
   Die Zeile muss die Höhe des Körpers annehmen statt der ihres Inhalts — sonst
   braucht jeder Screen wieder ein Inline-`height:100%`, wie es in unserem
   eigenen Beispiel stand. Den Körper dafür zur Flex-Spalte zu machen ist die
   einzige Fassung, die auch mit dem Wisch-Hinweis darüber stimmt: `height: 100%`
   allein wäre um dessen Höhe zu gross und kappte die Spalten unten.
   Nicht `.l-mobile__body` pauschal — nur wenn wirklich ein Board darin steht.
   Sonst bekäme jeder Telefon-Screen eine Flex-Spalte, die er nicht bestellt hat. */
.l-mobile__body:has(> .l-swipe) { display: flex; flex-direction: column; }
.l-swipe {
  display: flex;
  flex: 1;
  gap: var(--space-inset);
  padding: var(--space-inset) var(--space-group) var(--space-group);
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: x mandatory;
  scroll-padding-left: var(--space-group);
  scrollbar-width: none;
  min-height: 0;
}
.l-swipe::-webkit-scrollbar { display: none; }
/* Die Spalte ist dieselbe Fläche wie am Desktop. Ohne sie steht die
   Versalien-Überschrift auf dem Seitengrund, die Spalte hat keine sichtbare
   Grenze, der Spalt zur nächsten sagt nichts mehr und der Peek verliert seinen
   Sinn. Kein Schatten, anders als bei `.l-board__col`: am Telefon liegen die
   Spalten bildschirmbreit direkt auf dem Grund, und ein Schatten unter einer
   solchen Fläche liest sich als Kante statt als Erhebung.
   Unteres Polster grösser als das obere, aus demselben Grund wie an der
   Board-Spalte: die scrollende Spalte soll den Schatten der letzten Karte
   nicht abschneiden. Aus intelli-os (`.m-column`, 10/10/14 → 10.5/10.5/14.7).
   Das Korn trägt die Matt-Liste in ui.css — eine kornlose Fläche neben
   körnigen Karten wäre eine neue Materialklasse. */
.l-swipe__col {
  flex: 0 0 calc(100% - var(--swipe-peek));
  scroll-snap-align: start;
  display: flex; flex-direction: column; gap: var(--space-stack);
  padding: var(--space-inset) var(--space-inset) var(--space-group);
  background-color: var(--surface);
  border: 1px solid transparent;
  border-radius: var(--radius-3);
  overflow-y: auto;
  scrollbar-width: none;
  min-height: 0;
}
.l-swipe__col::-webkit-scrollbar { display: none; }
/* Der Wisch-Hinweis steht AUSSERHALB des Boards, nicht darin — sonst scrollt
   er mit der ersten Spalte weg und sagt nichts mehr. Zulässig, aber ein
   Eingeständnis: wenn die Bedienung eine Zeile Erklärung braucht, ist die
   erste Frage, ob sie sich nicht selbst erklären liesse.
   Aus intelli-os (`.m-swipehint`). */
.l-swipe-hint {
  flex: none;
  text-align: center;
  padding: var(--space-inline) 0 var(--space-stack);
  font-size: var(--text-1);
  line-height: var(--leading-1);
  color: var(--muted-foreground);
}

/* ── FAB — die eine Aktion, die immer erreichbar ist ──────────────────────
   Mit Wort, nicht nur Icon: ein Kreis mit Plus lässt offen, was entsteht.
   Höchstens einer je Screen, und wenn ein Screen ihn hat, liegt die
   Primäraktion NICHT zusätzlich oben im Kopf — sonst sucht man sie zweimal. */
.fab {
  /* In dieselbe Rasterzelle wie der Inhalt, unten rechts. Dadurch liegt er
     über dem Inhalt, aber nie auf der unteren Leiste. */
  grid-row: 2; grid-column: 1;
  align-self: end; justify-self: end;
  margin: var(--space-group);
  z-index: 6;
  display: flex; align-items: center; gap: var(--space-stack);
  min-height: var(--control-touch-lg);
  padding: 0 var(--space-group);
  border: none;
  border-radius: var(--radius-pill);
  background: var(--primary);
  color: var(--primary-foreground);
  box-shadow: var(--shadow-float);
  font-size: var(--text-3);
  line-height: var(--leading-3);
  font-weight: var(--weight-medium);
  cursor: pointer;
}

/* ═══════════════════════════════════════════════════════════════════════════
   ICON — ein Set, ein Sprachraum
   ═══════════════════════════════════════════════════════════════════════════ */

/* Strichzeichnung ist die Vorgabe für Icons IN Bedienelementen — auch dann,
   wenn jemand die Klasse `.icon` vergisst. Handgeschriebenes Markup vergisst
   sie regelmässig, und ohne diese Regel rendert ein rohes <svg> als schwarze
   Fläche statt als Strich.

   Die Regel hängt an einer LISTE von Bedienelementen und nicht am Element
   `svg`: Diagramme sind auch SVG. In intelli-os lag sie einmal global und gab
   jedem Balken einen 2px-Umriss in Textfarbe — jede Chart-Komponente musste
   ihn einzeln mit `stroke="none"` wieder abschalten. Der Grund steht bei ihnen
   zweimal (`ui/components.css:3-7`, `ui/icons.tsx:1-5`).

   `:where()`, damit die Gewichtung null bleibt und jede Klasse am Icon selbst
   ohne `!important` gewinnt. Wächst die Liste, gehört der neue Eintrag hierher
   und nicht in ein Bauteil.
   `stroke-width` bleibt roh: eine Strichstärke ist kein Abstand. */
.icon,
:where(.btn, .btn-icon, .filter-toggle, .filter-btn, .filter-pop__row,
       .segmented__item, .menu__item, .dropdown__trigger, .dropdown__opt,
       .datefield__trigger, .nav-item__icon, .input-lead, .search,
       .attach-tray__chip, .attach-tray__remove, .check-line__box,
       .mnav, .fab, .sheet__action, .row-toggle) svg {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.icon { width: 16px; height: 16px; flex: none; }
.icon--lg { width: 20px; height: 20px; }
.icon--sm { width: 14px; height: 14px; }
/* Das Icon der Telefonleiste ist grösser als das im Fliesstext: unter ihm steht
   nur ein Wort auf --text-1, das Icon ist das eigentliche Ziel. Ihre 22 rendern
   als 23.1 — eine Stufe, die nur an einem Ort gilt, ist keine Stufe, deshalb
   steht der Wert hier und nicht als vierte Grösse. Aus intelli-os (`.m-navico svg`). */
.mnav .icon { width: 24px; height: 24px; }

/* Eingeklappt: die Beschriftungen verschwinden, die Icons rücken in die Mitte.
   Gekürzte Labels wären schlechter als keine. */
.shell { transition: grid-template-columns var(--motion-base) var(--ease-out); }
.shell--collapsed .shell__nav { padding: var(--space-section) var(--space-inset); }
/* Eingeklappt steht die Marke ÜBER dem Schalter, beides zentriert — nebeneinander
   passen sie in 60px nicht. Aus intelli-os. */
.shell--collapsed .shell__top { flex-direction: column; gap: var(--space-inset); }
.shell--collapsed .shell__top .btn-icon { margin: 0 auto; }
.shell--collapsed .shell__brand { padding: 0; }
.shell--collapsed .shell__nav { align-items: center; }
.shell--collapsed .nav-item { padding: var(--space-stack) 0; }
.shell--collapsed .nav-item,
.shell--collapsed .identity,
.shell--collapsed .shell__brand { justify-content: center; padding-left: 0; padding-right: 0; }
/* Die Beschriftungen selbst. Bei intelli-os rendert die Anwendung sie gar nicht
   erst; reines CSS muss sie ausblenden — sonst quetscht sich der Text auf 60px
   und das Einklappen macht die Leiste unleserlich statt schmal.
   Was übrig bleibt, ist das Icon. Ein Eintrag ohne Icon ist eingeklappt leer —
   deshalb ist das Icon im .nav-item Pflicht, nicht Schmuck.

   Die Liste nennt die ECHTEN Klassen. Vorher hing das an einer Markierungs-
   klasse `.shell__label`, die nirgends deklariert und nirgends dokumentiert
   war: wer nach der Doku baute, setzte sie nicht — und dann klappte die Leiste
   ein, ohne dass etwas verschwand. Eine Klasse, deren einziger Zweck „ich
   verschwinde" ist, muss jeder Screen-Bauer kennen; das ist genau der Fehler,
   der schon einmal durchgerutscht ist. Die Gruppenüberschrift (`.nav-label`)
   stand dabei in KEINER der beiden Listen und blieb eingeklappt stehen. */
.shell--collapsed .shell__name,
.shell--collapsed .nav-item__label,
.shell--collapsed .nav-item__count,
.shell--collapsed .nav-label,
.shell--collapsed .nav-group__head,
.shell--collapsed .identity__text,
.shell--collapsed .identity__switch { display: none; }

/* ═══════════════════════════════════════════════════════════════════════════
   RESPONSIVE — bis zum halben Bildschirm
   Zwei Grenzen, mehr nicht. Regeln dazu in standards/09-responsive.md.

   Breakpoints lassen sich in CSS nicht als Variable in @media benutzen — sie
   stehen deshalb hier als Zahl und NUR hier. Wer eine dritte Grenze braucht,
   hat wahrscheinlich ein Layoutproblem und keinen Breakpoint-Bedarf.

     1000px  halbes grosses Fenster · geteilter Bildschirm
      760px  halbes kleines Fenster · untere Grenze für Desktop-Muster
   ═══════════════════════════════════════════════════════════════════════════ */

@media (max-width: 1000px) {
  /* Die Sidebar klappt von selbst ein — der Inhalt braucht den Platz mehr.
     Gleiche Breite wie beim Einklappen von Hand, sonst hat dieselbe Leiste
     zwei eingeklappte Zustände. */
  .shell { grid-template-columns: 60px 1fr; }
  .shell .shell__nav { padding: var(--space-group) var(--space-stack); }
  .shell .shell__top { flex-direction: column; gap: var(--space-inset); }
  .shell .shell__top .btn-icon { margin: 0 auto; }
  .shell .shell__brand { padding: 0; }
  .shell .shell__nav { align-items: center; }
  .shell .nav-item,
  .shell .identity,
  .shell .shell__brand { justify-content: center; padding-left: 0; padding-right: 0; }
  /* Dieselbe Liste wie beim Einklappen von Hand — sonst hat dieselbe Leiste
     zwei verschiedene eingeklappte Zustände. */
  .shell .shell__name,
  .shell .nav-item__label,
  .shell .nav-item__count,
  .shell .nav-label,
  .shell .nav-group__head,
  .shell .identity__text,
  .shell .identity__switch { display: none; }

  /* Detail rutscht unter die Liste, statt auf ein Drittel zu schrumpfen.
     Ein Detail-Panel unter ~320px zeigt nichts mehr, es deutet nur an.
     Untereinander kehrt sich die Scroll-Zuständigkeit um: jetzt scrollt die
     ganze Fläche, und die Meisterspalte hört auf, ein eigenes Scrollfeld zu
     sein. Bliebe sie eins, wäre die Liste ein kurzer Kasten mit dem Detail
     darunter und man scrollte in zwei Ebenen übereinander.
     Aus intelli-os, die dieselbe Umkehr an derselben Grenze fahren. */
  .l-master-detail { grid-template-columns: 1fr; overflow-y: auto; }
  .l-master-detail__master { overflow: visible; }

  /* Der Filterstreifen des Stroms legt sich als Zeile darüber. */
  .l-stream { grid-template-columns: 1fr; }
  .l-stream__side { flex-direction: row; flex-wrap: wrap; }

  /* Nebeneinander vergleichen geht hier nicht mehr — eine Pane bleibt. */
  .l-workspace { flex-direction: column; }
  .divider { width: 100%; height: var(--space-stack); cursor: row-resize; }
  .divider::before { width: 32px; height: 3px; }
}

@media (max-width: 760px) {
  .toolbar { flex-wrap: wrap; }
  .toolbar .search { order: 3; flex-basis: 100%; max-width: none; }
  .content { padding: var(--space-group) var(--space-group); }

  /* Die Tabelle scrollt waagrecht, statt Spalten zu quetschen. Ein Wert, der
     nicht mehr lesbar ist, ist schlechter als einer, den man erreichen muss. */
  .panel .table { display: block; overflow-x: auto; white-space: nowrap; }

  .l-stats { grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); }
}

/* ═══════════════════════════════════════════════════════════════════════════
   SORTIEREN — Einfüge-Linie und Ziel-Vorschau
   Zwei Fragen beim Ziehen: wo darf ich hin (alle Ziele leise) und an
   welche Stelle (die Linie). Aus intelli-os.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Hülle um jedes sortierbare Element. Trägt nur die Vorschau-Linie und
   NICHTS SONST — kein Padding, kein Rand. Hätte sie eigenen Abstand, rutschte
   beim Ziehen die ganze Liste. */
.slot { position: relative; }

.slot[data-insert]::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  /* Strichstärke, kein Abstand — deshalb px. */
  height: 2px;
  /* 1px auf einer 2px-Linie sind runde Enden — das IST die Pille. */
  border-radius: var(--radius-pill);
  background: var(--accent);
  /* Über dem Korn der Nachbarkarten, unter allem Schwebenden. */
  z-index: 2;
}

/* In die Lücke gezogen: die Linie sitzt zwischen zwei Karten, nicht auf einer
   von beiden. Aus intelli-os (.cardslot.drop-before/-after). */
.slot[data-insert="before"]::after { top: calc(var(--space-inline) * -1); }
.slot[data-insert="after"]::after  { bottom: calc(var(--space-inline) * -1); }

/* Behälter-Zustand: gesetzt, solange gezogen wird. Kein Ziel muss davon wissen. */
.l-board[data-dragging] .l-board__col {
  /* Innenrand: verschiebt nichts. Ein Aussenrand würde die Spalte um seine
     Breite wachsen lassen und die Nachbarn schieben. */
  box-shadow: var(--shadow-raised), inset 0 0 0 1px var(--border);
}

/* Das Ziel unter dem Zeiger — Fläche UND Rand, damit es sich von den anderen
   nicht nur um eine Nuance unterscheidet. */
.l-board[data-dragging] .l-board__col[data-drop] {
  background-color: var(--accent-surface);
  box-shadow: var(--shadow-raised), inset 0 0 0 2px var(--accent);
}

/* Die gezogene Karte bleibt an ihrem Platz und wird blass. Sie zu entfernen
   lässt die Liste zusammenspringen — dann verliert man beim Ziehen den Bezug,
   woher die Karte kam. */
.kcard[data-dragging] { opacity: 0.45; }

/* Die Filterspalte des Stroms ist selbst ein Ablageziel: einen Eintrag auf ein
   Projekt ziehen ordnet ihn dort ein. Dieselben zwei Stufen wie am Board und in
   der Swimlane — alle gültigen Ziele leise, das unter dem Zeiger laut. Ohne die
   erste Stufe rät man, wohin man überhaupt ziehen darf.
   Aus intelli-os (`.npanel.dragtargets .prow`).
   Der Zustand sitzt an der Filterspalte und nicht am Strom: er gehört an den
   Behälter der ZIELE, so wie `[data-dragging]` am Board an `.l-board` sitzt
   und nicht an der Pane darüber. */
.l-stream__side[data-dragging] .nav-item {
  box-shadow: inset 0 0 0 1px var(--border);
}
/* Der Eintrag „Alle" ist die ungefilterte Sicht und kein Projekt — er kann
   nichts aufnehmen und darf deshalb auch nicht leuchten. Ihre Fassung nennt ihn
   beim Namen (`.prow.allnotes`); bei uns sagt es das Markup, weil das CSS nicht
   wissen kann, welcher Eintrag der unfilterte ist. */
.l-stream__side[data-dragging] .nav-item[data-nodrop] {
  box-shadow: none;
}
.l-stream__side[data-dragging] .nav-item[data-drop] {
  background: var(--accent-surface);
  box-shadow: inset 0 0 0 2px var(--accent);
}

/* ═══════════════════════════════════════════════════════════════════════════
   SWIMLANE-BOARD — Zeile je Träger, Spalte je Status
   Wenn nicht nur der Status zählt, sondern auch, zu wem etwas gehört.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Anzahl Statusspalten kommt von aussen: style="--lanes: 5". Sonst müsste das
   CSS die Zustände des Fachbereichs kennen.
   Genau deshalb muss hier auch die WAAGRECHTE Antwort stehen, die ihre Fassung
   nicht braucht: sie schreiben fünf Spalten fest, bei uns kann es zwölf geben.
   Die Statusspalten haben eine Untergrenze (147), also wächst das Raster über
   die Fläche hinaus, sobald es zu viele sind — ohne `overflow-x` wären die
   hinteren Spalten unerreichbar. Es scrollt, wie das Spalten-Board auch, und
   braucht aus demselben Grund keinen Breakpoint. */
.l-lanes { --lanes: 5; flex: 1; overflow-y: auto; overflow-x: auto; min-height: 0; }

.l-lanes__head,
.l-lanes__row {
  display: grid;
  /* Erste Spalte fest (die Gruppe), Statusspalten elastisch mit Untergrenze —
     ohne die Untergrenze werden die Karten bei vielen Spalten zu Streifen. */
  grid-template-columns: 179px repeat(var(--lanes), minmax(147px, 1fr));
  gap: var(--space-inset);
}

/* Der Spaltenkopf steht einmal ganz oben und teilt sich das Grid der Zeilen. */
.l-lanes__head {
  padding: var(--space-stack) 0;
  font-size: var(--text-0);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-0);
  text-transform: uppercase;
  color: var(--muted-foreground);
}

.l-lanes__group { margin-bottom: var(--space-section); }
.l-lanes__group-title {
  display: flex;
  align-items: center;
  gap: var(--space-stack);
  padding: var(--space-inline) 0 var(--space-stack);
  margin-bottom: var(--space-inset);
  border-bottom: 1px solid var(--border);
  font-size: var(--text-2);
  line-height: var(--leading-2);
  font-weight: var(--weight-semibold);
}

.l-lanes__row { margin-bottom: var(--space-group); }

.l-lanes__label { display: flex; flex-direction: column; gap: var(--space-inline); padding-top: var(--space-stack); }
.l-lanes__label-name {
  font-size: var(--text-2);
  line-height: var(--leading-2);
  font-weight: var(--weight-semibold);
}
.l-lanes__label-meta {
  font-size: var(--text-1);
  line-height: var(--leading-1);
  color: var(--muted-foreground);
}
/* Innerhalb einer Gruppe rücken die Zeilen ein — das ersetzt eine zweite
   Überschriftsebene. */
.l-lanes__group .l-lanes__label { padding-left: var(--space-inset); }

/* Die Zelle ist auch dann sichtbar, wenn sie leer ist: sie ist eine Aussage
   und ein Ablageziel. Eine unsichtbare leere Zelle ist beides nicht. */
.l-lanes__cell {
  display: flex;
  flex-direction: column;
  gap: var(--space-stack);
  padding: var(--space-stack);
  border-radius: var(--radius-2);
  background: var(--surface-hover);
  min-height: 55px;
}
/* Ablageziel: dieselbe Akzentflaeche wie an jeder anderen Ablage im System. */
.l-lanes__cell[data-drop] { background: var(--accent-surface); }

/* Ziel-Vorschau wie beim Spalten-Board — zwei Stufen. */
.l-lanes[data-dragging] .l-lanes__cell { box-shadow: inset 0 0 0 1px var(--border); }
.l-lanes[data-dragging] .l-lanes__cell[data-drop] {
  background: var(--accent-surface);
  box-shadow: inset 0 0 0 2px var(--accent);
}

/* Kleine Karte für die Zelle: dieselbe Karte, eine Stufe kompakter.
   KEINE zweite Kartenart. */
.kcard--compact {
  padding: var(--space-stack);
  gap: var(--space-inline);
  /* Die Zelle darunter ist selbst angehoben. Eine Karte im selben Ton wäre nur
     noch am Schatten zu erkennen — deshalb hier eine undurchsichtige, tiefere
     Grundfläche statt der durchscheinenden der grossen Karte. */
  background-color: var(--background);
}
.kcard--compact .kcard__title { font-size: var(--text-1); line-height: var(--leading-1); }
