/* ── App styles ──────────────────────────────────────────────────────────────
   Design-Tokens leben in wwwroot/css/tokens/*.css, der DS-Klassenkontrakt in
   wwwroot/css/components.css (+ components/*.css) — beide verbatim-Importe aus
   dem Design-System-Projekt, geladen VOR dieser Datei via _StyleLinks.cshtml.
   Der Klassenkontrakt kommt dabei über wwwroot/css/ds-layer.css in layer(ds);
   diese Datei bleibt ungelayert und schlägt das DS deshalb UNABHÄNGIG von der
   Spezifität (DS B0b) — eine bespoke Regel muss nicht mehr spezifisch sein.
   Hier stehen nur (noch nicht migrierte) bespoke Komponenten/Layout und
   ausschließlich var(--…) — keine rohen Farb-/Schatten-/Spacing-Literale
   (scripts/check-token-drift.sh, CLAUDE.md). Baseline-Reset (border-box,
   margin:0), Link- und Icon-Klassen liefert components/base.css — hier kein
   eigener Reset/Icon-Block mehr (DS B0). */

body {
  font-family: var(--font-sans);
  background: var(--bg);
  color: var(--text);
  height: 100vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  -webkit-font-smoothing: antialiased;
}

/* ── App-Header (DS B6) ─────────────────────────────────────────────────────
   Die Kopfzeile ist das DS-Organism `.app-header` (+ `.app-brand`, `.app-nav`,
   `.nav-item`, `.app-header-actions`, `.app-menu-btn`) — hier stehen nur noch
   Layout-Reste, die das DS nicht modelliert. Der frühere bespoke Zwilling war
   ein **bare `header`-Element-Selektor**: ungelayert schlug er `layer(ds)`
   unabhängig von der Spezifität und hätte `position: relative` (killt sticky),
   `--glass` und sein Padding in jedes `.app-header` geleakt. Er ist ersatzlos
   gelöscht — wer hier wieder einen Element-Selektor aufmacht, reißt das auf.
   Die `.topnav`-Familie (Rücklink-Leiste der Unterseiten, dazu vier
   Handkopien im Razor) ist mit derselben Karte entfallen. */

/* Die Shell ist `body { height:100vh; overflow:hidden; flex-direction:column }`
   — ohne flex-shrink quetscht der Header, wenn der Inhalt drängt. Das `sticky`
   des DS ist in dieser Shell wirkungslos, aber harmlos. */
.app-header {
  flex-shrink: 0;
  /* Umbrechen statt überlaufen — und zwar OHNE Breakpoint. Die Board-Kopfzeile
     trägt Brand, Nav, die Kontextzeile und den Aktionsblock; nichts davon
     schrumpft (alles nowrap oder fix breit). Ohne Wrap ragt sie bei schmalen
     Fenstern heraus, und weil die Shell `overflow: hidden` hat, wird sie dort
     schlicht abgeschnitten — der Avatar am rechten Ende wäre unerreichbar.
     Mit Wrap rutscht stattdessen der Aktionsblock in eine zweite Zeile,
     genau dann wenn sie muss. Das ist robuster als ein geratener Breakpoint:
     die nötige Breite hängt an der Namenslänge und an der Sprache.
     (Die Suche zählte bis DS B12 mit — sie steht jetzt in der `.toolbar`; die
     Kopfzeile ist dadurch schmaler geworden, der Wrap bleibt trotzdem nötig.) */
  flex-wrap: wrap;
  row-gap: var(--space-8);
}

/* Kontextzeile des Boards. Sie trug bis DS B12 auch die Statistik
   („3 aktiv von 12") — die steht jetzt im `.toolbar-count`, wo sie auf die
   Filter reagieren kann; hier bleiben Rolle · Name. Sie kann seither leer sein
   und rendert darum bedingt (`x-show` in _Header.cshtml).
   --text-2 statt --text-3: bei --type-12 liegt --text-3 auf 4.19:1 und damit
   unter AA (bekannter DS-Befund). */
.app-header-meta {
  font-size: var(--type-12);
  color: var(--text-2);
  white-space: nowrap;
  /* Sicherheitsnetz gegen sehr lange Namen: sie gibt als erste nach, statt die
     Werkzeugleiste aus dem Band zu drücken. Ohne min-width:0 schrumpft ein
     Flex-Item mit nowrap-Text gar nicht. */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Kontext-Modus: Ort der Seite (Firma · Stelle · Ansicht). Kürzen statt die
   Aktionen rechts zu verdrängen. */
.app-header-title {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  min-width: 0;
  font-size: var(--type-14);
  font-weight: var(--weight-semibold);
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  /* Nähe: Rücklink und Ort beantworten dieselbe Frage („wo bin ich, wie komme
     ich raus"), die Brand ist eine dritte, globale Sache. Als gleichabständige
     Kinder des .app-header (24px) läsen sie als drei Einzelteile — vorher trug
     .topnav-left diese Klammer. Sie kommen darum auf die engste Stufe (10px);
     die volle Stufe bleibt zwischen Brand und dieser Gruppe. Ein Wrapper ginge
     nicht: der Rücklink kommt aus dem Partial, der Titel aus der Seite. */
  margin-left: calc(var(--space-10) - var(--space-24));
}
/* text-overflow gehört an die Kinder, nicht an den Flex-Container — dort wäre
   es wirkungslos. Die Kürzung leistet allein diese Regel. */
.app-header-title > span { overflow: hidden; text-overflow: ellipsis; }
/* --text-2, nicht --text-3: der Trenner ist echter Text, und --text-3 liegt hier
   auf 4.19:1 (der bekannte DS-Rollen-Befund). Leiser als der Titel bleibt er
   trotzdem — der trägt --text in semibold (15.8:1 gegen 7.6:1). Zusätzlich
   aria-hidden im Markup: vorgelesen trägt der Punkt nichts bei. */
.app-header-sep { color: var(--text-2); font-weight: var(--weight-regular); }

/* Der Rücklink ist strukturell ein Nav-Eintrag und nimmt darum das DS-`.nav-item`;
   er trägt nur den engeren Abstand zum Chevron. */
.app-header-back { gap: var(--space-4); }

/* Verankerung des Mobil-Menüs an seiner Affordance (Muster .avatar-menu, B4).
   Der Wrapper schaltet mit — nicht nur der Button darin: als unbedingtes
   inline-flex bliebe er oberhalb --bp-md ein 0px-breites Flex-Item im
   .app-header und der 24er-Gap zählte beidseitig, also 48px statt 24 zwischen
   Nav und Kontextzeile. Der Toggle spiegelt darum exakt den des DS-Buttons.
   Name mit -nav-: das DS beansprucht mit B6 den .app-*-Präfix, und ein
   künftiges DS-`.app-menu` (das Panel zum .app-menu-btn) wäre der naheliegende
   nächste Schritt — dann läge hier eine additive Leak-Kollision. */
.app-nav-menu-wrap { position: relative; display: none; align-items: center; }
.app-nav-menu { position: absolute; top: calc(100% + var(--space-6)); left: 0; min-width: 180px; }

/* gap: die Affordancen tragen den DS-Zähler `.counter`, der 4px über die rechte
   Ecke ragt — bei 6px blieben nur 2px Luft zum Nachbarn. Platzierung gehört der
   Fläche, nicht dem Atom (dasselbe in .modal-header-actions).
   Die frühere `border-left`-Trennlinie je Gruppe ist mit DS B6 entfallen: im
   ruhigeren DS-Band lasen drei Haarlinien als Lärm. Die Gruppierung trägt jetzt
   allein der Weißraum — Staffel 8 innerhalb < 16 zwischen den Gruppen
   (.app-header-actions unten), also Gestalt-Nähe statt Strich. */
.header-group {
  display: flex;
  align-items: center;
  gap: var(--space-8);
}
/* Auf die Flächen gescopet, die tatsächlich Cluster tragen: das DS modelliert
   in .app-header-actions eine flache Reihe, die App hat dort eine Ebene mehr
   (.header-group). Ein pauschaler Override würde still gegen jede künftige
   DS-Änderung des Werts gewinnen — mit :has() gilt er nur, wo die Ebene
   existiert (Board und Lebenslauf), sonst der DS-Wert. Die Ebene selbst gehört
   perspektivisch ins DS: Karte „gruppierte Action-Zone im App-Header". */
.app-header-actions:has(.header-group) { gap: var(--space-16); }

/* Unter --bp-md tauscht das DS die Nav gegen .app-menu-btn. Was das DS nicht
   regelt, ist der Rest der Zeile: die Kontextzeile des Boards und der
   Kontext-Titel weichen (die Aktionen sind wichtiger als die Beschriftung),
   und die Aktionen dürfen umbrechen statt zu quetschen — vorher trug das die
   .cv-page-Regel bei 760px, jetzt gilt es für alle sechs Flächen bei 768. */
/* Zweite Stufe oberhalb von --bp-md: die Board-Kopfzeile trägt Brand, Nav,
   Kontextzeile UND den vollen Aktionsblock in einer nicht umbrechenden Zeile.
   Zwischen ~769 und ~1100px reichte das nicht — gemessen bis 365px Überlauf,
   weil weder Brand noch Kontextzeile umbrechen. Eine halb abgeschnittene
   Kontextzeile wäre wertlos, also geht sie hier ganz: unter Laptop-Breite
   zählen die Aktionen, nicht der Name daneben.
   (Der Überlauf war vor DS B12 größer — damals stand auch das fix breite
   Suchfeld hier. Die Stufe bleibt trotzdem: Rolle + Name sind auf Laptop-
   Breite das Erste, was verzichtbar ist.)
   Der Wert liegt bewusst ÜBER der Breite, bei der der Wrap greifen würde
   (gemessen ~1150) — sonst bräche die Zeile erst um und spränge 50px später
   wieder zusammen. Die Kontextzeile weicht zuerst, der Wrap ist das Auffangnetz
   darunter. */
@media (max-width: 1200px) {
  .app-header-meta { display: none; }
}

@media (max-width: 768px) { /* --bp-md */
  .app-header { padding-block: var(--space-8); }
  .app-header-meta,
  .app-header-title { display: none; }
  .app-nav-menu-wrap { display: inline-flex; }
  /* Nur die Zeilenrichtung entspannen: ein pauschales gap:8 setzte den Abstand
     ZWISCHEN den Gruppen auf denselben Wert wie den INNERHALB — und damit
     kollabierte auf Mobil genau die Staffel, die seit dem Wegfall der
     Trennlinien das einzige Gruppierungssignal ist. */
  .app-header-actions { flex-wrap: wrap; row-gap: var(--space-8); }
}

/* ── Tooltip (DS B14) ──────────────────────────────────────────────────────
   Der Tooltip trägt den DS-Kontrakt `.tooltip` > `.tooltip-bubble`
   (components/ergaenzungen.css). Die bespoke Familie `.tooltip-wrap` /
   `.tooltip` / `.tooltip::before` ist gelöscht — sie war ein NAMENSZWILLING
   mit invertierter Semantik (App: `.tooltip` = Blase, DS: `.tooltip` =
   Wrapper) und leakte bereits additiv: das DS-`display: inline-flex` griff
   auf die bespoke Blase durch, weil deren Regelblock kein `display` setzte.
   Genau der Restfall, vor dem `ds-layer.css` warnt.

   Der Gewinn ist nicht die Optik, sondern der Fokus: die bespoke Fassung
   hing allein an `:hover` — per Tastatur gab es den Tooltip nie. Das DS
   zeigt ihn auch bei `:focus-within`, verzögert über die Motion-Tokens
   (und damit unter `prefers-reduced-motion` sofort).

   Der Pfeil entfällt ersatzlos (das DS führt keinen), style.css schreibt
   für den Tooltip nichts mehr — die Platzierung nach unten trägt der
   DS-Modifier `.tooltip-below`, nicht ein App-Override. */

/* ── Toolbar (DS B12) ──────────────────────────────────────────────────────
   Suche, Filter, Sortierung und Zähler tragen das DS-Pattern (.toolbar,
   .toolbar-search, .toolbar-count, .toolbar-filters — components/patterns.css).
   Die bespoke Suchfamilie .search-wrapper/-icon/-input/-clear ist mit dieser
   Karte gelöscht; sie war eine randlose Inline-Pille in der Kopfzeile und hatte
   im DS keinen Zwilling. Hier bleiben nur die Reste, die das DS nicht
   modelliert: die Einbettung in die Shell und das Auswahl-Häkchen im Menü. */

/* Die Zeile sitzt zwischen Kopfzeile und Board in einer Shell ohne Body-Scroll
   (body: height:100vh; overflow:hidden; flex-direction:column) — ohne
   flex-shrink:0 quetscht sie, sobald das Board drängt. Dieselbe Regel und
   derselbe Grund wie beim .app-header. Das seitliche Padding fluchtet mit dem
   des .board darunter, damit Suchfeld und erste Spalte auf einer Linie stehen. */
/* Nähe: die zwei Toolbar-Zeilen sind EINE Gruppe (8px innen) und stehen von
   Kopfzeile wie Board gleich weit ab (16px — unten liefert die 16 das Padding
   des .board). Wer eine der Stufen anfasst, prüft die andere mit: mit
   12/10/16 lagen die beiden Zeilen fast so weit auseinander wie die Gruppe
   von der Kopfzeile, und die Chips lasen als eigene Leiste statt als
   Fortsetzung der Toolbar. */
.toolbar {
  flex-shrink: 0;
  padding: var(--space-16) var(--space-16) 0;
}
.toolbar-filters {
  flex-shrink: 0;
  padding: var(--space-8) var(--space-16) 0;
}

/* Das Suchfeld nimmt die freie Breite (PO 2026-07-27). Das DS gibt ihm
   `flex: 0 1 280px` — gedacht für eine Toolbar über einer schmalen Liste. Hier
   ist die Zeile eine eigens aufgeklappte Such-/Filterfläche über einem Board,
   das die volle Fensterbreite füllt; ein 280px-Feld sähe darin verloren aus.
   `min-width: 180px` (DS) bleibt und trägt den Umbruchfall. */
.toolbar-search { flex: 1 1 auto; }

/* Leeren-Knopf der Suche — App-Zubehör, das DS-`.toolbar-search` führt nur das
   eingelegte Icon links. Spiegelbildlich dazu rechts, das Feld gibt ihm rechts
   Platz (links macht das DS dasselbe für die Lupe).

   ⚠️ Der DS-Selektor `.toolbar-search .icon` ist ein NACHFAHRE, kein Kind: er
   trifft auch das ✕ in diesem Button und legte es sonst absolut auf die Lupe.
   Die drei Rücknahmen unten sind darum load-bearing, nicht Kosmetik. */
.toolbar-search .field-input { padding-right: var(--space-32); }
.toolbar-search-clear {
  position: absolute;
  right: var(--space-6);
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--text-3);
  cursor: pointer;
}
.toolbar-search-clear:hover { color: var(--text); background: var(--bg-hover); }
.toolbar-search-clear:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.toolbar-search-clear .icon {
  position: static;      /* s. ⚠️ oben */
  transform: none;
  pointer-events: auto;
  color: currentColor;
}

/* `--text-3` reißt auf diesen zwei Flächen AA — gemessen am gerenderten OKLCH,
   nicht aus den HEX-Fallbacks gerechnet:

     .toolbar-count  13px auf --bg      3.76:1  → --text-2: 6.82:1
     .menu-label     12px auf --surface 4.19:1  → --text-2: 7.61:1

   Der Zähler ist die EINZIGE Ansage, dass ein Filter gerade etwas ausblendet
   („Filter verstecken nie stumm") — ein Pflichthinweis darf nicht der leiseste
   Text der Seite sein. Und `.menu-label` hatte bis B12 keinen einzigen Nutzer:
   wer eine ungenutzte Klasse in Betrieb nimmt, führt ihren Kontrastfehler NEU
   ein, statt einen bestehenden zu erben (`.menu-item-meta` steht mit denselben
   4.19:1 direkt daneben und bleibt bewusst unangetastet, weil er Bestand ist).

   ⚠️ ÜBERGANGSZUSTAND, kein Dauerrest. Das ist die Gegenrichtung zu B9 — dort
   wurde eine *bespoke* --text-3-Zeile gelöscht und der DS-Wert stehen gelassen,
   hier wird ein *DS-Wert überschrieben*. Richtig gelöst wird das im DS
   (Governance-Pfad wie `.menu-item-text` in 1.12.0 und `.table-row-interactive`
   in 1.13.0); Karte „DS: --text-3-Rollen-Befund" trägt es. Bis zu dem Sync ist
   `.menu-label` auf die Toolbar gescopet, damit der Override nicht still gegen
   jedes künftige Menü der App gewinnt. Beide Zeilen fallen mit dem Sync weg. */
.toolbar-count { color: var(--text-2); }
.toolbar-menu-pop .menu-label { color: var(--text-2); }

/* Anker für das Menü-Panel — Muster und Maße der Geschwister (.sync-menu,
   .avatar-dropdown, .app-nav-menu): der Anker ist die Affordance, nicht der
   Viewport, und der Versatz ist --space-6 wie bei allen dreien. */
.toolbar-menu { position: relative; }
.toolbar-menu-pop {
  position: absolute;
  top: calc(100% + var(--space-6));
  left: 0;
  min-width: 220px;
}

/* Auswahl im Menü. Sie hängt am ARIA-Zustand statt an einer eigenen Klasse,
   weil die menu()-Factory `.is-active` bereits mit dem Tastatur-Highlight
   belegt und sie beim Pfeiltasten-Gang über alle Einträge schiebt — ein
   zweiter Sinn darauf wäre still falsch, sobald jemand das Menü mit der
   Tastatur bedient. Ein Träger für eine Entscheidung, wie .is-current /
   aria-current in der Navigation.

   Das Häkchen bleibt bei „nicht gewählt" per visibility im Fluss stehen: mit
   display:none rutschten die Labels beim Umschalten um Icon + Gap (24px) nach
   links. Der Slot ist der Platzhalter, nicht das Icon.

   Gescopet auf das ERSTE Icon des Eintrags, nicht auf alle: ein Eintrag mit
   zusätzlichem Bedeutungs-Icon verlöre es sonst still, sobald er ungewählt ist.
   Und auf `.toolbar-menu-pop` gescopet, aus demselben Grund wie `.menu-label`
   zwei Blöcke höher: eine ungelayerte App-Regel auf einem DS-Kontrakt gewänne
   sonst still gegen jeden künftigen DS-Stand und gegen jedes andere Menü.

   ⚠️ Das DS führt für „Menü mit Einfachauswahl" keinen Kontrakt — wenn eine
   zweite Fläche ihn braucht, gehört er über den Governance-Pfad ins DS
   gehoben (wie .counter in 1.11.0), nicht hier ein zweites Mal abgeschrieben. */
.toolbar-menu-pop .menu-item[aria-checked="false"] > .icon:first-child { visibility: hidden; }
.toolbar-menu-pop .menu-item[aria-checked="true"]  > .icon:first-child { color: var(--accent-text); }

/* ── Statusfarbe ──────────────────────────────────────────────────────────
   `BoardConfig.Column.Color` liefert seit DS B7 den DS-Familiennamen statt eines
   Hex-Werts. Das Board und die Gast-Ansicht setzen daraus die DS-Klasse
   `.board-col-<familie>`; die beiden App-Flächen ohne DS-Pendant — der
   Pipeline-Balken im Wochenreport und der Verlaufs-Punkt im Modal — holen sich
   die Farbe hier. Der Umweg über eine Klasse ist der Punkt: ein Farbwert im
   Markup wäre themen-blind, die Tokens flippen mit `data-theme`.

   ⚠️ `--stage-fill` heißt so, weil es genau das liefert: den **Vivid** der
   Familie, also einen Flächenwert für `background`/`border-color` — nie einen
   Textwert. `color: var(--stage-fill)` ergäbe für Amber 1.99:1 auf `--bg`, den
   Fehler, gegen den die `-text`-Varianten existieren. Eine Textrolle bräuchte
   einen zweiten Kanal `--stage-text` aus den `--x-text`-Tokens.
   Neue Familie ⇒ hier UND in `BoardColumnsJsonTests` nachziehen. */
.stage-fill-accent  { --stage-fill: var(--accent); }
.stage-fill-cyan    { --stage-fill: var(--cyan); }
.stage-fill-green   { --stage-fill: var(--green); }
.stage-fill-amber   { --stage-fill: var(--amber); }
.stage-fill-red     { --stage-fill: var(--red); }
.stage-fill-indigo  { --stage-fill: var(--indigo); }
.stage-fill-neutral { --stage-fill: var(--text-3); }

/* ── Board ────────────────────────────────────────────────────────────────
   Fläche, Spaltenfond, Kopf-Typografie und die Statusfamilie kommen seit DS B7
   aus dem DS-Organism (.board / .board-col / .board-col-head / .board-col-title
   / .board-col-body, components/organisms.css). Hier bleiben nur die Zutaten,
   die das DS nicht modelliert: die vollflächige Shell, die Spaltendichte, der
   interne Spalten-Scroll und der Substufen-Aufbau. */
.board {
  flex: 1;
  /* Das DS setzt align-items:flex-start (Spalten so hoch wie ihr Inhalt) — auf
     einem Board mit sieben Spalten ist die Spalte aber selbst das Drop-Ziel und
     muss die volle Höhe füllen, sonst schrumpft die leere Spalte auf ihren
     Hinweistext zusammen und ist kaum noch zu treffen. Nicht „aufräumen": die
     Zeile stand bis B7 als DS-B0-Interim hier und ist jetzt Dauerzustand. */
  align-items: stretch;
  overflow-y: hidden;
  padding: var(--space-16);
  scrollbar-width: thin;
  scrollbar-color: var(--surface-3) transparent;
}
.board::-webkit-scrollbar { height: 5px; }
.board::-webkit-scrollbar-thumb { background: var(--surface-3); border-radius: 3px; }

/* ── Column ───────────────────────────────────────────────────────────────
   Dichte statt DS-Luft: das DS-.board-col ist 300px breit (Referenzfall 3–4
   Spalten), die App führt sieben Spalten in einer Shell ohne Body-Scroll —
   300px kosteten ~800px zusätzlichen Horizontal-Scroll. Substufen-Spalten
   tragen zwei Bahnen nebeneinander und damit die doppelte Breite. */
.board-col {
  flex: 0 0 210px;
  overflow: hidden;
  /* Die Hairline hielt der bespoke Zwilling, das DS-.board-col führt keine.
     Ohne sie trägt die Spaltenkontur allein den Fondwechsel --surface-2 gegen
     --bg — gemessen 1.027:1, praktisch unsichtbar. Wo Karten stehen, zeichnen
     die Karten die Spalte; die LEERE Spalte ist aber genau das Drop-Ziel, für
     das `align-items: stretch` oben die Höhe reserviert, und die verschwände
     sonst ganz. Ein subtraktiver Leak: beim Selektor-Diff unsichtbar, weil die
     Regel gelöscht und nicht überschrieben wurde. */
  border: 1px solid var(--border);
}
.board-col.has-substage { flex: 0 0 420px; }

.board-col-head { flex-shrink: 0; }

.board-col-title {
  flex: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* WIP-Zähler: Fond und Zustände (neutral/-red/-green) liefert das DS-.badge,
   hier bleibt nur die Zähler-Geometrie — feste Mindestbreite, damit die Spalten-
   köpfe nicht bei jedem Wechsel von "9" auf "10" springen. */
.col-badge {
  min-width: 22px;
  justify-content: center;
}


/* Das DS-.board-col-body ist ein Stapel mit Mindesthöhe; hier muss er zusätzlich
   die Restspalte füllen und intern scrollen — die Shell hat keinen Body-Scroll,
   eine überlange Spalte hätte sonst keinen Weg nach unten. */
.board-col-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding-bottom: var(--space-4);
}
.board-col-body::-webkit-scrollbar { width: 3px; }
.board-col-body::-webkit-scrollbar-thumb { background: var(--surface-3); border-radius: 2px; }

/* ── Substatus layout ────────────────────────────────────────────────────── */
/* Bahn↔Bahn muss weiter auseinander stehen als Karte↔Karte innerhalb einer Bahn
   (`.card-container`, --space-8) — sonst sagt der Weißraum das Gegenteil der
   Gruppierung. Bis DS B7 standen beide auf 6px: flach und mehrdeutig, aber noch
   nicht verkehrt herum. Wer eine der zwei Stufen anfasst, prüft die andere. */
.sub-row {
  display: flex;
  gap: var(--space-12);
  height: 100%;
}
.sub-col {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.sub-col .card-container {
  flex: 1;
}
/* ⚠️ Die Fallback-Bahn („Items ohne passende Substufe", _Board.cshtml) ist ein
   NACKTES .card-container direkt in der .sub-row — die Regel darüber greift für
   sie nicht, weil sie kein Kind einer .sub-col ist. Ohne diese Zeile gilt der
   Flex-Default `0 1 auto` mit `min-width: auto`: die Bahn kann nicht unter ihre
   Inhaltsbreite schrumpfen und drückt die echten Bahnen (die `min-width: 0`
   haben) auf NULL — gemessen 0px/0px gegen 378px, die Bahnen-Labels brechen
   dann buchstabenweise. Sie erscheint nur, wenn eine Stelle in einer Spalte mit
   Substufen keine passende Substufe trägt, also bei Datenlagen, die im Alltag
   selten sind; sichtbar geworden ist es 2026-07-30 an Demo-Stellen mit
   `sub_stage = NULL`. Die Bahn teilt die Zeile jetzt gleichberechtigt. */
.sub-row > .card-container {
  flex: 1;
  min-width: 0;
}

/* Wrapper for columns without sub-stage – mirrors sub-col layout */
.no-sub-wrapper {
  display: flex;
  flex-direction: column;
  min-height: 100%;
}
/* Reserve the same top strip as the DOING/READY lane labels (.sub-label) in
   sub-stage columns, so card tops align across plain and sub-stage columns.
   Height must track .sub-label — keep font-size + padding in sync. */
.no-sub-wrapper::before {
  content: '\00a0';
  font-size: var(--type-12);
  padding: 8px 4px 4px;
  flex-shrink: 0;
}
.no-sub-wrapper .card-container {
  flex: 1;
}

.sub-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--type-12);
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-3);
  padding: 8px 4px 4px;
}
.sub-label::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--sep);
  opacity: 0.6;
}
.sub-doing  .sub-label { color: var(--amber-text); }
.sub-ready  .sub-label { color: var(--accent-text); }
.sub-waiting .sub-label { color: var(--text-3); }

/* Sortable container — der eigentliche Kartenstapel. Er sitzt je nach Spalte in
   einer Substufen-Bahn oder im .no-sub-wrapper, trägt also den Stapel-Rhythmus
   des DS-.board-col-body an dessen Stelle. */
.card-container {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
  min-height: 28px;
}
/* ⚠️ `flex-shrink: 0` ist hier kein Feintuning, es hält eine Falle geschlossen.
   Der Stapel ist ein Spalten-Flex-Container mit gedeckelter Höhe (`.no-sub-wrapper
   .card-container { flex: 1 }`), seine Karten sind also Flex-Items. Ein Flex-Item
   darf normalerweise nicht unter seine Inhaltsgröße schrumpfen — das verhindert
   `min-height: auto`. Diese Automatik gilt aber NUR, solange `overflow: visible`
   ist; sobald sie es nicht ist, fällt die Mindesthöhe auf 0.
   Genau das trifft die gestreifte Kartenvariante `has-check-offline`: sie trägt
   seit DS B3 `overflow: hidden`, damit der 3px-Streifen die Kartenrundung
   mitmacht. Sie ist damit das einzige schrumpfbare Item im Stapel und schluckt
   in einer vollen Spalte das GESAMTE Höhendefizit — die Karte kollabiert auf
   ihre 2px Rahmen und ist unlesbar, aber weiter klickbar (sie ist seit B3 ein
   <button>). Real eingetreten 2026-07-23 in „Optionen" mit acht Karten und
   genau einer offline-markierten.
   ⚠️ Bis DS 2.0.0 waren es drei solche Varianten (`has-reminder`,
   `has-suggestion` kamen dazu); die beiden sind jetzt das `.card-signal`-Band
   und tragen kein `overflow` mehr. Die Regel bleibt trotzdem auf ALLE Karten
   gescopet und nicht auf die offline-Variante verengt: sie kostet nichts, und
   die nächste Karte mit `overflow` fällt sonst in dieselbe Falle.
   Die Spalte scrollt (`.board-col-body { overflow-y: auto }`) — Karten sollen
   also nie schrumpfen, sondern überlaufen. */
.card-container > .card { flex-shrink: 0; }
.archive-overflow {
  color: var(--text-3);
  font-size: var(--type-12);
  padding: 8px 4px;
  text-align: center;
  font-style: italic;
  opacity: 0.6;
  flex-shrink: 0;
}
.archive-toggle {
  background: none;
  border: none;
  color: var(--text-3);
  font-size: var(--type-12);
  padding: 8px 4px;
  text-align: center;
  cursor: pointer;
  width: 100%;
  font-style: italic;
  flex-shrink: 0;
}
.archive-toggle:hover { color: var(--accent-text); }
.archive-toggle:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: var(--radius-sm); }
/* Board-Spalten-Leere trägt seit DS B11 das DS-`.empty-sm` (Icon aus, kompakt).
   Was hier bleibt, ist der App-Anteil: die Board-Spalten sind schmäler als die
   vom DS gedachten Listen-Sektionen (Substufen-Spalten ~95px), darum das
   horizontale Inset zurücknehmen, damit ein einzeiliger Hint nicht buchstaben-
   weise bricht — ein Layout-Rest, den das DS nicht modelliert. Die toten
   bespoke Familien (.empty-sub, .empty-col, die .empty-col-Varianten und
   .empty-search) sind ersatzlos gelöscht (die letzten drei rendete kein Markup).

   ⚠️ Hier stand bis DS B12 ein `.empty-col-*` mit Stern-Slash — das schloss den
   Kommentar mitten im Satz, der Rest wurde zur Selektor-Prelude, und der
   Parser verwarf die Regel darunter samt Block. Diese als load-bearing
   dokumentierte Zeile lief also gar nicht. Kein Glob mit `*` direkt vor einem
   Slash in einen CSS-Kommentar schreiben. */
.board .empty-sm { padding-inline: var(--space-8); }

/* While a card is dragged over an empty column SortableJS inserts the
   placeholder into that column's container — the empty-state hint is then
   redundant clutter, so hide it for the hovered column only. */
.no-sub-wrapper:has(.is-drag-source) .empty-sm,
.card-container:has(.is-drag-source) .empty-sm {
  display: none;
}

/* ── Card ────────────────────────────────────────────────────────────────── */
/* Die Karte selbst ist seit DS B3 das DS-Molecule `.card.card-interactive` und
   seit DS B7 zusätzlich `.board-card` (Greif-Cursor, `.is-dragging`,
   `.is-drag-source`) — hier leben nur noch die Randstreifen, die Textzeilen und
   der eine Riegel, den das DS nicht kennen kann (siehe `transition: none`). */

/* Alpine setzt den Karten-Body per `x-if` ein, und der <template>-Knoten bleibt
   dabei als Element im DOM stehen — er steht also IMMER zwischen Header und
   Body. Das schlägt zwei strukturelle DS-Regeln auf einen Schlag, beide hier
   nachgezogen:
     1. `.card-header:last-child` (schließt eine Karte ohne Body unten) greift
        nie, weil dem Header noch das leere Template folgt.
     2. `.card-header + .card-body` (der engere Abstand Titel→Body, --space-10
        statt der vollen 16) greift nie, weil der Kombinator am Template
        scheitert — der Body behielte sonst still seine vollen 16px.
   Die Staffel Titel↔Body soll aber enger sein als Body↔Kartenrand. */
.card > .card-header:has(+ template:last-child) { padding-bottom: var(--space-16); }
.card > .card-header ~ .card-body { padding-top: var(--space-10); }

/* Der angehobene Klon am Zeiger trägt das DS-`.board-card.is-dragging`
   (Elevation lg, --z-raised, grabbing). Zwei Zutaten kann das DS nicht kennen,
   weil sie an SortableJS' Fallback-Modus hängen:

   1. `transition: none` ist load-bearing. SortableJS setzt den Klon bei jedem
      pointermove per Inline-`transform` neu und liest denselben transform
      danach zurück, um den nächsten Schritt zu rechnen. Eine transform-
      Transition auf der Karte lässt es einen veralteten Zwischenwert lesen —
      der Klon hinkt dem Cursor zunehmend hinterher. Aus demselben Grund hier
      auch kein eigenes `transform`: der gehört SortableJS.
   2. `opacity: 1` ist der Riegel gegen `.is-drag-source`. Der Klon ist ein
      cloneNode der Ursprungskarte, erbt also deren Klassen; SortableJS nimmt
      ihm die ghostClass heute selbst wieder ab. Das DS ordnet
      `.is-drag-source` aber NACH `.is-dragging` — landen beide je zusammen auf
      einem Knoten, bliche der fliegende Klon auf 0.4 aus. */
.board-card.is-dragging {
  opacity: 1;
  transition: none;
}

.card-company {
  font-size: var(--type-14);
  font-weight: var(--weight-semibold);
  line-height: 1.25;
  color: var(--text);
  flex: 1;
  /* Break a single word too long for the card width instead of letting it
     overflow into the relevance pill (e.g. "Personaldienstleistungen"). */
  overflow-wrap: break-word;
}
.card-position {
  font-size: var(--type-13);
  color: var(--text-3);
  margin-top: 2px;
  line-height: 1.3;
  font-weight: var(--weight-regular);
  overflow-wrap: break-word;
}

/* Relevanz pill */
/* Relevanz-Zahl in der Kartenkopfzeile: Chip vom DS-.badge, hier nur die
   Ausrichtung neben dem zweizeiligen Titel. */
.relevance { flex-shrink: 0; margin-top: 1px; }

/* Next step — der Abstand zu den Geschwistern kommt vom `gap` des DS-.card-body */
.card-next {
  font-size: var(--type-12);
  color: var(--text-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Der Archiv-Tag brauchte bis DS B3 eine eigene Zeilen-Klasse (`.card-archive`,
   nur `margin-top`); im DS-`.card-body` trägt das `gap` den Abstand — der Chip
   selbst ist seit DS B1b das DS-`.badge` aus `archiveBadgeClass()`. */

/* Changed — single relative-time line replacing the three-pill date row.
   `cardChangedLabel()` suppresses it for Stufe-1 cards so the urgency signal
   doesn't compete with itself. Hover surfaces the absolute date as a tooltip. */
.card-changed {
  font-size: var(--type-12);
  color: var(--text-3);
  font-weight: var(--weight-regular);
}

/* ── Modal / Sheet — App-Reste zum DS-Organism (DS B8) ─────────────────────
   Anatomie, Fond, Maß und die Akkommodations-Bewegung kommen aus dem DS
   (components/organisms.css): .modal-backdrop + .modal-layer > .modal mit
   -header/-title/-close/-body/-footer. Hier steht nur, was das DS NICHT
   modelliert. Ein neuer Modal-Zwilling in dieser Datei ist Drift, keine Option.

   Fast alle Reste unten haben EINE Ursache: das Entry-Modal ist das längste
   Formular der App und deckelt sich darum selbst auf 90vh mit eigenem
   Innenscroll, statt den `.modal-layer` scrollen zu lassen. Das ist rein
   additiv (das DS setzt weder max-height noch overflow) und es hält eine Falle
   geschlossen: ein Element, das in einem `place-items: center`-Scrollcontainer
   höher ist als dieser, wird nach OBEN aus dem Scrollbereich heraus zentriert —
   sein Kopf ist dann unerreichbar. Solange der Deckel greift, wird der Layer
   nie zum Scroller und die Falle nie scharf. Aus dem Deckel folgt der Rest:
   `.modal` ist ein Flex-Stapel, und Kopf, Tabs und Fuß dürfen darin nicht
   schrumpfen. */
.modal {
  max-height: 90vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  scrollbar-width: thin;
}
.modal::-webkit-scrollbar { width: 4px; }
.modal::-webkit-scrollbar-thumb { background: var(--surface-3); border-radius: 2px; }

.modal-header { flex-shrink: 0; }

/* Das DS-`.modal` bringt `animation: modal-in` mit (opacity + 12px + scale .98)
   — der gelöschte Zwilling hatte KEINE Animation, das ist also ein additiver
   Leak im Sinne der B1b-2-Lehre. Auf sechs Flächen ist er willkommen (er IST
   der Akkommodations-Eintritt des DS); auf dem Entry-Modal nicht: dort trägt
   den Auftritt der Karte↔Modal-Morph der View Transitions API, und
   `morphModal()` deckt das Blatt synchron im VT-Callback auf — die 450ms
   Scale/Translate liefen also GEGEN die Morph-Geometrie, im selben Fenster.
   Der Morph ist der Eintritt dieser Fläche; ohne API/mit reduzierter Bewegung
   öffnet sie bewusst sofort (s. morphModal()). */
.entry-morph-box { animation: none; }

/* Das DS-`.modal-close` ist auf „steht allein am Kopfrand" gebaut: 32×32 mit
   negativen Rändern als optische Randjustierung. Hier steht es als letztes
   Glied einer Reihe von 28px-`.btn-icon-header` — die Ränder zögen es aus der
   Gruppe heraus und hüben es gegen seine Nachbarn an. */
.modal-header-actions > .modal-close { margin: 0; }

/* Wo das DS ein einzelnes `.modal-title` erwartet, trägt das Entry-Modal eine
   Titel-GRUPPE: zweizeiliger Titel (Unternehmen + Position, beide Morph-Ziele)
   plus die ID-Zeile. Die Gruppe nimmt den freien Platz und darf schrumpfen,
   damit ein langer Firmenname ellipst statt die Kopf-Aktionen zu verdrängen. */
.modal-title-group { flex: 1; min-width: 0; }
.modal-title-id {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-top: var(--space-2);
  font-size: var(--type-12);
  color: var(--text-3);
}
.modal-title-id-copy {
  background: none;
  border: none;
  padding: 0 var(--space-2);
  line-height: 1;
  font: inherit;
  color: inherit;
  cursor: pointer;
}
.modal-title-id-copy:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* gap: s. .header-group — der DS-Zähler `.counter` auf dem Annotations-Button
   ragt 4px über die Ecke; 6px ließen nur 2px Luft. Hier zählt es doppelt, weil
   .btn-icon-header mit 28px der kleinste Träger ist.
   Kein Trennstrich vor dem Schließen-Knopf mehr: dieselbe Entscheidung wie bei
   den drei `.header-group`-Haarlinien in DS B6 — im ruhigeren Kopf las der
   Strich als Lärm, die Gruppierung trägt der Weißraum. */
.modal-header-actions {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  flex-shrink: 0;
}
/* Die runde, gefüllte Schließen-Affordanz der beiden verbliebenen Drawer
   (Posteingang, Erinnerungen). Die Modals tragen seit DS B8 das DS-`.modal-close`
   (transparent, Fond erst im Hover) — `.btn-close` bleibt bespoke, weil das DS
   kein Drawer führt und die Drawer in B8 bewusst keine Optikänderung bekommen.
   Sie wandert mit der Drawer-Karte, nicht vorher. */
.btn-close {
  width: 28px; height: 28px;
  border-radius: 50%;
  background: var(--fill);
  border: none;
  color: var(--text-2);
  font-size: 0.85rem;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: background 0.12s, color 0.12s;
  font-family: inherit;
}
.btn-close:hover { background: var(--fill-2); color: var(--text); }
.btn-close:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* Zweiter Träger des DS-Zählers `.counter` (components/atoms.css) neben
   `.icon-btn` — daher relative. Mit 28px der kleinste; wer die Blase im DS
   wachsen lässt, prüft zuerst hier. */
.btn-icon-header {
  position: relative;
  width: 28px; height: 28px; border-radius: 50%;
  background: var(--fill); border: none; cursor: pointer;
  color: var(--text-2);
  text-decoration: none;
  display: flex; align-items: center; justify-content: center;
  font-size: 0.85rem; line-height: 1;
  transition: background 0.12s, color 0.12s, transform 0.12s;
}
.btn-icon-header:hover { background: var(--fill-2); color: var(--text); transform: translateY(-1px); }
.btn-icon-header:active { transform: translateY(0); }
.btn-icon-header.active { background: var(--accent-soft); color: var(--accent-text); }
.btn-icon-header:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* Layout-Rest zur DS-`.tabs` (B14): das DS führt keinen Tab-SLOT im Modal — wo
   die Leiste sitzt, ist Sache der Fläche. Der DS-Kopf endet ohne Bodenabstand
   (`padding: 20 24 0`) und der DS-Body bringt seine 20px selbst mit; die Luft
   ZWISCHEN Kopf und Leiste muss die Leiste also selbst stellen. Ihr Inset
   spiegelt bewusst das horizontale Padding von `.modal-header`/`.modal-body`,
   sonst steht der Trennstrich schmaler als der Inhalt darüber und darunter.

   Gelöscht mit B14: `.modal-tabs button` (+ `:hover`, `.active`). ⚠️ Der
   Element-Selektor war (0,1,1) und hätte `.tab` (0,1,0) auch UNGELAYERT
   geschlagen — hier genügte kein Hinzufügen der DS-Klasse, der Zwilling musste
   weg. Mit ihm gehen zwei DS-Verstöße: der aktive Tab stand in `--accent-text`
   auf einem Accent-Strich (das DS: Ort ist kein Status, der aktuelle Tab trägt
   Tinte), und die Leiste zog ihren Trennstrich in `--border` statt `--sep`. */
/* ⚠️ Der `margin-top` ist NICHT frei wählbar, er trägt DS-Prinzip 2 („Nähe
   schafft Bedeutung"): die Leiste gehört zu dem Panel, das sie steuert, nicht
   zum Modal-Kopf darüber. Unter ihr liegen fest 20px (`.modal-body`-Padding,
   DS-eigen) — oben muss also MEHR stehen, sonst sitzt sie näher an dem, was sie
   nicht steuert. Bis DS B14 stand hier 16 und die Staffel war invertiert.
   24 ist die einzige passende Stufe: `--space-28` gibt es bewusst nicht, und 32
   wäre die Sektionsstufe. Sie deckt sich zugleich mit dem horizontalen Inset,
   die Leiste steht damit in einem 24er-Rahmen. Wer die 20 unten anfasst (oder
   das DS sie ändert), rechnet diese Zeile nach. */
.modal-tabs {
  padding: 0 var(--space-24);
  flex-shrink: 0;
  margin-top: var(--space-24);
}

/* Padding kommt vom DS; `flex: 1` lässt den Body im 90vh-Deckel den Rest der
   Höhe nehmen, damit Kopf und Fuß an ihren Enden bleiben. */
.modal-body { flex: 1; }


.modal-field-pair {
  display: flex;
  gap: var(--space-6);
}

.history-row-contents { display: contents; }

/* Form sections — the DS .form/.field markup lives inside these; the section
   only carries its label and the rhythm between sections. */
.form-section { margin-bottom: var(--space-32); }

.form-section-label {
  font-size: 0.64rem;
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-3);
  padding: 0 var(--space-4);
  margin-bottom: var(--space-10);
}

/* The box that holds a *list* plus its add-row (Erinnerungen, Kontakte, Punkte,
   Gespräche, Verlauf) — not a form. Reine Feld-Sektionen tragen stattdessen das
   DS-.form (B1c: der frühere .form-group-box war beides). Der Dateien-Kasten ist
   seit DS B13 der eine ohne Add-Zeile (die .dropzone steht darunter) — und der
   eine, der ganz verschwindet, wenn die Liste leer ist. */
.list-box {
  background: var(--surface-2);
  border-radius: var(--radius);
  border: 1px solid var(--border);
  overflow: hidden;
}
/* Im Entry-Modal (weißes Blatt) vertieft sich der Listenkasten eine Stufe, damit
   er gegen das Weiß liest — dasselbe Verhältnis wie Well zu Sheet. */
.entry-modal .list-box { background: var(--surface-3); }

/* Layout-Rest: Zahlenfelder sollen nicht über die volle Breite laufen. */
.field-num .field-input { max-width: 96px; }

/* Relevanz row */
/* Bewusste DS-Ausnahme (B1c): das DS modelliert keinen Range-Slider. Er steht
   seit B1c als Control *unter* seinem .field-label (vorher rechts in der
   iOS-Zeile) — daher linksbündig und ohne flex:1, das im Feld-Stack die Höhe
   dehnen würde statt der Breite. */
.relevance-ios {
  display: flex;
  align-items: center;
  gap: var(--space-10);
}
.relevance-ios input[type=range] {
  flex: 1;
  max-width: 160px;
  accent-color: var(--accent);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}
.relevance-ios input[type=range]:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.relevance-val {
  font-size: 0.85rem;
  font-weight: var(--weight-bold);
  color: var(--text);
  min-width: 20px;
  text-align: right;
}
/* The settings threshold slider ranges 0–10, so its readout can reach a
   two-digit "10.0". Reserve a fixed width there so the value can't widen
   and shove the track as the number changes. The shared modal relevance
   row only ever shows single-digit values, so it keeps the tighter default. */
.settings-subsection .relevance-val {
  min-width: 2.25rem;
}

/* ── Modal-Fuß — App-Reste zum DS ────────────────────────────────────────── */
/* Der DS-Fuß trägt Layout, Gap und seitliches Padding selbst und richtet nach
   rechts aus. Übrig bleibt die vertikale Zentrierung (der Fuß mischt volle und
   `-sm`-Buttons), der Schrumpf-Riegel aus dem 90vh-Deckel — und der Abstand
   nach oben.
   Der ist die Nähe-Entscheidung dieser Karte: die Haarlinie über dem Fuß ist
   mit dem DS entfallen (wie die im Kopf), also trägt Weißraum die Grenze allein.
   Der DS-Fuß beginnt bei `padding-top: 0`, womit der Body-Innenabstand von 20px
   den ganzen Abstand stellte — gemessen 20px im Confirm, 23px im CV-Editor, und
   damit knapp über Feld↔Feld (16). Eine Aktionszeile ist aber die stärkste
   Grenze im Modal, nicht die schwächste: 12 + 20 hebt sie auf die
   Sektionsstufe (32) der App-Staffel. Wer den Body-Innenabstand im DS ändert,
   rechnet diesen Wert nach. */
.modal-footer {
  align-items: center;
  flex-shrink: 0;
  padding-top: var(--space-12);
}
/* Die letzte Formularsektion gibt ihren Sektionsabstand ab: sonst summierte er
   sich im Entry-Modal mit Body-Padding und Fuß-Abstand auf 64px, während die
   sechs anderen Modals bei 32 stehen. Der Abstand zum Fuß gehört dem Fuß. */
.modal-body > .form-section:last-child { margin-bottom: 0; }
/* Bricht die Rechtsbündigkeit des DS-Fußes für genau ein Kind auf: die leise
   destruktive Aktion sitzt am linken Ende, weit weg von der Primäraktion.
   Sie steht im Markup VOR Abbrechen/Speichern — stünde sie dahinter, hinge die
   Primäraktion je nach Modus mal links, mal rechts (s. Kommentar dort). */
.btn-delete-wrap {
  margin-right: auto;
  display: flex;
  align-items: center;
}
.btn-delete-confirm {
  display: flex;
  align-items: center;
  gap: var(--space-6);
}

/* ── Confirm dialog (reusable, replaces native confirm()) ─────────────────── */
/* Liegt über dem Entry-Modal (--z-modal): der Confirm wird AUS dem offenen
   Modal heraus geöffnet. Backdrop und Layer teilen die Stufe, ihre Reihenfolge
   entscheidet die DOM-Position (Backdrop zuerst) — s. die z-index-Notiz in
   CLAUDE.md zur zweckentfremdeten --z-popover-Rolle. */
#confirm-dialog .modal-backdrop,
#confirm-dialog .modal-layer { z-index: var(--z-popover); }
.confirm-modal { max-width: 420px; }
.confirm-message {
  color: var(--text-2);
  font-size: 0.9rem;
  line-height: 1.55;
  white-space: pre-line;   /* renders the \n in multi-line messages (e.g. duplicate warning) */
}

/* ── Status History ─────────────────────────────────────────────────────── */
.history-list {
  display: flex;
  flex-direction: column;
  /* 14px side inset aligns the rows with the form rows above; the box itself
     carries no padding. Lives here as the single source of truth — was
     previously duplicated as an inline style on the element. */
  padding: 10px 14px 0;
}
.history-item {
  display: flex;
  gap: 12px;
  position: relative;
}
.history-item:not(:last-child)::before {
  content: '';
  position: absolute;
  left: 5px; top: 17px; bottom: -3px;
  width: 2px;
  background: var(--sep);
}
.history-dot {
  width: 12px; height: 12px;
  border-radius: 50%;
  flex-shrink: 0;
  margin-top: 4px;
  border: 2px solid var(--surface);
  box-shadow: 0 0 0 1.5px var(--sep);
  /* Statusfarbe des Ziel-Status (`.stage-fill-*`, s. oben). Der Fallback trägt die
     Zeile, wenn der Verlauf einen Status nennt, den BoardConfig nicht mehr
     kennt — eine alte status_changes-Zeile nach einer Spaltenumbenennung. */
  background: var(--stage-fill, var(--text-3));
}
.history-dot.dot-email {
  border-radius: 3px;
}
.history-dot.dot-incoming { background: var(--cyan); }
.history-dot.dot-outgoing { background: var(--green); }
.history-content {
  padding-bottom: 13px;
  flex: 1;
  min-width: 0;
}
.history-status {
  font-size: 0.84rem;
  font-weight: var(--weight-semibold);
  color: var(--text);
  margin-bottom: 1px;
}
.history-sub {
  font-size: 0.73rem;
  color: var(--text-3);
  margin-bottom: 3px;
}
.history-meta {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
}
.history-date {
  font-size: 0.71rem;
  color: var(--text-3);
}
/* E-Mail-Aktivitäten */
.history-item.is-email .history-icon {
  width: 12px; height: 12px;
  border-radius: 3px;
  flex-shrink: 0;
  margin-top: 4px;
  display: flex; align-items: center; justify-content: center;
  font-size: 9px;
  line-height: 1;
}
.history-betreff {
  font-size: 0.82rem;
  font-weight: var(--weight-semibold);
  color: var(--text);
  margin-bottom: 1px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.history-betreff a {
  color: inherit;
  text-decoration: none;
  transition: color 0.15s;
}
.history-betreff a:hover {
  color: var(--accent-text);
  text-decoration: underline;
}
.history-betreff a::after {
  content: ' ↗';
  font-size: 0.65rem;
  opacity: 0.45;
}
.history-betreff a:hover::after {
  opacity: 1;
}
.history-vorschau {
  font-size: 0.72rem;
  color: var(--text-3);
  margin-bottom: 3px;
  font-style: italic;
}
/* ── Punkte ──────────────────────────────────────────────────────────────── */
.points-list { padding: 0; }

/* List row — mirrors the kontakt-item pattern: category badge inline with the
   topic, drag handle + actions revealed on hover, subtle hover background. */
.punkt-item { border-bottom: 1px solid var(--sep); }
.punkt-item:last-child { border-bottom: none; }
.punkt-item:not(.is-editing):hover { background: var(--bg-hover); }
.punkt-view {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 38px;
  padding: 6px 12px;
}
.punkt-drag {
  flex-shrink: 0;
  width: 12px;
  text-align: center;
  cursor: grab;
  color: var(--text-3);
  opacity: 0;
  font-size: 0.8rem;
  line-height: 1.5;
  user-select: none;
  transition: opacity 0.15s;
}
.punkt-view:hover .punkt-drag { opacity: 0.5; }
.punkt-drag:active { cursor: grabbing; }
.punkt-body { flex: 1; min-width: 0; }
.punkt-top {
  display: flex;
  align-items: center;
  gap: 6px;
}
.punkt-thema {
  flex: 1;
  min-width: 0;
  font-size: 0.8rem;
  font-weight: var(--weight-medium);
  color: var(--text);
  line-height: 1.4;
  overflow-wrap: anywhere;
}
.punkt-notiz {
  font-size: 0.73rem;
  color: var(--text-3);
  margin-top: 2px;
  line-height: 1.4;
  white-space: pre-wrap;
}
.punkt-actions {
  flex-shrink: 0;
  display: flex;
  gap: 2px;
  opacity: 0;
  transition: opacity 0.15s;
}
.punkt-view:hover .punkt-actions { opacity: 1; }
.punkt-sortable-ghost  { opacity: 0.4; background: var(--surface-3); }
.punkt-sortable-chosen { background: var(--surface-2); }
.punkt-sortable-drag   { opacity: 0.9; }
/* Category icon — small round glyph badge (Information neutral, Frage accent).
   'i' / '?' mirror the annotation marker glyphs; full label is the tooltip. */
.punkt-badge {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  font-size: 0.66rem;
  font-weight: var(--weight-bold);
  line-height: 1;
}
.punkt-badge-information { color: var(--text-3); background: var(--surface-3); font-style: italic; }
.punkt-badge-question    { color: var(--accent-text); background: var(--accent-soft); }
/* Category picker — labelled icon segmented control, identical in add + edit.
   Both options stay visible (icon + text), the active one is a raised pill, so
   the choice is self-explanatory and discoverable. */
.punkt-type-seg {
  display: inline-flex;
  align-self: flex-start;
  gap: 2px;
  padding: 2px;
  background: var(--surface-3);
  border-radius: 8px;
}
.punkt-type-seg button {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 11px;
  font-size: 0.74rem;
  font-family: inherit;
  color: var(--text-3);
  background: none;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  transition: color 0.15s, background 0.15s;
}
.punkt-type-seg button:hover { color: var(--text); }
.punkt-type-seg button.active {
  color: var(--text);
  background: var(--surface);
  box-shadow: var(--shadow-xs);
}
.punkt-seg-ico {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  font-size: 0.6rem;
  font-weight: var(--weight-bold);
  line-height: 1;
}
/* Edit form within a list row. */
.punkt-item.is-editing { padding: var(--space-8) var(--space-12); }
.punkt-edit-btns {
  display: flex;
  gap: 6px;
  justify-content: flex-end;
}
/* Live point count appended to the "Punkte" section label. */
.points-count {
  margin-left: 5px;
  color: var(--text-3);
  font-weight: var(--weight-medium);
  text-transform: none;
  letter-spacing: 0;
}
/* ── Shared list "add" affordance ────────────────────────────────────────────
   One integrated, full-width list row (Apple-style) tinted green so the "+"
   reads instantly as the add action, vs. the red ✕ delete. Shared across every
   modal list (Punkte, Kontakte, Gespräche, Erinnerungen, Aktivität) so they all
   open their add form the same way. `.list-add-area` is the padded wrapper the
   form sits in, anchored at the bottom of the box (new items append).
   Dateien fehlen seit DS B13 in der Reihe: dort ist die Affordanz die .dropzone
   unter dem Kasten — Hochladen öffnet kein Inline-Formular. */
.list-add-area { padding: var(--space-8) var(--space-14) var(--space-4); }
/* Layout-Rest: die Add-Formulare im Listenkasten stapeln enger als das DS-.form
   (--space-16) — sie sind label-lose Wells in einem ohnehin schmalen Kasten.
   --space-12 statt der früheren --space-8: seit DS B10 bindet ein .field-Wrapper
   Control + Fehler auf --space-6, und gegen 8 wäre die Nähe-Staffel nur 2px
   (unter der Wahrnehmungsschwelle) — 6 < 12 trägt die Gruppierung sichtbar. */
.list-add-area .form { gap: var(--space-12); }
/* Shared compact action buttons for every add-form — right-aligned, smaller
   than the modal-footer pills so they sit lightly inside the list box. */
.list-add-area .btn-row {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}
.list-add-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 100%;
  min-height: 38px;
  padding: 6px 12px;
  border: none;
  /* Separator via shadow (not border) so it doesn't eat into the border-box
     height — keeps the content band exactly 38px like the list rows. */
  box-shadow: inset 0 1px 0 var(--sep);
  background: none;
  cursor: pointer;
  font-size: 0.8rem;
  font-weight: var(--weight-medium);
  font-family: inherit;
  color: var(--green-text);
  transition: background 0.15s;
}
.list-add-row:hover { background: var(--bg-hover); }
/* Tastaturfokus. Der Separator-Schatten muss mitgeführt werden, weil box-shadow
   die Basisregel komplett ersetzt. Der Radius greift nur, wenn die Zeile *kein*
   Randkind des Kastens ist — sonst gewinnt die spätere .list-box > :last-child-
   Regel (Kastenform), und genau das ist dort richtig.
   Seit DS B13 ist die Zeile überall ein <button>: der Datei-Upload war der eine
   <label>-Träger und ist zur .dropzone geworden, die den Ring selbst führt. */
.list-add-row:focus-visible {
  outline: none;
  box-shadow: inset 0 1px 0 var(--sep), var(--focus-ring);
  border-radius: var(--radius-sm);
}
/* Circle + "+" live in ONE svg (see markup) so a single renderer rasterises
   them together. With the old split — CSS `background`/`border-radius` circle
   under a separate svg "+" — the two layers could land on slightly different
   sub-pixel grids at fractional positions, so the "+" drifted *relative* to the
   circle (reads as off-centre, esp. in Safari). Co-rendering removes that drift
   in every browser. `color` tints the circle (fill: currentColor); "+" is white. */
.list-add-ico {
  flex-shrink: 0;
  display: block;
  width: 16px;
  height: 16px;
  color: var(--green-text);
}
.list-add-ico svg { display: block; width: 16px; height: 16px; }
/* ── Erinnerungen (modal · Allgemein) ────────────────────────────────────────
   Stored, user-created reminders: a done checkbox, a due date, an optional note.
   Row rhythm mirrors .punkt-item / .kontakt-item — a --sep divider between rows,
   --bg-hover on hover, and a hover-revealed ✕ delete (vs. the always-present check). */
/* First element inside the group box, like .points-list — no top hairline. */
.reminders-list { padding: 0; }
.reminder-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-8);
  padding: var(--space-8) var(--space-12);
  border-bottom: 1px solid var(--sep);
}
.reminder-row:last-child { border-bottom: none; }
.reminder-row:hover { background: var(--bg-hover); }
/* Ein *Wert* unter seinem Feld-Label (Blocker-Beginn), kein Hilfetext — .field-help
   meint in der DS-Anatomie die Erklärung unter dem Control. */
.blocker-since { font-size: var(--type-13); color: var(--text-3); line-height: var(--leading-ui); }

/* Layout-Rest: die Erledigt-Box sitzt oben an der ersten Zeile ihres Eintrags,
   das Aussehen trägt das DS-.check. */
.reminder-check { flex-shrink: 0; margin-top: var(--space-2); }
.reminder-body { flex: 1; min-width: 0; }
.reminder-date {
  font-size: 0.8rem;
  font-weight: var(--weight-medium);
  color: var(--text);
}
.reminder-note {
  font-size: 0.73rem;
  color: var(--text-3);
  margin-top: 2px;
  line-height: 1.4;
  white-space: pre-wrap;
}
/* Done: struck through and muted, so open reminders stay the visual foreground. */
.reminder-row.is-done .reminder-date {
  text-decoration: line-through;
  color: var(--text-3);
}
.reminder-row.is-done .reminder-note { text-decoration: line-through; }
/* reminder-del: nur noch Reveal-Hook auf dem DS-Button (Zeilen-Hover zeigt).
   :focus-within mit DS B9 nachgezogen — ohne sie war der per Tastatur
   angesteuerte Löschen-Knopf ein unsichtbarer Fokus (dieselbe Lücke, die B9 in
   der Dateien-Tabelle schließt; dort ist diese Regel das zitierte Muster). Die
   rohe Dauer ist mit auf das Motion-Token gewandert: mit `0.15s` griff
   `prefers-reduced-motion` hier nie (die B4-Lehre). */
.reminder-del { flex-shrink: 0; opacity: 0; transition: opacity var(--duration-fast) var(--ease-out); }
.reminder-row:hover .reminder-del,
.reminder-row:focus-within .reminder-del { opacity: 1; }

/* ── Toast: Reste zum DS-.toast (DS B5) ──────────────────────────────────── */
/* Der Toast selbst ist das DS-Molecule (components/molecules.css). Hier bleiben
   nur zwei Dinge, die das DS nicht modelliert: die Zeile der optionalen
   Inline-Aktion (der Button darin ist das DS-Atom .btn) und der Abgang — das DS
   bringt nur die Eingangs-Animation mit. Beides über die Motion-Tokens, damit
   prefers-reduced-motion greift. */
/* Negativer Einzug = das horizontale Padding des .btn-sm: so steht das Label der
   Aktion auf der Textkante der Meldung darüber, nicht 12px daneben. */
.toast-actions { margin-top: var(--space-6); margin-left: calc(-1 * var(--space-12)); }
.toast.is-leaving {
  opacity: 0;
  transform: translateY(var(--space-4));
  transition: opacity var(--duration-fast) var(--ease-out),
              transform var(--duration-fast) var(--ease-out);
}

/* ── Card-Badges: Layout-Zeile der Karten-Chips ─────────────────────────── */
/* Nur der Container — die Chips selbst sind seit DS B1b-2 DS-.badges; der
   Abstand nach oben kommt vom `gap` des DS-.card-body. */
.card-badges {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
}

/* ── Randstreifen: das eine App-Signal ohne DS-Pendant ────────────────────
   Stelle offline (rot). Die drei anderen — Blocker, fällige Erinnerung,
   offener Vorschlag — sind seit DS 2.0.0 das `.card-signal`-Band im Kartenkopf
   und stehen hier nicht mehr: sie sind nach PRODUCT.md Pattern 1 Stufe-1-
   Signale und mussten auf Distanz lesbar werden, was ein 3px-Streifen nicht
   leistet. Offline bleibt bewusst Streifen — es ist eine Tatsache über die
   Anzeige, keine Handlungsaufforderung, und gehört damit nicht auf Stufe 1.

   Nebeneffekt des Umzugs: die frühere stille Falle ist weg. Alle drei Streifen
   teilten EIN `::before`, ihre Reihenfolge in dieser Datei entschied also den
   Vorrang — der Vorschlags-Streifen überschrieb den Reminder-Streifen, genau
   gegen Pattern 1. Der Vorrang ist jetzt explizit in `cardSignal()` (board.js).

   Als Pseudo-Element statt `border-left`: die DS-Karte trägt ihr Padding in
   `.card-header`/`.card-body`, ein echter Rahmen bräuchte also an jedem Kind
   eine Kompensation (früher am `.card` selbst: `padding-left: 9px` gegen
   3px Rahmen). Absolut positioniert kostet der Streifen kein Layout — und er
   überlebt Hover und Fokus, die beide den `box-shadow` der Karte belegen. */
/* `overflow: hidden` ist hier der eigentliche Kniff: der Streifen ist 3px breit
   und kann der --radius-Rundung der Karte nicht selbst folgen — ein eigener
   border-radius würde vom Browser aufs Breitenverhältnis heruntergerechnet
   (12px → ~1.5px) und liefe damit eckig an der Kartenecke vorbei. Beschnitten
   von der Karte übernimmt er ihre Form exakt, oben wie unten.
   ⚠️ Es hat einen zweiten, teureren Preis — es schaltet die automatische
   Mindesthöhe eines Flex-Items ab; dagegen steht `.card-container > .card
   { flex-shrink: 0 }` weiter unten. Wer diese Regel entfernt, prüft das mit. */
.card.has-check-offline { position: relative; overflow: hidden; }

.card.has-check-offline::before {
  content: '';
  position: absolute;
  inset: 0 auto 0 0;
  width: 3px;
  background: var(--red);
}

/* ── Sync split-button + dropdown ─────────────────────────────────────────
   Consolidates the four manual triggers (Email Check / Job Check / Kalender
   / Stellen-Check) into one header affordance. Pill shape mirrors the old
   .btn-action so the header rhythm stays familiar; the dropdown carries the
   per-item label + „zuletzt"-line + per-item spinner. */
.sync-group { position: relative; }

/* Split-Button-Extras auf DS .btn.btn-secondary.sync-btn (Basis kommt vom DS) */
.sync-btn.is-open { background: var(--fill-2); color: var(--text); }
.sync-btn.is-busy { color: var(--accent-text); }
.btn-sync-icon { display: block; }
.btn-sync-icon.is-spinning { animation: sync-spin 1s linear infinite; }
.btn-sync-caret { margin-left: 2px; opacity: 0.7; }

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

/* Layout-Rest zum DS-.menu (B4): nur die Verankerung am Trigger und die Breite,
   die die zweizeiligen Einträge brauchen. Fond, Rahmen, Elevation und Ebene
   kommen vom DS. */
.sync-menu {
  position: absolute;
  top: calc(100% + var(--space-6));
  right: 0;
  min-width: 240px;
}

/* Der laufende Eintrag dreht sein Icon — Verhaltens-Hook ohne DS-Pendant,
   gilt für jedes .menu-item (heute nur das Sync-Menü hat laufende Einträge). */
.menu-item .icon.is-spinning { color: var(--accent-text); animation: sync-spin 1s linear infinite; }

/* ── Results Modal ──────────────────────────────────────────────────────── */
/* Wie der Confirm über der Modal-Ebene: das Ergebnis eines Sync-Laufs kann
   auftauchen, während das Entry-Modal offen ist. */
#results-modal .modal-backdrop,
#results-modal .modal-layer { z-index: var(--z-popover); }
.results-modal { max-width: 520px; }

.results-loading {
  display: flex;
  align-items: center;
  gap: 10px;
  justify-content: center;
  padding: 40px 0;
  color: var(--text-2);
  font-size: 0.9rem;
}
.results-spinner {
  width: 18px; height: 18px;
  border: 2px solid var(--fill-2);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.results-error {
  background: var(--red-soft);
  border: 1px solid var(--red);
  border-radius: var(--radius);
  padding: 14px 16px;
  color: var(--red-text);
  font-size: 0.85rem;
}
.results-error strong { display: block; margin-bottom: 4px; }

.results-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
  gap: 12px;
  /* Bottom pads to the section rhythm below (--space-16 + the item's 4px = 20px),
     landing at 24px: the stats→list boundary stays the wider one without tearing
     a hole. Raising it here would out-gap the section separation it introduces. */
  padding: 8px 0 var(--space-8);
}
.results-stat { text-align: center; }
.results-stat-val {
  display: block;
  font-size: var(--type-24);
  font-weight: var(--weight-bold);
  color: var(--text);
  line-height: 1.2;
}
.results-stat-label {
  font-size: 0.72rem;
  color: var(--text-3);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.results-stat--error { color: var(--red-text); }

/* Section gap must beat the title→items gap (8px) — the job-check body can stack four
   of these (übersprungen / Duplikate / Importiert / Errors) and a flat rhythm reads
   them as one undifferentiated mass. Effective gap is 20px, not 16: the preceding
   .results-item adds its own 4px margin-bottom. Shared with email-check/calendar-sync. */
.results-list { margin-top: var(--space-16); }
.results-list-title {
  font-size: 0.78rem;
  font-weight: var(--weight-semibold);
  color: var(--text-2);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 8px;
}
.results-list-title--error { color: var(--red-text); }

.results-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  border-radius: var(--radius);
  background: var(--fill);
  margin-bottom: 4px;
  font-size: 0.83rem;
}
.results-item--error { background: var(--red-soft); color: var(--red-text); }
.results-item--skipped { background: var(--fill); color: var(--text-2); }
.results-item--warning { background: var(--amber-soft); color: var(--text-2); }
.results-item-link {
  font-size: 0.78rem;
  color: var(--accent-text);
  text-decoration: none;
  flex-shrink: 0;
  margin-left: auto;
}
.results-item-link:hover { text-decoration: underline; }
.results-item--stacked { align-items: flex-start; }
.results-item-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}
.results-item-text .results-item-company {
  white-space: normal;
  overflow-wrap: anywhere;
  flex-shrink: 1;
}
.results-item-reason {
  font-size: var(--type-12);
  color: var(--text-3);
  line-height: 1.3;
}
.results-item-company { font-weight: var(--weight-semibold); color: var(--text); flex-shrink: 0; }
.results-item-detail {
  color: var(--text-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

/* Dauer links, Knopf rechts: die Auto-Margin bricht die Rechtsbündigkeit des
   DS-Fußes für dieses eine Kind auf — dasselbe Muster wie `.btn-delete-wrap`
   (dort steht der Rest ebenfalls am linken Element, nicht am rechten). */
.results-duration {
  margin-right: auto;
  font-size: 0.78rem;
  color: var(--text-3);
}

/* Check-Ergebnis-Panel (ähnlich Email-Panel) */
.check-panel-overlay {
  position: fixed;
  inset: 0;
  background: var(--backdrop-tint);
  backdrop-filter: var(--backdrop-bokeh);
  -webkit-backdrop-filter: var(--backdrop-bokeh);
  z-index: var(--z-backdrop);
  display: block;
}
.check-panel {
  position: fixed;
  top: 60px; right: 16px;
  width: 360px;
  max-height: calc(100vh - 80px);
  background: var(--surface-2);
  border: 1px solid var(--sep);
  border-radius: 14px;
  box-shadow: var(--shadow-md);
  display: flex;
  flex-direction: column;
  z-index: calc(var(--z-backdrop) + 1);
  overflow: hidden;
}
.check-panel-header {
  padding: 14px 16px 10px;
  border-bottom: 1px solid var(--sep);
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.check-panel-title {
  font-size: 0.88rem;
  font-weight: var(--weight-semibold);
}
.check-panel-close {
  background: none;
  border: none;
  color: var(--text-3);
  font-size: 1.1rem;
  cursor: pointer;
  padding: 2px 6px;
}
.check-panel-close:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: var(--radius-sm); }
.check-panel-summary {
  padding: 10px 16px;
  font-size: 0.78rem;
  color: var(--text-2);
  border-bottom: 1px solid var(--sep);
  display: flex;
  gap: 14px;
}
.check-sum-item { display: flex; align-items: center; gap: 4px; }
.check-panel-list {
  overflow-y: auto;
  flex: 1;
  padding: 8px 0;
}
.check-panel-entry {
  padding: 8px 16px;
  border-bottom: 1px solid var(--sep);
  display: flex;
  align-items: flex-start;
  gap: 10px;
}
.check-panel-entry:last-child { border-bottom: none; }
.check-entry-icon { font-size: var(--type-16); flex-shrink: 0; margin-top: 1px; }
.check-entry-text { flex: 1; min-width: 0; }
.check-entry-company { font-size: 0.81rem; font-weight: var(--weight-semibold); }
.check-entry-pos { font-size: 0.72rem; color: var(--text-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ── Icon-Buttons (Header Info-Panels) — bespoke runde Affordance, die das DS
   nicht modelliert (Toggle-State + Zähler); .icon-btn statt .btn-icon, um
   die Kollision mit dem DS-Modifier .btn-icon aufzulösen (DS B1a).
   Trägt den DS-Zähler `.counter` (components/atoms.css) — daher relative. ───── */
.icon-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  background: var(--fill);
  border: none;
  border-radius: 50%;
  color: var(--text-2);
  cursor: pointer;
  font-family: inherit;
  transition: background 0.15s, color 0.15s;
}
.icon-btn:hover { background: var(--fill-2); color: var(--text); }
.icon-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.icon-btn svg { display: block; }

/* ── Email-Panel ─────────────────────────────────────────────────────────── */
.email-panel-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-backdrop);
  background: var(--backdrop-tint);
  backdrop-filter: var(--backdrop-bokeh);
  -webkit-backdrop-filter: var(--backdrop-bokeh);
  display: block;
}
.email-panel {
  position: absolute;
  top: 60px;
  right: 16px;
  width: 420px;
  max-height: 560px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xl);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.email-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 18px 12px;
  border-bottom: 1px solid var(--sep);
}
.email-panel-header h3 {
  font-size: 0.95rem;
  font-weight: var(--weight-semibold);
  color: var(--text);
}
.email-panel-body {
  overflow-y: auto;
  padding: 12px;
  flex: 1;
}
/* Classification badge — shared by the Vorschläge + Nicht-zugeordnet inbox sections. */


/* ── Suggestion-Panel ────────────────────────────────────────────────────── */
.suggestion-item {
  padding: 10px 0;
  border-bottom: 1px solid var(--sep);
}
.suggestion-item:last-child { border-bottom: none; }
.suggestion-item-top {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
}
.suggestion-item-company {
  font-weight: var(--weight-semibold);
  font-size: 0.82rem;
  color: var(--text);
}
.suggestion-item-subject {
  font-size: 0.78rem;
  color: var(--text-2);
  margin-bottom: 4px;
}
.suggestion-item-action {
  font-size: var(--type-12);
  font-weight: var(--weight-semibold);
  color: var(--accent-text);
  margin-bottom: 4px;
}
.suggestion-item-detail {
  font-size: 0.73rem;
  color: var(--text-3);
  margin-bottom: 2px;
}
.suggestion-item-actions {
  display: flex;
  gap: 8px;
  margin-top: 8px;
}

/* ── Reminder-Panel ──────────────────────────────────────────────────────── */
.reminder-item {
  background: var(--surface-2);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  margin-bottom: 8px;
  border-left: 3px solid var(--amber);
}
.reminder-item:last-child { margin-bottom: 0; }
.reminder-item-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 3px;
}
.reminder-item-company {
  font-size: 0.88rem;
  font-weight: var(--weight-semibold);
  color: var(--text);
}
.reminder-item-position {
  font-size: 0.78rem;
  color: var(--text-2);
  margin-bottom: 3px;
}
.reminder-item-meta {
  font-size: 0.72rem;
  color: var(--text-3);
}

/* Inbox/Reminder-Leere tragen seit DS B11 das DS-`.empty` (Voll-Variante mit
   Icon). Der Ladefehler bleibt ein Fehler (nie als „keine Daten" getarnt): er
   nimmt dieselbe `.empty`-Layoutstruktur, aber mit triangle-alert-Icon,
   Fehlertext und rot getöntem Titel über diesen Marker. */
.email-panel-error .empty-title { color: var(--red-text); }

/* ── Inbox sections (fused panel) ───────────────────────────────────────
   One panel, two sections: Vorschläge / Nicht zugeordnet. Each section
   reuses the same wrapper + header shape; the section-count pill picks its
   colour per section so the eye groups the right items without the panel
   having to label-shout. */
.inbox-section {
  margin-bottom: 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--sep);
}
.inbox-section:last-child {
  margin-bottom: 0;
  padding-bottom: 0;
  border-bottom: none;
}
.inbox-section-header {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.78rem;
  font-weight: var(--weight-semibold);
  color: var(--text-2);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin-bottom: 8px;
}
.inbox-section-title { flex: 1; }
.unassigned-item {
  background: var(--surface-2);
  border-left: 3px solid var(--amber);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  margin-bottom: 8px;
}
.unassigned-item:last-child { margin-bottom: 0; }
.unassigned-item-top {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
}
.unassigned-item-sender {
  font-size: 0.78rem;
  color: var(--text-3);
  flex: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.unassigned-item-subject {
  font-size: 0.85rem;
  color: var(--text);
  font-weight: var(--weight-medium);
  margin-bottom: 4px;
}
.unassigned-item-subject a { color: inherit; text-decoration: none; }
.unassigned-item-subject a:hover { text-decoration: underline; }
.unassigned-item-snippet {
  font-size: var(--type-12);
  color: var(--text-3);
  margin-bottom: 8px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.unassigned-item-actions {
  display: flex;
  gap: 6px;
  align-items: center;
}
/* Layout-Rest: die Zuordnungs-Auswahl füllt die Zeile neben ihren Buttons. */
.unassigned-select { flex: 1; }

/* Aussortierte (Decision = not_relevant) — quiet recovery surface for LLM
   false-negatives. Toggle is muted to discourage casual exploration; items
   keep the orange accent so the visual relationship to pending unassigned
   stays intact, just dimmed. */
.dismissed-toggle-row {
  margin: 12px 0 8px;
  text-align: left;
}
.dismissed-toggle-btn {
  background: transparent;
  border: none;
  color: var(--text-3);
  font-size: 0.78rem;
  cursor: pointer;
  padding: 4px 6px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.dismissed-toggle-btn:hover { color: var(--text-2); }
.dismissed-toggle-btn.is-open { color: var(--text-2); }
.dismissed-toggle-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.inbox-section--dismissed .unassigned-item {
  border-left-color: var(--text-3);
  opacity: 0.82;
}
.inbox-section--dismissed .unassigned-item:hover { opacity: 1; }

/* ── Wochenreport-Modal ──────────────────────────────────────────────────── */
[x-cloak] { display: none !important; }

/* Der Wochenreport ist ein MODAL, kein Drawer — „Panel" war ein Ablage-Begriff
   (er liegt in _Panels.cshtml neben den drei echten Drawern). Er blockiert die
   Seite mit einem Backdrop, ist ein zentriertes Blatt mit Kopf/Schließen/Body
   und mit 520px sogar schmaler als das DS-Modal. Seit DS B8 trägt er darum den
   DS-Kontrakt; hier bleibt nur die Deckelung auf 520px und der mitlaufende Kopf. */
.report-modal { max-width: 520px; }

/* Der einzige Modal-Kopf, der beim Scrollen stehen bleibt: der Report ist lang,
   und der KW-Bezug im Titel muss lesbar bleiben, während man durch die Woche
   scrollt. Der DS-Kopf endet ohne Bodenabstand — unter einem mitlaufenden Kopf
   liefe der Inhalt sonst direkt an ihm entlang, also Padding und Trennlinie hier. */
.report-modal .modal-header {
  position: sticky;
  top: 0;
  background: var(--surface);
  z-index: var(--z-raised);
  padding-bottom: var(--space-12);
  border-bottom: 1px solid var(--border);
}

/* Trägt den DS-`.modal-body`-Kontrakt (Markup: `class="modal-body report-body"`);
   übrig bleibt der engere Abstand nach oben, weil hier eine Trennlinie über dem
   Inhalt steht statt eines offenen Kopfes. */
.report-body { padding-top: var(--space-16); }

/* KW-Header mit Navigation */
/* Layout-Rest zur DS-`.tabs` (B14) — nur der Abstand zum Panel darunter. Die
   bespoke `.report-tab` (+ `:hover`, `.active`) ist gelöscht; ihre Farbwahl war
   als einzige der beiden Leisten schon DS-konform (Tinte statt Accent), die
   Leiste selbst zog ihren Strich aber in `1.5px solid var(--fill)` — ein roher
   Wert, den der DS-Kontrakt mit `1px solid var(--sep)` ablöst. */
.report-tabs { margin-bottom: var(--space-16); }
/* ÜBERGANGSZUSTAND, kein Layout-Rest (Muster `.toolbar-count` aus B12,
   `.dropzone-hint` aus B13): das DS stellt `.tab-count` auf --text-3 — auf dem
   --surface-Fond des Report-Modals bei 13px unter AA. Der bekannte
   --text-3-Rollen-Befund, gelöst wird er IM DS (Karte „DS: --text-3-Rollen-
   Befund"), nicht hier. Und er ist hier nicht zu erben: `.tab-count` war bis
   B14 ungenutzt, den Fehler in Betrieb zu nehmen hieße, ihn neu einzuführen.
   Bewusst NICHT auf `.report-tabs` gescopet — der Befund hängt am Token, nicht
   an dieser Leiste; eine zweite Leiste mit Zähler hätte dasselbe Problem.
   Leiser als das Tab-Label bleibt der Zähler über Größe (13 vs. 14px) und
   Gewicht (regular vs. medium), die beide vom DS kommen. */
.tab-count { color: var(--text-2); }
/* ⚠️ Der `margin-bottom` ist der Abstand ÜBER der Tab-Leiste und trägt damit
   dieselbe Nähe-Entscheidung wie der `margin-top` von `.modal-tabs`
   (DS-Prinzip 2): unter der Leiste stehen 16 (`.report-tabs`), hier müssen also
   mehr stehen, sonst gehört sie sichtbar zu keiner Seite. Bis DS B14 war es
   16/16 — symmetrisch und damit stumm. Die Staffel ist jetzt 24 oben / 16 unten,
   dasselbe Verhältnis, das die DS-Karte `Tabs.html` in ihrer eigenen Demo zeigt. */
.report-kw-header {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  margin-bottom: var(--space-24);
}
.report-kw-center {
  flex: 1;
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
}
.report-kw {
  font-size: 1.4rem;
  font-weight: var(--weight-bold);
  color: var(--text);
}
.report-kw-year {
  font-size: 0.85rem;
  color: var(--text-3);
  font-weight: var(--weight-regular);
}
.report-kw-range {
  font-size: 0.78rem;
  color: var(--text-3);
}

/* Kennzahlen */
.report-metrics {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  grid-auto-rows: 1fr;
  gap: 10px;
  margin-bottom: 20px;
}
.report-metric {
  background: var(--surface-2);
  border-radius: 10px;
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  position: relative;
}
.report-metric-val {
  font-size: var(--type-24);
  font-weight: var(--weight-bold);
  color: var(--text);
  line-height: 1.1;
}
.report-metric-lbl {
  font-size: 0.68rem;
  color: var(--text-3);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
/* Delta-Chip in der Ecke der Metrik-Kachel — Chip vom DS, Platzierung von hier. */
.report-metric-delta {
  position: absolute;
  top: var(--space-8);
  right: var(--space-10);
}

/* Sections */
.report-section { margin-bottom: 18px; }
.report-section-title {
  font-size: 0.72rem;
  font-weight: var(--weight-semibold);
  color: var(--text-3);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 8px;
}

/* Pipeline */
.report-pipeline { display: flex; flex-direction: column; gap: 8px; }
.report-pipe-item {
  display: grid;
  grid-template-columns: auto 130px 1fr auto;
  align-items: center;
  gap: 10px;
  font-size: 0.78rem;
}
.report-pipe-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--stage-fill, var(--text-3));
}
.report-pipe-label { color: var(--text-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.report-pipe-bar-wrap {
  height: 8px;
  background: var(--fill);
  border-radius: 4px;
  overflow: hidden;
}
.report-pipe-bar {
  height: 100%;
  border-radius: 4px;
  background: var(--stage-fill, var(--text-3));
  transition: width 0.4s ease;
}
.report-pipe-count { font-weight: var(--weight-semibold); color: var(--text); min-width: 20px; text-align: right; }

/* Termine */
.report-termin {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 8px;
  padding: 8px 10px;
  border-radius: 8px;
  cursor: pointer;
  font-size: 0.78rem;
  transition: background 0.15s;
}
.report-termin:hover { background: var(--bg-hover); }
.report-termin-date {
  color: var(--accent-text);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
}
.report-termin-company { color: var(--text); font-weight: var(--weight-medium); }
.report-termin-pos { color: var(--text-3); text-align: right; }

/* Nachfass */
.report-nachfass {
  background: var(--amber-soft);
  border: 1px solid var(--amber);
  border-radius: 10px;
  padding: 10px 14px;
  font-size: 0.8rem;
  color: var(--amber-text);
  margin-bottom: 18px;
}

.report-total-context {
  border-top: 1px solid var(--sep);
  padding-top: 12px;
  margin-top: 6px;
  font-size: var(--type-12);
  color: var(--text-3);
  text-align: center;
}

.report-perspective {
  border-left: 2px solid var(--green);
  padding: 8px 12px;
  font-size: 0.78rem;
  color: var(--text-2);
  margin-bottom: 18px;
}

/* Aktivitäten-Timeline */
.report-list-item {
  display: flex; align-items: center; gap: var(--space-8);
  padding: 6px 8px; border-radius: 8px; cursor: pointer;
  font-size: 0.78rem; transition: background 0.15s;
}
.report-list-item:hover { background: var(--bg-hover); }
.report-list-icon { display: flex; align-items: center; flex-shrink: 0; }
.report-list-company { font-weight: var(--weight-semibold); color: var(--text); }
.report-list-pos { color: var(--text-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Der Archiv-Ausgang ist derselbe Status wie auf der Board-Karte und trägt darum
   auch hier das DS-`.badge`; die Zeile schiebt es nur ans rechte Ende. */
.report-list-item .badge { margin-left: auto; }
/* „+N": Chip vom DS, hier nur die Affordance — der Rest der Badges steckt im
   title, und ohne cursor:help sieht das niemand. */
.card-badge-more { cursor: help; }

/* Zähler neben der Sektionsüberschrift: Chip vom DS, hier nur die Platzierung
   in der Textzeile. */
.report-section-count { margin-left: var(--space-4); vertical-align: middle; }
.report-toggle-btn {
  display: block; width: 100%; padding: 6px 8px; margin-top: 2px;
  font-size: 0.72rem; color: var(--accent-text); background: none;
  border: none; border-radius: 8px; cursor: pointer;
  text-align: left; transition: background 0.15s;
}
.report-toggle-btn:hover { background: var(--accent-soft); }
.report-toggle-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.report-day-group + .report-day-group { margin-top: 14px; }
.report-day-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 4px 8px 6px;
  border-bottom: 1px solid var(--border-1);
  margin-bottom: 4px;
}
.report-day-label {
  font-size: 0.72rem;
  font-weight: var(--weight-semibold);
  color: var(--text-2);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.report-day-count {
  font-size: 0.68rem;
  color: var(--text-3);
}

.report-cluster.is-multi {
  position: relative;
  padding-left: 10px;
  margin: 2px 0;
}
.report-cluster.is-multi::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 12px;
  bottom: 12px;
  width: 2px;
  background: var(--border-1);
  border-radius: 1px;
}
.report-activity.is-cluster-child { padding-top: 3px; padding-bottom: 3px; }
.report-activity.is-cluster-child .report-act-content { gap: 0; }

.report-status-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 0.68rem;
  color: var(--text-3);
  min-width: 0;
}
.report-status-pill .pill-from {
  opacity: 0.7;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.report-status-pill .pill-arrow { opacity: 0.5; flex-shrink: 0; }
.report-status-pill .pill-to {
  color: var(--text-2);
  font-weight: var(--weight-medium);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.report-status-pill.dir-backward .pill-arrow { color: var(--amber-text); opacity: 0.8; }
.report-status-pill.dir-archive .pill-to { color: var(--text-3); }

.report-activity.is-merged { cursor: default; }
.report-activity.is-merged:hover { background: transparent; }
/* Cluster-Multiplikator am Aktivitätseintrag — Platzierung in der Textzeile. */
.report-merge-badge { margin-left: var(--space-6); vertical-align: middle; }
.report-merge-companies {
  color: var(--text-3);
  font-size: 0.66rem;
  margin-top: 1px;
  white-space: normal;
  line-height: 1.35;
}

.report-activity {
  display: grid;
  grid-template-columns: 24px 1fr auto;
  gap: 8px;
  padding: 7px 8px;
  border-radius: 8px;
  cursor: pointer;
  font-size: 0.78rem;
  transition: background 0.15s;
  align-items: center;
}
.report-activity:hover { background: var(--bg-hover); }
.report-act-icon { display: flex; align-items: center; justify-content: center; }
.report-act-content {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}
.report-act-company {
  font-weight: var(--weight-medium);
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.report-act-detail {
  color: var(--text-3);
  font-size: 0.7rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.report-act-time {
  color: var(--text-3);
  font-size: 0.65rem;
  white-space: nowrap;
}


/* ── Kontakte ────────────────────────────────────────────────────────────── */
.kontakt-item {
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  transition: background 0.15s;
}
.kontakt-item:last-child { border-bottom: none; }
.kontakt-item:hover { background: var(--bg-hover); }

.kontakt-top {
  display: flex;
  align-items: center;
  gap: 8px;
}
.kontakt-name {
  font-weight: var(--weight-semibold);
  font-size: 0.82rem;
  color: var(--text);
}
.kontakt-actions {
  margin-left: auto;
  display: flex;
  gap: 4px;
  opacity: 0;
  transition: opacity 0.15s;
}
.kontakt-item:hover .kontakt-actions { opacity: 1; }

.kontakt-rolle {
  font-size: 0.72rem;
  color: var(--text-3);
  margin-top: 2px;
}
/* Recurrence hint — a read-only "also appears across N other positions" assertion, shared by the
   contact recurrence (CRM Slice 2, .kontakt-hint) and the recurring-employer hint (Company Slice 2,
   .recurrence-hint under the company field). The same tertiary meta level as .kontakt-rolle, and
   deliberately not --accent-text: it names other positions but does not link to them, so it must not
   read as interactive. It gets the wider gap (--space-4, like .kontakt-details) rather than the 2px
   that binds the role to the name — the recurrence is an assertion across positions, not a second line
   of the field above it, and it must clear a stacked .typeahead-linked. Kursiv is taken by .kontakt-notiz. */
.kontakt-hint,
.recurrence-hint {
  font-size: 0.72rem;
  color: var(--text-3);
  margin-top: var(--space-4);
}
.kontakt-details {
  display: flex;
  gap: 12px;
  margin-top: 4px;
  flex-wrap: wrap;
}
.kontakt-link {
  font-size: 0.72rem;
  color: var(--accent-text);
  text-decoration: none;
  transition: color 0.15s;
}
.kontakt-link:hover { text-decoration: underline; }

.kontakt-notiz {
  font-size: 0.72rem;
  color: var(--text-3);
  margin-top: 4px;
  font-style: italic;
}

/* The shared typeahead: a dropdown of existing rows to link, anchored under the input that spawned it.
   Two users — the contact form's person suggest-on-match (CRM Slice 1) and the position form's company
   autocomplete — so the class names stay about the pattern, not about either domain. The dropdown
   overlays the fields below its input as a white panel reading against the --surface-2 wells. */
.typeahead { position: relative; }
.typeahead-dropdown {
  position: absolute;
  top: calc(100% + var(--space-4));
  left: 0;
  right: 0;
  z-index: var(--z-popover);
  max-height: 220px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
}
.typeahead-item {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-6) var(--space-8);
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  text-align: left;
  cursor: pointer;
  font-family: inherit;
}
.typeahead-item:hover,
.typeahead-item:focus-visible { background: var(--bg-hover); }
.typeahead-name { font-size: 0.82rem; font-weight: var(--weight-semibold); color: var(--text); }
/* Secondary identity line the user reads to pick the right person → AA (--text-2), holds ≥4.5:1 on the
   white panel and its --bg-hover hover, not the meta --text-3. */
.typeahead-email { font-size: 0.74rem; color: var(--text-2); }
.typeahead-hint { font-size: 0.72rem; color: var(--accent-text); }
/* "Create X" closes the list: a different kind of act than picking an existing row. Only rendered when
   the server says the name is still free, so it never sits alone above an identical match. */
.typeahead-item-create {
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  font-size: 0.78rem;
  color: var(--accent-text);
}
/* The rule separates the two kinds of act — so it is drawn only where they actually meet. With no
   matches above it the create element stands alone, and a border there would double the panel's own.
   General sibling, not adjacent: Alpine leaves its <template> elements in the DOM between the buttons,
   so the create element is never a match's *direct* next sibling.
   (padding compensates the 1px border, keeping the item rhythm) */
.typeahead-item ~ .typeahead-item-create {
  border-top: 1px solid var(--border);
  margin-top: var(--space-2);
  padding-top: calc(var(--space-6) - 1px);
}

/* Der Typeahead sitzt seit B1c *im* DS-Feld: er ersetzt das Control unter dem
   .field-label, trägt also dessen volle Breite und stapelt seine Annotation
   darunter (der Feld-Stack macht die frühere rechtsbündige Sonderbehandlung
   überflüssig). */
.typeahead-row { min-width: 0; }
.typeahead-row .field-input { width: 100%; }
/* Das Dropdown ist absolut positioniert und würde vom Radius-Clip des
   Listenkastens abgeschnitten. Nur der Kasten mit Typeahead steigt aus. */
/* Hält der Kasten eine Tabelle, übernimmt er die Aufgabe, die das DS dem
   `.table-wrap` gibt: waagerecht scrollen statt clippen. Ohne das schneidet das
   `overflow: hidden` oben unter ~560px Kastenbreite die Aktionsspalte ab (die
   Flex-Zeile davor konnte nicht überlaufen, sie hatte `min-width: 0` + Ellipsis
   — also eine Regression, kein Ausgangszustand). `overflow-y` bleibt `hidden`,
   sonst fiele der Ecken-Clip weg. */
.list-box:has(.table) { overflow-x: auto; overflow-y: hidden; }
.list-box:has(.typeahead) { overflow: visible; }
/* Gleiche Klammer für den Fokus-Ring der Add-Zeile: sie ist width:100%, ihr
   Ring läge komplett im geclippten Bereich und wäre unsichtbar. Nur der Kasten
   mit der gerade fokussierten Zeile steigt aus. */
.list-box:has(.list-add-row:focus-visible) { overflow: visible; }
/* Der Ecken-Clip war eine Nebenwirkung des overflow: hidden — ein geöffneter
   Kasten verliert ihn, und der Hover-Fond der Rand-Zeilen (reminder-row,
   .table-interactive-Zeile, kontakt-item, punkt-item) zeichnet dann quadratisch über die
   gerundete Kastenecke. Die Randkinder tragen die Kastenform darum selbst;
   im geschlossenen Zustand ist die Regel folgenlos (gleicher Radius). */
.list-box > :first-child { border-radius: var(--radius) var(--radius) 0 0; }
.list-box > :last-child { border-radius: 0 0 var(--radius) var(--radius); }
/* An annotation of the name field, not a form row: pulled tight under the name input (--space-4) so its
   referent is closer than the --space-8 gap to the next field (Gestalt proximity). */
.typeahead-linked {
  margin-top: var(--space-4);
  padding: 0 var(--space-2);
  font-size: 0.74rem;
  color: var(--accent-text);
}

/* Picked meeting participants — removable chips above the participant typeahead.
   Der Chip selbst ist seit DS B1b das DS-`.tag` (+ `.tag-remove`): ein gewählter
   Teilnehmer ist ein neutrales Etikett, kein Status — Farbe bliebe hier Dekor. */
.participant-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}
/* ── Files ────────────────────────────────────────────────────────────── */
/* Seit DS B9 trägt die Datei-Liste den .table-Kontrakt; der bespoke .file-item-
   Zwilling ist weg — und mit ihm der DS-B0-Interim-Override, der den vollen
   Rahmen des gleichnamigen DS-Patterns wegdrücken musste. Übrig bleibt, was
   das DS nicht modelliert: */
/* Dateinamen sind beliebig lang, und die .list-box clippt (sie scrollt nicht wie
   das DS-.table-wrap) — der Name bricht darum um, statt die Zelle zu sprengen.
   `break-word` und NICHT `anywhere`: letzteres wirkt zusätzlich auf die
   min-content-Breite, die Tabelle gibt der Spalte dann fast nichts und bricht
   den Namen buchstabenweise (gemessen). `break-word` bricht erst, wenn das
   Wort wirklich nicht passt. */
.files-table .table-primary { overflow-wrap: break-word; }
/* Die Zeilen-Aktion bleibt leise, bis die Zeile angesteuert wird (Muster
   .reminder-del). :focus-within ist dabei Pflicht, nicht Kosmetik: ohne sie
   wäre der getabbte Löschen-Knopf ein unsichtbarer Fokus. */
.files-table .table-actions .btn {
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-out);
}
.files-table tbody tr:hover .table-actions .btn,
.files-table tbody tr:focus-within .table-actions .btn { opacity: 1; }

/* DS B13: die Dropzone unter der Liste ist das DS-.dropzone; der bespoke
   .files-dropzone-Zwilling (der ganze Listenkasten als Drop-Ziel) ist weg.
   Übrig bleibt, was das DS nicht modelliert: */
/* Liste und Dropzone sind EINE Gruppe — und zwar mit dem Sektionslabel zusammen:
   die Nähe ist ZWEIstufig, nicht dreistufig. Innen liegen Label(10), Liste und
   Dropzone(8) praktisch gleich eng; getrennt wird erst zur nächsten Sektion (32,
   via .form-section). Dass 8 und 10 sich kaum unterscheiden, ist RICHTIG, nicht
   knapp — ein Sektionslabel gehört zu dem, was unter ihm steht (app-weite
   Konvention: .form-section-label margin-bottom 10 gegen .form-section 32).
   ⚠️ Die 8 ist KEIN Kontrast zur 10. Wer sie „zur Verdeutlichung" verkleinert,
   erzeugt eine Trennung zwischen Label und Inhalt, die es nicht geben soll.
   Das `gap` überspringt den Kasten korrekt, wenn er fehlt (ein <template> ist
   kein Flex-Item). */
.files-stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
}
/* Der Auslöser ist ein <label> (es umschließt den sr-only File-Input) und kann
   selbst keinen Fokus tragen — den hält der Input, der Ring gehört an den
   sichtbaren Knopf. Bewusst auf die Dropzone gescopet: es gibt genau einen
   Label-Button, und ungescopet wäre die Regel eine Behauptung über jedes
   künftige .btn. */
.dropzone .btn:has(input:focus-visible) {
  outline: none;
  box-shadow: var(--focus-ring);
}
/* Upload läuft — dieselbe Sprache wie die frühere Add-Zeile. Anders als dort
   sitzt die Klasse am Elternteil: der Knopf setzt `cursor: pointer` auf sich
   selbst und behielte sonst Zeigefinger und Hover-Fond, obwohl sein Input
   `:disabled` ist — ein Bedienelement, das auf Hover antwortet und dann nichts
   tut. `pointer-events: none` statt eines Hover-Resets: der müsste den
   DS-Ruhezustand des Knopfes nachbauen und driftete gegen jede künftige
   DS-Änderung — so fängt der Knopf schlicht keine Zeiger-Events mehr ab, das
   `cursor: wait` der Fläche schlägt durch, und der Fokus bleibt ihm (die
   Eigenschaft wirkt nur auf den Zeiger; der Input ist ohnehin `:disabled`). */
.dropzone.is-busy { opacity: 0.6; cursor: wait; }
.dropzone.is-busy .btn { pointer-events: none; }
/* ÜBERGANGSZUSTAND, kein Layout-Rest (Muster .toolbar-count aus DS B12): das DS
   stellt den Hint auf --text-3, gemessen 3.35:1 auf dem --surface-3-Fond der
   Dropzone bei 13px — unter AA. Der bekannte --text-3-Rollen-Befund; gelöst wird
   er im DS (Karte „DS: --text-3-Rollen-Befund"). Beim Sync ist diese Zeile zu
   PRÜFEN, nicht blind zu löschen: sie flacht die DS-Hierarchie ab (Text und
   Hint stehen beide auf --text-2, unterschieden nur durch 13 vs. 14px), also
   ist danach zu sehen, ob der Hint seine Zweitrangigkeit zurückbekommt.
   Anders als bei geerbten Fundstellen (.menu-item-meta) ist er hier nicht
   zu erben: .dropzone-hint war bis B13 ungenutzt, den Fehler in Betrieb zu
   nehmen hieße ihn neu einzuführen. --text-2 misst 6.08:1; leiser als der Text
   darüber bleibt der Hint über Größe (13 vs. 14px) und Position. */
.dropzone-hint { color: var(--text-2); }

/* ── Avatar Menu (header profile dropdown) ───────────────────────────────── */
.avatar-menu {
  position: relative;
  display: inline-flex;
  align-items: center;
}
/* DS B14: Kreis, Größe, Fond, Initialen-Typografie und das `img`-Cropping kommen
   vom DS-Atom `.avatar` (components/ergaenzungen.css) — der Trigger trägt es
   direkt (`class="avatar avatar-btn"`), wie die Board-Karte in B3 ein
   `<button class="card card-interactive">` ist. Hier bleibt nur der Button-Rest:
   Browser-Chrome ab, Hover-Ring, Fokus-Ring. `background`/`color`/`border-radius`
   /`overflow`/`width`/`height` stehen bewusst NICHT mehr hier — sie waren eine
   Wort-für-Wort-Kopie des Atoms.
   Gelöscht mit derselben Karte: `.avatar-initials` (+ `-lg`, + ` svg`) und
   `.avatar-dropdown-header img`. Die Initialen sind seither schlicht der
   Textinhalt des `.avatar`; die Typografie (semibold, --type-12, uppercase,
   --tracking-normal) liefert das Atom. ⚠️ `.avatar-initials-lg` war 40px und
   sah wie eine DS-Größenvariante aus, war aber keine — `.avatar-lg` ist 48. */
.avatar-btn {
  padding: 0;
  border: none;
  cursor: pointer;
  transition: box-shadow var(--duration-fast) var(--ease-out);
  font-family: inherit;
}
.avatar-btn:hover {
  box-shadow: 0 0 0 2px var(--accent-soft);
}
.avatar-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* Layout-Rest zum DS-.menu (B4): am .avatar-menu verankert statt an
   Viewport-Pixeln. Die frühere fixed-Position (top: 56px) war auf die Höhe des
   Board-Headers gerechnet und hing auf den fünf Unterseiten schief. */
.avatar-dropdown {
  position: absolute;
  top: calc(100% + var(--space-6));
  right: 0;
  min-width: 240px;
}

/* Der Kopf-Eintrag ist ein normales .menu-item (Name als Label, E-Mail als
   .menu-item-meta) — nur sein Avatar ist größer als das 16px-Icon des Kontrakts. */
.avatar-dropdown-header { gap: var(--space-10); padding: var(--space-10); }
/* Name und E-Mail kürzen, statt das Menü in die Breite zu ziehen. */
.avatar-dropdown-header .menu-item-text > * {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* ── Settings Page ──────────────────────────────────────────────────────── */
.settings-content {
  max-width: 640px;
  margin: 0 auto;
  padding: 0 24px 80px;
  /* Stapelabstand der Karten — lag bis DS B3 als margin-bottom auf der
     bespoke .settings-card, die jetzt das DS-.card ist. */
  display: flex;
  flex-direction: column;
  gap: var(--space-20);
}
.settings-hero {
  max-width: 640px;
  margin: 0 auto;
  padding: 60px 24px 40px;
}
.settings-hero h1 {
  font-size: 1.6rem;
  font-weight: var(--weight-bold);
  color: var(--text);
  margin-bottom: 4px;
}
.settings-hero p {
  font-size: 0.9rem;
  color: var(--text-3);
}
/* Die Karte ist seit DS B3 das DS-`.card` > `.card-body`; der Titel ist KEIN
   DS-`.card-title` (type-15, Tinte), sondern ein Sektions-Eyebrow — er bleibt
   App-Klasse. Sein früherer `margin-bottom` ist weg: den Abstand zum ersten
   Abschnitt trägt jetzt das `gap` des `.card-body`. */
.settings-card-title {
  font-size: 0.8rem;
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-3);
  /* 6 + die --space-10 des `.card-body`-gap = --space-16: der Eyebrow
     labelt die ganze Karte und braucht eine eigene Stufe über den
     Sektionen (6 < 16 < 32). Gleiche Gap-Verrechnung wie bei
     .settings-subsection. */
  margin-bottom: var(--space-6);
}
/* Sektionsstufe: 22px + die --space-10 des `.card-body`-gap ergeben die
   --space-32 der Nähe-Staffel (Label↔Control < Feld↔Feld < Sektion↔Sektion).
   Off-Scale-Nudge wie die Border-Kompensationen — der sichtbare Wert ist der
   Token, der rohe ist die Gap-Verrechnung. Wer den Gap ändert, rechnet nach. */
.settings-subsection { margin-bottom: 22px; }
.settings-subsection:last-of-type { margin-bottom: 0; }
.settings-section-title {
  font-size: 0.8rem;
  font-weight: var(--weight-semibold);
  color: var(--text-2);
  margin-bottom: 8px;
}
.settings-hint {
  font-size: 0.72rem;
  color: var(--text-3);
  margin-bottom: 10px;
}
/* Layout-Rest: das Feld füllt die Zeile neben seinem Button. */
.settings-input-wide { flex: 1; }

/* Bewusste DS-Ausnahme (B1c): der Prompt-Editor setzt monospace — im Prompt ist
   die Zeilenstruktur bedeutungstragend. Trägt sonst den vollen
   .field-input-Kontrakt, abweichend sind nur Schrift und Höhe. */
textarea.settings-textarea {
  min-height: 180px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--type-13);
}
.settings-job-title {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 6px;
}

/* ── /cv — Lebenslauf timeline ────────────────────────────────────────── */
/* /cv is a full-height stage. The app header carries the actions
   (like the board) and the head/section chrome is gone; the timeline sits
   top-anchored below the nav (full width, with a gap above) — the empty space
   below it is deliberate, the bars stay slim rather than stretching to fill. */
.cv-page { display: flex; flex-direction: column; min-height: 100vh; }
/* padding-bottom 96px = off-scale (2×--space-48): breathing room so the fixed
   dirty-bar never overlaps the content; the --space-* scale tops out at 48. */
.cv-main { flex: 1; display: flex; flex-direction: column; padding-bottom: 96px; min-height: 0; }
.cv-stage { flex: 1; display: flex; flex-direction: column; min-height: 0; }

/* paste-extract — hosted in a modal now (opened from the header action),
   consistent with the editor modal instead of an inline toolbar panel. */
.cv-paste-modal { max-width: 460px; }
/* Bewusste DS-Ausnahme (B1c): eingefügter Rohtext wird monospace gesetzt — die
   Zeilenstruktur eines kopierten Lebenslaufs ist die Information. Trägt sonst
   den vollen .field-input-Kontrakt (Well, Fokus, Farben), abweichend ist nur
   die Schrift und die Höhe. */
textarea.cv-paste-input {
  min-height: 104px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--type-13);
}
/* The note under an extraction input — "here is what will happen to what you paste". Shared by the
   CV paste modal and the create modal's URL step, the two places where the product asks for raw input
   and hands it to the LLM. Same tertiary meta level as the other read-only hints. */
.cv-paste-hint,
.url-step-hint { font-size: var(--type-12); color: var(--text-3); line-height: 1.5; margin: var(--space-10) 0 0; }

/* work-station / cert list — a plain editing surface. Concurrency lives in the
   horizontal viz above, so the list itself is flat: no spine, no dots.
   Slice 3: on desktop the timeline is the primary + editable surface, so the
   list is hidden here and revealed only as the mobile fallback (@760px), where
   it centres in a readable column. */
.cv-list { list-style: none; margin: 0 auto; padding: 0; max-width: 640px; width: 100%; display: none; }
.cv-list-item { padding-bottom: var(--space-24); }
.cv-list-item:last-child { padding-bottom: 0; }

.cv-period {
  font-size: 0.7rem; font-weight: 650; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--text-3); margin-bottom: 7px;
}
.cv-list-item.is-running .cv-period { color: var(--accent-text); }

/* Die Fläche ist seit DS B3 das DS-`.card`; hier bleibt nur das engere Padding
   der Listenkarte (die Liste ist der Mobile-Fallback des Zeitstrahls, s. /cv)
   und der Editier-Ring. Kein `.card-body`: die Karte hat kein Header/Body-Paar,
   sondern eine Kopfzeile mit Aktionen und optionalen Zusatzzeilen. */
.cv-card {
  padding: 15px 17px;
  transition: box-shadow .15s;
}
.cv-card.is-editing { box-shadow: 0 0 0 2px var(--accent); }

.cv-card-top {
  display: flex; justify-content: space-between; align-items: flex-start; gap: 12px;
}
.cv-role { font-size: 1.04rem; font-weight: 650; margin: 0; line-height: 1.3; }
.cv-org { font-size: 0.86rem; color: var(--text-2); margin-top: 3px; }
.cv-org-name { font-weight: 550; color: var(--text); }
.cv-loc { color: var(--text-3); }
.cv-summary { font-size: 0.85rem; line-height: 1.6; color: var(--text-2); margin: 9px 0 0; }

/* „Aktuell" an der laufenden Station — eigene Zeile unter den Stationsdaten. */
.cv-current-pill { margin-top: var(--space-8); }

.cv-actions { display: flex; gap: 2px; flex: 0 0 auto; }
.cv-icon-btn {
  background: none; border: none; cursor: pointer; color: var(--text-3);
  padding: 5px; border-radius: 6px; display: inline-flex;
  transition: color .15s, background .15s;
}
.cv-icon-btn:hover { color: var(--accent-text); background: var(--bg-hover); }
.cv-icon-btn.cv-icon-danger:hover { color: var(--red-text); }
.cv-icon-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* Slice-2 editor modal — hosts the DS .form/.field markup that the list used to
   show inline. Narrower than the default .modal. PO-Entscheid: Löschen links,
   Schließen/Speichern rechts (.cv-editor-actions pushed right). Speichern sits
   disabled until the entry is dirty — a resting state the board modal never shows
   — so it needs an explicit dimmed look. */
.cv-editor-modal { max-width: 460px; }
.cv-editor-actions {
  margin-left: auto; display: flex; align-items: center; gap: var(--space-8);
}


/* certificate cards in the editing list — lightweight, tighter padding. */
.cv-cert-card { padding: 11px var(--space-16); }
.cv-cert-row {
  display: flex; justify-content: space-between; align-items: center; gap: var(--space-12);
}
.cv-cert-info { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.cv-cert-name { font-size: 0.92rem; font-weight: var(--weight-semibold); line-height: 1.3; }
.cv-cert-sub { font-size: 0.8rem; color: var(--text-2); }
.cv-cert-meta {
  font-size: 0.78rem; color: var(--text-3); margin-top: var(--space-8);
  display: flex; gap: var(--space-12); align-items: center; flex-wrap: wrap;
}
.cv-cert-link { color: var(--accent-text); text-decoration: none; }
.cv-cert-link:hover { text-decoration: underline; }

/* ── Timeline viz: horizontal parallel timeline ──────────────────────────────
   Spatial overview — time = space. Dated stations are bars on a shared year
   axis; concurrent roles stack into parallel tracks; certificates ride marker
   tracks below the bars. On desktop the timeline IS the interaction/edit surface
   (bars/markers are buttons); the card list is the mobile fallback. The plot
   scrolls horizontally when its span outgrows the viewport.
   The timeline owns the full stage (width AND height): a flex chain down through
   the plot fills the height, the lanes stay top-anchored (with a gap below the
   Kopfzeile) and only the axis drops to the bottom, so the grid spans the height.
   Inline padding keeps the bars off the screen edge. */
.cv-viz {
  width: 100%;
  margin: var(--space-32) 0 var(--space-24);
  padding: 0 var(--space-24);
  box-sizing: border-box;
  flex: 1; display: flex; flex-direction: column; min-height: 0;
}
/* flex column so the plot/tracks stretch via flex:1 — a plain height:100%
   doesn't resolve through an overflow-scroll parent. */
.cv-viz-scroll { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow-x: auto; overflow-y: hidden; }
.cv-viz-plot {
  position: relative; width: 100%; flex: 1; min-height: 0; padding-bottom: var(--space-4);
  display: flex; flex-direction: column;
}

.cv-viz-gridline {
  position: absolute; top: 0; bottom: 0; width: 1px; background: var(--sep);
}

/* station tracks — one row per parallel lane */
/* lanes stack top-down, row by row, at natural height right under the header
   gap; the certs sit just below the bars. Only the axis drops to the bottom
   (margin-top:auto) so the grid still spans the whole stage. */
.cv-viz-tracks {
  position: relative; display: flex; flex-direction: column; gap: var(--space-10);
}
.cv-viz-track { position: relative; flex: 0 0 auto; height: 44px; }
/* Slice-3: bars are real <button>s (self-accessible timeline) — reset the UA
   button font/alignment; the label spill relies on left-aligned text. */
.cv-viz-bar {
  position: absolute; top: 0; bottom: 0; min-width: var(--space-16);
  box-sizing: border-box; display: flex; align-items: center;
  padding: 0 var(--space-8); font-family: inherit; text-align: left;
  background: var(--surface); border: 1px solid var(--border-1);
  border-radius: var(--radius-sm); box-shadow: var(--shadow-xs);
  cursor: pointer; transition: border-color .15s, box-shadow .15s;
}
/* Slice-2: bars and markers open the editor on click. */
.cv-viz-bar:hover { border-color: var(--accent); box-shadow: var(--shadow-sm); }
.cv-viz-bar:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px; border-color: var(--accent);
}
/* Running bar: opaque (surface base + accent-soft tint on top), not the bare
   translucent token — an opaque fill is what lets the next bar in the lane mask
   a previous bar's label overrun instead of it bleeding through. */
.cv-viz-bar.is-running {
  background: linear-gradient(var(--accent-soft), var(--accent-soft)), var(--surface);
  border-color: var(--accent);
}
/* The label may be wider than a short bar — it spills to the right onto the
   track instead of being clipped to a few pixels, so narrow entries stay
   readable. A later bar in the same lane (painted after, opaque) masks any
   overrun; the last bar in a lane spills into open space and stays fully read. */
/* Slice-3 follow-up: taller bars → stack role over company (fills the height and
   narrows the footprint, so short bars spill less). Still nowrap per line, so a
   later opaque bar in the lane masks any overrun (unchanged Slice-1 trick).
   gap:1px = intentional off-scale hairline between the two label lines (below
   the --space-2 floor). */
.cv-viz-bar-label {
  flex: 0 0 auto; display: flex; flex-direction: column; justify-content: center; gap: 1px;
  white-space: nowrap; pointer-events: none;
}
.cv-viz-bar-role { font-size: 0.72rem; font-weight: var(--weight-semibold); color: var(--text); }
.cv-viz-bar.is-running .cv-viz-bar-role { color: var(--accent-text); }
.cv-viz-bar-org { font-size: 0.68rem; color: var(--text-3); }

/* year axis — labels centred in each band, on a baseline under the tracks */
.cv-viz-axis {
  position: relative; height: 18px; margin-top: auto;
  border-top: 1px solid var(--border-1);
}
.cv-viz-year {
  position: absolute; top: var(--space-4); transform: translateX(-50%);
  font-size: 0.66rem; font-weight: var(--weight-semibold); color: var(--text-3);
  letter-spacing: 0.03em; white-space: nowrap;
}

/* certificate markers — dots under the axis. Slice 3: one row per lane (greedy
   packed in cv.js so dense cert dates never collide), and each marker is a real
   <button> (self-accessible) — reset the UA button chrome down to the bare dot. */
.cv-viz-certs {
  display: flex; flex-direction: column; gap: var(--space-4); margin-top: var(--space-6);
}
.cv-viz-cert-track { position: relative; height: 16px; }
.cv-viz-cert {
  position: absolute; top: 2px; transform: translateX(-50%);
  padding: 0; background: none; border: none; cursor: pointer; line-height: 0;
}
.cv-viz-cert-dot {
  display: block; width: 9px; height: 9px; border-radius: 50%;
  background: var(--cyan); box-shadow: 0 0 0 2px var(--surface);
  transition: transform .12s;
}
.cv-viz-cert:hover .cv-viz-cert-dot { transform: scale(1.3); }
.cv-viz-cert:focus-visible { outline: none; }
.cv-viz-cert:focus-visible .cv-viz-cert-dot {
  box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--accent);
}

/* "Ohne Datum" strip (Slice 3): undated stations have no axis slot, so with the
   list gone on desktop they'd be unreachable — chips keep them editable. Lives
   in the centred column (not full-bleed like .cv-viz), hidden on mobile where
   the list carries them. */
.cv-viz-undated {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-8);
  margin: 0 0 var(--space-32); padding: 0 var(--space-24);
}
.cv-viz-undated-label {
  font-size: 0.7rem; font-weight: 650; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--text-3);
}
.cv-viz-undated-chip {
  font-family: inherit; font-size: 0.78rem; cursor: pointer;
  display: inline-flex; align-items: baseline; gap: var(--space-4);
  padding: var(--space-4) var(--space-10);
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border-1); border-radius: 999px;
  transition: border-color .15s, box-shadow .15s;
}
.cv-viz-undated-chip:hover { border-color: var(--accent); box-shadow: var(--shadow-sm); }
.cv-viz-undated-chip:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px; border-color: var(--accent);
}
.cv-viz-undated-org { font-size: 0.72rem; color: var(--text-3); }

/* Slice 3 breakpoint at 760px (just under the 768px tablet-portrait width, so
   iPad-portrait keeps the timeline): below it the horizontal timeline would
   force cramped side-scrolling, so swap it for the vertical card list — which
   stays fully editable there. One a11y surface per viewport: the hidden side
   drops out of the accessibility tree via display:none.
   Die 8px Versatz zum Header-Breakpoint (768 = --bp-md, DS B6) sind gewollt und
   kein Angleich-Kandidat: bei genau 768 klappt die Navigation hinter den
   Menü-Button, während der Zeitstrahl noch steht — auf iPad-Portrait ist beides
   richtig. Wer hier auf 768 „vereinheitlicht", verliert den Zeitstrahl genau
   auf dem Gerät, für das die 760 gewählt wurden. */
@media (max-width: 760px) {
  .cv-viz, .cv-viz-undated { display: none; }
  .cv-list { display: block; }
  /* 96px bottom = off-scale (2×--space-48), matches the desktop dirty-bar gap */
  .cv-main { padding: var(--space-16) var(--space-16) 96px; }
}

.settings-actions {
  margin-top: 20px;
  display: flex;
  justify-content: flex-end;
}
.settings-save-btn {
  background: var(--accent);
  color: var(--text-on-color);
  border: none;
  border-radius: var(--radius-sm);
  padding: 8px 24px;
  font-size: 0.8rem;
  font-weight: var(--weight-semibold);
  cursor: pointer;
  font-family: inherit;
  transition: opacity 0.15s;
}
.settings-save-btn:hover { opacity: 0.85; }
.settings-save-btn:disabled { opacity: 0.5; cursor: default; }

/* Dirty Bar (unified save for all settings sections) */
.settings-dirty-bar {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%) translateY(16px);
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 10px 12px 10px 20px;
  background: var(--surface);
  border: 1px solid var(--sep);
  border-radius: 999px;
  box-shadow: var(--shadow-md);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease, transform 0.18s ease;
  z-index: var(--z-sticky);
}
.settings-dirty-bar.is-visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
  pointer-events: auto;
}
.settings-dirty-label {
  font-size: 0.8rem;
  color: var(--text-2);
  font-weight: var(--weight-medium);
  white-space: nowrap;
}
.settings-dirty-actions {
  display: flex;
  gap: 8px;
}

/* ── Share link (guest) ────────────────────────────────────────────────── */
/* flex-shrink kam bis DS B6 vom bare `header`-Element-Selektor mit; der ist
   gelöscht, der Riegel gegen die Stauchung in der body-Flexspalte muss also
   hier explizit stehen. Mitgegangen ist das ererbte `display: flex` — und
   damit war `.share-header-inner` ein shrink-to-fit-Flex-Item, dessen eigenes
   `max-width` + `margin: 0 auto` + `space-between` wirkungslos blieben (Titel
   und Read-only-Badge klebten links aneinander). Als Block-Kind greift die
   Regel jetzt so, wie sie immer geschrieben war. */
.share-header {
  background: var(--surface);
  border-bottom: 1px solid var(--sep);
  padding: 14px 24px;
  flex-shrink: 0;
}
.share-header-inner {
  max-width: 1400px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}
.share-title {
  font-size: var(--type-16);
  font-weight: var(--weight-bold);
  color: var(--text);
}
.share-subtitle {
  font-size: 0.78rem;
  color: var(--text-3);
  margin-top: 2px;
}
.share-board { padding: 18px 24px 40px; }
/* Die Gast-Karte ist read-only: sie nimmt das DS-`.card`, aber NICHT
   `.card-interactive` — kein Klickziel, kein Hover-Heben, kein Zeiger. */
/* Layout-Rest: Breite + Zentrierung der Gast-Entsperrmaske. Control und
   Button tragen den DS-Kontrakt (.form/.field-input/.btn). */
.share-unlock-form {
  max-width: 280px;
  margin: var(--space-24) auto 0;
}
/* Layout-Rest (DS B10): die Server-Summary steht hier in der zentrierten
   .landing-hero und erbt deren `text-align: center` — Titel und Aufzählung
   liefen sonst mittig neben dem linksstehenden Icon. Auf Formularbreite
   gedeckelt, damit sie zu dem Feld gehört, auf das sie zeigt, statt über die
   vollen 720px der Hero zu spannen. */
.landing-hero > .form-summary {
  max-width: 280px;
  margin: var(--space-24) auto 0;
  text-align: left;
}

/* ── Share link (owner card on /profile) ─────────────────────────────── */
/* Layout-Rest: Abstand zur Karte darüber. Felder tragen den DS-Kontrakt. */
.profile-share-form { margin-top: var(--space-8); }
.profile-share-status { display: flex; flex-direction: column; gap: 12px; }
.profile-share-meta {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px 16px;
  font-size: 0.8rem;
}
.profile-share-meta > div { color: var(--text-3); }
.profile-share-meta strong { color: var(--text); font-weight: var(--weight-semibold); margin-left: 4px; }
.profile-share-actions { display: flex; gap: 8px; align-items: center; }

/* ── Meetings ──────────────────────────────────────────────────────────── */

.meeting-item {
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  transition: background 0.15s;
}
.meeting-item:last-child { border-bottom: none; }
.meeting-item:hover { background: var(--bg-hover); }

.meeting-item.is-next {
  border-left: 3px solid var(--green);
  padding-left: 11px;
}
.meeting-item.is-past {
  opacity: 0.7;
}

.meeting-collapsed {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  padding: 2px 0;
}
.meeting-collapsed:hover { opacity: 1; }
.meeting-expand {
  margin-left: auto;
  color: var(--text-3);
  font-size: 0.7rem;
}

.meeting-header {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 4px;
}


.meeting-actions {
  margin-left: auto;
  display: flex;
  gap: 4px;
  opacity: 0;
  transition: opacity 0.15s;
}
.meeting-item:hover .meeting-actions { opacity: 1; }


.meeting-datetime {
  font-size: 0.8rem;
  color: var(--text);
  font-weight: var(--weight-medium);
}

.meeting-location {
  font-size: 0.72rem;
  color: var(--text-2);
  margin-top: 4px;
  display: flex;
  align-items: center;
  gap: 4px;
}
.meeting-link {
  color: var(--accent-text);
  text-decoration: none;
  font-size: 0.72rem;
  word-break: break-all;
}
.meeting-link:hover { text-decoration: underline; }

.meeting-meta {
  font-size: 0.72rem;
  color: var(--text-3);
  margin-top: 3px;
}

.meeting-notes {
  font-size: 0.72rem;
  color: var(--text-3);
  margin-top: 4px;
  font-style: italic;
  white-space: pre-line;
}

.meeting-footer {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 6px;
}

.meeting-date-short {
  font-size: var(--type-12);
  color: var(--text-2);
}

/* ── Annotations Page ──────────────────────────────────────────────── */

.ann-page-body {
  margin: 0;
  height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  overflow: hidden;
}
.ann-wrapper {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

.ann-count {
  font-size: 0.72rem;
  color: var(--text-3);
}

/* Kleiner Inline-Status in der Annotations-Kopfzeile beim Re-Extrahieren des
   Stellentexts (x-show="extracting") — ein Mikro-Status über bestehendem Inhalt,
   KEIN Voll-Skeleton (der greift beim Erstladen der leeren Fläche, DS B11). */
.ann-loading-indicator { font-size: 0.78rem; color: var(--text-3); }

/* Body: two columns */
.ann-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 32px 0 64px;
}

/* Reading column (centered) with right-side gutter for marker symbols */
.ann-reading {
  position: relative;
  width: 100%;
  max-width: 720px;
  margin: 0 auto;
  padding: 0 32px;
  box-sizing: border-box;
}


/* Job posting content */
.ann-section {
  margin-bottom: 32px;
}
.ann-section-body {
  font-size: var(--type-16);
  line-height: 1.75;
  color: var(--text);
  cursor: text;
  user-select: text;
}
.ann-section-body p {
  margin: 0 0 0.85em;
}
.ann-section-body p:last-child { margin-bottom: 0; }
.ann-section-body ul, .ann-section-body ol {
  margin: 0.3em 0 0.85em;
  padding-left: 1.1em;
  list-style: none;
}
.ann-section-body li {
  margin-bottom: 0.35em;
  padding-left: 0.6em;
  position: relative;
}
.ann-section-body li::before {
  content: "–";
  position: absolute;
  left: -0.9em;
  color: var(--text-3);
  font-weight: var(--weight-regular);
}
.ann-section-body li:last-child { margin-bottom: 0; }
.ann-section-body ol { list-style: decimal; }
.ann-section-body ol li::before { content: none; }
.ann-section-body strong { font-weight: var(--weight-semibold); }
.ann-section-body em { font-style: italic; color: var(--text-2); }
.ann-section-body h2 {
  font-size: 0.88rem;
  font-weight: var(--weight-bold);
  margin: 1.2em 0 0.4em;
  color: var(--text);
}
.ann-section-body h3 {
  font-size: 0.85rem;
  font-weight: var(--weight-semibold);
  margin: 1em 0 0.3em;
  color: var(--text-2);
}
.ann-section-body code {
  font-size: 0.85em;
  padding: 0.15em 0.35em;
  background: var(--bg-hover);
  border-radius: 3px;
}
.ann-section-body blockquote {
  margin: 0.5em 0 0.85em;
  padding: 0.4em 0 0.4em 1em;
  border-left: 3px solid var(--sep);
  color: var(--text-2);
  font-style: italic;
}

/* Highlights */
.ann-hl {
  border-radius: 2px;
  padding: 1px 0;
  cursor: pointer;
  transition: box-shadow 0.15s;
}
.ann-hl:hover { box-shadow: var(--shadow-ring); }
.ann-hl-erfahrung  { background: var(--green-hl); }
.ann-hl-interessant { background: var(--cyan-hl); }
.ann-hl-frage      { background: var(--accent-hl); }
.ann-hl-warnsignal { background: var(--amber-hl); }
.ann-hl-pulse {
  animation: ann-pulse 1.2s ease-out;
}
@keyframes ann-pulse {
  0%   { box-shadow: 0 0 0 0 var(--accent); }
  70%  { box-shadow: 0 0 0 6px transparent; }
  100% { box-shadow: none; }
}

/* Type dots (still used in selection popup) */
.ann-type-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  display: inline-block;
}
.ann-dot-erfahrung   { background: var(--green); }
.ann-dot-interessant { background: var(--cyan); }
.ann-dot-frage       { background: var(--accent); }
.ann-dot-warnsignal  { background: var(--amber); }

/* Marginal symbol layer (right of reading column) */
.ann-margin-layer {
  position: absolute;
  top: 0;
  left: calc(100% - 32px);
  width: 0;
  pointer-events: none;
}
.ann-marker-group {
  position: absolute;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 4px;
  padding: 2px 4px;
  pointer-events: auto;
  transform: translateY(2px);
}
.ann-marker {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.78rem;
  font-weight: var(--weight-bold);
  font-family: inherit;
  line-height: 1;
  cursor: pointer;
  border: 1px solid var(--sep);
  background: var(--surface);
  color: var(--text-2);
  transition: transform 0.12s, box-shadow 0.12s;
  padding: 0;
}
.ann-marker:hover {
  transform: scale(1.1);
  box-shadow: var(--shadow-xs);
}
.ann-marker-erfahrung   { color: var(--green-text); border-color: var(--green); background: var(--green-soft); }
.ann-marker-interessant { color: var(--cyan-text); border-color: var(--cyan); background: var(--cyan-soft); }
.ann-marker-frage       { color: var(--accent-text); border-color: var(--accent); background: var(--accent-soft); }
.ann-marker-warnsignal  { color: var(--amber-text); border-color: var(--amber); background: var(--amber-soft); }

/* Hover popover */
.ann-popover {
  position: absolute;
  width: 320px;
  max-height: 420px;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--sep);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: 8px;
  z-index: var(--z-dropdown);
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.ann-popover-item {
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  border-left: 3px solid transparent;
}
.ann-popover-item + .ann-popover-item { margin-top: 0; }
.ann-card-erfahrung   { border-left-color: var(--green); }
.ann-card-interessant { border-left-color: var(--cyan); }
.ann-card-frage       { border-left-color: var(--accent); }
.ann-card-warnsignal  { border-left-color: var(--amber); }

.ann-card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 4px;
}

.ann-card-actions {
  display: flex;
  gap: 2px;
}
.ann-card-actions button {
  background: none;
  border: none;
  font-size: 0.78rem;
  color: var(--text-3);
  cursor: pointer;
  padding: 2px 6px;
  border-radius: 4px;
}
.ann-card-actions button:hover { color: var(--text); background: var(--fill); }

.ann-card-text {
  font-size: 0.78rem;
  color: var(--text);
  line-height: 1.45;
  margin-bottom: 4px;
}
.ann-card-comment {
  font-size: var(--type-12);
  color: var(--text-2);
  font-style: italic;
  margin-bottom: 4px;
}
.ann-card-footer {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.ann-card-date {
  font-size: 0.65rem;
  color: var(--text-3);
}
.ann-point-linked {
  font-size: 0.65rem;
  color: var(--green-text);
}

/* Edit form inside card */
.ann-card-edit { display: flex; flex-direction: column; gap: 6px; }
.ann-card-edit-type {
  display: flex;
  gap: 4px;
}
.ann-card-edit-type button {
  font-size: 0.68rem;
  padding: 3px 8px;
  border: 1px solid var(--sep);
  border-radius: 4px;
  background: transparent;
  color: var(--text-2);
  cursor: pointer;
}
.ann-card-edit-type button.active {
  background: var(--text);
  color: var(--surface);
  border-color: var(--text);
}
/* Layout-Rest: im Hover-Popover ist für die DS-Mindesthöhe (88px) kein Platz —
   das Feld selbst trägt den .field-input-Kontrakt. */
.ann-card-edit textarea.field-input { min-height: 56px; font-size: var(--type-12); }
.ann-card-edit-actions {
  display: flex;
  gap: 6px;
}

/* Empty- & Fehler-Zustände tragen seit DS B11 das DS-`.empty`. Was hier bleibt,
   ist der App-Anteil, den das DS nicht modelliert:
   (1) das Ausblenden der Board-Spalten hinter dem jeweiligen Zustand,
   (2) das Zentrieren des `.empty` im Board-Flex (die Spalten weg, ein Kind übrig),
   (3) der Fehler-Marker: ein Ladefehler bleibt ein Fehler und wird nie als
       neutrale „keine Daten"-Leere getarnt — er nimmt die `.empty`-Struktur,
       aber mit triangle-alert-Icon, Fehlertext und rot getöntem Titel.
   Die annotationseigene `.ann-empty-state` füllt die Sidebar-Höhe und zentriert
   das `.empty` (bzw. das manuelle Einfüge-Formular) darin — der Rest ist DS. */
.board--empty .board-col,
.board--error .board-col,
.board--no-match .board-col,
.board--loading .board-col { display: none; }
.board--empty > .empty,
.board--error > .empty,
.board--no-match > .empty { margin: auto; }
.board-error .empty-title { color: var(--red-text); }
/* align-items: center zentriert BEIDE Zweige quer: das DS-`.empty` (selbst-
   zentriert, hier folgenlos) UND das manuelle Einfüge-Formular (.ann-manual-form,
   max-width: 65ch) — ohne dieses align-items stünde die Form auf breiten
   Viewports linksbündig statt mittig. */
.ann-empty-state { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; }

/* Leiser Zweitweg unter der EINEN Primäraktion einer `.empty` (DS B11). Das DS
   modelliert nur den einen Button; die Annotations-Leere braucht eine Ausweich-
   Option („Text manuell einfügen"), die bewusst KEIN zweiter Button ist — ein
   Text-Link hält das „genau eine Aktion"-Prinzip. */
.empty-link {
  background: none;
  border: none;
  padding: var(--space-4) var(--space-6);
  font: inherit;
  font-size: var(--type-14);
  color: var(--accent-text);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
  border-radius: var(--radius-sm);
}
.empty-link:hover { color: var(--accent-strong); }
.empty-link:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* Skeleton-Layout (DS B11): das DS liefert die Atome (.skeleton*, statisch —
   kein Puls), nicht ihre Anordnung. .skeleton-stack stapelt Zeilen mit dem
   Listenrhythmus; .skeleton-board legt die Karten-Platzhalter je Board-Spalte
   in die Spaltenbreite. Der ~300ms-Defer + aria-busy sitzen in JS
   (skeletonState() in core.js). */
.skeleton-stack { display: flex; flex-direction: column; gap: var(--space-10); }
/* Board-Erstladung: eine Zeile aus Spalten-Platzhaltern in Board-Spaltenbreite
   (die echten .board-col sind via .board--loading verborgen). */
.skeleton-board { display: flex; gap: var(--space-16); margin: var(--space-16); }
.skeleton-board-col { flex: 0 0 210px; display: flex; flex-direction: column; gap: var(--space-8); }
.skeleton-card { min-height: var(--space-64); }
.ann-manual-form {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  width: 100%;
  max-width: 65ch;
}
.ann-manual-form h3 {
  font-size: 0.92rem;
  color: var(--text-2);
  font-weight: var(--weight-semibold);
}
/* Nur die erklärende Zeile, NICHT jedes <p> im Formular: seit DS B10 steht hier
   auch ein `.field-error`, und ein Element-Selektor aus dem ungelayerten
   style.css schlägt den DS-Kontrakt unabhängig von der Spezifität — der Fehler
   rendete grau in --text-3 statt rot (Farbe trägt Status). */
.ann-manual-form > p:not([class]) {
  font-size: 0.8rem;
  color: var(--text-3);
  text-align: center;
}
/* Der Container zentriert seine Kinder (align-items: center); der .field-Stapel
   aus Editor + Fehler muss aber die volle Breite behalten, sonst schrumpft der
   Editor auf seine Inhaltsbreite und der Fehler steht mittig statt bündig. */
.ann-manual-form > .field { align-self: stretch; }
/* Bewusste DS-Ausnahme (B1c): vollflächige Schreibfläche (contenteditable), kein
   Formularfeld — weißes Blatt statt grauem Well, Höhe vom Viewport. Fokus folgt
   trotzdem dem DS-Kontrakt. */
.ann-manual-editor {
  width: 100%;
  min-height: 300px;
  max-height: 60vh;
  overflow-y: auto;
  padding: 20px 24px;
  border: 1px solid var(--sep);
  border-radius: var(--radius);
  background: var(--surface);
  cursor: text;
}
.ann-manual-editor:focus-visible {
  outline: none;
  border-color: transparent;
  box-shadow: var(--focus-ring);
}
/* Invalid state, mirroring .field-input.is-invalid (DS B10). Written out here
   because the DS rule is scoped to .field-input and this is deliberately not
   one — a contenteditable that the Constraint Validation API cannot see. */
.ann-manual-editor.is-invalid { border-color: var(--red); }
.ann-manual-editor.is-invalid:focus-visible { box-shadow: var(--focus-ring-danger); }
.ann-manual-editor:empty::before {
  content: attr(data-placeholder);
  color: var(--text-3);
  pointer-events: none;
}

/* Inline markdown edit — swaps into the reading column in place of the rendered
   text. Bewusste DS-Ausnahme (B1c) wie .ann-manual-editor: sie ersetzt die
   Leseansicht und bleibt darum ein weißes Blatt, kein grauer Well. */
.ann-edit-textarea {
  display: block;
  width: 100%;
  min-height: 60vh;
  padding: var(--space-20) var(--space-24);
  border: 1px solid var(--sep);
  border-radius: var(--radius);
  background: var(--surface);
  font-family: inherit;
  resize: vertical;
  box-sizing: border-box;
}
.ann-edit-textarea:focus-visible {
  outline: none;
  border-color: transparent;
  box-shadow: var(--focus-ring);
}
.ann-edit-textarea::placeholder { color: var(--text-3); }

/* Orphaned annotations — no longer anchor into the edited text, so they live as an icon-first list in
   the right margin (alongside where the surviving markers sit) instead of vanishing. */
.ann-orphans {
  position: absolute;
  top: 0;
  left: 100%;
  margin-left: var(--space-16);
  width: 200px;
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
  pointer-events: auto;
}
.ann-orphans-title {
  font-size: 0.7rem;
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-3);
}
.ann-orphan-item {
  display: flex;
  align-items: flex-start;
  gap: var(--space-8);
}
.ann-orphan-icon {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.7rem;
  font-weight: var(--weight-bold);
  line-height: 1;
  border: 1px solid var(--sep);
}
.ann-orphan-body { flex: 1; min-width: 0; }
.ann-orphan-text {
  font-size: var(--type-12);
  color: var(--text);
  line-height: 1.4;
}
.ann-orphan-comment {
  font-size: 0.72rem;
  color: var(--text-2);
  font-style: italic;
  margin-top: var(--space-2);
}
/* Below the width where the 200px right-margin list would overflow the centered reading column
   (720px + 2×~216px), drop it into normal flow beneath the text instead of clipping — .ann-body
   scrolls only vertically, so there is no horizontal safety net. */
@media (max-width: 1160px) {
  .ann-orphans {
    position: static;
    width: auto;
    margin-left: 0;
    margin-top: var(--space-32);
    padding-top: var(--space-16);
    border-top: 1px solid var(--sep);
  }
}
.ann-manual-actions {
  display: flex;
  gap: 8px;
}

/* Selection popup */
.ann-selection-popup {
  position: absolute;
  z-index: var(--z-popover);
  background: var(--surface);
  border: 1px solid var(--sep);
  border-radius: var(--radius);
  box-shadow: var(--shadow-xl);
  padding: 8px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 220px;
}
.ann-popup-types {
  display: flex;
  gap: 4px;
}
.ann-popup-type {
  font-size: 0.72rem;
  padding: 5px 10px;
  border: 1px solid var(--sep);
  border-radius: 6px;
  background: var(--surface-2);
  color: var(--text-2);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 4px;
  flex: 1;
  justify-content: center;
}
.ann-popup-type:hover { background: var(--fill); }
/* Layout-Rest: im Auswahl-Popup ist für die DS-Mindesthöhe kein Platz, und die
   Größe ist fix (das Popup folgt der Textauswahl). */
textarea.ann-popup-comment { min-height: 48px; font-size: var(--type-12); resize: none; }

/* ── Landing Page ───────────────────────────────────────────────────────── */
.landing-body {
  background: var(--bg);
  color: var(--text);
  /* Bis DS B6 stand hier ein roher System-Stack — die letzte offene Ausnahme
     aus DS A2, aufgeschoben mit der Begründung „die Landing wird ohnehin
     ersetzt". Die trug nicht mehr: .landing-body sitzt auch auf /cv, /settings
     und /profile, also auf drei Auth-Arbeitsflächen, die niemand ersetzt.
     B6 machte es sichtbar, indem es dieselbe .app-brand auf beiden Welten
     nebeneinanderstellte — gleiche 15px, aber SF Pro rendert 20% höher als
     Source Sans 3. Kein Gate fängt so etwas: check-token-drift.sh greppt
     Farben, nie font-family. */
  font-family: var(--font-sans);
  line-height: 1.6;
  overflow-x: hidden;
  overflow-y: auto;
  /* Always reserve the scrollbar gutter so the centered content keeps a
     constant width whether or not the page is tall enough to scroll. */
  scrollbar-gutter: stable;
  height: auto;
  position: relative;
}
.landing-glow {
  position: absolute;
  top: -120px;
  left: 50%;
  transform: translateX(-50%);
  width: 1100px;
  height: 720px;
  pointer-events: none;
  z-index: 0;
  background:
    radial-gradient(40% 45% at 30% 30%, var(--amber-soft), transparent 70%),
    radial-gradient(45% 50% at 70% 40%, var(--accent-soft), transparent 70%),
    radial-gradient(60% 60% at 50% 55%, var(--amber-soft), transparent 75%);
  filter: blur(4px);
}
/* Ausnahme von der z-Skala (DS B4): rein lokale Stapelung Glow ↔ Inhalt auf der
   Landing-Page. Sie wird ohnehin komplett ersetzt (PO 2026-07-17) — die Skala
   zieht dort mit dem Redesign ein, nicht per Einzeltausch. */
.landing-body > nav,
.landing-body > section,
.landing-body > footer { position: relative; z-index: 1; }


.landing-nav {
  max-width: 640px;
  margin: 0 auto;
  padding: 20px 24px 0;
  display: flex;
  align-items: center;
  gap: 16px;
}
.landing-nav-brand {
  font-size: 0.9rem;
  font-weight: var(--weight-semibold);
  color: var(--text);
  text-decoration: none;
  letter-spacing: -0.01em;
  margin-right: auto;
  transition: opacity 0.15s;
}
.landing-nav-brand:hover { opacity: 0.75; }
.landing-nav-user {
  font-size: 0.85rem;
  color: var(--text-2);
}
.landing-nav-link {
  font-size: 0.85rem;
  color: var(--text-2);
  text-decoration: none;
  transition: color 0.15s;
}
.landing-nav-link:hover { color: var(--text); }
.landing-nav-logout { color: var(--text-3); }

.landing-nav + .landing-hero { padding-top: 40px; }

.landing-hero {
  max-width: 720px;
  margin: 0 auto;
  padding: 100px 24px 80px;
  text-align: center;
}
.landing-hero-eyebrow {
  display: inline-block;
  font-size: 0.72rem;
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--amber-text);
  background: var(--amber-soft);
  padding: 6px 12px;
  border-radius: 999px;
  margin-bottom: 20px;
}
.landing-hero h1 {
  font-size: 2.6rem;
  font-weight: var(--weight-bold);
  color: var(--text);
  letter-spacing: -0.025em;
  line-height: 1.1;
}
.landing-tagline {
  font-size: 1.2rem;
  color: var(--text-2);
  margin-top: 12px;
}
.landing-subtitle {
  font-size: var(--type-16);
  color: var(--text-3);
  margin-top: 16px;
  max-width: 520px;
  margin-left: auto;
  margin-right: auto;
}

.landing-cta {
  display: inline-block;
  margin-top: 32px;
  padding: 12px 32px;
  background: var(--text);
  color: var(--surface);
  border-radius: var(--radius-sm);
  text-decoration: none;
  font-size: 0.95rem;
  font-weight: var(--weight-medium);
  transition: opacity 0.15s, transform 0.15s;
}
.landing-cta:hover { opacity: 0.88; transform: translateY(-1px); }

.landing-hero-visual {
  margin: 56px auto 0;
  max-width: 440px;
  filter: var(--drop-shadow-float);
}
.landing-hero-visual svg {
  width: 100%;
  height: auto;
  display: block;
  border-radius: var(--radius-lg);
}

.landing-section {
  max-width: 720px;
  margin: 0 auto;
  padding: 0 24px 96px;
}
.landing-section-head {
  display: flex;
  align-items: baseline;
  gap: 14px;
  margin-bottom: 14px;
}
.landing-section-num {
  font-size: 0.95rem;
  font-weight: var(--weight-bold);
  color: var(--amber-text);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
}
.landing-section h2 {
  font-size: 0.82rem;
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--text-3);
  margin: 0;
}
.landing-section-intro {
  font-size: 1.05rem;
  color: var(--text-2);
  margin: 0 0 28px;
  max-width: 600px;
  line-height: 1.55;
}

.landing-grid {
  display: grid;
  gap: 16px;
}
.landing-grid-1 { grid-template-columns: 1fr; }
.landing-grid-2 { grid-template-columns: 1fr 1fr; }

.landing-card {
  background: var(--surface);
  border-radius: var(--radius);
  padding: 22px 24px;
  box-shadow: var(--shadow-xs);
  transition: transform 0.2s, box-shadow 0.2s;
}
.landing-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-xs);
}
.landing-card-icon {
  color: var(--amber-text);
  margin-bottom: 14px;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  background: var(--amber-soft);
  border-radius: 10px;
}
.landing-card h3 {
  font-size: 0.98rem;
  font-weight: var(--weight-semibold);
  color: var(--text);
  margin: 0 0 8px;
  letter-spacing: -0.005em;
}
.landing-card p {
  font-size: 0.9rem;
  color: var(--text-2);
  margin: 0;
  line-height: 1.55;
}

.landing-closing {
  max-width: 640px;
  margin: 0 auto;
  padding: 40px 24px 100px;
  text-align: center;
}
.landing-closing h2 {
  font-size: var(--type-24);
  font-weight: var(--weight-bold);
  color: var(--text);
  letter-spacing: -0.015em;
  margin-bottom: 8px;
}
.landing-closing p {
  font-size: 0.95rem;
  color: var(--text-2);
  margin: 0 auto;
  max-width: 440px;
}

.landing-footer {
  max-width: 960px;
  margin: 0 auto;
  padding: 48px 24px 56px;
  border-top: 1px solid var(--sep);
}
.landing-footer-inner {
  display: grid;
  grid-template-columns: 1.6fr 1fr;
  gap: 40px;
  margin-bottom: 32px;
}
.landing-footer-col-brand { max-width: 440px; }
.landing-footer-brand {
  display: block;
  font-size: 0.95rem;
  font-weight: var(--weight-semibold);
  color: var(--text);
  letter-spacing: -0.01em;
  margin-bottom: 8px;
}
.landing-footer-tagline {
  font-size: 0.85rem;
  color: var(--text-3);
  margin: 0;
  line-height: 1.55;
}
.landing-footer-links {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.landing-footer-heading {
  font-size: 0.72rem;
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-3);
  margin-bottom: 4px;
}
.landing-footer-links a {
  font-size: 0.88rem;
  color: var(--text-2);
  text-decoration: none;
  transition: color 0.15s;
}
.landing-footer-links a:hover { color: var(--text); }
.landing-footer-meta {
  display: flex;
  justify-content: center;
  gap: 8px;
  padding-top: 24px;
  border-top: 1px solid var(--sep);
  font-size: 0.78rem;
  color: var(--text-3);
}
.landing-footer-sep { opacity: 0.6; }
.landing-footer-meta a {
  color: var(--text-3);
  text-decoration: none;
  transition: color 0.15s;
}
.landing-footer-meta a:hover { color: var(--text); }

/* Legal pages (Impressum, Datenschutz) — static content in one column, grouped
   by proximity. No card: the content IS the page, not a module on it. Spacing
   carries the grouping — the label sits tight to its value, a wider gap separates
   the field groups. Column width + horizontal padding match .landing-nav so the
   heading and footer rule align under the brand. */
/* The .landing-body is a centering flex column whose children carry margin:0 auto,
   so without an explicit width they shrink-wrap to content and only share a center
   axis (ragged left edges). .legal-body pins nav, content and footer to one 640px
   column so all three share the same left/right edge. Scoped so the landing and
   login pages keep their shrink-wrapped centered nav. */
.legal-body > .landing-nav { width: 100%; }
.legal-page {
  width: 100%;
  max-width: 640px;
  margin: 0 auto;
  padding: 48px 24px 80px;
}
.legal-page h1 {
  font-size: 1.65rem;
  font-weight: var(--weight-bold);
  color: var(--text);
  letter-spacing: -0.02em;
  margin: 0 0 6px;
}
.legal-lede {
  font-size: 0.92rem;
  color: var(--text-3);
  margin: 0 0 40px;
}
.legal-group + .legal-group { margin-top: 28px; }
.legal-label {
  font-size: 0.72rem;
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-3);
  margin: 0 0 6px;
}
.legal-value {
  font-size: 0.95rem;
  color: var(--text);
  line-height: 1.65;
  font-style: normal;
  margin: 0;
}
.legal-value a {
  color: var(--amber-text);
  text-decoration: none;
}
.legal-value a:hover { text-decoration: underline; }
.legal-footer {
  width: 100%;
  max-width: 640px;
  margin: 0 auto;
  padding: 24px 24px 56px;
  border-top: 1px solid var(--sep);
  font-size: 0.78rem;
  color: var(--text-3);
  display: flex;
  gap: 16px;
  align-items: baseline;
}
.legal-footer a {
  color: var(--text-3);
  text-decoration: none;
  transition: color 0.15s;
}
.legal-footer a:hover { color: var(--text); }

/* Prose for longer legal documents (Datenschutz). Scoped to .legal-section so the
   page lede, the responsible-party block and the disclaimer keep their own styles. */
.legal-section { margin-top: 36px; }
.legal-section h2 {
  font-size: var(--type-16);
  font-weight: var(--weight-semibold);
  color: var(--text);
  letter-spacing: -0.01em;
  margin: 0 0 12px;
}
.legal-section p {
  font-size: 0.92rem;
  color: var(--text-2);
  line-height: 1.65;
  margin: 0 0 12px;
}
.legal-section ul {
  margin: 0 0 12px;
  padding-left: 20px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.legal-section li {
  font-size: 0.92rem;
  color: var(--text-2);
  line-height: 1.6;
}
.legal-section strong { color: var(--text); font-weight: var(--weight-semibold); }
.legal-note {
  margin: 14px 0 0;
  padding: 14px 16px;
  background: var(--amber-soft);
  border-radius: var(--radius-sm);
  font-size: 0.86rem;
  line-height: 1.6;
}
.legal-disclaimer {
  margin-top: 40px;
  font-size: 0.82rem;
  color: var(--text-3);
}

/* Login page */

.login-google-btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-top: 32px;
  padding: 12px 28px;
  background: var(--surface);
  color: var(--text);
  border: none;
  border-radius: var(--radius-sm);
  font-size: 0.95rem;
  font-weight: var(--weight-medium);
  text-decoration: none;
  box-shadow: var(--shadow-xs);
  transition: box-shadow 0.15s;
  cursor: pointer;
}
.login-google-btn:hover {
  box-shadow: var(--shadow-sm);
}
.login-google-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.login-google-btn svg { width: 20px; height: 20px; }

/* Login only, and now the name is honest — it used to sit on the share unlock
   page and the settings danger zone too, both of which moved to the DS
   .form-summary with DS B10. It stays bespoke because it is NOT a form summary:
   this page has no form and no field (just the Google button), so its message
   reports a failed sign-in attempt, not a field the user could go fix. The DS
   carries no alert/banner contract to take it — if one ever lands, this goes. */
.login-error {
  margin-top: var(--space-24);
  padding: var(--space-12) var(--space-16);
  background: var(--red-bg);
  color: var(--red-text);
  border-radius: var(--radius-sm);
  font-size: var(--type-13);
  line-height: var(--leading-ui);
}

/* Profile Page */

.profile-content {
  max-width: 640px;
  margin: 0 auto;
  padding: 0 24px 80px;
  /* s. .settings-content — Stapelabstand statt margin-bottom je Karte. */
  display: flex;
  flex-direction: column;
  gap: var(--space-20);
}
.profile-hero {
  max-width: 640px;
  margin: 0 auto;
  padding: 60px 24px 40px;
}
.profile-hero h1 {
  font-size: 1.6rem;
  font-weight: var(--weight-bold);
  color: var(--text);
  margin-bottom: 4px;
}
.profile-hero p {
  font-size: 0.9rem;
  color: var(--text-3);
}

/* s. .settings-card-title — Sektions-Eyebrow im DS-`.card-body`, kein
   DS-`.card-title`; Abstand nach unten trägt der `.card-body`-gap. */
.profile-card-title {
  font-size: 0.8rem;
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-3);
  /* 6 + die --space-10 des `.card-body`-gap = --space-16: der Eyebrow
     labelt die ganze Karte und braucht eine eigene Stufe über den
     Sektionen (6 < 16 < 32). Gleiche Gap-Verrechnung wie bei
     .settings-subsection. */
  margin-bottom: var(--space-6);
}

/* User info */
.profile-user {
  display: flex;
  align-items: center;
  gap: 16px;
}
/* DS B14: `.profile-avatar` und `.profile-avatar-placeholder` sind gelöscht —
   beides war eine Kopie des DS-Atoms `.avatar.avatar-lg` (48px). Der Platzhalter
   stand dabei auf --surface-3/--text-3 statt --fill/--text-2 und war damit
   leiser als derselbe Platzhalter im Avatar-Menü; das Atom vereinheitlicht sie
   und hebt den Kontrast des Icons von 3.35:1 auf 6.81:1 (--text-3 auf
   --surface-3 gegen --text-2 auf --fill — gemessen, nicht geschätzt). */
.profile-user-name {
  font-size: var(--type-16);
  font-weight: var(--weight-semibold);
  color: var(--text);
}
.profile-user-email {
  font-size: 0.85rem;
  color: var(--text-3);
  margin-top: 2px;
}

/* Google connection */
.profile-status {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 12px;
}
.profile-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}
.profile-dot.connected { background: var(--green); }
.profile-dot.disconnected { background: var(--red); }
.profile-status-text {
  font-size: 0.9rem;
  color: var(--text-2);
}
.profile-scopes {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
}

/* Token create form */
.profile-token-form {
  display: flex;
  gap: 8px;
  margin-bottom: 16px;
  /* Der Button behält seine Eigenhöhe, wenn der .field-Wrapper daneben um die
     Fehlerzeile wächst — sonst zöge ihn das Default-`stretch` auf 60px (DS B10,
     dieselbe Mechanik wie `.btn` als Kind eines `.card-body`). */
  align-items: flex-start;
}
/* Layout-Rest: das Feld füllt die Zeile neben dem Erstellen-Button. Seit DS B10
   trägt das der .field-Wrapper — in ihm wäre `flex: 1` eine HÖHEN-Angabe (er ist
   eine Spalten-Flexbox), das Feld schrumpfte auf seine Inhaltsbreite. */
.profile-token-form > .field { flex: 1; min-width: 0; }
.profile-input { width: 100%; }

/* Newly created token (one-time display) */
.profile-new-token {
  background: var(--amber-soft);
  border-left: 3px solid var(--amber);
  border-radius: var(--radius-sm);
  padding: 12px 16px;
  margin-bottom: 16px;
}
.profile-new-token-label {
  font-size: 0.8rem;
  font-weight: var(--weight-semibold);
  color: var(--amber-text);
  margin-bottom: 6px;
}
.profile-new-token-value {
  display: flex;
  align-items: center;
  gap: 8px;
}
.profile-new-token-value code {
  flex: 1;
  font-size: 0.82rem;
  color: var(--text);
  word-break: break-all;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* Das DS-Zellpadding (--space-16 seitlich) ist auf das paddingfreie
   `.table-wrap` gerechnet. Sitzt eine Tabelle stattdessen in einem Kasten, der
   sein eigenes Padding trägt — `.card-body` hat --space-16 —, addieren sich
   beide: die Zeilen stünden 32px vom Kartenrand, während der Eyebrow
   (`.profile-card-title`) und die Statistikzeile bei 16 bleiben. Die Randzellen
   geben ihr Außenpadding darum ab; der Rhythmus zwischen den Spalten bleibt
   unberührt. Ein Opt-in und kein pauschaler `.table`-Override, weil der Fall
   genau umgekehrt gilt, sobald der Kasten selbst kein Padding hat: in der
   `.list-box` IST das Zellpadding das Zeilenpadding und muss stehen bleiben.
   (Spiegelbild zu `.docs-sidebar-label` in DS B6-2, wo das Inset aus demselben
   Fluchtlinien-Grund nachgezogen statt gekappt wurde.) */
.table-flush > * > tr > :first-child { padding-left: 0; }
.table-flush > * > tr > :last-child { padding-right: 0; }

/* Kleiner gedämpfter Absatz in den Profil-Karten. Trägt seit DS B11 NUR noch die
   Beschreibungstexte (Board-teilen / Daten-Export) — die echten Leer-Zustände
   (keine KI-Aktionen / keine Tokens) tragen das DS-`.empty-sm`, die Lade-Zustände
   das DS-Skeleton. Der Name ist historisch (kein Rename-Sweep für Kosmetik). */
.profile-empty {
  font-size: 0.85rem;
  color: var(--text-3);
  padding: 8px 0;
}

/* AI Actions */
.ai-actions-stats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-16);
  padding-bottom: var(--space-12);
  margin-bottom: var(--space-12);
  border-bottom: 1px solid var(--sep);
  font-size: 0.85rem;
  color: var(--text-2);
}
.ai-actions-stats span { color: var(--text-3); margin-right: var(--space-4); }
.ai-actions-stats strong { color: var(--text); font-weight: var(--weight-medium); }

/* Die Timeline trägt seit DS B9 den .table-Kontrakt — das frühere .ai-action-row
   war bereits ein Grid mit fixen Spalten, also eine Tabelle ohne <table>. Was
   bleibt, ist das, was das DS nicht modelliert: die Gruppierung je Lauf. */
/* Zeit und Detail-Suffix standen bis DS B9 auf --text-3 und lagen damit bei
   4.19:1 unter AA (der bekannte --text-3-Rollen-Befund, der ins DS gehört).
   Beim Anfassen der Fläche fällt er hier von selbst weg: die Zurückhaltung
   trägt schon der Sprung zur Primärspalte (--text + semibold, 15.8:1), also
   bleibt die DS-Zellenfarbe --text-2 stehen (7.61:1). */
.ai-action-time {
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.ai-action-type { font-weight: var(--weight-medium); }
.ai-action-app { color: var(--text-2); font-weight: var(--weight-regular); }
.ai-action-err-label { color: var(--red-text); font-weight: var(--weight-medium); }

/* Jeder Lauf ist ein eigenes <tbody> (s. Profile.cshtml) — zwei Dinge folgen
   daraus, und beide hängen an derselben Regel:
   1. Die DS-Regel `tbody tr:last-child td { border-bottom: 0 }` meint „letzte
      Zeile der Tabelle" und nähme sonst jedem Lauf seinen Trenner zum nächsten.
   2. `:last-child` ist hier ohnehin kein verlässlicher Anker: Alpines x-if/x-for
      lassen ihren <template>-Knoten im DOM stehen, also ist je nach Aufklapp-
      Zustand mal ein <template> das letzte Kind des <tbody> und mal ein <tr>.
      Ungefixt endet die Tabelle mit einem überflüssigen Strich, während ein
      aufgeklappter Lauf seinen Trenner verliert (gemessen, nicht vermutet).
      `:last-of-type` zählt nur <tr> — dieselbe Alpine-Falle wie beim Blocker
      der Board-Karte in DS B3, nur an einem anderen Selektor. */
.ai-actions-table tbody tr:last-of-type td { border-bottom: 1px solid var(--sep); }
.ai-actions-table tbody:last-of-type tr:last-of-type td { border-bottom: 0; }

/* Nur die Laufzeile ist anklickbar, ihre Unterzeilen nicht — sie trägt darum
   das DS-`.table-row-interactive` (die Zeilen-Variante, DS 1.13.0) und nicht
   `.table-interactive`, das jede Zeile hovert. Der frühere bespoke Zwilling
   `.ai-action-group-head` ist damit entfallen; er trug ausschließlich diese
   Interaktivität. Die Status-Hooks `-error`/`-partial` unten bleiben. */
/* Der Aufklapp-Knopf sitzt IN der Primärzelle, nie auf dem <tr> — Browser-Chrome
   ab, damit er als Text in der Zeile liest. */
.ai-action-toggle {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.ai-action-toggle:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--radius-sm);
}
/* Feste Breite statt em: die Caret-Spalte ist 12 + 4 = --space-16 breit, und die
   Einrückung der Unterzeilen unten rechnet gegen genau diesen Wert. */
.ai-action-caret {
  display: inline-block;
  width: var(--space-12);
  margin-right: var(--space-4);
  color: var(--text-3);
  font-size: var(--type-12);
}

/* Unterzeilen: eingerückt unter ihrem Lauf, gestrichelt getrennt — die
   Einrückung sitzt auf der Primärzelle, weil dort die Fluchtlinie steht.
   Die Laufzeile beginnt --space-16 (Zellenpadding) + --space-16 (Caret-Spalte)
   vom Rand; --space-24 Einrückung dahinter macht die Summe. */
.ai-action-sub-row .table-primary { padding-left: calc(var(--space-32) + var(--space-24)); }
.ai-action-sub-row td { border-bottom-style: dashed; }

.ai-action-error td,
.ai-action-error .ai-action-type,
.ai-action-error .ai-action-app,
.ai-action-group-error td,
.ai-action-group-error .ai-action-type,
.ai-action-group-error .ai-action-app { color: var(--red-text); }
/* Partial run = some sub-actions failed but the run mostly succeeded — a milder
   signal than a full error, so only the status label goes amber (warning), not
   the whole row. */
.ai-action-group-partial .ai-action-err-label { color: var(--amber-text); }


@media (max-width: 600px) {
  .landing-hero { padding: 70px 20px 60px; }
  .landing-hero h1 { font-size: 1.9rem; }
  .landing-hero-visual { margin-top: 40px; }
  .landing-section { padding: 0 20px 72px; }
  .landing-grid-2 { grid-template-columns: 1fr; }
  .landing-closing { padding: 24px 20px 72px; }
  .landing-footer { padding: 40px 20px 40px; }
  .landing-footer-inner { grid-template-columns: 1fr; gap: 28px; }
  .landing-glow { width: 800px; height: 540px; }

  .profile-hero { padding: 40px 20px 24px; }
  .profile-content { padding: 0 20px 60px; }
  .profile-token-form { flex-direction: column; }

  .settings-hero { padding: 40px 20px 24px; }
  .settings-content { padding: 0 20px 60px; }
}

/* ── Bottom-Sheet: App-Breitendeckel mit aufheben (DS B8) ───────────────────
   Unter --bp-sm macht das DS aus dem Modal ein Bottom-Sheet und hebt dafür den
   Deckel von `.modal` auf (max-width: none). Die surface-eigenen Deckel müssen
   mit — sonst klebt ein 420px-Blatt mittig am unteren Rand eines 600px-Fensters,
   statt die Breite zu füllen. Steht hier am Dateiende, weil die einzelnen
   Deckel über die ganze Datei verteilt bei ihren Flächen stehen und ein
   früherer Reset von ihnen überschrieben würde (gleiche Spezifität, später
   gewinnt). Ein neuer Modal-Breitendeckel gehört in diese Liste. */
@media (max-width: 640px) {
  .confirm-modal,
  .results-modal,
  .report-modal,
  .cv-paste-modal,
  .cv-editor-modal { max-width: none; }
}

/* ── Overlay accommodation transitions ──────────────────────────────────────
   Die Akkommodations-Bewegung für die drei verbliebenen Drawer (Posteingang,
   Stellen-Check, Erinnerungen). Die Modals brauchen sie seit DS B8 nicht mehr —
   das DS-`.modal-backdrop` bringt dieselbe Bewegung als CSS-`animation` mit.
   Sie steht am Dateiende, weil sie die background/backdrop-filter-Regeln der
   einzelnen Drawer überschreiben muss.
   Eintritt langsam (aktives Scharfstellen), Austritt kürzer (passives Lösen) —
   dieselbe Asymmetrie wie bei `.menu-pop-*`. Die Dauern liefen bis B8 als rohe
   ms (280/320/180/220) und gingen damit an den Motion-Tokens vorbei; nur über
   die Tokens kollabieren sie unter prefers-reduced-motion auf 1ms (die
   B4-Lehre). Rohe ms sind hier ab jetzt Drift. */
.overlay-accom-enter,
.overlay-accom-leave {
  transition: opacity var(--duration-overlay) var(--ease-accommodation),
              backdrop-filter var(--duration-overlay) var(--ease-accommodation),
              -webkit-backdrop-filter var(--duration-overlay) var(--ease-accommodation),
              background-color var(--duration-overlay) var(--ease-accommodation);
}
.overlay-accom-leave {
  transition: opacity var(--duration-base) var(--ease-out),
              backdrop-filter var(--duration-base) var(--ease-out),
              -webkit-backdrop-filter var(--duration-base) var(--ease-out),
              background-color var(--duration-base) var(--ease-out);
}
.overlay-accom-enter-start,
.overlay-accom-leave-end {
  opacity: 0;
  background-color: var(--backdrop-tint-rest);
  backdrop-filter: var(--backdrop-bokeh-rest);
  -webkit-backdrop-filter: var(--backdrop-bokeh-rest);
}
.overlay-accom-enter-end,
.overlay-accom-leave-start {
  opacity: 1;
  background-color: var(--backdrop-tint);
  backdrop-filter: var(--backdrop-bokeh);
  -webkit-backdrop-filter: var(--backdrop-bokeh);
}

/* ── Menu pop transitions (DS B4) ───────────────────────────────────────────
   Beide Menüs (Sync + Avatar) teilen eine Bewegung — sie sind dasselbe
   Molecule. Kurze Distanz, kein Bounce; der Ausgang gibt die 4px nicht zurück,
   sondern blendet nur, damit Schließen leichter wirkt als Öffnen (dieselbe
   Asymmetrie wie bei den Overlays oben).
   Bewusst NICHT Alpines nacktes `x-transition`: das bringt eigene ms und eine
   eigene Kurve mit und geht damit an den Motion-Tokens vorbei — und nur über
   die Tokens kollabiert die Dauer unter prefers-reduced-motion auf 1ms. */
.menu-pop-enter {
  transition: opacity var(--duration-fast) var(--ease-out),
              transform var(--duration-fast) var(--ease-out);
}
.menu-pop-leave { transition: opacity var(--duration-fast) var(--ease-out); }
.menu-pop-enter-start { opacity: 0; transform: translateY(-4px); }
.menu-pop-enter-end,
.menu-pop-leave-start { opacity: 1; transform: none; }
.menu-pop-leave-end { opacity: 0; }

/* ── Card ⇄ entry-modal morph (View Transitions API) ─────────────────────────
   The clicked board card morphs into the entry modal (and back) via shared
   view-transition-names: entry-morph for the surface, entry-company /
   entry-position for the header texts. The names are assigned DYNAMICALLY in
   modal.js (setCardMorphNames / setModalMorphNames) — never statically here — so
   each name sits on exactly one element per snapshot and the duplicate-name abort
   ("Multiple elements found with view-transition-name") can't happen. On open the
   modal is revealed synchronously inside the VT callback (Alpine's x-show applies
   display too late for the NEW snapshot otherwise). Backdrop dimming rides the
   `root` group with the eye's asymmetric accommodation timing (slower focus-in on
   open, quicker release on close), driven by the data-vt-phase attribute
   morphModal() stamps on <html>. No-API / reduced-motion browsers never enter a
   transition (modal.js skips startViewTransition), so the open is simply instant;
   the entry modal therefore uses neither the Alpine overlay-accom fade nor the DS
   `backdrop-in` animation as its opening cue. Only the position subline keeps
   static styling — the rest of the modal's look is the DS organism (DS B8). */
.modal-title-position {
  font-size: 0.85rem;
  color: var(--text-2);
  margin-top: 2px;
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

::view-transition-group(entry-morph),
::view-transition-group(entry-company),
::view-transition-group(entry-position) {
  animation-duration: 300ms;
  animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
}
/* Box surface crossfade fit. The company/position texts ride their own groups,
   so this only blends the non-text remainder of card ↔ modal. cover|contain is
   the knob if the surface reads as squashed — start with cover. */
::view-transition-old(entry-morph),
::view-transition-new(entry-morph) {
  height: 100%;
  width: 100%;
  object-fit: cover;
  overflow: clip;
}

html[data-vt-phase="open"]::view-transition-group(root) {
  animation-duration: 320ms;
  animation-timing-function: ease-out;
}
html[data-vt-phase="close"]::view-transition-group(root) {
  animation-duration: 220ms;
  animation-timing-function: ease-in;
}

/* Bokeh without a transition "flash". A LIVE backdrop-filter and View
   Transitions clash differently per direction:

   OPEN — the overlay is revealed in the VT callback; between that and the
   snapshots appearing, the live blurred overlay would paint one frame over the
   board (sharp→blur→sharp flicker). So on open we SUPPRESS the live blur and
   carry the bokeh on the root SNAPSHOT instead, as a STATIC filter — only opacity
   cross-fades it, the blur radius never animates, so it can't flicker the way
   animating blur() on the snapshot does.

   CLOSE — the overlay is already blurred at rest, so we must NOT suppress: the
   old snapshot captures the live blur and the board stays blurred until it
   refocuses through the default cross-fade. Suppressing here would instead snap
   the board sharp for a frame (a close flicker).

   At rest (no data-vt-phase) the live backdrop-filter is present and matches the
   last frame either way. The tint (backdrop background) is never suppressed —
   10% light and unblurred, it doesn't flash.

   Since DS B8 this hangs on the DS `.modal-backdrop` (it used to name the bespoke
   `.overlay`, which was backdrop and layer in one element). `!important` also
   beats the DS's own `backdrop-in` keyframes, which animate the same property —
   that is wanted: during the morph the bokeh rides the root snapshot, not the
   live element. Scoping it to every backdrop rather than the entry modal's is
   harmless because data-vt-phase is only ever set while that one is morphing. */
html[data-vt-phase="open"] .modal-backdrop {
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
}
html[data-vt-phase="open"]::view-transition-new(root) {
  filter: var(--backdrop-bokeh);
}

/* ── Cover Letter Editor (Welle 9a) ──────────────────────────────────── */

.cl-body { display: flex; }
.cl-content {
  flex: 1;
  min-width: 0;
  padding: 32px 40px;
  overflow-y: auto;
}
.cl-col-label {
  font-size: 0.7rem;
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-3);
  margin-bottom: 14px;
}
.cl-editor {
  width: 52%;
  min-width: 420px;
  border-left: 1px solid var(--sep);
  background: var(--surface);
  display: flex;
  flex-direction: column;
  position: relative;
  overflow: hidden;
}
.cl-editor-toolbar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 14px 20px;
  border-bottom: 1px solid var(--sep);
  background: var(--surface);
}
.cl-toolbar-right {
  margin-left: auto;
  display: flex;
  gap: 6px;
}
/* Bewusste DS-Ausnahme (B1c): das Anschreiben-Blatt ist eine Schreibfläche, kein
   Formularfeld — randlos, serif, füllt den Editor. Es trägt den Fokusring
   trotzdem — :focus-visible greift nur bei Tastaturnavigation, der klickende
   Schreiber sieht ihn nie. */
.cl-textarea {
  flex: 1;
  border: none;
  padding: 28px 32px;
  font-family: ui-serif, Georgia, "Times New Roman", serif;
  font-size: 0.95rem;
  line-height: 1.7;
  color: var(--text);
  background: transparent;
  resize: none;
}
.cl-textarea:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.cl-textarea::placeholder { color: var(--text-3); }
.cl-hint {
  padding: 10px 20px;
  font-size: var(--type-12);
  color: var(--text-3);
  border-top: 1px solid var(--sep);
  background: var(--fill);
}
.cl-hint a { color: var(--accent-text); text-decoration: none; }
.cl-hint a:hover { text-decoration: underline; }
.cl-save-state {
  font-size: 0.72rem;
  color: var(--text-3);
}
/* `.cl-confirm*` ist mit DS B8 ersatzlos entfallen — die Frage „Bestehenden
   Entwurf überschreiben?" läuft jetzt über confirmDialog(), die EINE
   Confirm-Grammatik der App (alertdialog, destruktiver Knopf, Folge-Benennung).
   Sie war zuvor eine dritte, handgebaute Art dieselbe Frage zu stellen: das
   einzige Overlay auf `--glass` mit hartem blur(8px) statt der Backdrop-Tokens,
   per `position: absolute; top: 52px` im Editor klemmend, und ohne role, ohne
   Fokus, ohne Escape. Wer hier wieder eine eigene Fläche baut, hat drei. */

/* ── Meeting Mode ───────────────────────────────────────────────────────── */
.mm-wrapper { display: flex; flex-direction: column; height: 100vh; }
.mm-body { display: flex; flex: 1; min-height: 0; overflow: hidden; }
.mm-content { flex: 1; overflow-y: auto; padding: 32px 40px; }

.mm-sidebar {
  width: 52%;
  min-width: 420px;
  border-left: 1px solid var(--sep);
  background: var(--surface);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.mm-qa {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  border-bottom: 1px solid var(--sep);
}
.mm-section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 20px 6px;
  gap: 8px;
}

.mm-q-list { flex: 1; overflow-y: auto; padding: 6px 20px 12px; }
.mm-q-item {
  background: var(--fill);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  margin-bottom: 8px;
  border: 1px solid var(--sep);
}
.mm-q-head { display: flex; align-items: center; gap: 6px; }
/* Bewusste DS-Ausnahme (B1c): die Frage ist im Gesprächsmodus eine Überschrift,
   die man antippen und ändern kann — ein grauer Well würde sie zum Formularfeld
   machen. Randlos, aber mit DS-Fokusring (der Ring ersetzt das frühere
   Outline-plus-Padding-Muster, das beim Fokus die Zeile verschoben hat). */
.mm-q-topic {
  flex: 1;
  border: none;
  background: transparent;
  font-size: 0.88rem;
  font-weight: var(--weight-semibold);
  color: var(--text);
  padding: 2px 0;
  min-width: 0;
}
.mm-q-topic:focus-visible { outline: none; border-radius: var(--radius-sm); box-shadow: var(--focus-ring); }

/* Layout-Rest: die Antwort sitzt unter ihrer Frage und bleibt kompakt. */
textarea.mm-q-answer { margin-top: var(--space-6); min-height: 38px; font-size: var(--type-13); }

/* Die Fragen-Leere trägt seit DS B11 das DS-`.empty-sm`; die eine Hervorhebung
   im Hinweistext bleibt als `<em>`-Regel (das DS-`.empty-text` kennt keine). */
.empty-text em { color: var(--text-2); font-style: normal; font-weight: var(--weight-medium); }

/* Band-Chrome. Das Nebeneinander von Feld und Button trägt seit DS B10 die
   innere .mm-q-add-row, damit der Fehler als zweites Kind des .field-Stapels
   INNERHALB des Bandes steht (mit dessen Inset) statt darunter. */
.mm-q-add {
  padding: 8px 20px 14px;
  border-top: 1px solid var(--sep);
  background: var(--surface);
}
.mm-q-add-row {
  display: flex;
  gap: 6px;
}
/* Layout-Rest: das Feld füllt die Zeile neben seinem Hinzufügen-Button. */
.mm-q-add-row .field-input { flex: 1; }

.mm-notes {
  flex: 0 0 32%;
  min-height: 160px;
  display: flex;
  flex-direction: column;
  padding: 14px 20px;
}
/* Bewusste DS-Ausnahme (B1c): Mitschrift während des Gesprächs — eine
   Schreibfläche wie das Anschreiben-Blatt (serif, füllt die Spalte), kein
   Formularfeld. Fokus folgt dem DS-Kontrakt. */
.mm-notes-area {
  flex: 1;
  border: 1px solid var(--sep);
  border-radius: var(--radius-sm);
  padding: var(--space-10) var(--space-12);
  font-family: ui-serif, Georgia, "Times New Roman", serif;
  font-size: 0.9rem;
  line-height: 1.6;
  color: var(--text);
  background: var(--surface);
  resize: none;
}
.mm-notes-area:focus-visible { outline: none; border-color: transparent; box-shadow: var(--focus-ring); }

/* ─── /docs user documentation ──────────────────────────────────────────────
   Die Kopfzeile ist seit DS B6-2 das DS-`.app-header` (Auth-Zweig mit Nav +
   Avatar, Gast-Zweig im Public-Modus) — die frühere `.landing-nav`-Kopie samt
   ihrem `.docs-nav-right` ist entfallen. Darunter die zweispaltige Shell; den
   Fuß trägt weiter `.legal-footer`. Die Seitenleisten-Einträge sind ebenfalls
   DS (`.nav-item` / `.is-current`), hier bleibt nur ihre Stapelung.
   Alle Farben + Abstände laufen über Tokens; die Prosa spiegelt die erprobten
   .legal-section-Größen. */
.docs-body {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
.docs-shell {
  width: 100%;
  max-width: 900px;
  margin: 0 auto;
  padding: var(--space-32) var(--space-24) var(--space-48);
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: var(--space-48);
  flex: 1;
}
/* Ohne Leiste (nur ein sichtbares Ziel, s. `_DocsLayout`) fiele der Inhalt sonst
   in die 200px-Spalte. Statt nur die Spalte zu tauschen wird die ganze Shell
   schmaler: sie trägt `margin: 0 auto`, also bleibt der Text so mittig wie auf
   den Legal-Seiten — eine 640er-Spalte am linken Rand einer 900er-Fläche läse
   als angeschnittenes Layout. */
.docs-shell-solo { grid-template-columns: minmax(0, 1fr); max-width: 640px; }
.docs-sidebar {
  border-right: 1px solid var(--sep);
  padding-right: var(--space-24);
}
/* padding-left spiegelt das horizontale Inset des DS-`.nav-item` (--space-12):
   ohne es stünde der Gruppentitel links neben seiner eigenen Liste, und in einer
   200px-Spalte liest ein versetzter Titel als eigenes Element statt als deren
   Beschriftung. Kein neuer Abstand — die Übernahme des Insets, das die Einträge
   ohnehin tragen. */
.docs-sidebar-label {
  font-size: 0.72rem;
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-3);
  margin: 0 0 var(--space-12);
  padding-left: var(--space-12);
}
/* Die Einträge sind das DS-`.nav-item`; hier steht nur ihre Stapelung. Der Gap
   spiegelt bewusst den der horizontalen `.app-nav` (--space-4) — es ist dieselbe
   Komponente, nur um 90° gedreht, und zwei verschiedene Rhythmen für dasselbe
   Atom wären genau die Inkonsistenz, gegen die DS B6 antritt. Die Einträge
   dehnen sich auf die Spaltenbreite (Flex-Default `stretch`) — das ist hier
   gewollt: in einer Leiste ist die ganze Zeile das Ziel, anders als beim `.btn`
   im `.card-body`, wo genau diese Dehnung ein Fehler wäre (DS B3). */
.docs-sidebar-nav {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.docs-content { min-width: 0; }
.docs-h1 {
  font-size: 1.65rem;
  font-weight: var(--weight-bold);
  color: var(--text);
  letter-spacing: -0.02em;
  margin: 0 0 var(--space-6);
}
.docs-lede {
  font-size: 0.92rem;
  color: var(--text-3);
  margin: 0 0 var(--space-32);
}
.docs-section { margin-top: var(--space-32); }
.docs-section h2 {
  font-size: var(--type-16);
  font-weight: var(--weight-semibold);
  color: var(--text);
  letter-spacing: -0.01em;
  margin: 0 0 var(--space-12);
}
.docs-section p {
  font-size: 0.92rem;
  color: var(--text-2);
  line-height: 1.65;
  margin: 0 0 var(--space-12);
}
.docs-cards {
  display: flex;
  flex-direction: column;
  gap: var(--space-12);
}
/* Die Fläche ist seit DS B3 das DS-`.card.card-interactive` (Fond, Hairline,
   Radius, Elevation xs→sm→xs, Fokus-Ring); hier bleibt nur der Innenrhythmus —
   das DS-`.card-body` passt nicht, weil die Karte kein Header/Body-Paar hat,
   sondern zwei Textzeilen mit engem Zeilenabstand. */
.docs-card {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: var(--space-16);
}
.docs-card-title { font-size: 0.95rem; font-weight: var(--weight-semibold); color: var(--text); }
/* Body-tier summary text (not a meta label) — --text-2 holds AA on --surface,
   wo --text-3 unter 4.5:1 fiele. (Der frühere --surface-3-Hover-Fond ist mit
   DS B3 entfallen: die Karte hebt, statt den Fond zu wechseln.) */
.docs-card-summary { font-size: 0.85rem; color: var(--text-2); line-height: 1.5; }
/* Wie .docs-card das DS-`.card` — beide sitzen auf /docs übereinander, eine
   abweichende Elevation wäre genau die Inkonsistenz, gegen die DS B3 antritt.
   Rest: das großzügigere Polster und die linksbündige Stapelung. */
.docs-gate {
  padding: var(--space-24);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-12);
}
.docs-gate p { font-size: 0.92rem; color: var(--text-2); margin: 0; }
.docs-footer { margin-top: 0; }
@media (max-width: 640px) {
  /* Einspaltig sind Leiste und Inhalt zwei Zeilen, und `.docs-shell` ist als
     `flex: 1` höher als sein Inhalt — mit dem Grid-Default `stretch` bekäme die
     Leistenzeile den ganzen Überschuss und ihre Trennlinie stünde weit unter
     dem letzten Eintrag. Zweispaltig ist genau dieses Strecken gewollt (die
     senkrechte Linie läuft bis zum Fuß), darum nur hier. */
  .docs-shell { grid-template-columns: 1fr; gap: var(--space-24); align-content: start; }
  .docs-sidebar {
    border-right: none;
    border-bottom: 1px solid var(--sep);
    padding-right: 0;
    padding-bottom: var(--space-16);
  }
}

/* ── Print: das Blatt verschwindet, die Liste bleibt (DS B9) ────────────────
   Der DS-Print-Block setzt den Blatt-Reset auf `.table-wrap`. Diese App führt
   kein `.table-wrap` — jede ihrer Tabellen sitzt schon in einem Kasten, der das
   Blatt trägt (`.card` auf der Profilseite, `.list-box` im Detail-Modal), und
   ein Blatt im Blatt wäre ein Kasten im Kasten. Gleiche Absicht, anderer
   Träger: auf Papier fällt das Chrome des Kastens weg. Die drei übrigen
   DS-Print-Regeln greifen unverändert, weil sie an `.table` hängen und nicht am
   Wrapper — Kopflinie in Tinte, `.table-actions` aus, Zeilen brechen nicht um.
   Es ist der einzige @media-print-Block der App; die pt-Ausprägung der
   Print-Templates ist eine eigene Karte. */
@media print {
  .card,
  .list-box {
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }
}
