/* =============================================================================
   Suriro Portal — shell.css
   Design tokens from branding/theme.json v1.0.0
   ONE-RED-RULE: max 1 red element per screen
   NO hardcoded hex values — all via var(--color-*)
   ============================================================================= */

/* -----------------------------------------------------------------------------
   1. CSS Custom Properties (Design Tokens)
   ----------------------------------------------------------------------------- */
:root {
  /* Colors */
  --color-red:        #EE3224;
  --color-red-700:    #AF2C22;
  --color-red-100:    #F8DCD8;
  --color-ink:        #181228;
  --color-ink-hdr:    #0F0A21;
  --color-ink-2:      #231F36;
  --color-ink-3:      #2C2A3D;
  --color-ink-4:      #434951;
  --color-cream:      #F5F2EE;
  --color-cream-3:    #F6F3EF;
  --color-paper:      #FFFDF8;
  --color-slate:      #5A6470; /* Ticket #277: von #7C8693 abgedunkelt — 5,4:1 auf Cream (WCAG AA, R12.3); theme.json v1.1.0 */
  --color-slate-3:    #C4CCD2;
  --color-sand:       #C7B6A9;
  /* Zone V — private Geldquelle (S163). Eine eigene, sehr ruhige Sandtoenung:
     sie trennt die Zeilen, ohne mit dem Akzent um Aufmerksamkeit zu
     konkurrieren. Sie traegt die Aussage NIE allein — an jedem so
     hinterlegten Element steht die Zone auch als Wort (Styleguide:
     Farbe allein traegt keine Aussage). Eingefuehrt 02.09.2026 fuer den
     Import-Bildschirm, der Gesellschafts- und Privatkonten nebeneinander
     zeigt. */
  --color-zone-privat:      #F2ECE5;
  --color-zone-privat-rand: #C7B6A9;
  --color-hairline:   #EAE7E2;
  --color-white:      #FFFFFF;
  /* Fremdmarke, keine Suriro-Farbe: das Blau von LinkedIn, ausschliesslich fuer
     den LinkedIn-Link an einem Kontakt (.evt-li-open — seit #807 traegt er den
     URL-Text statt einer Marke "in"). Es steht hier, weil R1
     keinen Hex-Wert ausserhalb dieses Blocks zulaesst - und weil eine
     Fremdmarke als solche benannt gehoert, statt unbemerkt in einer Regel zu
     stehen. Nicht fuer eigene Zwecke weiterverwenden: als zweiter Akzent neben
     --color-red waere es ein Verstoss gegen R1.3. */
  --color-fremd-linkedin: #2867B2;

  /* Typography */
  --font-family:      "Glancyr", "Inter", system-ui, -apple-system, sans-serif;
  --font-mono:        "Sometype Mono", ui-monospace, monospace;
  --weight-light:     300;
  --weight-regular:   400;
  --weight-medium:    500;

  /* Spacing (4px base unit) */
  --space-1:   4px;
  --space-2:   8px;
  --space-3:   12px;
  --space-4:   16px;
  --space-5:   20px;
  --space-6:   24px;

  /* ── Klebende Leisten (Ticket #385, 2026-08-03) ────────────────────────────
     Zwei Leisten kleben uebereinander: die Kopfzeilen-Pillen (Blog-Idee, Tour,
     Hilfe, Feedback) und die Sektions-Reiterleiste. Die Pillen liegen mit
     z-index:50 und deckendem Hintergrund ueber der Reiterleiste (z-index:5).
     GEMESSEN am 2026-08-03 bei scrollY=700: Pillen 24-66px, Reiterleiste 0-60px --
     36 der 60 Pixel waren verdeckt, sichtbar blieb nur deren Polsterung. Die
     Reiterleiste "klebte korrekt" und war trotzdem unsichtbar; die Guided Tour
     zielt auf genau diese Reiter.
     Eine Entscheidung fuer ALLE Bildschirme: jede weitere klebende Leiste beginnt
     unterhalb der Pillen.

     NACHTRAG 2026-08-03 (Widerlegung dieser Reparatur). Die 26px waren geraten, und
     die Rechnung traegt genau EINE Pillenzeile. Zwei Wege haben sie gerissen:
       1. `@media (max-width: 768px)` setzt `flex-wrap: wrap`. Bricht die Leiste um,
          wird sie 76px hoch -- die Klebekante steht weiter auf 66px, also sind 34
          der 60 Pixel wieder verdeckt. Derselbe Befund, nur schmalseitig.
       2. Die 26px folgen der Schriftgroesse nicht. Eine Browser-Mindestschriftgroesse
          hebt die Pille auf 32px, ohne eine der Polstervariablen zu aendern.
     Beides ist mit CSS nicht zu rechnen: die Hoehe entsteht erst im Layout. Deshalb
     MISST `shell.js` die Leiste jetzt (ResizeObserver) und schreibt --sticky-unterkante
     auf :root. Der Wert hier bleibt als Rueckfall, bis das Skript laeuft und fuer den
     Fall, dass es gar nicht laeuft -- er ist richtig fuer den haeufigsten Fall
     (eine Zeile, Standardschrift), nur eben nicht fuer jeden. */
  --sticky-kopf-top:       var(--space-6);
  --sticky-kopf-hoehe:     calc(var(--space-2) * 2 + 26px);
  --sticky-unterkante:     calc(var(--sticky-kopf-top) + var(--sticky-kopf-hoehe));
  --space-8:   32px;
  --space-12:  48px;
  --space-16:  64px;
  --space-24:  96px;
  --space-32:  128px;

  /* Radius */
  --radius-input:     8px;
  --radius-card:      12px;
  --radius-container: 20px;
  --radius-pill:      999px;

  /* Shadows (hover only) */
  --shadow-md:  0 6px 18px rgba(24, 18, 40, 0.10);

  /* Motion */
  --motion-fast:   140ms ease-out;
  --motion-base:   220ms ease-out;
  --motion-slow:   420ms ease-out;

  /* Layout */
  --sidebar-width: 232px;
  --bottom-nav-height: 60px;
}






/* -----------------------------------------------------------------------------
   2. Reset + Base
   ----------------------------------------------------------------------------- */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

/* `hidden` ist eine Aussage ueber SICHTBARKEIT und wird von keiner
   Darstellungsregel ueberstimmt (#807, 12.09.2026).

   DER MECHANISMUS. Der Browser setzt `[hidden] { display: none }` in seinem
   EIGENEN Stylesheet — Ursprung "User-Agent". Jede Autorenregel schlaegt ihn,
   ganz gleich wie unspezifisch sie ist: schon `.btn { display: inline-flex }`
   genuegt, und das Element steht wieder da, obwohl im Markup `hidden` steht.
   Lautlos, ohne Fehlermeldung, und nur am Bildschirm zu sehen.

   GEMESSEN AM 12.09.2026, Prod, /events/bp26/vorher?prio=A (Martins Chrome):
   Bei „David B. Hofman" standen der blaue Link, der Knopf „aendern" UND das
   Eingabefeld mit der URL NEBENEINANDER — 22 von 70 Zeilen zeigten beides. Am
   Feld gemessen:  hidden = true · display = "block" · visibility = "visible"
   · 388 x 44 px.  Der Grund steht in Abschnitt 6: die Basisregel fuer
   `input[type="text"]` (Spezifitaet 0,1,1) schlaegt `[hidden]` (0,1,0).

   WARUM GLOBAL UND NICHT WIEDER EINZELN. Es war das FUENFTE Auftreten
   derselben Falle in dieser Datei. Vor dieser Zeile stand sie fuenfmal einzeln
   geheilt — `.shell-header-pill[hidden]` (zweimal, einmal davon in der
   Mobil-Abfrage), `.shell-header-note[hidden]`, `input[type="file"][hidden]`
   (03.09.), `#lauf-probe-oeffnen[hidden]` (#586) und `.btn[hidden]` (03.09.),
   dessen Kommentar den Schritt hierher bereits angekuendigt hat. Jede dieser
   Reparaturen heilt EIN Element und laesst die Klasse stehen; der naechste
   Autor, der `hidden` benutzt, faellt wieder hinein und merkt es erst, wenn
   jemand die Seite ansieht.

   WARUM `!important` HIER KEIN TRICK IST. Es ist der Sinn der Regel: `hidden`
   sagt „dieses Element ist derzeit nicht relevant" — das ist keine Angabe zur
   Darstellung und darf von einer Darstellungsangabe nicht ueberstimmt werden.
   Die Regel kann nur verbergen, was das Attribut TRAEGT, und das setzt immer
   der Autor der Ansicht mit genau dieser Absicht. Ein Element, das `hidden`
   traegt und trotzdem sichtbar sein soll, waere ein Widerspruch in sich.
   Nachgemessen am Bestand (12.09.2026): 51 Fundstellen im Ansichtsbaum, alle
   sollen verborgen sein, und JEDE Stelle, die so ein Element wieder einblendet,
   tut das ueber die Eigenschaft (`el.hidden = false` bzw.
   `removeAttribute('hidden')`) — KEINE ueber `style.display`. Wer kuenftig
   `style.display` gegen ein gesetztes `hidden` setzt, sieht nichts; das ist
   beabsichtigt, denn dann ist das Attribut schlicht falsch gesetzt.

   Gehuetet von shell/test/unit/hidden-ist-unsichtbar.test.js — der rechnet die
   Kaskade fuer `display` aus dieser Datei nach, statt das Markup zu lesen.
   Nicht wegraeumen, ohne dort nachzusehen. */
[hidden] {
  display: none !important;
}

html {
  font-size: 16px;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  font-family: var(--font-family);
  font-size: 16px;
  font-weight: var(--weight-regular);
  line-height: 1.5;
  color: var(--color-ink);
  background-color: var(--color-cream);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Betonung ohne Bold — Entscheid Martin 26.08.2026, „Headlines Light, nie Bold".
 *
 * DIE PALETTE KENNT KEIN BOLD. Es gibt genau drei Gewichte: --weight-light (300),
 * --weight-regular (400), --weight-medium (500). `suriro-design-rules.yaml` fuehrt
 * "Bold" und "SemiBold" unter `forbidden_font_hints` und setzt `bold: forbidden`.
 *
 * `<strong>` und `<b>` umgingen das trotzdem — nicht ueber eine Regel, sondern
 * ueber die BROWSER-VORGABE (font-weight: bolder). Fuer `strong` stand in dieser
 * Datei keine einzige Zeile, und 80 View-Dateien benutzen das Element. Ein
 * Token-System, das an keiner Stelle „bold" sagt, hat also portalweit fett
 * dargestellt — und niemandem fiel es auf, weil nirgends etwas Falsches
 * geschrieben stand.
 *
 * Aufgefallen ist es an der neuen Einfuehrungsseite (S200-P2), wo drei
 * Zwischenzeilen als <strong> stehen. Der Befund ist aber nicht die Seite,
 * sondern die fehlende Regel.
 *
 * medium (500) statt regular: die Betonung bleibt sichtbar. Sie ersatzlos zu
 * streichen waere die andere Haelfte des Fehlers — dann saehe eine
 * hervorgehobene Zeile aus wie Fliesstext, und der Autor haette sie umsonst
 * ausgezeichnet. „Glancyr Med" steht in `allowed_font_families`.
 */
strong,
b {
  font-weight: var(--weight-medium);
}

/* Die Zwischenueberschriften der Einfuehrungsseite folgen der Treppe, nicht dem
 * Gewicht — Entscheid Martin 26.08.2026.
 *
 * Global ist H3 Regular 400 (`suriro-design-rules.yaml`: "H1/H2 Light, H3/Body
 * Regular"), und das ist fuer dichte Fachbildschirme richtig. Die Einfuehrung ist
 * das Gegenteil davon: drei luftige Abschnitte, deren Ueberschriften wie H2 wirken
 * sollen, nur eine Stufe kleiner. Deshalb hier Light — der Rang entsteht ueber
 * GROESSE und ABSTAND, nicht ueber Gewicht.
 *
 * Bewusst auf `.einfuehrung-inhalt` beschraenkt: H3 anderswo bleibt Regular. Eine
 * globale Aenderung haette dutzende Fachbildschirme mitgenommen, die niemand
 * angesehen hat.
 */
.einfuehrung-inhalt h3 {
  font-size: 19px;
  font-weight: var(--weight-light);
  letter-spacing: -0.01em;
  margin: 0 0 var(--space-2) 0;
}

img, svg {
  display: block;
  max-width: 100%;
}

a {
  color: inherit;
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

ul, ol {
  list-style: none;
}

button {
  cursor: pointer;
  font-family: inherit;
  border: none;
  background: none;
}

input, select, textarea, button {
  font-family: inherit;
  font-size: inherit;
}

table {
  border-collapse: collapse;
  width: 100%;
}

/* -----------------------------------------------------------------------------
   3. Focus Ring (ONE global rule — counts towards red budget per component)
   ----------------------------------------------------------------------------- */
:focus-visible {
  outline: 2px solid var(--color-red-700);
  outline-offset: 2px;
  border-radius: 2px;
}

/* -----------------------------------------------------------------------------
   4. Typography
   ----------------------------------------------------------------------------- */
h1 {
  font-size: 40px;
  font-weight: var(--weight-light);
  line-height: 1.15;
  letter-spacing: -0.01em;
}

h2 {
  font-size: 28px;
  font-weight: var(--weight-light);
  line-height: 1.25;
}

h3 {
  font-size: 22px;
  font-weight: var(--weight-regular);
  line-height: 1.3;
}

h4 {
  font-size: 18px;
  font-weight: var(--weight-regular);
  line-height: 1.4;
}

p {
  line-height: 1.5;
}

/* Eyebrow — counts as the ONE red element when used */
.eyebrow {
  display: block;
  font-size: 12px;
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--color-red);
  line-height: 1.4;
}

/* #590/#591 — ONE-RED-RULE (R1.3): ein Eyebrow als UEBERSCHRIFT ist kein Seitenlabel.

   BEFUND, gemessen am 17.08.2026 auf portal.suriro.com/leadgen/leads/16 (1440x825):
   vier rote Elemente gleichzeitig im Bild, alle rgb(238,50,36) — drei Abschnitts-
   ueberschriften und die Markierung in der Seitenleiste. Die Seite traegt insgesamt
   SECHS Eyebrows. Die Farbe war richtig (R1.2), die Anzahl nicht.

   DIE URSACHE IST DIESE KLASSE, NICHT DREI STELLEN. Der Kommentar darueber sagt es
   selbst: das Eyebrow „counts as the ONE red element" — also genau EINES je Screen.
   Benutzt wird es aber in zwei voellig verschiedenen Rollen, und die Auszaehlung ueber
   alle Vorlagen trennt sie sauber (17.08.2026, shell/src/views/**):

     14x  <span class="eyebrow">          Seitenlabel, je Datei GENAU EINMAL
                                          (setup/*, error, 404, coming-soon,
                                          login-totp-setup, belege, styleguide, lauf)
      3x  <span class="eyebrow section-label">  dasselbe, ueber _section_header.ejs
     14x  <h2..h6 class="eyebrow">        ABSCHNITTSUEBERSCHRIFT, mehrfach je Datei
                                          (leads/akte 5x, quellen 3x, hooks 3x,
                                          kontakte 2x, _zielpersonen 1x)

   Nur die dritte Gruppe verstoesst — und sie ist an der Auszeichnung erkennbar. Die
   Regel haengt deshalb an der Ueberschrift und nicht an der Sorgfalt (dasselbe Muster
   wie `.radar-stufe--rot` in content/radar.ejs, E-RAD-4): jedes kuenftige
   `<h3 class="eyebrow">` ist von sich aus nicht rot.

   WARUM NICHT EINFACH `.eyebrow` ENTROETEN: dann verloeren die 14 Seitenlabel ihr Rot,
   und setup/*, error, 404, coming-soon und login-totp-setup stehen ausserhalb von
   _section_header.ejs — sie haetten danach NULL rote Elemente. R1.3 verlangt genau
   eines, nicht hoechstens eines. Gemessen, nicht vermutet: alle 14 sind <span>.

   WELCHES ELEMENT DAS ROT BEHAELT: das Seitenlabel im Sektionskopf
   (`.eyebrow.section-label` aus _section_header.ejs, auf /leadgen/leads/16 „LEADGEN").
   Begruendung: es steht strukturell genau einmal je Bildschirm, es ist auf jedem
   Portal-Bildschirm dasselbe Element, und die Hausregel dazu ist bereits
   ausgeschrieben (views/recherche/liste.ejs: „Das Eyebrow im Sektionskopf ist rot und
   bleibt es"). Eine Abschnittsueberschrift dagegen gibt es je Seite beliebig oft —
   sie kann das eine rote Moment gar nicht sein. */
h1.eyebrow, h2.eyebrow, h3.eyebrow, h4.eyebrow, h5.eyebrow, h6.eyebrow {
  color: var(--color-ink-2);
}

.caption {
  font-size: 12px;
  font-weight: var(--weight-regular);
  line-height: 1.4;
  color: var(--color-slate);
}

.label {
  font-size: 14px;
  font-weight: var(--weight-medium);
  line-height: 1.4;
  color: var(--color-ink);
}

.body-large {
  font-size: 18px;
  font-weight: var(--weight-light);
  line-height: 1.7;
}

/* Monospace numbers */
.mono,
.num {
  font-family: var(--font-mono);
  font-weight: var(--weight-regular);
  font-variant-numeric: tabular-nums;
}

/* -----------------------------------------------------------------------------
   5. Shell Layout
   ----------------------------------------------------------------------------- */
.shell-layout {
  display: flex;
  min-height: 100vh;
}

/* Sidebar */
.shell-sidebar {
  width: var(--sidebar-width);
  flex-shrink: 0;
  background-color: var(--color-ink);
  display: flex;
  flex-direction: column;
  position: fixed;
  top: 0;
  left: 0;
  height: 100vh;
  overflow-y: auto;
  z-index: 100;
}

.shell-sidebar-logo {
  padding: var(--space-6) var(--space-6) var(--space-4);
}

.shell-wordmark {
  font-family: var(--font-family);
  font-size: 24px;
  font-weight: var(--weight-light);
  color: var(--color-paper);
  letter-spacing: -0.02em;
  text-transform: lowercase;
}

.shell-sidebar-nav {
  /* NICHT `flex: 1`. Das bedeutet `flex-basis: 0` -- der Block bekaeme "den Rest"
     der 100vh zugewiesen, und sobald die Modulliste hoeher ist als dieser Rest,
     quillt sein Inhalt heraus und schiebt .shell-sidebar-bottom (Meldungen, Hilfe,
     Administration, Profil, Abmelden) aus dem Bild. Die Sidebar merkt davon nichts
     und scrollt deshalb auch nicht -- ihr eigenes overflow-y: auto greift nie.

     Gemeldet von Martin am 16.09.2026: "wo ist der Admin Bereich hingekommen???
     Scheint verschwunden im Seitenmenu" -- auf Nachfrage: "unten kommt nichts
     mehr!!!". Es fehlte der GANZE untere Block, nicht nur Administration.

     Mit `flex: 0 0 auto` waechst die Sidebar ueber die Fensterhoehe hinaus, ihr
     overflow-y greift, und der untere Block ist wieder erreichbar. */
  flex: 0 0 auto;
  padding: var(--space-2) 0;
}

.shell-sidebar-bottom {
  padding: var(--space-4) 0 var(--space-6);
  border-top: 1px solid var(--color-ink-3);
}

/* Main content */
.shell-main {
  flex: 1;
  margin-left: var(--sidebar-width);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background-color: var(--color-cream);
}

.shell-content {
  flex: 1;
  /* Tickets #291/#306: oben schlanker -- die Pill-Leiste darüber bringt bereits ihren
     eigenen Abstand mit, ein zusätzlicher 32px-Innenabstand schob die Überschrift
     unnötig weit nach unten. Seiten/unten unverändert.
     Ticket #618 (Martin, 20.08.2026): "oben ist zu viel Platz verschwendet" -- noch
     eine Stufe schlanker, space-4 (16px) -> space-3 (12px), portalweit. Nicht auf 0:
     der Seitenkopf (_section_header.ejs) braucht sichtbaren Abstand zur Sidebar-Kante
     obendrüber, sonst klebt das Sektions-Label am Rand. */
  padding: var(--space-3) var(--space-8) var(--space-8);
  max-width: 1200px;
  width: 100%;
}

.shell-footer {
  padding: var(--space-4) var(--space-8);
  color: var(--color-slate);
  font-size: 12px;
  border-top: 1px solid var(--color-hairline);
  display: flex;
  justify-content: space-between;
  align-items: center;
}

/* Seitenkopf-Aktionen (P30 §2.6 — Specs + Feedback-Pill oben rechts) */
/* Tickets #244/#245: bleibt beim Scrollen sichtbar (sticky), etwas kompakter. */
/* Ticket #244 (wieder geöffnet): "selbe Höhe 'Heute'" -- die Leiste klebte bislang am
   reinen Viewport-Rand (top:0), während "Heute" im Sidebar-Nav durch Logo-Block +
   Nav-Padding erst bei 84px beginnt (gemessen, .shell-sidebar-logo + .shell-sidebar-nav
   Padding-Summe). margin-top UND sticky-top auf denselben Wert setzen: reserviert den
   Abstand im normalen Fluss (kein Überlappen der Headline darunter) und haelt die
   Leiste beim Scrollen exakt auf dieser Höhe fest (nicht bei 0).

   Tickets #291/#306 (2026-07-27): Die 84px-Ausrichtung auf "Heute" verschenkte zusammen
   mit dem 32px-Innenabstand von .shell-content rund 124px, bevor die Überschrift begann --
   Martin: "immer noch zu viel Platz ganz oben; Blog-Idee/Hilfe/Feedback höher setzen, dann
   die Zeile darunter auch". Die Leiste orientiert sich jetzt an der WORTMARKE (Oberkante
   des Logo-Blocks, 24px) statt an "Heute". Das hebt die Vorgabe aus #244 bewusst auf --
   dort ging es darum, dass die Leiste nicht am nackten Viewport-Rand klebt; das bleibt
   erfüllt. */
.shell-header-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-8);
  margin-top: var(--space-6);
  position: sticky;
  top: var(--sticky-kopf-top);
  z-index: 50;
  background: var(--color-cream);
}
.shell-header-pill {
  background: none;
  border: 1px solid var(--color-hairline);
  border-radius: 999px;
  color: var(--color-slate);
  font-size: 12px;
  font-family: var(--font-family);
  padding: 3px var(--space-3);
  cursor: pointer;
  text-decoration: none;
  transition: border-color var(--motion-fast), color var(--motion-fast);
  line-height: 1.5;
}
.shell-header-pill:hover { border-color: var(--color-ink); color: var(--color-ink); }
/* Die Tour-Pille rendert verborgen und wird von help-panel.js nur eingeblendet, wenn
   fuer die Route ueberhaupt Tour-Schritte hinterlegt sind (S33 §2.2). Hier stand dafuer
   `.shell-header-pill[hidden] { display: none; }` — die ERSTE der fuenf Einzelreparaturen
   dieser Falle. Sie ist seit #807 (12.09.2026) aufgeloest: `[hidden]` traegt in Abschnitt 2
   global `display: none !important` und kann von `display:` auf .shell-header-pill nicht
   mehr ausgehebelt werden. Der Anlass bleibt hier stehen, die Regel nicht. */

/* Klickweg-Hinweis (#576) ohne admin.use: dieselbe Pille, aber kein Ziel — sie ist
   eine Aussage und kein Weg. Nur der Zeiger unterscheidet sie, keine zweite Farbe
   und kein zweites Rot (R1.3). */
.shell-header-pill-still { cursor: default; }
.shell-header-pill-still:hover { border-color: var(--color-hairline); color: var(--color-slate); }

/* Klartext-Meldung unter der Pillen-Zeile, wenn eine Tour zwar hinterlegt ist, aber keiner
   ihrer Ankerpunkte mehr existiert (Entscheidung Martin, 2026-07-27: sichtbar kaputt schlaegt
   unsichtbar). Nutzt die neutrale .hint-box -- kein zusaetzliches Rot, die Kopfzeilen-Pillen
   sind Chrome und kein Inhalt (R1.3). */
.shell-header-note {
  margin: 0 var(--space-8) var(--space-4);
  font-size: 12.5px;
}
/* Hier stand `.shell-header-note[hidden] { display: none; }` — zweite Einzelreparatur
   derselben Falle, seit #807 (12.09.2026) durch die globale `[hidden]`-Regel in
   Abschnitt 2 abgeloest. */

/* Ticket #243: dezenter Hinweis-Punkt, solange eine neue Guided Tour fuer die
   aktuelle Route noch nicht entdeckt wurde -- Ink, keine zweite Saturationsfarbe. */
.shell-header-pill-new { position: relative; border-color: var(--color-ink); color: var(--color-ink); }
.shell-header-pill-new::after {
  content: '';
  position: absolute;
  top: -2px;
  right: -2px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--color-ink);
  border: 1.5px solid var(--color-cream);
}

/* #729: Hilfe-Volltextsuche (help-search.js) — portalweit statt nur auf /hilfe,
   deshalb hier statt im <style>-Block einer einzelnen Vorlage. Zwei Einbauorte
   (help/overview.ejs UND layout.ejs #ctx-panel) teilen sich diese eine Fassung. */
.help-search-input { width: 100%; box-sizing: border-box; }
#help-search-results, #ctx-panel-search-results { display: flex; flex-direction: column; gap: var(--space-2); }
.help-search-hit { border: 1px solid var(--color-hairline); border-radius: var(--radius-md); overflow: hidden; }
.help-search-hit > a { display: block; padding: var(--space-3) var(--space-4); text-decoration: none; color: var(--color-ink); }
.help-search-hit > a:hover { background: var(--color-cream-3); }
.help-search-hit .hit-title { font-size: 13.5px; font-weight: var(--weight-medium); }
.help-search-hit .hit-excerpt { font-size: 12.5px; color: var(--color-ink-4); margin-top: 2px; }
.help-search-hit .hit-excerpt mark { background: var(--color-tint); color: var(--color-ink); padding: 0 2px; }
/* Sekundaerer Link auf die Fach-Seite selbst (nicht auf den Hilfetext) — eigene
   Zeile unter dem Treffer, damit der Klick auf die Karte selbst (-> Hilfetext)
   nicht mit diesem Ziel verwechselt wird. KEIN Rot: eine zweite Handlung ist
   kein Alarm (R1.3). */
.help-search-hit .hit-route {
  display: block;
  padding: var(--space-2) var(--space-4);
  border-top: 1px solid var(--color-hairline);
  font-size: 11.5px;
  color: var(--color-slate);
  text-decoration: none;
  background: var(--color-cream-3);
}
.help-search-hit .hit-route:hover { color: var(--color-ink); }

/* Page header — Ticket #116: gilt für alle Seiten, Headline + optionale Actions in einer Zeile */
/* Tickets #244/#245: etwas kompakter (space-8 -> space-6). */
.page-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin-bottom: var(--space-6);
}

.page-header h1 {
  margin-top: var(--space-2);
}

/* Section-Label — Ticket #116: Parent-Nav-Zeile ganz oben, vor der Sub-Navigation */
/* Tickets #244/#245: etwas kompakter (space-4 -> space-3). */
.section-label {
  margin-bottom: var(--space-3);
}

/* Rueck-Link — Ticket #510.
   `.back-link` gab es seit Langem in acht Views, aber NIRGENDS eine Regel dazu:
   die Klasse war rein semantisch, und wer das Aussehen brauchte, schrieb es als
   style="" daneben (recherche/_recherche_nav, invoices/lauf) — die uebrigen
   blieben unformatiert. Genau daran war zu sehen, welche Seite den Link zuletzt
   bekommen hat. Eine Regel, ein Aussehen; die Inline-Fassungen setzen exakt
   dieselben Werte und aendern damit nichts, wo sie noch stehen.
   Steht ueber dem Section-Label und ist das oberste Element des Inhalts. */
.back-link {
  display: inline-block;
  margin-bottom: var(--space-2);
  font-size: 13px;
  color: var(--color-slate);
}

/* -----------------------------------------------------------------------------
   6. Sidebar Nav Items
   ----------------------------------------------------------------------------- */

/* Gruppenueberschrift (Paket C #461): Zwischenlabel ueber benachbarten Modulen
   mit gleichem nav_group-Wert (core.module.nav_group; gerendert in layout.ejs). */
.nav-group-label {
  display: block;
  padding: var(--space-4) var(--space-6) var(--space-1);
  font-size: 11px;
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--color-slate-3);
  opacity: 0.7;
}

/* Ticket #509: Die Gruppe "Admin" ist die erste Zeile im unteren Sidebar-Block.
   Der Block bringt schon Abstand und eine Trennlinie mit — zusammen mit dem
   eigenen Innenabstand des Labels stuende die Ueberschrift sonst deutlich weiter
   vom Trennstrich entfernt als die uebrigen Gruppenlabels von ihrem Vorgaenger. */
.shell-sidebar-bottom .nav-group-label:first-child {
  padding-top: 0;
}

.nav-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-6);
  color: var(--color-slate-3);
  font-size: 14px;
  font-weight: var(--weight-regular);
  text-decoration: none;
  min-height: 44px;
  transition: color var(--motion-fast), background-color var(--motion-fast);
  border-left: 3px solid transparent;
  position: relative;
}

.nav-item:hover {
  color: var(--color-paper);
  background-color: var(--color-ink-2);
  text-decoration: none;
}

.nav-item.active,
.nav-item[aria-current="page"] {
  color: var(--color-paper);
  border-left-color: var(--color-red);
  background-color: var(--color-ink-2);
}

.nav-item-icon {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  opacity: 0.7;
}

.nav-item.active .nav-item-icon,
.nav-item[aria-current="page"] .nav-item-icon {
  opacity: 1;
}

/* -----------------------------------------------------------------------------
   7. Bottom Nav (mobile)
   ----------------------------------------------------------------------------- */
.shell-bottom-nav {
  display: none;
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  height: var(--bottom-nav-height);
  background-color: var(--color-ink);
  z-index: 100;
  border-top: 1px solid var(--color-ink-3);
}

.bottom-nav-list {
  display: flex;
  height: 100%;
}

.bottom-nav-item {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  color: var(--color-slate-3);
  font-size: 11px;
  font-weight: var(--weight-medium);
  text-decoration: none;
  min-height: 44px;
  padding: var(--space-2);
  transition: color var(--motion-fast);
  border-top: 2px solid transparent;
}

.bottom-nav-item:hover,
.bottom-nav-item.active,
.bottom-nav-item[aria-current="page"] {
  color: var(--color-paper);
  text-decoration: none;
}

.bottom-nav-item.active,
.bottom-nav-item[aria-current="page"] {
  border-top-color: var(--color-red);
}

.bottom-nav-icon {
  width: 22px;
  height: 22px;
}

/* S149 P0: Die Leiste traegt jetzt fuenf Eintraege (Heute · Bewerb. · LeadGen ·
   News · Mehr) statt drei bis vier. Die Flex-Kinder von .bottom-nav-list sind die
   <li>, nicht die Links — das `flex: 1` auf .bottom-nav-item lief deshalb bisher
   ins Leere und die Spalten waren unterschiedlich breit. Bei 375px sind fuenf
   gleiche Spalten zu je 75px genau die Rechnung, mit der die Kurznamen belegt
   wurden; ungleiche Spalten wuerden sie wieder abschneiden. */
.bottom-nav-list > li {
  flex: 1 1 0;
  display: flex;
  min-width: 0;
}

.bottom-nav-list > li > .bottom-nav-item {
  width: 100%;
}

/* "Mehr" ist ein <button>, kein <a> — die Beschriftung darf trotzdem nicht
   umbrechen und der Punkt muss frei positionierbar sein. */
.bottom-nav-more {
  position: relative;
  font-family: inherit;
}

.bottom-nav-item > span {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Ungelesen-Punkt. Stand bis S149 als Inline-Style in layout.ejs; als Klasse ist
   er an beiden Stellen derselbe — in der Seitenleiste neben "Meldungen", in der
   unteren Leiste auf "Mehr" (S149 §6 P0 a: Meldungen zieht in die Schublade und
   verloere dort seine einzige Anzeige). Er ist das EINE rote Element der
   Navigation (R1.3); die Aktiv-Markierung der Leiste ist Chrome. */
.nav-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--color-red);
  flex-shrink: 0;
}

.bottom-nav-dot {
  position: absolute;
  top: 8px;
  right: calc(50% - 18px);
}

/* -----------------------------------------------------------------------------
   7b. Navigations-Schublade (mobil) — S149 P0

   Es gibt genau ZWEI Schubladen im Portal, und beide benutzen dasselbe
   Grundmuster .ctx-panel-drawer (Hilfe-Panel). Hier stehen nur die Abweichungen:
   von links statt von rechts (Daumenweg zur "Mehr"-Taste unten rechts waere
   laenger, aber die Seitenleiste kommt am Rechner ebenfalls von links — die
   Schublade IST die Seitenleiste), Ink-Grund, damit die uebernommenen
   .nav-item-Regeln unveraendert greifen, und Platz fuer die untere Leiste.
   ----------------------------------------------------------------------------- */
.nav-drawer-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, .3);
  z-index: 800;
}

.nav-drawer-overlay.is-open { display: block; }

/* Zwei Klassen als Selektor (0,2,0) statt einer: .ctx-panel-drawer steht in einem
   <style>-Block IM BODY von layout.ejs und damit spaeter im Dokument als diese
   Datei — bei gleicher Spezifitaet gewaenne dort jede Angabe. Die zusaetzliche
   Klasse macht die Ueberschreibung unabhaengig von der Reihenfolge. */
.nav-drawer-overlay .nav-drawer-panel {
  right: auto;
  left: 0;
  width: min(320px, 88vw);
  background: var(--color-ink);
  box-shadow: 4px 0 24px rgba(0, 0, 0, .18);
  /* Die untere Leiste bleibt sichtbar und bedienbar, waehrend die Schublade
     offen ist — sonst waere "Mehr" der einzige Knopf, den man nicht mehr sieht,
     obwohl er der Schalter ist. */
  bottom: var(--bottom-nav-height);
}

.nav-drawer-overlay .nav-drawer-head {
  border-bottom-color: var(--color-ink-3);
}

.nav-drawer-titel {
  font-size: 13px;
  font-weight: var(--weight-medium);
  color: var(--color-slate-3);
  text-transform: uppercase;
  letter-spacing: 0.18em;
}

.nav-drawer-close {
  background: none;
  border: 1px solid var(--color-ink-3);
  color: var(--color-slate-3);
  font-size: 16px;
  line-height: 1;
}

.nav-drawer-close:hover {
  color: var(--color-paper);
  border-color: var(--color-slate-3);
  background: var(--color-ink-2);
}

.nav-drawer-nav {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-2) 0 var(--space-4);
  -webkit-overflow-scrolling: touch;
}

.nav-drawer-unten {
  margin-top: var(--space-4);
  padding-top: var(--space-2);
  border-top: 1px solid var(--color-ink-3);
}

/* #894: der Griff, der den weggeschobenen Kopf zurueckholt, ist am Telefon zu
   Hause — `data-kopf` wird nur unter 768 px auf `weg` gesetzt (shell.js,
   `matchMedia`, ausdruecklich „NUR AM TELEFON"). Seine drei Regeln standen
   jedoch ALLE im Block darunter, und damit galt am Schreibtisch keine einzige:
   ohne `display: none` stand „Kopf zurueckholen" ueber JEDER Seite, waehrend
   der Kopf vollstaendig sichtbar war. Die Regel gehoert deshalb nach DRAUSSEN.
   Die Sichtbarkeit im Zustand `weg` bleibt drinnen: dort ist sie richtig, und
   `html[data-kopf="weg"] .kopf-griff` sticht diese Regel ohnehin. */
.kopf-griff { display: none; }

/* Mobile: show bottom nav, hide sidebar */
@media (max-width: 768px) {
  .shell-sidebar {
    display: none;
  }

  .shell-main {
    margin-left: 0;
    padding-bottom: var(--bottom-nav-height);
  }

  .shell-bottom-nav {
    display: block;
  }

  .shell-content {
    padding: var(--space-4) var(--space-4);
  }

  /* Seitenkopf-Aktionen: mit der Tour-Pille sind es vier, bei 375px wird die Zeile eng
     (gemessen ~310px Pillen-Breite bei 343px nutzbarer Breite -- passt, aber knapp).
     Zwei rein gestalterische Griffe, keine Pille wird entfernt:
     1. Innenabstand auf die .shell-content-Kante ziehen (32px -> 16px je Seite, +32px Luft).
     2. flex-wrap als Netz fuer breitere Schriftmetriken. Unter Platzdruck rutscht dabei
        "Blog-Idee" als erste in die zweite Zeile (order), nicht die Tour-Pille -- die Tour
        hat Vorrang (Vorgabe Martin); Blog-Idee ist ohnehin nur mit content.use sichtbar. */
  .shell-header-actions {
    padding-left: var(--space-4);
    padding-right: var(--space-4);
    flex-wrap: wrap;
  }
  .shell-header-actions [data-open-idea-modal] { order: 1; }

  /* ── #894 — DER KLEBENDE KOPF SCHRUMPFT BEIM SCROLLEN ────────────────────────
     Martin, 14.09.2026 auf /application/pipeline: "Er streckt sich ueber mehrere
     Zeilen und das ist zu lang. Idealerweise schiebt sich das dann einfach
     irgendwie hoch und wenn man drauf klickt, ist es wieder ausgeklappt."

     GEMESSEN VORHER, 375 x 812 px, echtes Markup gegen diese Datei:
         .shell-header-actions   112 px  (3 Zeilen -- flex-wrap oben)
         .section-subnav         248 px  (5 Zeilen, 12 Reiter à 44 px)
         ------------------------------
         klebender Kopf          360 px  =  44 % der Bildhoehe
     Beide Leisten kleben gegen das Viewport (kein Vorfahr oeffnet einen
     Scrollport -- .shell-content traegt bewusst `overflow-x: clip` und nicht
     `auto`, s. Kommentar dort), und die zweite haengt per --sticky-unterkante an
     der Unterkante der ersten. Der Stapel waechst also mit jedem Umbruch mit.

     DIE ANTWORT IST NICHT "WENIGER REITER": das Umbrechen kam aus #704, weil ein
     einzelner Reiter sonst aus dem Kasten lief. Stattdessen bekommt der Kopf
     einen ZWEITEN Zustand. Am Seitenanfang steht er vollstaendig da -- dort
     nimmt er niemandem etwas weg. Sobald gescrollt wird, wird er einzeilig:
     beide Leisten `nowrap` und waagerecht schiebbar. Ein Tippen auf den
     Schalter holt ihn zurueck (und haelt ihn, bis man wieder oben ist).

     WAAGERECHT SCHIEBBAR und nicht abgeschnitten: einzeilig mit `hidden` waere
     nicht gespart, sondern verschwunden -- acht der zwoelf Reiter waeren dann
     ueber die Oberflaeche nicht mehr erreichbar (R21). */
  /* ── #1409 — KOMPAKT IST DER RUHEZUSTAND, NICHT DER ZWEITE ──────────────────
     Martin, 07.10.2026 auf einer Bewerbungs-Detailseite: "Der helle Bereich auf
     dem Handy ist zu gross. Aktuell sehe ich drei, vielleicht sogar vier Zeilen
     Menuefuehrung ... Das muss verborgen werden, sonst habe ich keinen Bereich
     zum Navigieren und Lesen."

     GEMESSEN AM 08.10.2026, 375 x 812 px, in einer eigenen Instanz aus dem
     Arbeitsbaum -- es sind SECHS Zeilen, nicht drei bis vier:
         .shell-header-actions   112 px  (2 Zeilen)
         .section-subnav         296 px  (6 Zeilen, 14 Reiter)
         Kopfschalter             44 px
         ------------------------------
         klebender Kopf          452 px  =  56 % der Bildhoehe
         feste Leiste unten       60 px
         zum Lesen bleiben       300 px  =  37 %
     Die erste Ueberschrift beginnt bei 509 px -- 63 % des Schirms sind vorbei,
     bevor Inhalt anfaengt. Die Zahlen im Block darueber (360 px, 12 Reiter)
     stammen vom 14.09.2026; seitdem sind zwei Reiter dazugekommen, und genau
     deshalb steht hier das Messdatum und nicht nur die Zahl.

     ES FEHLTE KEINE FUNKTION, NUR DIE VORGABE. Der Schalter bringt den Kopf von
     452 px auf 165 px -- nachgemessen. Jede Seite startete aber voll aufgeklappt,
     und die Wahl wird nicht gemerkt: am Telefon hiess das, auf JEDER Seite neu
     zuzuklappen.

     WARUM IM STYLESHEET UND NICHT IM SKRIPT: `shell.js` haengt als
     `type="module"` am Seitenende, laeuft also nach dem ersten Bild. Ein Setzen
     per Skript zeigte erst 452 px und klappte dann zu -- genau das, worueber das
     Ticket geht, nur kurz. Ein Inline-Skript im Kopf scheidet aus: `script-src
     'self'` ohne `'unsafe-inline'` blockt es lautlos (die CSP-Falle aus
     CLAUDE.md, bisher viermal einzeln gefunden).

     DESHALB `:not([data-kopf="voll"])` statt `[data-kopf="kompakt"]`: ohne jedes
     Attribut -- also beim ersten Bild -- gilt kompakt. `voll` entsteht nur noch
     durch ein ausdrueckliches Tippen auf den Schalter. Im Zustand `weg` gelten
     diese Regeln mit; das war vorher schon so, denn `weg` wird nur aus `kompakt`
     erreicht. Die Eigenart der Schreibweise: `:not()` erbt die Gewichtung seines
     Arguments, die Regeln wiegen also genau so viel wie vorher.

     GEGENPROBE, die bei dieser Umstellung zaehlt: ueber 768 px aendert sich
     NICHTS -- der ganze Block steht in `@media (max-width: 768px)`. */
  html:not([data-kopf="voll"]) .shell-header-actions {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
  }
  html:not([data-kopf="voll"]) .shell-header-actions::-webkit-scrollbar { display: none; }
  html:not([data-kopf="voll"]) .section-subnav {
    flex-wrap: nowrap;
    margin-bottom: var(--space-4);
  }
  html:not([data-kopf="voll"]) .shell-header-actions > *,
  html:not([data-kopf="voll"]) .section-subnav > * { flex: 0 0 auto; }

  /* ── #894, zweite Stufe — DER KOPF WEICHT GANZ (Entscheid Martin 22.09.2026)
     Nach einem Mock-up mit drei Verhaltensweisen hat Martin Variante C gewaehlt:
     erst einzeilig, bei laengerem Runterscrollen ganz weg, Hochscrollen oder ein
     Griff holt ihn zurueck.

     ZWEI VERSCHIEBUNGEN, NICHT EINE, und das ist kein Zierat: `.section-subnav`
     klebt per --sticky-unterkante an der UNTERKANTE der Aktionsleiste. Schoebe
     man nur die obere weg, stuende die untere allein oben — halb gewonnener
     Platz und ein Kopf, der aussieht wie ein Fehler. Jede Leiste geht deshalb um
     ihre eigene Hoehe PLUS ihren Abstand von oben, und die Abstaende sind
     dieselben Variablen, die auch das Kleben steuern. Keine zweite Rechnung, die
     auseinanderlaufen koennte.

     `visibility: hidden` am Ende der Bewegung: waehrend die Leisten oben aus dem
     Bild ragen, sind sie fuer Zeigegeraet und Tastatur noch da. Ein Reiter, den
     man nicht sieht und trotzdem trifft, ist schlimmer als einer, der fehlt. */
  html[data-kopf="weg"] .shell-header-actions {
    transform: translateY(calc(-100% - var(--sticky-kopf-top, 0px)));
    visibility: hidden;
    transition: transform .2s ease, visibility 0s linear .2s;
  }
  html[data-kopf="weg"] .section-subnav {
    transform: translateY(calc(-100% - var(--sticky-unterkante, 0px)));
    visibility: hidden;
    transition: transform .2s ease, visibility 0s linear .2s;
  }
  /* #821: die zweite Reiterzeile klebt an --sticky-unterkante-2 und geht deshalb
     um IHRE Kante mit nach oben — dieselbe Regel wie oben, eine Zeile tiefer. */
  html[data-kopf="weg"] .section-subnav-secondary {
    transform: translateY(calc(-100% - var(--sticky-unterkante-2, 0px)));
  }
  /* #821: weicht der Kopf, stuende die Docu/Action-Leiste sonst unter zwei
     unsichtbaren Reiterzeilen mitten im Bild. Sie rueckt an die Kante, an der sie
     auch vor #821 im weg-Zustand stand. */
  html[data-kopf="weg"] .evt-docu { top: var(--sticky-unterkante, 0px); }
  .shell-header-actions,
  .section-subnav { transition: transform .2s ease; }

  /* DER GRIFF. Martins Wortlaut: "wenn man drauf klickt, ist es wieder
     ausgeklappt." Ohne ihn waere Hochscrollen der einzige Weg zurueck — wer
     unten in einer langen Liste steht, scrollte durch die halbe Seite.
     Er erscheint NUR im Zustand `weg`; in jedem anderen ist er nicht da, nicht
     bloss unsichtbar (kein Ziel fuer die Tabulatortaste, das niemand sieht). */
  .kopf-griff { display: none; }
  html[data-kopf="weg"] .kopf-griff {
    display: block;
    position: fixed;
    top: var(--sticky-kopf-top, 0px);
    left: 0; right: 0; z-index: 6;
    border: 0; width: 100%;
    font: inherit; font-size: 12px; letter-spacing: .04em;
    background: var(--color-ink); color: var(--color-paper);
    padding: 5px 0; cursor: pointer;
  }
  html[data-kopf="weg"] .kopf-griff:focus-visible {
    outline: 2px solid var(--color-paper); outline-offset: -4px;
  }

  @media (prefers-reduced-motion: reduce) {
    .shell-header-actions, .section-subnav { transition: none; }
  }
  /* Ende #894 Kopf weicht — Schlussmarke fuer die Tests (#1294), nicht entfernen */


  .shell-header-note {
    margin-left: var(--space-4);
    margin-right: var(--space-4);
  }

  h1 {
    font-size: 28px;
  }

  h2 {
    font-size: 22px;
  }
}

/* -----------------------------------------------------------------------------
   8. Buttons
   ----------------------------------------------------------------------------- */

/* Base button */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-6);
  font-family: var(--font-family);
  font-size: 15px;
  font-weight: var(--weight-medium);
  line-height: 1;
  border-radius: var(--radius-pill);
  border: 1.5px solid transparent;
  min-height: 44px;
  cursor: pointer;
  text-decoration: none;
  transition:
    background-color var(--motion-fast),
    color var(--motion-fast),
    border-color var(--motion-fast);
  white-space: nowrap;
}

.btn:hover {
  text-decoration: none;
}

.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}

/* Primary — red CTA (counts as the ONE red element) */
.btn-primary {
  background-color: var(--color-red);
  color: var(--color-white);
  border-color: var(--color-red);
}

.btn-primary:hover {
  background-color: var(--color-red-700);
  border-color: var(--color-red-700);
  color: var(--color-white);
}

/* Secondary — ink pill */
.btn-secondary {
  background-color: var(--color-ink);
  color: var(--color-paper);
  border-color: var(--color-ink);
}

.btn-secondary:hover {
  background-color: var(--color-ink-2);
  border-color: var(--color-ink-2);
  color: var(--color-paper);
}

/* Danger — destruktive Aktion (Ticket #678). Ein DEZENTER Rot-Outline, kein zweiter
   solider Rot-CTA: transparenter Grund, roter Text, hell-roter Rahmen, Hover fuellt hell-rot.
   So hebt sich eine UNWIEDERBRINGLICHE Aktion (Endgueltig entfernen, Papierkorb leeren)
   sichtbar von den ink-farbenen `.btn-secondary` ab, ohne das EINE solide Rot (`.btn-primary`)
   des Bildschirms zu verdoppeln (One-Red-Rule, R1.3). Nur CSS-Variablen, keine Hex-Werte.
   REVERSIBLE Loeschungen (Loeschen -> Papierkorb) bleiben bewusst ink — sie stehen je Zeile
   und duerfen kein Rot tragen. */
.btn-danger {
  background-color: transparent;
  color: var(--color-red-700);
  border-color: var(--color-red-100);
}

.btn-danger:hover {
  background-color: var(--color-red-100);
  color: var(--color-red-700);
  border-color: var(--color-red);
}

/* ── Mehrfachauswahl (Tickets #760 / #763) ───────────────────────────────────
   Die Mechanik steht in /assets/auswahl.js und wird von ZWEI Listen benutzt
   (Ablage-Dateien, Aufbereiten-Befunde). Deshalb steht ihr Aussehen hier und
   nicht in einem der beiden Bildschirme: zwei Kopien liefen auseinander, und
   dieselbe Geste saehe an zwei Stellen verschieden aus.

   KEIN ROT (R1.3). Die Leiste ist ein Zustand, kein Alarm — Creme als Flaeche,
   Haarlinie als Kante, dieselben Token wie die uebrigen Sammelflaechen. Rot
   traegt hoechstens ein EINZELNER Knopf darin, und nur wenn er unwiederbringlich
   ist (dann `.btn-danger`, s. oben). */
.aus-leiste {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  padding: var(--space-2) var(--space-3);
  margin-bottom: var(--space-2);
  border: 1px solid var(--color-hairline);
  border-radius: 8px;
  background-color: var(--color-cream-3);
}

.aus-zahl {
  font-size: 12px;
  color: var(--color-ink);
  margin-right: var(--space-2);
}

/* Der Knopf in der Leiste ist kleiner als ein Seitenknopf — er gehoert zu einer
   Auswahl, nicht zur Seite. */
.aus-leiste .aus-klein {
  font-size: 11px;
  padding: 3px 9px;
}

/* Das Kaestchen traegt die Akzentfarbe des Portals statt der Browser-Vorgabe
   (die ist je Betriebssystem eine andere, meist Blau — eine Farbe, die es im
   Portal sonst nirgends gibt). Anfassflaeche: ein 13-px-Kaestchen ist auf einem
   Tastfeld nicht zu treffen. */
.aus-kaest {
  accent-color: var(--color-ink);
  cursor: pointer;
  width: 15px;
  height: 15px;
  vertical-align: middle;
}

/* Ghost — outlined */
.btn-ghost {
  background-color: transparent;
  color: var(--color-ink);
  border-color: var(--color-hairline);
}

.btn-ghost:hover {
  border-color: var(--color-sand);
  background-color: var(--color-cream-3);
  color: var(--color-ink);
}

/* Ghost on dark background */
.btn-ghost-dark {
  background-color: transparent;
  color: var(--color-slate-3);
  border-color: var(--color-ink-3);
}

.btn-ghost-dark:hover {
  border-color: var(--color-slate-3);
  color: var(--color-paper);
  background-color: var(--color-ink-2);
}

/* Small button variant */
.btn-sm {
  padding: var(--space-2) var(--space-4);
  font-size: 13px;
  min-height: 36px;
}

/* Icon-only button.
   S149 P1 (M4): Die Klasse gab es seit dem ersten Tag und wurde KEIN EINZIGES MAL
   benutzt — jeder Symbolknopf im Portal war ad hoc gebaut, das ✕ in news/config
   mass rund 16 × 12 px und loescht einen Eintrag. Damit sie ohne zweite Regel
   danebengesetzt werden kann, richtet sie das Zeichen jetzt selbst mittig aus;
   vorher musste jede Fundstelle display/align nachreichen und tat es nicht. */
.btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-2);
  min-height: 44px;
  min-width: 44px;
  border-radius: var(--radius-input);
  line-height: 1;
}

/* -----------------------------------------------------------------------------
   9. Cards
   ----------------------------------------------------------------------------- */
.card {
  background-color: var(--color-paper);
  border-radius: var(--radius-card);
  border: 1px solid var(--color-hairline);
  padding: var(--space-6);
  transition:
    box-shadow var(--motion-base),
    transform var(--motion-base);
}

.card:hover {
  box-shadow: var(--shadow-md);
}

.card-header {
  margin-bottom: var(--space-4);
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--space-3);
}

.card-title {
  font-size: 18px;
  font-weight: var(--weight-regular);
}

.card-body {
  color: var(--color-ink);
}

/* Ticket #108 (2026-07-15): .detail-list rendert bisher pures dl/dt/dd-Browser-Default
   (jedes Label+Wert-Paar auf 2 Zeilen) — kompaktes Label:Wert-Grid, eine Zeile je Feld.
   Wirkt auf contracts/detail.ejs + application/bewerbung.ejs (beide nutzen dieselbe Klasse). */
.detail-list {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--space-1) var(--space-4);
  margin: 0;
}
.detail-list dt {
  color: var(--color-slate);
  font-size: 13px;
}
.detail-list dd {
  margin: 0;
  font-size: 13px;
}

.card-footer {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-hairline);
}

/* -----------------------------------------------------------------------------
   10. Module Cards (home page)
   ----------------------------------------------------------------------------- */
.module-card-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-6);
  margin-bottom: var(--space-8);
}

/* Ticket #539 — Zwischenueberschriften ueber den Kacheln auf "Heute", damit die
   Startseite dieselbe Gliederung traegt wie die Seitenleiste (Markt · Intern ·
   Admin). Bewusst DIESELBEN Schriftwerte wie .nav-group-label weiter unten: es ist
   dieselbe Aussage an zwei Orten, und zwei Abstufungen desselben Labels laesen sich
   als zwei verschiedene Dinge. Unterschiedlich sind nur die Abstaende — die
   Seitenleiste rueckt mit ihrem eigenen Innenrand ein, der Inhaltsbereich nicht.
   Der Abstand nach unten wandert von der Kachelreihe auf die Gruppe, sonst stuende
   zwischen Ueberschrift und Kacheln der volle Gruppenabstand. */
.module-card-gruppe {
  margin-bottom: var(--space-8);
}

.module-card-gruppe:last-child {
  margin-bottom: 0;
}

.module-card-gruppe .module-card-grid {
  margin-bottom: 0;
}

.module-card-gruppe-label {
  display: block;
  margin: 0 0 var(--space-3);
  font-size: 11px;
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--color-slate-3);
}

.module-card {
  background-color: var(--color-paper);
  border-radius: var(--radius-card);
  border: 1px solid var(--color-hairline);
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  text-decoration: none;
  color: var(--color-ink);
  transition:
    box-shadow var(--motion-base),
    transform var(--motion-base);
  min-height: 108px;
}

.module-card:hover {
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
  text-decoration: none;
  color: var(--color-ink);
}

.module-card-icon {
  width: 28px;
  height: 28px;
  color: var(--color-slate);
}

.module-card-name {
  font-size: 15px;
  font-weight: var(--weight-medium);
}

.module-card-summary {
  font-size: 13px;
  color: var(--color-slate);
  flex: 1;
}

.module-card-cta {
  font-size: 13px;
  font-weight: var(--weight-medium);
  color: var(--color-ink-4);
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

@media (max-width: 1024px) {
  .module-card-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 600px) {
  .module-card-grid {
    grid-template-columns: 1fr;
    gap: var(--space-3);
  }
}

/* ─── Ticket #722: Kernstatus-Kacheln oben auf "/" ───────────────────────────
   Fuenf KPI-Kacheln oberhalb des Heute-Blocks (routes/home.js, home.ejs).
   `.kernstatus-kachel` erbt von `.card` (gleicher Rahmen/Radius wie ueberall
   sonst) und wird zusaetzlich klickbar/hoverbar wie `.module-card` — bewusst
   KEINE eigene Kartenfarbe oder -form, sonst waere es eine dritte Kachel-
   Optik neben `.card` und `.module-card`. */
.kernstatus-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-4);
  margin-bottom: var(--space-8);
}

.kernstatus-kachel {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  text-decoration: none;
  color: var(--color-ink);
  transition:
    box-shadow var(--motion-base),
    transform var(--motion-base);
}

.kernstatus-kachel:hover {
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
  text-decoration: none;
  color: var(--color-ink);
}

.kernstatus-titel {
  font-size: 13px;
  font-weight: var(--weight-medium);
  color: var(--color-slate);
  margin-bottom: var(--space-1);
}

.kernstatus-zeile {
  font-size: 14px;
}

.kernstatus-zeile--nebensaechlich {
  font-size: 13px;
  color: var(--color-slate);
}

@media (max-width: 600px) {
  .kernstatus-grid {
    grid-template-columns: 1fr;
    gap: var(--space-3);
  }
}

/* -----------------------------------------------------------------------------
   11. Badges
   ----------------------------------------------------------------------------- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 3px var(--space-2);
  border-radius: var(--radius-pill);
  font-size: 12px;
  font-weight: var(--weight-medium);
  line-height: 1.4;
  white-space: nowrap;
  border: 1px solid transparent;
}

/* Active / done */
.badge-ink {
  background-color: var(--color-ink);
  color: var(--color-paper);
}

/* Neutral / waiting */
.badge-slate {
  background-color: var(--color-cream-3);
  color: var(--color-slate);
  border-color: var(--color-hairline);
}

/* Action needed */
.badge-tint {
  background-color: var(--color-red-100);
  color: var(--color-ink);
}

/* Vorbei — flach, ohne Rand, mit vorangestelltem Zeichen (✓ / ×).
   Befund Martin 08.09.2026: „zu verifizieren vs. verifiziert — hat beides
   dieselbe Button-Farbe." Gemessen war es woertlich dieselbe Klasse (`.badge`)
   fuer beide Zustaende, und dieselbe wie die Art-Marke daneben.
   Der Unterschied darf hier NICHT ueber Farbe entstehen: R1.3 laesst ein rotes
   Element je Bildschirm zu, Ampelfarben sind verboten, und eine Liste mit 700
   erledigten Zeilen darf keine davon einfaerben.
   Also Form statt Farbton, in EINER neutralen Tinten-Familie: `badge-ink` ist
   gefuellt (liegt bei einem Menschen), `badge-slate` umrandet (laeuft), und
   diese hier ist randlos und flach — die ruhigste der drei Stufen. Das Zeichen
   setzt ticketStatusForm.js davor; es traegt die Unterscheidung auch in
   Graustufen und ohne Farbwahrnehmung.
   Kein Bold (boldPolicy): das Gewicht bleibt wie bei allen Abzeichen. */
.badge-erledigt {
  background-color: transparent;
  color: var(--color-ink-4);
  border-color: transparent;
  padding-left: 0;
}

/* Error — counts as the ONE red element */
.badge-red {
  background-color: var(--color-red);
  color: var(--color-white);
}

/* -----------------------------------------------------------------------------
   11b. Hint boxes (toasts + inline errors)

   Befund F-NEWS-1 (2026-07-27): .hint-box und .hint-box-red werden in sieben Views
   benutzt (invoices/admin, invoices/dashboard, invoices/lauf, news/admin, news/brief,
   news/config, partials/ai-admin-screen), waren hier aber NIE definiert — jede
   Erfolgsmeldung und jede Fehlerbox rendert seit jeher als nackter Fliesstext ohne
   Rahmen und ohne Farbe. Aufgefallen beim Bau der News-Fehlerkarte.

   .hint-box-red traegt bewusst nur einen roten Rand und Red-Tint als Flaeche, keine
   satte rote Flaeche: die Box steht oft neben einem Status-Badge, und zwei satte Rot
   auf einem Screen verstossen gegen die One-Red-Rule (R1.3).
   ----------------------------------------------------------------------------- */
.hint-box {
  background-color: var(--color-cream-3);
  border: 1px solid var(--color-hairline);
  border-left: 3px solid var(--color-slate);
  border-radius: var(--radius-input);
  padding: var(--space-3) var(--space-4);
  font-size: 13.5px;
  color: var(--color-ink);
}

.hint-box-red {
  background-color: var(--color-red-100);
  border-color: var(--color-red-100);
  border-left-color: var(--color-red);
}

/* -----------------------------------------------------------------------------
   11c. Progress-Visual (S232, wiederverwendbarer Fortschritts-Baustein)

   Herkunft: docs/governance/S232-Mockup-Progress-Belege.html — dieselben
   Klassennamen, dieselbe Farblogik (nur die aktuelle Stufe traegt Rot, R1.3).
   Zwei Groessen (kompakt fuer Tabellenzeilen, gross fuers Detail-Panel) teilen
   sich denselben Stufenbegriff: erledigt (Ink, gefuellt) / aktuell (Ink-Punkt
   mit rotem Ring — das eine rote Moment DIESES Bausteins) / ausstehend
   (Outline, Slate). Ein Seitenweg (Duplikat/Quarantaene/Storno/Abgelehnt/
   Aufgeteilt) ist KEIN Fortschritt auf demselben Pfad und bekommt deshalb ein
   eigenes, eckiges Segment statt eines Platzes auf der Leiste — vgl.
   `statusArt()` (screenDaten.js): "slate = Seitenweg", nicht rot, weil ein
   Seitenweg nicht per se ein Fehlzustand ist.
   ----------------------------------------------------------------------------- */
.pv-compact { display: flex; align-items: center; min-width: 150px; }
.pv-compact .pv-step { position: relative; flex: 1; display: flex; align-items: center; justify-content: center; height: 20px; }
.pv-compact .pv-step:not(:last-child)::after {
  content: ''; position: absolute; top: 50%; left: 50%; width: 100%; height: 2px;
  background: var(--color-slate-3); transform: translateY(-50%); z-index: 0;
}
.pv-compact .pv-step.is-done:not(:last-child)::after { background: var(--color-ink); }
.pv-compact .pv-dot {
  position: relative; z-index: 1; width: 11px; height: 11px; border-radius: 50%;
  background: var(--color-paper); border: 2px solid var(--color-slate-3);
}
.pv-compact .pv-step.is-done .pv-dot { background: var(--color-ink); border-color: var(--color-ink); }
.pv-compact .pv-step.is-current .pv-dot {
  background: var(--color-ink); border-color: var(--color-red);
  box-shadow: 0 0 0 3px var(--color-red-100);
}

.pv-label-row { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.pv-label-row .pv-text { font-size: 12.5px; color: var(--color-ink); white-space: nowrap; }
.pv-label-row .pv-age { font-size: 11.5px; color: var(--color-slate); white-space: nowrap; }

.pv-sidepath { display: inline-flex; align-items: center; gap: 6px; }
.pv-sidepath .pv-sq {
  width: 11px; height: 11px; border-radius: 3px; background: var(--color-slate-3);
  display: inline-block; flex: none;
}
.pv-sidepath .pv-sq.pv-sq-tint { background: var(--color-red-100); border: 1px solid var(--color-red-700, var(--color-red)); }

.pv-large { display: flex; align-items: flex-start; margin: var(--space-4) 0 var(--space-2); }
.pv-large .pv-step { position: relative; flex: 1; display: flex; flex-direction: column; align-items: center; text-align: center; }
.pv-large .pv-step:not(:last-child)::after {
  content: ''; position: absolute; top: 13px; left: 50%; width: 100%; height: 2px;
  background: var(--color-slate-3); z-index: 0;
}
.pv-large .pv-step.is-done:not(:last-child)::after { background: var(--color-ink); }
.pv-large .pv-dot-wrap { position: relative; z-index: 1; }
.pv-large .pv-dot {
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--color-paper); border: 2px solid var(--color-slate-3);
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; color: var(--color-slate-3);
}
.pv-large .pv-step.is-done .pv-dot { background: var(--color-ink); border-color: var(--color-ink); color: var(--color-paper); }
.pv-large .pv-step.is-current .pv-dot {
  background: var(--color-ink); border-color: var(--color-red); color: var(--color-paper);
  box-shadow: 0 0 0 4px var(--color-red-100);
}
.pv-large .pv-step-label { font-size: 12.5px; margin-top: var(--space-2); color: var(--color-ink); }
.pv-large .pv-step.is-pending .pv-step-label { color: var(--color-slate); }
.pv-large .pv-step-note { font-size: 11px; color: var(--color-slate); margin-top: 2px; max-width: 15ch; }

/* -----------------------------------------------------------------------------
   12. Tables
   ----------------------------------------------------------------------------- */
.table-container {
  overflow-x: auto;
  border-radius: var(--radius-card);
  border: 1px solid var(--color-hairline);
}

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
}

.table thead {
  background-color: var(--color-ink-4);
}

.table thead th {
  padding: var(--space-3) var(--space-4);
  color: var(--color-cream);
  font-size: 12px;
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: 0.18em;
  text-align: left;
  white-space: nowrap;
  border-bottom: 1px solid var(--color-ink-3);
}

.table thead th.text-right {
  text-align: right;
}

.table tbody tr {
  border-bottom: 1px solid var(--color-hairline);
  transition: background-color var(--motion-fast);
}

.table tbody tr:last-child {
  border-bottom: none;
}

.table tbody tr:nth-child(odd) {
  background-color: var(--color-paper);
}

.table tbody tr:nth-child(even) {
  background-color: var(--color-cream-3);
}

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

.table tbody td {
  padding: var(--space-3) var(--space-4);
  color: var(--color-ink);
  vertical-align: middle;
}

.table td.text-right,
.table th.text-right {
  text-align: right;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

.table td.mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* Summary row */
.table tfoot tr {
  border-top: 2px solid var(--color-hairline);
  background-color: var(--color-cream-3);
}

.table tfoot td {
  padding: var(--space-3) var(--space-4);
  font-weight: var(--weight-medium);
}

/* Row actions */
.table-actions {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  justify-content: flex-end;
}

/* -----------------------------------------------------------------------------
   13. Forms
   ----------------------------------------------------------------------------- */
.form-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

.form-field label {
  font-size: 14px;
  font-weight: var(--weight-medium);
  color: var(--color-ink);
}

.form-field .field-hint {
  font-size: 12px;
  color: var(--color-slate);
}

.form-field .field-error {
  font-size: 12px;
  color: var(--color-ink);
  background-color: var(--color-red-100);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-input);
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="search"],
input[type="url"],
input[type="tel"],
input[type="number"],
input[type="date"],
input[type="datetime-local"],
input[type="time"],
select,
textarea {
  display: block;
  width: 100%;
  padding: var(--space-3) var(--space-4);
  font-family: var(--font-family);
  font-size: 15px;
  font-weight: var(--weight-regular);
  color: var(--color-ink);
  background-color: var(--color-paper);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-input);
  min-height: 44px;
  transition:
    border-color var(--motion-fast),
    box-shadow var(--motion-fast);
  appearance: none;
  -webkit-appearance: none;
}

input[type="text"]:hover,
input[type="email"]:hover,
input[type="password"]:hover,
input[type="search"]:hover,
input[type="url"]:hover,
input[type="tel"]:hover,
input[type="number"]:hover,
input[type="date"]:hover,
input[type="datetime-local"]:hover,
input[type="time"]:hover,
select:hover,
textarea:hover {
  border-color: var(--color-sand);
}

input[type="text"]:focus,
input[type="email"]:focus,
input[type="password"]:focus,
input[type="search"]:focus,
input[type="url"]:focus,
input[type="tel"]:focus,
input[type="number"]:focus,
input[type="date"]:focus,
input[type="datetime-local"]:focus,
input[type="time"]:focus,
select:focus,
textarea:focus {
  outline: none;
  border-color: var(--color-ink);
  box-shadow: 0 0 0 2px var(--color-ink-4);
}

input[readonly],
input[disabled],
select[disabled],
textarea[readonly],
textarea[disabled] {
  background-color: var(--color-cream-3);
  color: var(--color-slate);
  cursor: not-allowed;
}

textarea {
  min-height: 120px;
  resize: vertical;
  line-height: 1.5;
}

select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%235A6470' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--space-4) center;
  padding-right: var(--space-8);
}

/* Datei-Auswahl — S149 P1 (M4).
   `input[type="file"]` fehlte in der Basisregel darueber: acht Datei-Felder im
   Portal hatten damit keine Mindesthoehe und lagen als nackter Browser-Default
   deutlich unter 44 px. Bewusst eine EIGENE Regel statt der Aufnahme in die
   Liste oben: jene setzt `appearance: none`, was bei Datei-Feldern in mehreren
   Browsern den "Durchsuchen"-Knopf mit entfernt — das Feld waere dann zwar hoch
   genug und nicht mehr bedienbar. */
input[type="file"] {
  display: block;
  width: 100%;
  min-height: 44px;
  padding: var(--space-2) var(--space-3);
  font-family: var(--font-family);
  font-size: 14px;
  color: var(--color-ink);
  background-color: var(--color-paper);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-input);
}

/* ...und `hidden` muss diese Regel schlagen koennen.
   BEFUND 03.09.2026, von Martin am Bildschirm gefunden: im Import standen zwei
   Bedienelemente uebereinander — das native "Dateien auswaehlen | Keine
   ausgewaehlt" MIT Rahmen und runden Ecken, darunter unser Knopf. Im Markup
   trug das Feld `hidden`. Das half nichts: die Regel oben ist
   `input[type="file"]` (0,1,1) und schlaegt die Browser-Regel `[hidden]`
   (0,1,0). Das Feld blieb also sichtbar, obwohl es versteckt sein SOLLTE — und
   brachte nebenbei Rahmen und Radius mit, die es im Suriro-Bild nicht gibt.

   Die Reparatur war damals bewusst eng auf `input[type="file"][hidden]` gelegt
   und nicht global — mit der Begruendung, eine globale Regel laufe ueber alle
   Elemente. Diese Abwaegung ist mit #807 (12.09.2026) entschieden: dieselbe
   Falle traf als FUENFTE Stelle `input[type="text"]` in der Basisregel gleich
   darueber, auf Prod sichtbar bei 22 von 70 Kontaktzeilen. `[hidden]` traegt
   deshalb in Abschnitt 2 jetzt global `display: none !important`, und die
   Einzelregel, die hier stand, ist damit aufgeloest. */

/* Checkbox / Radio */
input[type="checkbox"],
input[type="radio"] {
  width: 18px;
  height: 18px;
  min-height: unset;
  padding: 0;
  accent-color: var(--color-ink);
  cursor: pointer;
  flex-shrink: 0;
}

.form-group {
  margin-bottom: var(--space-4);
}

.form-group label {
  display: block;
  font-size: 14px;
  font-weight: var(--weight-medium, 500);
  margin-bottom: var(--space-2);
  color: var(--color-ink);
}

.form-hint {
  display: block;
  font-size: 13px;
  color: var(--color-slate);
  font-weight: var(--weight-regular);
  margin-top: 2px;
}

.checkbox-label {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  cursor: pointer;
  min-height: 44px;
}

/* Ticket #107: Add/Remove-Zeilen-UI für field_schema type=list (contracts-list-field.js) */
.list-field-table {
  width: 100%;
  border-collapse: collapse;
  margin-bottom: var(--space-2);
}
/* #1190: Hausfarbton ink-4 wie jeder Tabellenkopf (vorher slate ohne Flaeche). */
.list-field-table th {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-cream);
  background-color: var(--color-ink-4);
  text-align: left;
  padding: 4px var(--space-2);
}
.list-field-table td {
  padding: 2px var(--space-2) 2px 0;
}
.list-field-table td input {
  width: 100%;
  box-sizing: border-box;
}
.list-field-table td button {
  font-size: 13px;
  padding: 2px 8px;
}

/* -----------------------------------------------------------------------------
   Ticket #152: Feldschema-Editor (visueller Formular-Editor statt JSON-Textarea
   für contracts.template.field_schema). Nutzt dieselbe Add/Remove-Zeilen-Optik
   wie .list-field-table (#107) für Optionen/itemFields.
   ----------------------------------------------------------------------------- */
.fse-raw-warning {
  display: none;
  margin-bottom: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-input);
  border: 1px solid var(--color-hairline);
  background: var(--color-cream-3);
  color: var(--color-slate);
  font-size: 13px;
}

.fse-field-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}

.fse-field-card {
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-input);
  padding: var(--space-4);
  background: var(--color-cream-3);
}

.fse-field-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}

.fse-field-index {
  font-size: 13px;
  font-weight: var(--weight-medium, 500);
  color: var(--color-ink);
}

.fse-field-header-actions {
  display: flex;
  gap: var(--space-2);
}

.fse-field-header-actions .btn {
  padding: 2px 10px;
  min-height: 32px;
}

.fse-field-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 0 var(--space-4);
}

.fse-field-grid .form-group {
  margin-bottom: var(--space-3);
}

.fse-subsection {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-hairline);
}

.fse-subsection > label {
  display: block;
  font-size: 14px;
  font-weight: var(--weight-medium, 500);
  margin-bottom: var(--space-2);
}

.fse-string-list-rows {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.fse-string-list-row {
  display: flex;
  gap: var(--space-2);
  align-items: center;
}

.fse-string-list-row input {
  flex: 1;
}

.form-check {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 44px;
  cursor: pointer;
}

.form-check label {
  font-weight: var(--weight-regular);
  cursor: pointer;
}

/* Form row */
.form-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4);
}

@media (max-width: 600px) {
  .form-row {
    grid-template-columns: 1fr;
  }
}

/* Form actions */
.form-actions {
  display: flex;
  gap: var(--space-3);
  align-items: center;
  margin-top: var(--space-6);
}

/* Collapsible section */
.collapsible-trigger {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  background: none;
  border: none;
  font-family: var(--font-family);
  font-size: 14px;
  font-weight: var(--weight-medium);
  color: var(--color-slate);
  cursor: pointer;
  padding: var(--space-2) 0;
  transition: color var(--motion-fast);
}

.collapsible-trigger:hover {
  color: var(--color-ink);
}

.collapsible-trigger[aria-expanded="true"] .collapsible-icon {
  transform: rotate(180deg);
}

.collapsible-icon {
  transition: transform var(--motion-fast);
}

.collapsible-body {
  overflow: hidden;
  transition: max-height var(--motion-base);
}

.collapsible-body[aria-hidden="true"] {
  max-height: 0;
}

/* -----------------------------------------------------------------------------
   14. Dialog / Sheet
   ----------------------------------------------------------------------------- */
.dialog-overlay {
  position: fixed;
  inset: 0;
  background-color: rgba(24, 18, 40, 0.55);
  backdrop-filter: blur(2px);
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--motion-base);
}

.dialog-overlay.is-open {
  opacity: 1;
  pointer-events: auto;
}

.dialog {
  background-color: var(--color-paper);
  border-radius: var(--radius-card);
  padding: var(--space-8);
  width: 100%;
  max-width: 520px;
  max-height: 90vh;
  overflow-y: auto;
  position: relative;
  transform: translateY(8px);
  transition: transform var(--motion-base);
  box-shadow: var(--shadow-md);
}

.dialog-overlay.is-open .dialog {
  transform: translateY(0);
}

.dialog-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: var(--space-6);
}

.dialog-title {
  font-size: 22px;
  font-weight: var(--weight-regular);
}

.dialog-close {
  background: none;
  border: none;
  padding: var(--space-2);
  cursor: pointer;
  color: var(--color-slate);
  min-height: 44px;
  min-width: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-input);
  transition: color var(--motion-fast), background-color var(--motion-fast);
}

.dialog-close:hover {
  color: var(--color-ink);
  background-color: var(--color-cream-3);
}

.dialog-footer {
  margin-top: var(--space-6);
  display: flex;
  justify-content: flex-end;
  gap: var(--space-3);
}

@media (max-width: 600px) {
  .dialog-overlay {
    align-items: flex-end;
    padding: 0;
  }

  .dialog {
    border-radius: var(--radius-card) var(--radius-card) 0 0;
    max-height: 85vh;
    max-width: 100%;
  }
}

/* -----------------------------------------------------------------------------
   15. Toast Notifications
   ----------------------------------------------------------------------------- */
.toast-container {
  position: fixed;
  bottom: var(--space-6);
  right: var(--space-6);
  z-index: 300;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  max-width: 380px;
  width: calc(100% - var(--space-8));
}

@media (max-width: 768px) {
  .toast-container {
    bottom: calc(var(--bottom-nav-height) + var(--space-3));
    left: var(--space-3);
    right: var(--space-3);
  }
}

.toast {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-4);
  background-color: var(--color-ink);
  color: var(--color-paper);
  border-radius: var(--radius-card);
  font-size: 14px;
  line-height: 1.4;
  box-shadow: var(--shadow-md);
  animation: toast-in var(--motion-base) ease-out;
}

.toast.toast-error {
  background-color: var(--color-ink-hdr);
  border-left: 3px solid var(--color-red);
}

.toast-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  margin-top: 1px;
  color: var(--color-slate-3);
}

.toast.toast-error .toast-icon {
  color: var(--color-red-100);
}

.toast-message {
  flex: 1;
}

.toast-dismiss {
  background: none;
  border: none;
  color: var(--color-slate-3);
  cursor: pointer;
  padding: 0;
  min-height: 24px;
  min-width: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: color var(--motion-fast);
}

.toast-dismiss:hover {
  color: var(--color-paper);
}

@keyframes toast-in {
  from {
    opacity: 0;
    transform: translateX(20px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes toast-out {
  from {
    opacity: 1;
    transform: translateX(0);
  }
  to {
    opacity: 0;
    transform: translateX(20px);
  }
}

.toast.is-exiting {
  animation: toast-out var(--motion-base) ease-out forwards;
}

/* -----------------------------------------------------------------------------
   16. Wizard Stepper
   ----------------------------------------------------------------------------- */
.wizard-steps {
  display: flex;
  align-items: flex-start;
  gap: 0;
  margin-bottom: var(--space-12);
  overflow-x: auto;
}

.wizard-step {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex: 1;
  position: relative;
  min-width: 80px;
}

/* Connector line */
.wizard-step:not(:last-child)::after {
  content: '';
  position: absolute;
  top: 16px;
  left: calc(50% + 16px);
  right: calc(-50% + 16px);
  height: 1px;
  background-color: var(--color-hairline);
}

.wizard-step.done:not(:last-child)::after,
.wizard-step.active:not(:last-child)::after {
  background-color: var(--color-ink-3);
}

.wizard-step-indicator {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 2px solid var(--color-hairline);
  background-color: var(--color-paper);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  font-weight: var(--weight-medium);
  color: var(--color-slate);
  z-index: 1;
  transition:
    background-color var(--motion-base),
    border-color var(--motion-base),
    color var(--motion-base);
  flex-shrink: 0;
}

.wizard-step.active .wizard-step-indicator {
  background-color: var(--color-ink);
  border-color: var(--color-ink);
  color: var(--color-paper);
}

.wizard-step.done .wizard-step-indicator {
  background-color: var(--color-ink-3);
  border-color: var(--color-ink-3);
  color: var(--color-paper);
}

.wizard-step-label {
  margin-top: var(--space-2);
  font-size: 12px;
  font-weight: var(--weight-regular);
  color: var(--color-slate);
  text-align: center;
  line-height: 1.3;
}

.wizard-step.active .wizard-step-label {
  color: var(--color-ink);
  font-weight: var(--weight-medium);
}

.wizard-step.done .wizard-step-label {
  color: var(--color-ink-4);
}

/* -----------------------------------------------------------------------------
   17. Login Page
   ----------------------------------------------------------------------------- */
.login-page {
  min-height: 100vh;
  background-color: var(--color-ink);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-6);
  position: relative;
  overflow: hidden;
}

/* Berg-Silhouette as SVG background via CSS mask */
.login-bg {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  overflow: hidden;
}

.login-bg svg {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  height: auto;
  min-height: 280px;
  opacity: 0.12;
}

.login-card {
  position: relative;
  z-index: 1;
  background-color: var(--color-paper);
  border-radius: var(--radius-container);
  padding: var(--space-12) var(--space-8);
  width: 100%;
  max-width: 420px;
  box-shadow: 0 24px 64px rgba(0, 0, 0, 0.35);
}

.login-wordmark {
  font-family: var(--font-family);
  font-size: 32px;
  font-weight: var(--weight-light);
  color: var(--color-ink);
  letter-spacing: -0.02em;
  text-transform: lowercase;
  text-align: center;
  margin-bottom: var(--space-8);
}

.login-divider {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-6) 0;
  color: var(--color-slate);
  font-size: 12px;
}

.login-divider::before,
.login-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background-color: var(--color-hairline);
}

.login-error {
  background-color: var(--color-red-100);
  color: var(--color-ink);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-input);
  font-size: 14px;
  margin-bottom: var(--space-4);
}

/* -----------------------------------------------------------------------------
   18. Section Sub-Nav — Ticket #116: einheitliche Sub-Navigation auf allen Seiten
   (vormals .admin-subnav — portal-weit generalisiert; sticky wie ehem. .app-tab-nav)
   ----------------------------------------------------------------------------- */
/* Ticket #704 (Martin, 31.08.2026, gemeldet auf /accounting/reisen): „Titelzeile
   Auswahl wird einfach abgeschnitten - soll in zweite Zeile umgebrochen werden -
   so kommt man an einige Bereiche gar nicht ran."

   URSACHE: die Zeile war `flex` OHNE `flex-wrap` — alle Reiter blieben in EINER
   Zeile. `overflow-x: auto` machte daraus eine waagerecht scrollbare Leiste,
   `scrollbar-width: none` (plus die ::-webkit-scrollbar-Regel darunter) nahm ihr
   die einzige sichtbare Anzeige dafuer. Wer nicht ahnt, dass man hier wischen
   kann, kommt an die hinteren Reiter nicht heran — im Accounting sind das bei
   dreizehn Reitern die letzten mehreren.

   Das Umbrechen loest es an der Wurzel: was nicht in die Breite passt, steht in
   der naechsten Zeile und ist ohne Wischgeste erreichbar. `overflow-x: auto`
   bleibt als Rueckfall fuer den Fall, in dem es nichts zu umbrechen gibt — ein
   EINZELNER Reiter breiter als der Kasten (sehr schmales Fenster, langes Label).
   Portalweit, weil die Falle es auch ist: jede Modul-Leiste mit genug Reitern
   trifft sie, /accounting/reisen war nur der Bildschirm, auf dem Martin stand. */
.section-subnav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  padding: var(--space-4) 0 0;
  border-bottom: 1px solid var(--color-hairline);
  margin-bottom: var(--space-6);
  overflow-x: auto;
  scrollbar-width: none;
  position: sticky;
  /* Ticket #385: war top:0 und lag damit UNTER der deckenden Pillenleiste. */
  top: var(--sticky-unterkante);
  z-index: 5;
  background-color: var(--color-cream);
}
.section-subnav::-webkit-scrollbar { display: none; }

/* #894 — der Schalter, der den kompakten Kopf wieder aufklappt.
   VORGABE `display: none`: er gehoert zum Telefon-Zustand (s. @media weiter oben).
   `position: sticky; left: 0` ist kein Schmuck: im kompakten Zustand ist das
   Reiterband selbst waagerecht scrollbar (`overflow-x: auto` oben), und ein
   normaler Knopf waere nach zwei Reitern aus dem Bild -- dieselbe Falle, die am
   14.09. auf der Events-Seite zuschlug (ein klebendes Element an einem
   Elternteil, das selbst scrollt). 44 px in beide Richtungen: er ist selbst ein
   Tippziel (R17). Kein Rot -- er steht auf JEDEM Bildschirm im Bild und darf den
   einen roten Platz nicht belegen (R1.3). */
.kopf-schalter {
  display: none;
  align-items: center;
  justify-content: center;
  position: sticky;
  left: 0;
  z-index: 1;
  flex: 0 0 auto;
  min-width: 44px;
  min-height: 44px;
  padding: 0 var(--space-2);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-input, 8px);
  background-color: var(--color-cream);
  color: var(--color-ink-2);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
}
.kopf-schalter:hover { background-color: var(--color-cream-3); }

/* Sichtbar NUR am Telefon -- am Schreibtisch bricht nichts um, und ein Knopf
   ohne Wirkung ist Rauschen. Dieser Block steht HIER und nicht oben bei den
   uebrigen Telefon-Regeln: `display` gegen `display` ist gleiche Spezifitaet,
   da entscheidet die Reihenfolge, und die Vorgabe `none` darueber haette
   gewonnen. Beim ersten Anlauf am 15.09. genau so passiert -- der Schalter war
   auf keinem Geraet zu sehen, und der Test sah die falsche der beiden Regeln. */
@media (max-width: 768px) {
  .kopf-schalter { display: inline-flex; }
}

/* Zweite Subnav-Zeile (_section_header.ejs, subnavItems2). Bis 05.10. statisch: auf
   derselben Kante wie die erste klebend, laege sie UEBER ihr.
   #821 (Martin 12.09.: „nur die oberste Zeile ist sticky, die Unter-Tabs nicht — man
   muss zum Wechseln staendig hoch/runter scrollen"; Entscheid 05.10.: „Unter-Reiter
   kleben"): sie klebt jetzt an der UNTERKANTE der ersten Reiterzeile
   (--sticky-unterkante-2, gemessen in shell.js installStickyKante) — nicht auf
   derselben Kante. z-index eins unter der ersten Zeile: beim Wegschieben gleitet
   sie unter ihr hindurch, nicht darueber. Enger an die erste Zeile gerueckt, die
   eigene border-bottom bleibt. */
.section-subnav-secondary {
  position: sticky;
  top: var(--sticky-unterkante-2, var(--sticky-unterkante, 0px));
  z-index: 4;
  padding-top: 0;
  margin-top: calc(-1 * var(--space-6) + var(--space-2));
}

/* Befund 2026-07-28 (Tour-Reparatur): .section-subnav ist position:sticky mit z-index:5 und
   eroeffnet damit einen eigenen Stacking-Kontext. Die Spotlight-Regel .tour-highlight
   (z-index:901, s. layout.ejs) kommt aus diesem Kontext nicht heraus — ein markierter
   Subnav-Reiter lag deshalb UNTER dem Tour-Overlay (z-index:900) und leuchtete spuerbar
   schwaecher als jeder andere Schritt. Kein Randfall: Subnav-Reiter sind auf einer
   Modul-Einstiegsseite oft der einzige stabile Ankerpunkt (News → Konfiguration,
   Docs → Meldungen).

   Den z-index der Subnav generell anzuheben waere falsch — sie laege dann auch ueber
   Modals und Panel (beide 800/900). Stattdessen lokal und nur fuer die Dauer eines
   Tour-Schritts: der Kontext wird angehoben, solange er ein hervorgehobenes Element
   enthaelt, und faellt zurueck, sobald .tour-highlight weiterwandert. Waehrend einer Tour
   ist kein Modal offen (der Panel-Knopf schliesst das Panel vor dem Start), es gibt also
   keine Kollision. Gleiches gilt fuer die sticky Kopfzeilen-Aktionen (z-index:50), deren
   Pillen kuenftig selbst Ankerpunkte sein koennen. */
.section-subnav:has(.tour-highlight),
.shell-header-actions:has(.tour-highlight) { z-index: 901; }

/* Nachtrag 2026-07-28 (Ticket #334, zweite Haelfte): dieselbe Falle, nur eine Etage
   groesser — bei der Navigation wurde die Gegenregel oben schlicht vergessen.
   .shell-sidebar ist position:fixed mit z-index:100 (s. Abschnitt 5), also ebenfalls ein
   eigener Stacking-Kontext; .tour-highlight wirkt nur INNERHALB der Sidebar und verliert
   als Ganzes gegen das Overlay. Gemessen: ein markierter Sidebar-Eintrag liegt UNTER dem
   Overlay (elementFromPoint trifft das Overlay) — jeder Navigations-Schritt leuchtete
   nicht. .shell-bottom-nav hat exakt dieselbe Konstruktion (position:fixed, z-index:100)
   und denselben Fehler; unter 768px ist sie die Navigation, deshalb steht sie hier mit.

   Zweiter, bisher unbemerkter Fehler: ist die Sidebar SELBST der Anker (sel: "#nav"),
   gewinnt .tour-highlight { position: relative } gegen .shell-sidebar { position: fixed }
   — gleiche Spezifitaet, und der <style>-Block in layout.ejs steht nach diesem
   Stylesheet. Die Sidebar faellt dann in den Fluss, waehrend .shell-main weiterhin
   margin-left: var(--sidebar-width) traegt. Gemessen: .shell-main springt um 232px nach
   rechts (= --sidebar-width) und schiebt 26px aus dem Fenster. Die zweite Regel gewinnt
   ueber die hoehere Spezifitaet (0,2,0) und haelt die Sidebar fixiert.

   Wie oben gilt: angehoben wird nur fuer die Dauer des Schritts, und angehoben wird der
   ganze Container — der Rest der Sidebar ist waehrend eines Navigations-Schritts also
   nicht abgedunkelt. Das ist dieselbe Abwaegung wie bei der Subnav und die guenstigere:
   ein leuchtender Eintrag in einer normal dargestellten Leiste schlaegt einen Eintrag,
   der gar nicht leuchtet. */
.shell-sidebar:has(.tour-highlight),
.shell-bottom-nav:has(.tour-highlight) { z-index: 901; }

.shell-sidebar.tour-highlight,
.shell-bottom-nav.tour-highlight { position: fixed; z-index: 901; }

/* Befund 2026-07-28 (Nachtrag, Ticket #339-Umfeld): .section-subnav hat overflow-x:auto
   fuer das waagerechte Scrollen auf dem Telefon. Nach Spezifikation wird die andere Achse
   damit ebenfalls zu auto — overflow-y:visible ist dort nicht durchsetzbar —, und die
   Spotlight-Umrandung liegt mit outline-offset:3px AUSSERHALB der Box. Gemessen: unten
   werden 5px abgeschnitten (oben traegt das padding-top der Leiste). Das wiegt schwer,
   weil Schritt 1 vieler Touren auf einen Subnav-Reiter zeigt (News: alle vier Touren).

   Gemessen wurden drei Wege. Senkrechter Innenabstand nur bei :has(.tour-highlight)
   (5px, mit und ohne Gegenmarge) LOEST ES NICHT: die Haarlinie unter den Reitern
   wandert fuer die Dauer des Schritts um 5px nach unten — ein sichtbarer Sprung genau
   unter dem Element, auf das der Schritt zeigt —, und beschnitten bleibt es trotzdem,
   weil .section-subnav-item mit margin-bottom:-1px 1px ueber die Content-Box hinausragt.

   Was traegt: den Ring nach INNEN legen. Er liegt damit immer innerhalb der Scrollbox,
   wird nie beschnitten, veraendert kein einziges Mass (Hoehe, Haarlinie, Inhalt darunter,
   Reiter alle 0px Versatz) und laesst das waagerechte Scrollen unberuehrt. Spezifitaet
   0,2,0 schlaegt .tour-highlight (0,1,0) aus layout.ejs trotz spaeterer Position. */
.section-subnav .tour-highlight { outline-offset: -2px; }

.section-subnav-item {
  display: flex;
  align-items: center;
  padding: var(--space-2) var(--space-4);
  font-size: 14px;
  font-weight: var(--weight-regular);
  color: var(--color-slate);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  white-space: nowrap;
  min-height: 44px;
  transition:
    color var(--motion-fast),
    border-color var(--motion-fast);
}

.section-subnav-item:hover {
  color: var(--color-ink);
  text-decoration: none;
}

.section-subnav-item.active,
.section-subnav-item[aria-current="page"] {
  color: var(--color-ink);
  border-bottom-color: var(--color-ink);
  font-weight: var(--weight-medium);
}

/* -----------------------------------------------------------------------------
   18b. Schrittleiste im Monatslauf — Ticket #549, Variante B
   -----------------------------------------------------------------------------
   Gemeldet: „Für den User ist nicht so gut erkennbar, welche Schritte er wie tun muss
   und wo er gerade steht. Mache diese Zeile auf dem Handy etwas deutlicher. Vielleicht
   auch auf dem PC."

   Bewusst eine EIGENE Komponente statt einer Sonderregel auf .section-subnav: die
   Subnav trägt Admin, News, Docs und Contracts mit; sie für einen Screen umzubauen,
   hieße vier andere anzufassen. .lauf-steps gilt nur im Monatslauf.

   ZWEITE FASSUNG, 17.08.2026 — Martins Wahl aus dem Mockup (docs/governance/
   Mockup-Invoice-Schrittzeile.html, Variante B): senkrechte Kette mit einer
   Handlungszeile, statt des Kachelrasters vom 15.08. Das Raster hatte den Scroll und die
   Farbe-statt-Wort-Frage behoben und die eigentliche offen gelassen: fünf gleichrangige
   Kacheln sagen nicht, welcher Schritt JETZT dran ist, und ein Raster hat keine
   Richtung — bei Lauf 68 stand „Rechnungen ✓" neben „Timesheets offen", ohne dass die
   Reihenfolgeverletzung irgendwo sichtbar wurde.

   Drei Bauteile, jedes gegen einen Befund:
     1. DIE SCHIENE (::before am Glied) zeichnet die Reihenfolge. An einem übergangenen
        Schritt läuft sie gestrichelt vorbei — das Auge stolpert genau dort, wo bei
        Lauf 68 die Lücke saß. Gestrichelt und nicht farbig: die Aussage darf nicht an
        einer Farbe hängen (R12), und eine zweite Saturationsfarbe gäbe es hier nicht
        (R1.3).
     2. GENAU EIN Schritt trägt „jetzt dran" — gefüllte Fläche plus Ink-Kante plus das
        Wort. Drei Träger, keiner davon allein.
     3. Der Handlungssatz (.lauf-step-tun) steht EINGERÜCKT UNTER diesem einen Schritt,
        auf der Höhe seines Textes — er gehört sichtbar zu ihm und nicht zur Liste.

   R17 (Handy zuerst): eine Spalte, also nichts, was umbrechen müsste, kein seitlicher
   Überstand, Tippziel 44 px an jeder Zeile. Am Rechner (>= 768 px) wird dieselbe Kette
   nur größer gesetzt — kein zweiter Bauweg.
   Keine neue Farbe, kein rotes Element: der Screen hat sein eines Rot im Eyebrow
   (One-Red-Rule R1.3); „jetzt dran" ist Red-Tint = Handlung nötig (R1.7), erledigt ist
   Ink, offen ist Slate. Keine Ampel. */
.lauf-steps {
  margin: 0 0 var(--space-5);
  padding: var(--space-3) var(--space-4) var(--space-4);
  background-color: var(--color-paper);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-card);
}

.lauf-steps-lage {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
  margin: 0 0 var(--space-3);
  font-size: 13px;
  color: var(--color-slate);
}

.lauf-steps-fortschritt {
  color: var(--color-ink);
  font-weight: var(--weight-medium);
}

.lauf-steps-liste {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Ein Glied der Kette. `position: relative` trägt die Schiene; `min-width: 0` bleibt,
   damit ein langer Schrittname umbrechen kann statt zu schieben (R17.2). */
.lauf-steps-glied {
  position: relative;
  min-width: 0;
  margin: 0;
}

/* DIE SCHIENE. Sie sitzt in der Mitte des 24-px-Nummernkreises (12 px + 8 px
   Innenabstand des Knopfes = 20 px vom linken Rand des Gliedes) und läuft vom unteren
   Rand des Kreises bis zum nächsten. Nicht am letzten Glied — sonst hängt unten ein
   Strich ins Leere. */
.lauf-steps-glied:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 20px;
  top: 38px;
  bottom: 0;
  width: 1px;
  background-color: var(--color-hairline);
}

/* Übergangener Schritt: die Schiene läuft GESTRICHELT an ihm vorbei. Ein Bruch im
   Strich, nicht eine andere Farbe — die Aussage darf nicht an einer Farbe hängen (R12),
   und das Wort daneben („übergangen — Schritt N lief ohne ihn") trägt sie ohnehin.

   `:not(:last-child)` steht hier MIT ABSICHT, obwohl es fachlich nichts auswählt (ein
   letztes Glied kann nicht übergangen sein — dahinter läuft nichts). Es hält die
   Spezifität gleich: ohne das Stück gewann die Regel oben, und dann stand die
   ununterbrochene 1-px-Fläche NEBEN der gestrichelten Kante — eine doppelte Linie, halb
   durchgezogen, also genau nicht das Signal, um das es geht.
   GEMESSEN am 17.08.2026 bei 375 px (Chrome, computed style des ::before): vorher
   `background-color: rgb(234,231,226)` trotz `transparent` in dieser Regel. Angesehen
   hätte man das für „gestrichelt" gehalten (R17.8: gemessen, nicht angesehen). */
.lauf-steps-glied-uebergangen:not(:last-child)::before {
  background-color: transparent;
  border-left: 1px dashed var(--color-slate-3);
  width: 0;
}

.lauf-step {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  width: 100%;
  min-height: 44px;
  /* Links schmaler, damit der Nummernkreis auf der Schiene sitzt. */
  padding: var(--space-2) var(--space-3) var(--space-2) var(--space-2);
  border: 1px solid transparent;
  border-radius: var(--radius-input);
  background-color: transparent;
  color: var(--color-slate);
  font-family: inherit;
  font-size: 14px;
  text-align: left;
  cursor: pointer;
  transition:
    border-color var(--motion-fast),
    background-color var(--motion-fast);
}

/* Die Nummer ist ein runder Kreis auf der Schiene — 24 px, damit die Schiene ihn
   trifft. Bewusst über `.lauf-step .badge` und NICHT über eine eigene Klasse am Badge:
   der Status-Poll in invoices-lauf.js setzt dessen `className` komplett neu und würde
   eine Zusatzklasse wegwerfen (gemessen an derselben Stelle, Zeile ~494). */
.lauf-step .badge {
  flex: none;
  width: 24px;
  height: 24px;
  margin-top: 1px;
  padding: 0;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 11px;
  border-radius: var(--radius-pill);
  /* Der Kreis darf nie oval werden, auch nicht bei zweistelliger Nummer. */
  box-sizing: border-box;
}

/* Der Kreis eines übergangenen Schrittes ist gestrichelt umrandet — dieselbe Sprache wie
   die Schiene, die an ihm vorbeiläuft. Die Regel greift am KNOPF und nicht am Badge:
   der Status-Poll setzt `.badge`-className komplett neu, eine Klasse am Badge selbst wäre
   nach dem ersten Poll verschwunden (s. Kommentar bei `.lauf-step .badge`). */
.lauf-step-uebergangen .badge {
  border-color: var(--color-slate-3);
  border-style: dashed;
}

.lauf-step-text {
  display: flex;
  min-width: 0;
  flex: 1 1 auto;
  flex-direction: column;
}

.lauf-step-name {
  color: var(--color-ink);
  font-weight: var(--weight-medium);
  /* Umbrechen statt abschneiden: ein halber Schrittname ist keine Beschriftung. Mit
     `hyphens: auto` und lang="de" am <html> (layout.ejs) trennt der Browser nach Silben
     statt mitten im Wort; `break-word` bleibt das Netz darunter. In der Kette ist die
     Zeile breiter als im alten Raster, aber die Regel bleibt — bei 375 px und einem
     langen Kundennamen im Satz darunter zählt jedes Wort, das nicht schiebt (R17.2). */
  hyphens: auto;
  overflow-wrap: break-word;
}

.lauf-step-lage {
  font-size: 12px;
  color: var(--color-slate);
}

/* Die Zusätze zur Reihenfolge — „übergangen — Schritt 4 lief ohne ihn" und die
   Gegenseite „ohne Schritt 3 gelaufen". Eigene Elemente, weil der Status-Poll
   `.lauf-step-lage` per textContent neu setzt und einen Zusatz darin verlöre. */
.lauf-step-hinweis {
  font-size: 12px;
  color: var(--color-ink-4);
  font-weight: var(--weight-medium);
}

.lauf-step-jetzt-wort {
  font-size: 12px;
  color: var(--color-ink);
  font-weight: var(--weight-medium);
}

/* Der Handlungssatz. Einzug = Kreis (24) + Abstand (12) + linker Innenabstand (8) = 44,
   damit er unter dem Text des Schrittes beginnt und nicht unter seiner Nummer. */
.lauf-step-tun {
  margin: 2px 0 var(--space-3) 44px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--color-ink-4);
}

.lauf-step:hover { border-color: var(--color-slate-3); }

/* „Hier stehst Du": gefüllte Fläche und Ink-Kante. Zusammen mit dem Wort „jetzt dran"
   und dem Tint-Kreis sind das drei Träger derselben Aussage — keiner allein. */
.lauf-step.lauf-step-jetzt {
  background-color: var(--color-cream);
  border-color: var(--color-ink);
}

/* Der ausgewählte REITER (welches Panel gerade offen ist) ist etwas anderes als „jetzt
   dran" (welcher Schritt zu tun ist). Zwei Aussagen, zwei Marker — und in dieser
   Rangfolge: die Aufgabe gewinnt.
   GEMESSEN und korrigiert am 17.08.2026: mit der Ink-Kante aus der ersten Fassung sah
   Schritt 1 (beim Seitenaufbau immer `.active`) genauso wichtig aus wie der Schritt, der
   dran ist — im Bild bei 375 px war die dunkle Kante von Schritt 1 sogar auffälliger als
   die Füllung von Schritt 3. Damit hätte Variante B ihren eigenen Zweck untergraben:
   „welcher ist es jetzt" wäre wieder eine Frage gewesen. Der Reiter trägt deshalb eine
   Slate-Kante, „jetzt dran" die Ink-Kante samt Füllung. */
.lauf-step.active {
  border-color: var(--color-slate-3);
  box-shadow: inset 0 0 0 1px var(--color-slate-3);
}

/* Ist der offene Reiter ZUGLEICH der Schritt, der dran ist, gewinnt „jetzt dran" — sonst
   überschriebe die spätere Slate-Regel gerade den wichtigeren Marker. */
.lauf-step.active.lauf-step-jetzt {
  border-color: var(--color-ink);
  box-shadow: inset 0 0 0 1px var(--color-ink);
}

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

.lauf-steps-nachzug {
  margin: var(--space-2) 0 0;
  font-size: 12px;
  color: var(--color-slate);
}

/* Am Rechner wird dieselbe Kette nur reicher gesetzt — kein zweiter Bauweg (R17: was
   auf 375 px trägt, gilt; der Schreibtisch bekommt Platz, keine andere Bauart).
   Umbruchpunkt 768 aus R17.1. */
@media (min-width: 768px) {
  .lauf-step { font-size: 15px; }
  .lauf-step-lage,
  .lauf-step-hinweis,
  .lauf-step-jetzt-wort { font-size: 13px; }
  .lauf-step-tun { font-size: 14px; max-width: 82ch; }
}

/* -----------------------------------------------------------------------------
   19. Status Cards (health)
   ----------------------------------------------------------------------------- */
.status-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: var(--space-4);
  margin-bottom: var(--space-6);
}

.status-card {
  background-color: var(--color-paper);
  border-radius: var(--radius-card);
  border: 1px solid var(--color-hairline);
  padding: var(--space-4) var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.status-card-name {
  font-size: 12px;
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--color-slate);
}

.status-card-value {
  font-size: 15px;
  font-weight: var(--weight-regular);
  color: var(--color-ink);
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* -----------------------------------------------------------------------------
   20. Pagination
   ----------------------------------------------------------------------------- */
.pagination {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-6);
  justify-content: center;
}

.pagination-item {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--radius-input);
  font-size: 14px;
  color: var(--color-slate);
  text-decoration: none;
  border: 1px solid var(--color-hairline);
  background-color: var(--color-paper);
  transition:
    background-color var(--motion-fast),
    color var(--motion-fast);
}

.pagination-item:hover {
  background-color: var(--color-cream-3);
  color: var(--color-ink);
  text-decoration: none;
}

.pagination-item.active {
  background-color: var(--color-ink);
  color: var(--color-paper);
  border-color: var(--color-ink);
}

.pagination-ellipsis {
  color: var(--color-slate);
  padding: 0 var(--space-2);
}

/* -----------------------------------------------------------------------------
   21. Section / Page structure helpers
   ----------------------------------------------------------------------------- */
.section {
  margin-bottom: var(--space-8);
}

.section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: var(--space-4);
}

.section-title {
  font-size: 18px;
  font-weight: var(--weight-regular);
}

.divider {
  border: none;
  border-top: 1px solid var(--color-hairline);
  margin: var(--space-6) 0;
}

/* Grid utilities */
.grid-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-6);
}

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

/* S149 P2 (M1) — die drei ungleichen Raster.

   `.grid-2`/`.grid-3` decken nur gleiche Spalten ab. Daneben standen 22 Stellen
   mit einem starren Inline-`grid-template-columns` OHNE Umbruch, die meisten
   davon in einer von drei Formen. Sie bekommen hier je eine Klasse, statt jede
   Ansicht ihre eigene Media-Query erfinden zu lassen — genau das Erfinden ist
   der Befund der Spec: von 126 Ansichten trugen sechs eine eigene Umbruchregel.

   `minmax(0, …)` statt `…fr`: eine `fr`-Spur hat als Mindestbreite ihren Inhalt.
   Eine Tabelle oder ein langer Pfad in der Hauptspur drueckte das Raster deshalb
   auseinander, obwohl die Anteile stimmten.

   Die Breite der Nebenspalte kommt aus einer Custom Property, damit 280 px und
   300 px nicht zwei fast gleiche Klassen brauchen. */
.grid-haupt-neben {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  gap: var(--space-6);
}

.grid-inhalt-leiste {
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--neben-breite, 300px);
  gap: var(--space-8);
  align-items: start;
}

.grid-leiste-inhalt {
  display: grid;
  grid-template-columns: var(--leiste-breite, 220px) minmax(0, 1fr);
  gap: var(--space-8);
  align-items: start;
}

/* Kachelreihen (Kennzahlen). Ersetzt `repeat(5, 1fr)` und Verwandte: fuenf
   gleiche Spuren ergeben bei 343 px nutzbarer Breite 68 px je Kachel. Die
   Kachelzahl ist hier ohnehin nie die Aussage — die Kacheln sind es. */
.grid-kacheln {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--space-4);
}

@media (max-width: 768px) {
  .grid-2,
  .grid-3,
  .grid-haupt-neben,
  .grid-inhalt-leiste,
  .grid-leiste-inhalt {
    grid-template-columns: 1fr;
  }
}

/* Flex utilities */
.flex { display: flex; }
.flex-col { flex-direction: column; }
.flex-wrap { flex-wrap: wrap; }
.items-center { align-items: center; }
.items-start { align-items: flex-start; }
.justify-between { justify-content: space-between; }
.justify-end { justify-content: flex-end; }
.gap-2 { gap: var(--space-2); }
.gap-3 { gap: var(--space-3); }
.gap-4 { gap: var(--space-4); }

/* Text utilities */
.text-slate { color: var(--color-slate); }
.text-ink { color: var(--color-ink); }
.text-right { text-align: right; }
.text-center { text-align: center; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}

/* Spacing utilities */
.mt-2 { margin-top: var(--space-2); }
.mt-4 { margin-top: var(--space-4); }
.mt-6 { margin-top: var(--space-6); }
.mt-8 { margin-top: var(--space-8); }
.mb-4 { margin-bottom: var(--space-4); }
.mb-6 { margin-bottom: var(--space-6); }
.mb-8 { margin-bottom: var(--space-8); }

/* -----------------------------------------------------------------------------
   22. Error pages
   ----------------------------------------------------------------------------- */
.error-page {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--space-8);
  text-align: center;
  background-color: var(--color-cream);
}

.error-code {
  font-family: var(--font-mono);
  font-size: 80px;
  font-weight: var(--weight-light);
  color: var(--color-hairline);
  line-height: 1;
  margin-bottom: var(--space-6);
}

.error-title {
  font-size: 28px;
  font-weight: var(--weight-light);
  color: var(--color-ink);
  margin-bottom: var(--space-4);
}

.error-message {
  color: var(--color-slate);
  margin-bottom: var(--space-8);
  max-width: 400px;
}

/* -----------------------------------------------------------------------------
   23. QR Code container
   ----------------------------------------------------------------------------- */
.qr-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-6);
  background-color: var(--color-cream-3);
  border-radius: var(--radius-card);
  border: 1px solid var(--color-hairline);
}

.qr-code img {
  width: 180px;
  height: 180px;
  border-radius: var(--radius-input);
  background-color: var(--color-white);
  padding: var(--space-3);
}

.qr-secret {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--color-slate);
  letter-spacing: 0.08em;
  word-break: break-all;
  text-align: center;
}

/* -----------------------------------------------------------------------------
   24. Permission matrix
   ----------------------------------------------------------------------------- */
/* Martin, 10.09.2026, nach dem Zuordnen der Rechte fuer Julian auf /admin/groups:
   „beim Scrollen durch die Rechte soll die User-Spalte und auch die
   Spaltenueberschriften fixiert sein". Ohne die Fixierung sieht man ein Haekchen
   und weiss nicht mehr, zu welcher Zeile und welchem Modul es gehoert.

   DER BEZUGSRAHMEN IST DER BEHAELTER MIT `overflow`, nicht das Fenster. Die
   Tabelle steht in `.table-container`, und der traegt `overflow-x: auto` — CSS
   macht daraus zwangslaeufig auch senkrecht einen Scroll-Behaelter (`visible`
   ist neben `auto` nicht moeglich). Eine Kopfzeile mit `top: 0` klebt deshalb
   NUR innerhalb dieses Kastens; scrollt die SEITE, wandert der ganze Kasten mit
   und die Kopfzeile mit ihm. Darum bekommt der Behaelter hier eine Hoehe: die
   Matrix scrollt in ihrem eigenen Kasten, und erst dadurch hat `top: 0` einen
   Anker. Wer die Hoehe wieder herausnimmt, nimmt die senkrechte Fixierung mit. */
.permission-matrix,
.ai-admin-permission-matrix {
  overflow: visible;
}

.permission-matrix .table-container,
.ai-admin-permission-matrix .table-container {
  overflow: auto;
  max-height: min(70vh, 640px);
}

.permission-matrix table,
.ai-admin-permission-matrix table {
  min-width: 600px;
}

/* Kopfzeile (Modulnamen) — senkrecht fixiert. `position: sticky` gehoert an die
   ZELLEN; an `thead` oder `tr` wirkt es in mehreren Browsern nicht. Und weil
   eine fixierte Zelle ueber dem Rest liegt, braucht sie eine eigene Flaeche —
   sonst scheinen die Zeilen darunter durch (Token, kein Hex-Wert). */
.permission-matrix thead th,
.ai-admin-permission-matrix thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background-color: var(--color-ink-4);
}

/* Erste Spalte (Zeilenbeschriftung) — waagerecht fixiert. Die Flaeche erbt die
   Zeilenfarbe (Zebra + Hover), damit die Spalte nicht aus der Zeile faellt. Der
   Strich rechts ersetzt die Zellgrenze, die bei `border-collapse: collapse`
   unter der fixierten Zelle verschwindet. */
.permission-matrix tbody td:first-child,
.ai-admin-permission-matrix tbody td:first-child {
  position: sticky;
  left: 0;
  z-index: 1;
  background-color: inherit;
  box-shadow: inset 0 -1px 0 var(--color-hairline), 1px 0 0 var(--color-hairline);
}

/* Die Ecke gehoert BEIDEN Fixierungen. Sie braucht beide Richtungen und die
   hoechste Lage — sonst schiebt sich eine der beiden darueber und verdeckt sie. */
.permission-matrix thead th:first-child,
.ai-admin-permission-matrix thead th:first-child {
  left: 0;
  z-index: 3;
  min-width: 160px;
  /* Ticket 1190 (ohne Raute: im Regelrumpf liest der R1-Waechter sie als
     Hex-Wert): ink-4 wie die uebrigen Kopfzellen — vorher ink-hdr, die Ecke stand
     dunkler als der Kopf daneben. */
  background-color: var(--color-ink-4);
}

/* R17, 375 px: die fixierte Spalte darf auf dem Telefon nicht den halben
   Bildschirm fressen — dort bleibt sie schmaler und bricht statt zu draengen. */
@media (max-width: 480px) {
  .permission-matrix thead th:first-child,
  .ai-admin-permission-matrix thead th:first-child {
    min-width: 104px;
    white-space: normal;
  }

  .permission-matrix tbody td:first-child,
  .ai-admin-permission-matrix tbody td:first-child {
    max-width: 116px;
    overflow-wrap: anywhere;
  }
}

.permission-cell {
  text-align: center;
}

.permission-cell input[type="checkbox"] {
  display: block;
  margin: 0 auto;
}

/* -----------------------------------------------------------------------------
   25. Module drag-to-reorder
   ----------------------------------------------------------------------------- */
.draggable-row {
  cursor: grab;
  user-select: none;
}

.draggable-row:active {
  cursor: grabbing;
}

.draggable-row.drag-over {
  background-color: var(--color-cream) !important;
  outline: 2px dashed var(--color-slate-3);
  outline-offset: -2px;
}

.drag-handle {
  color: var(--color-slate-3);
  padding: var(--space-2);
  display: flex;
  align-items: center;
}

/* Toggle switch (is_active) */
.toggle {
  position: relative;
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  min-height: 44px;
}

.toggle input[type="checkbox"] {
  opacity: 0;
  width: 0;
  height: 0;
  position: absolute;
}

.toggle-track {
  width: 40px;
  height: 22px;
  background-color: var(--color-slate-3);
  border-radius: var(--radius-pill);
  position: relative;
  transition: background-color var(--motion-fast);
}

.toggle input:checked + .toggle-track {
  background-color: var(--color-ink);
}

.toggle-thumb {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 16px;
  height: 16px;
  background-color: var(--color-white);
  border-radius: 50%;
  transition: transform var(--motion-fast);
}

.toggle input:checked ~ .toggle-thumb,
.toggle input:checked + .toggle-track .toggle-thumb {
  transform: translateX(18px);
}

/* Recovery codes */
.recovery-codes {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-2);
  padding: var(--space-4);
  background-color: var(--color-cream-3);
  border-radius: var(--radius-card);
  border: 1px solid var(--color-hairline);
  font-family: var(--font-mono);
  font-size: 14px;
  letter-spacing: 0.06em;
}

/* Log viewer */
.log-level-error { color: var(--color-ink); font-weight: var(--weight-medium); }
.log-level-warn  { color: var(--color-slate); }
.log-level-info  { color: var(--color-ink-4); }
.log-level-debug { color: var(--color-slate); opacity: 0.7; }

/* Passkey item */
.passkey-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--color-hairline);
}

.passkey-item:last-child {
  border-bottom: none;
}

.passkey-info {
  flex: 1;
}

.passkey-name {
  font-weight: var(--weight-medium);
  margin-bottom: var(--space-1);
}

.passkey-meta {
  font-size: 12px;
  color: var(--color-slate);
}

/* -----------------------------------------------------------------------------
   26. Chip-Toggle (Ticket #99) — <details><summary class="chip-toggle"> Muster
   für aufklappbare Abschnitte, die eindeutig als klickbar erkennbar sein sollen.
   Nutzt natives <details>[open] statt JS für den Caret-Dreh.
   ----------------------------------------------------------------------------- */
/* Der Abstand stand auf 6px — zwischen Fliesstext und einer Pille mit eigener Flaeche
   und Rundung liest sich das wie ein Zusammenkleben. 8px (--space-2) statt eines
   weiteren nackten Pixelwerts, damit der Abstand am Raster haengt und nicht am Zufall.

   ACHTUNG, hier wurde #513 zuerst falsch behoben: der Kommentar an dieser Stelle
   behauptete, das sei die Regel fuer /leadgen/signale. Ist es nicht. Die Chips dort
   tragen `class="chip-toggle contract-chip-summary"`, und .contract-chip-summary steht
   weiter unten in dieser Datei mit derselben Spezifitaet und eigenem `gap` — bei
   Gleichstand gewinnt die spaetere Regel. Diese Zeile wirkt also nur auf die NACKTEN
   .chip-toggle (z.B. „Verwerfen …" in recherche/liste.ejs). Der echte Eingriff fuer
   #513 steht in Abschnitt 26b bei .contract-chip-summary. */
.chip-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 4px 12px;
  border-radius: 20px;
  font-size: 11px;
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  /* war var(--color-cream-2) — in :root und theme.json nie definiert, dadurch
     ungueltig und ohne Hintergrund. Zweischritt mit der echten Palette. */
  background: var(--color-cream-3);
  border: 1px solid var(--color-hairline);
  color: var(--color-ink);
  cursor: pointer;
  user-select: none;
}
.chip-toggle:hover {
  background: var(--color-cream);
}
.chip-toggle-caret {
  font-size: 9px;
  color: var(--color-ink-4);
  transition: transform var(--motion-fast);
  display: inline-block;
}
details[open] > .chip-toggle .chip-toggle-caret {
  transform: rotate(180deg);
}

/* -----------------------------------------------------------------------------
   26a. Chips (Schlagwort-Pillen) — .chip wurde in application/suchprofil.ejs,
   news/brief.ejs und news/config.ejs benutzt, ohne dass die Regel je existierte
   (im Verlauf nicht auffindbar). Die Spans rendern dort bis hierher als nackter
   Text. Editierbare Pille: Wert + Entfernen-Kreuz, dazu die Eingabezeile.
   Kein Rot — Ausschluss wird ueber die gestrichelte Kante gelesen (R1.3).
   ----------------------------------------------------------------------------- */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: var(--radius-pill);
  font-size: 13px;
  line-height: 1.4;
  background-color: var(--color-cream-3);
  border: 1px solid var(--color-hairline);
  color: var(--color-ink);
  max-width: 100%;
  overflow-wrap: anywhere;
}

/* Ausschluss-Begriff: gestrichelt und zurueckgenommen, nicht rot. */
.chip--exclude {
  background-color: transparent;
  border-style: dashed;
  color: var(--color-slate);
}

.chip-remove {
  border: none;
  background: none;
  padding: 0;
  margin: 0;
  font-size: 14px;
  line-height: 1;
  color: var(--color-slate);
  cursor: pointer;
  transition: color var(--motion-fast);
}
.chip-remove:hover {
  color: var(--color-ink);
}

/* Nur die Schlagwort-Listen fliessen in der Zeile. Die Fachobjekt-Listen aus
   26b tragen kein data-field und bleiben untereinander. Denselben Unterscheider
   benutzt application-suchprofil.js beim Nachtragen neuer Chips. */
.chip-list[data-field] {
  flex-direction: row;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.chip-add-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
.chip-input {
  flex: 1;
  min-width: 0;
}

/* -----------------------------------------------------------------------------
   26b. Contract Chips (Ticket #151, R1.7 Listen-Interaktionsmuster) — volle
   Zeilenbreite statt kleiner Pille; baut auf .chip-toggle auf (Caret-Dreh-CSS
   oben wird wiederverwendet, .contract-chip-summary überschreibt nur das Layout).
   Muster für künftige Fachobjekt-Listen (Job-Leads, Targets, Templates, …).
   ----------------------------------------------------------------------------- */
.chip-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.contract-chip {
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-card);
  background-color: var(--color-paper);
  overflow: hidden;
}

.contract-chip-summary {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
  width: 100%;
  box-sizing: border-box;
  padding: var(--space-3) var(--space-4);
  min-height: 44px;
  border-radius: 0;
  background: transparent;
  border: none;
  text-transform: none;
  letter-spacing: normal;
  font-size: 14px;
  font-weight: var(--weight-regular);
}

.contract-chip-summary:hover {
  background-color: var(--color-cream-3);
}

.contract-chip-summary::-webkit-details-marker {
  display: none;
}

.contract-chip-no {
  display: inline-flex;
  align-items: center;
  padding: 3px var(--space-2);
  border-radius: var(--radius-pill);
  background-color: var(--color-ink);
  color: var(--color-paper);
  font-size: 12px;
  white-space: nowrap;
}

.contract-chip-type {
  color: var(--color-ink);
  font-size: 13px;
  white-space: nowrap;
}

.contract-chip-party {
  color: var(--color-slate);
  font-size: 13px;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Ticket #513 (Martin, 11.08.2026, /leadgen/signale): „die Status-Pille ist zu dicht
   am Text — Spacing etwas erweitern".

   GEMESSEN am 13.08. auf /leadgen/signale bei 1280 px Fensterbreite: der Titeltext
   endet bei x=833, die erste Pille beginnt bei x=845 — 12 px. Von Pille zu Pille sind
   es ebenfalls 12 px. Genau daher kommt der Eindruck: die Kopfzeile hat zwei Zonen,
   die BESCHREIBUNG (Klasse, Titel, Datum) und den ZUSTAND (die Pillen), und zwischen
   den Zonen steht derselbe Abstand wie innerhalb. Die Pillen lesen sich dadurch als
   Fortsetzung des Satzes statt als eigene Spalte.

   Der Eingriff trennt deshalb die ZONEN und nicht alles: 8 px zusaetzlich vor der
   ersten Pille nach dem Titel (12 + 8 = 20 px), Pille-zu-Pille bleibt bei 12 px. Den
   gemeinsamen `gap` hochzudrehen haette beide Abstaende vergroessert und die
   Pillenreihe auseinandergezogen, ohne die Zonen zu trennen.

   Der Nachbarschafts-Selektor greift ueber Kommentare und Zeilenumbrueche hinweg
   (CSS zaehlt nur Elementgeschwister) — die EJS-Vorlagen setzen zwischen Titel und
   erste Pille reichlich davon. Auf Chips ohne Pille wirkt die Regel nicht.

   Unter 420 px steht die Kopfzeile untereinander (Regel weiter unten); dort ist der
   linke Aussenabstand sinnlos und wird zurueckgenommen. */
.contract-chip-summary .contract-chip-party + .badge {
  margin-left: var(--space-2);
}

.contract-chip-summary .chip-toggle-caret {
  margin-left: auto;
}

.contract-chip-body {
  padding: 0 var(--space-4) var(--space-4);
  border-top: 1px solid var(--color-hairline);
  background-color: var(--color-cream-3);
  border-radius: 0 0 var(--radius-card) var(--radius-card);
}

.contract-chip-body .detail-list {
  padding-top: var(--space-3);
}

.contract-chip-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: var(--space-2);
}

.contract-chip-tag {
  display: inline-flex;
  padding: 2px var(--space-2);
  border-radius: var(--radius-pill);
  background-color: var(--color-paper);
  border: 1px solid var(--color-hairline);
  color: var(--color-slate);
  font-size: 11px;
}

/* The one red element of the expanded panel (Addendum §3) */
.contract-chip-detail-link {
  display: inline-block;
  margin-top: var(--space-3);
  color: var(--color-red);
  font-weight: var(--weight-medium);
  text-decoration: none;
}

.contract-chip-detail-link:hover {
  text-decoration: underline;
}

@media (max-width: 420px) {
  .contract-chip-summary {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2);
  }

  /* #513 trennt zwei Zonen, die NEBENeinander stehen. Untereinander gibt es diese
     Zonen nicht — ein linker Aussenabstand rueckte die erste Pille dann nur
     grundlos gegen die anderen ein. */
  .contract-chip-summary .contract-chip-party + .badge {
    margin-left: 0;
  }

  .contract-chip-summary .chip-toggle-caret {
    margin-left: 0;
    align-self: flex-end;
  }
}

/* -----------------------------------------------------------------------------
   26b. Mobil — die vier zentralen Eingriffe aus S149 P1

   Die Erhebung fand 8 unbenutzbare und 32 muehsame Bildschirme; sie sind nicht
   40 Einzelfaelle, sondern vier Entscheidungen, die je an EINER Stelle fallen.
   Was hier steht, wirkt deshalb auf fast jede Fundstelle zugleich.
   ----------------------------------------------------------------------------- */

/* --- M3 · Das Auffangnetz -----------------------------------------------------

   Weder body, html, .shell-main, .shell-content noch .card hatten eine Regel
   gegen waagerechten Ueberlauf. Ein einziges zu breites Element schob damit das
   GESAMTE Dokument seitwaerts, samt Kopfzeile und Navigation — das ist der
   "alles ist verrutscht"-Effekt, den man als Nutzer sieht.

   Zwei Griffe, die zusammengehoeren:

   1. `min-width: 0` auf .shell-main. Das ist die eigentliche Ursache und gilt
      auf JEDER Breite: .shell-layout ist ein Flex-Container, .shell-main sein
      Kind mit `flex: 1` — und Flex-Kinder haben `min-width: auto`, duerfen also
      nie schmaler werden als ihr breitester Inhalt. Eine 900-px-Tabelle machte
      damit das Hauptfenster 900 px breit und schob alles daneben mit.

   2. `overflow-x: clip` auf .shell-content unterhalb von 768 px. Was danach noch
      uebersteht, wird abgeschnitten statt zum Dokumentscroll zu werden.

   WARUM `clip` UND NICHT `auto`/`hidden`: beide erzeugen einen Scroll-Container.
   Ein Scroll-Container haengt jedes `position: sticky` DARIN aus — und genau
   darin steckt .section-subnav (shell.css: sticky, z-index 5), die Reiterzeile
   fast jeder Modulseite. Sie klebte auf dem Telefon nicht mehr, waehrend die
   Rettung greift. `clip` schneidet, ohne einen Scrollport aufzumachen.

   Der Preis ist benannt: abgeschnittener Inhalt ist nicht erreichbar. Deshalb
   ist das NUR das Netz — die Tabellen bekommen in P1.2 ihren eigenen
   Scrollbehaelter und die Raster in P2 ihren Umbruch. Faengt das Netz etwas,
   ist an dieser Stelle noch etwas offen. */
.shell-main {
  min-width: 0;
}

/* `min-width: 0` als Disziplin fuer Rasterkinder: dieselbe Mechanik wie oben,
   eine Ebene tiefer. Ohne sie kann eine Rasterspalte nicht schrumpfen, auch wenn
   das Raster selbst umbricht — eine breite Tabelle in .grid-2 sprengte die
   Spalte trotz 768-px-Regel.

   Bewusst NICHT `.flex > *`: `.flex` ist eine reine Hilfsklasse und steht meist
   ueber Knopfzeilen. Deren Knoepfe tragen `white-space: nowrap` — `min-width: 0`
   erlaubte dem Flex-Algorithmus, sie unter ihre Textbreite zu druecken, und die
   Beschriftung liefe aus dem Knopf heraus. Flex-Zeilen werden in P2 ueber
   `flex-wrap` geloest, nicht ueber Schrumpfen. Wo eine einzelne Flex-Spur
   schrumpfen SOLL, gibt es .min-w-0. */
.grid-2 > *,
.grid-3 > * {
  min-width: 0;
}

.min-w-0 { min-width: 0; }

@media (max-width: 768px) {
  .shell-content {
    overflow-x: clip;
  }
}

/* --- M4 · Tippziele -----------------------------------------------------------

   `.btn` setzt korrekt 44 px, `.btn-sm` aber 36 px — benutzt in 21 Ansichten.
   Am Zeigegeraet ist das richtig (dichtere Listen, praezisere Maus); am Finger
   ist es zu klein. Deshalb an der EINGABEART festgemacht (`pointer: coarse`),
   nicht an der Fensterbreite: ein schmales Browserfenster am Rechner behaelt die
   kompakte Fassung, ein Tablet im Querformat bekommt die grossen Ziele. */
@media (pointer: coarse) {
  .btn-sm {
    min-height: 44px;
  }

  /* Die vier Pillen der Kopfzeile (Blog-Idee · Tour · Hilfe · Feedback) sind
     26 px hoch — live gemessen. Martins Entscheid vom 10.08.2026: vergroessern,
     nicht wegraeumen. Ziel 44 px. Die Zeile darf dafuer umbrechen, das tut sie
     unter 768 px ohnehin schon (.shell-header-actions oben). */
  .shell-header-pill {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    padding-top: 0;
    padding-bottom: 0;
  }

  /* Hier stand die Wiederholung von `.shell-header-pill[hidden]` — die Regel
     darueber setzt `display: inline-flex`, und ohne sie zeigte sich die
     Tour-Pille bei 375 px trotz `hidden`. Seit #807 (12.09.2026) traegt
     `[hidden]` in Abschnitt 2 global `display: none !important`; eine wichtige
     Autorenregel schlaegt jede normale, unabhaengig von Spezifitaet und
     Medienabfrage. Damit ist auch diese Ausnahme aufgeloest. */
}

/* --- M6 · Lange Zeichenketten -------------------------------------------------

   Es gab kein globales `overflow-wrap`. Rohe Hashes, JSON und URLs erzeugen
   echten Ueberlauf — ein sha256 misst als eine Zeichenkette rund 420 px, also
   mehr als die 343 px nutzbare Breite bei 375 px Geraet.

   `anywhere` statt `break-word`: `break-word` bricht erst, wenn die Zeile sonst
   ueberliefe, und wird bei der Mindestbreitenrechnung des Elternteils NICHT
   beruecksichtigt — ein Hash in einer Rasterspalte haette die Spalte weiterhin
   auseinandergedrueckt. `anywhere` zaehlt mit. */
code,
kbd,
samp,
.break-anywhere {
  overflow-wrap: anywhere;
}

/* <pre> bricht per `white-space: pre` grundsaetzlich nicht um — dort hilft nur
   ein eigener Scrollbehaelter. Betrifft die JSON-Ausgaben (QA-Report u. a.). */
pre {
  max-width: 100%;
  overflow-x: auto;
}

/* --- M2 · Hilfe-Tabellen, der Sonderfall ---------------------------------------

   Martin am Geraet, 10.08.2026, auf /hilfe: "Seite ist etwas breiter als der
   Mobile-Browser — muss ich rueberschieben."

   Diese Tabellen kann man nicht einzeln umschliessen: sie entstehen aus den
   Markdown-Hilfedateien und kommen als fertiges HTML in die Ansicht
   (`<%- moduleHtml %>`). Wie viele Spalten sie haben, weiss erst die Datei.

   Deshalb der Behaelter per CSS statt per Markup: `display: block` macht aus der
   Tabelle selbst den Scrollbereich. Der Preis ist bekannt — eine Tabelle als
   Block verliert die gemeinsame Spaltenbreite ueber alle Zeilen, deshalb
   `width: max-content`, damit die Spalten weiterhin miteinander rechnen. Fuer
   ein Fliesstext-Dokument mit kleinen Tabellen ist das der richtige Tausch.

   Gilt fuer beide Hilfe-Ansichten (/hilfe/:modul und /hilfe/:modul/:screen).
   Das Hilfe-Panel bekommt dieselbe Regel in layout.ejs — dort steht seine
   Tabellenformatierung, und der <style>-Block dort laege im Dokument nach
   dieser Datei und wuerde die Breite hier wieder ueberschreiben. */
.help-doc table {
  display: block;
  width: max-content;
  max-width: 100%;
  overflow-x: auto;
}

/* Print */
@media print {
  .shell-sidebar,
  .shell-bottom-nav,
  .toast-container,
  .btn {
    display: none !important;
  }

  .shell-main {
    margin-left: 0;
  }
}

/* Ticket #153: Vertrags-Template-Vorschau — gerendertes Markdown lesbar darstellen */
.contract-preview #preview-body {
  font-family: var(--font-family);
  color: var(--color-ink);
  line-height: 1.6;
}
.contract-preview #preview-body h1,
.contract-preview #preview-body h2,
.contract-preview #preview-body h3 {
  margin: 1.25em 0 0.5em;
  /* 26.08.2026: war 600. Ueberschriften sind bei Suriro Light oder Regular,
     nie SemiBold — hier Medium, weil die Vertragsvorschau dicht gesetzt ist. */
  font-weight: var(--weight-medium);
  color: var(--color-ink);
}
.contract-preview #preview-body h1 { font-size: 1.4rem; }
.contract-preview #preview-body h2 { font-size: 1.15rem; }
.contract-preview #preview-body h3 { font-size: 1rem; }
.contract-preview #preview-body p,
.contract-preview #preview-body ul,
.contract-preview #preview-body ol {
  margin: 0 0 0.75em;
}
.contract-preview #preview-body table {
  border-collapse: collapse;
  width: 100%;
  margin: 0.75em 0;
  font-size: 0.875rem;
}
.contract-preview #preview-body th,
.contract-preview #preview-body td {
  border: 1px solid var(--color-hairline);
  padding: 0.4rem 0.6rem;
  text-align: left;
}
.contract-preview #preview-body code {
  font-family: var(--font-mono);
  font-size: 0.85em;
}

/* -----------------------------------------------------------------------------
   27. Accounting (S148 W6, Mock-up v0.5) — Chip-Zeilen, Akkordeon, Lightbox.
   Chip-Zeile = Pillen-Zeile mit Klick → Detail-Dialog (S145 §6 Interaktions-
   muster); Akkordeon nutzt die data-collapsible-Mechanik aus shell.js.
   One-Red-Rule: das eine rote Moment je Screen ist der Engpass (Alert/Dot),
   nie der Arbeitsvorrat.
   ----------------------------------------------------------------------------- */
.acct-prow {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  background-color: var(--color-cream-3);
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  padding: var(--space-2) var(--space-4);
  margin-bottom: var(--space-2);
  cursor: pointer;
  text-align: left;
  width: 100%;
  font-family: inherit;
  font-size: 13px;
  color: var(--color-ink);
}
.acct-prow:hover {
  border-color: var(--color-slate-3);
  background-color: var(--color-cream);
}
.acct-prow-static {
  cursor: default;
  border-radius: var(--radius-card);
}
.acct-prow .acct-d {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--color-ink-4);
  min-width: 46px;
  white-space: nowrap;
}
.acct-prow .acct-t {
  flex: 1;
  min-width: 0;
}
.acct-prow .acct-t small {
  display: block;
  color: var(--color-slate);
  font-size: 11px;
}
/* Einzeilige Variante (Martin, 20.08.2026): Titel und Zusatz nebeneinander statt
   untereinander. Bei einer Liste, die man von oben nach unten abarbeitet, halbiert
   das die Hoehe je Eintrag — bei 18 offenen Auftraegen ist das der Unterschied
   zwischen einem Bildschirm und zweien. */
.acct-prow .acct-t-einzeilig {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  overflow: hidden;
  white-space: nowrap;
}
.acct-prow .acct-t-einzeilig small {
  display: inline;
  overflow: hidden;
  text-overflow: ellipsis;
}
.acct-prow:has(.acct-t-einzeilig) {
  padding: var(--space-1) var(--space-4);
  margin-bottom: 4px;
}
.acct-prow .acct-amt {
  font-family: var(--font-mono);
  font-size: 13px;
  min-width: 100px;
  text-align: right;
  white-space: nowrap;
}
.acct-prow .acct-go {
  color: var(--color-slate);
  font-size: 16px;
}
.acct-subwrap {
  margin: 0 0 var(--space-2) var(--space-8);
  overflow: hidden;
  transition: max-height var(--motion-base);
}
.acct-subwrap .acct-prow {
  background-color: var(--color-cream);
}
.acct-chev {
  display: inline-block;
  transition: transform var(--motion-fast);
  color: var(--color-slate);
  font-size: 14px;
  min-width: 14px;
}
[aria-expanded="true"] .acct-chev {
  transform: rotate(90deg);
}
.acct-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background-color: var(--color-red);
  margin-right: 6px;
}
.acct-pdfbox {
  background-color: var(--color-cream-3);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-input);
  min-height: 150px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-slate);
  font-size: 12px;
  margin-bottom: var(--space-3);
  cursor: zoom-in;
  text-align: center;
  padding: var(--space-3);
}
.acct-pdfbox-big {
  min-height: 70vh;
  cursor: zoom-out;
  font-size: 14px;
}
.acct-detail-panel {
  background-color: var(--color-cream-3);
  border-radius: var(--radius-card);
  padding: var(--space-4);
  position: sticky;
  top: var(--space-3);
}
.acct-split {
  display: flex;
  gap: var(--space-4);
  align-items: flex-start;
  flex-wrap: wrap;
}
.acct-split > .acct-split-liste { flex: 1.4; min-width: 320px; }
.acct-split > .acct-detail-panel { flex: 1; min-width: 260px; }
.acct-pfadbox {
  font-family: var(--font-mono);
  font-size: 12px;
  background-color: var(--color-cream-3);
  border-radius: var(--radius-input);
  padding: var(--space-3) var(--space-4);
  margin: var(--space-3) 0;
  overflow-x: auto;
  white-space: pre;
}

/* -----------------------------------------------------------------------------
   S246-A — Events-UX-Ueberarbeitung (U/V/M-Tickets)
   Quelle: Suriro/82 BitsAndPreztels Event/Spec-S246-Events-UX-Ueberarbeitung.md
   + events-ux-mockup.html (visuelle Vorlage). Nur bestehende Tokens (Ink/Cream/
   Rot, --space-*, --radius-*) — KEINE Ampelfarben (gruen/gelb/orange), das
   Portal kennt sie nicht (kein --color-good/--color-warn in :root). Der
   Pipeline-Trichter (U3) benutzt deshalb eine INK-Abstufung statt Ampelfarben,
   und "das eine Rot" bleibt der primaere CTA je Bildschirm (R1.3).
   ----------------------------------------------------------------------------- */

/* U2 — Ziel-Kacheln: vier schmale Kacheln im Raster statt grosser Bloecke. */
.evt-goals {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-3);
}

@media (max-width: 700px) {
  .evt-goals { grid-template-columns: repeat(2, 1fr); }
}

.evt-goal {
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-card);
  background: var(--color-paper);
  padding: var(--space-3) var(--space-4);
}

.evt-goal-label {
  display: flex;
  justify-content: space-between;
  font-size: 11px;
  color: var(--color-slate);
  letter-spacing: .02em;
}

.evt-goal-value {
  font-family: var(--font-mono);
  font-size: 20px;
  margin: 2px 0 6px;
  letter-spacing: -.02em;
}

.evt-goal-value small {
  font-size: 12px;
  color: var(--color-slate);
  font-weight: var(--weight-regular);
}

.evt-track {
  height: 5px;
  background: var(--color-cream-3);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-pill);
  overflow: hidden;
}

.evt-track > i {
  display: block;
  height: 100%;
  background: var(--color-ink);
}

/* U3/U5 — Pipeline als verschachtelte Teilmengen-Balken, klickbar (Filter). */
.evt-nest {
  position: relative;
  height: 96px;
  border-radius: var(--radius-input);
  overflow: hidden;
  background: var(--color-cream);
}

.evt-nest-bar {
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding-right: var(--space-2);
  border-right: 1px solid var(--color-paper);
  text-decoration: none;
  transition: filter var(--motion-fast);
}

.evt-nest-bar:hover,
.evt-nest-bar:focus-visible {
  filter: brightness(1.08);
}

.evt-nest-bar .evt-nest-v {
  font-family: var(--font-mono);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

.evt-nest-bar.is-gesamt { background: var(--color-red-100); color: var(--color-red-700); }
.evt-nest-bar.is-eingeladen { background: var(--color-slate-3); color: var(--color-ink); }
.evt-nest-bar.is-angenommen { background: var(--color-ink-4); color: var(--color-paper); }
.evt-nest-bar.is-getroffen { background: var(--color-ink); color: var(--color-paper); }

.evt-nest.is-mini { height: 68px; }

.evt-nest-legend {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px var(--space-4);
  margin-top: var(--space-3);
  font-size: 12px;
  color: var(--color-ink-3);
}

@media (max-width: 700px) {
  .evt-nest-legend { grid-template-columns: 1fr; }
}

.evt-nest-legend-item {
  display: flex;
  align-items: center;
  gap: 7px;
  border: 1px solid transparent;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  padding: 4px 6px;
  border-radius: 7px;
  cursor: pointer;
  text-decoration: none;
  min-height: 44px;
}

.evt-nest-legend-item:hover,
.evt-nest-legend-item:focus-visible {
  background: var(--color-cream-3);
  border-color: var(--color-hairline);
}

.evt-nest-legend-item .evt-sw {
  width: 11px;
  height: 11px;
  border-radius: 3px;
  flex: none;
}

.evt-nest-legend-item b { font-weight: var(--weight-medium); font-family: var(--font-mono); }
.evt-nest-legend-item small { color: var(--color-slate); }

.evt-pipe-filter {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  margin-bottom: var(--space-3);
  background: var(--color-red-100);
  border: 1px solid var(--color-red-100);
  border-radius: var(--radius-input);
  font-size: 13px;
  color: var(--color-red-700);
}

.evt-pipe-filter a.evt-pipe-x {
  margin-left: auto;
  color: var(--color-red-700);
  text-decoration: none;
  font-size: 16px;
  line-height: 1;
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* U4 — "Heute fällig" Akkordeon: Zeile mit Haken + Flag, Details per Klick. */
.evt-acc-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 11px var(--space-4);
  width: 100%;
  border: 0;
  background: transparent;
  text-align: left;
  color: inherit;
  font-size: 14px;
  min-height: 44px;
}

.evt-acc-row .evt-acc-title { flex: 1; min-width: 0; }
.evt-acc-row .evt-acc-title.is-erledigt { color: var(--color-slate); text-decoration: line-through; }

/* V2/V3/V4/V7 — Kontaktliste: Name·Firma·Position gestapelt, Hook editierbar. */
.evt-who-name { font-size: 14px; }
.evt-who-name.is-offen { color: var(--color-ink-3); font-style: italic; }
.evt-who-firma { font-size: 12.5px; color: var(--color-ink-2); }
.evt-who-rolle { font-size: 12px; color: var(--color-slate); }

/* S246 Korrektur (Screenshot-Befund): das Prio-<select> erbt sonst die
   globale `select { width:100%; min-height:44px; }`-Regel (Zeile ~1524) —
   IN EINEM FLEX-ELTERN wird `width:100%` zum Flex-Basis-Wunsch, den der
   Browser gegen die unterschiedlich breite Geschwister-Spalte (Name/Firma/
   Position) je Zeile anders ausgleicht. Ergebnis: das Feld ist nicht nur zu
   breit, sondern in JEDER Zeile eine andere Breite (genau der gemeldete
   Befund). Eine EIGENE, FESTE Breite + `flex: 0 0 auto` beendet den
   Aushandlungsprozess, bevor er beginnt — gilt unveraendert auf Desktop
   UND Mobil (dieselbe Karte teilt sich `.evt-mk-top`, kein eigener Fall
   noetig). */
.prio-pick {
  width: 64px;
  min-width: 64px;
  max-width: 64px;
  flex: 0 0 64px;
  /* #833 (gemessen 11.09.: 64 x 32 px): die feste BREITE war die Korrektur,
     die Hoehe wurde nur mit ueberschrieben. Tippziel 44 px (R17.4). */
  min-height: 44px;
  padding: 3px 4px;
  font-family: var(--font-mono);
  font-size: 12px;
  box-sizing: border-box;
}

/* S246 Nachtrag (Inline-Statuswechsel in der Zeile, zusaetzlich zur
   Overlay-Sektion "Vor Ort"): dieselbe Breite-Falle wie beim Prio-<select>
   oben (globales `select { width:100%; }` in einem Flex-Elternteil) — darum
   dieselbe Loesung: feste Breite + `flex: 0 0 auto`. Etwas breiter als die
   Prio-Spalte (110px statt 64px), weil die Statuslabels laenger sind
   ("Meeting gebucht", "Kein Interesse" usw.). */
.evt-status-inline { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.evt-status-pick {
  width: 110px;
  min-width: 110px;
  max-width: 110px;
  flex: 0 0 110px;
  min-height: 32px;
  padding: 3px 4px;
  font-size: 12px;
  box-sizing: border-box;
}

.evt-hook-box {
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-input);
  background: var(--color-cream-3);
  padding: 6px 9px;
  font-size: 12.5px;
  color: var(--color-ink-2);
  min-height: 34px;
  outline: none;
}

.evt-hook-box:focus { border-color: var(--color-red); background: var(--color-paper); }

.evt-hook-bar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: 4px;
}

.evt-hook-bar .evt-cnt { font-size: 11px; color: var(--color-slate); font-family: var(--font-mono); }
.evt-hook-bar .evt-cnt.is-ueber { color: var(--color-red); }

.evt-flash {
  color: var(--color-ink);
  font-size: 11px;
  opacity: 0;
  transition: opacity var(--motion-fast);
}

.evt-flash.is-on { opacity: 1; }

/* #887 — eine Zeile, die nach einem Statuswechsel nicht mehr zum aktiven Filter
   passt. Sie wird MARKIERT statt entfernt (Begruendung in
   assets/events-kontakt-zeile.js): ein Verschwinden waere selbst eine
   Umpositionierung, und die Trefferzahl daneben waere danach falsch.
   KEINE Ampel und kein Rot (R1.3): eine gestrichelte Kante in der Linienfarbe
   und eine gedaempfte Flaeche — "gilt hier gerade nicht mehr", nicht "Fehler".
   Der Klartext dazu steht bei der Trefferzahl ([data-filter-abweichung]). */
[data-ausser-filter] {
  outline: 1px dashed var(--color-sand);
  outline-offset: -1px;
  background-color: var(--color-cream-3);
}

/* V7/V8 — LinkedIn-URL einfuegen -> lesen & Hook generieren (Platzhalter bis S246-L). */
.evt-li-paste {
  width: 100%;
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-input);
  padding: 7px 9px;
  font-family: inherit;
  font-size: 12.5px;
  background: var(--color-paper);
  color: var(--color-ink);
}

.evt-li-paste:focus { border-color: var(--color-red); outline: none; }

.evt-genwrap { display: flex; flex-direction: column; gap: 6px; }
.evt-genrow { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.evt-genhint { font-size: 11px; color: var(--color-slate); }

/* S246 Erweiterung (Ticket #800-#803, Overlay-Umbau): der Fortschritt einer
   einzelnen kontextuellen Aktion (Namen ermitteln / Person recherchieren /
   Hook erzeugen-neu formulieren, je in ihrem eigenen Abschnitt) — ersetzt die
   fruehere feste 3-Schritt-Leiste `.evt-hookgen-steps` des alten, EINEN
   Assistenten-Overlays (drei Schritte immer zusammen). Jede Aktion hat jetzt
   ihren EIGENEN, kurzen Statustext an genau der Stelle, an der sie wirkt. */
.evt-ov-status { font-size: 12px; color: var(--color-slate); margin-top: 6px; }
.evt-ov-status.is-aktiv { color: var(--color-ink); }
.evt-ov-status.is-fehler { color: var(--color-red-700); }

/* S246 Erweiterung: LinkedIn-URL inline editierbar fuer JEDEN Kontakt (nicht
   nur "Name noch offen" — Martins Auftrag).
   #807 (Martin 11.09.): Die URL SELBST ist der Link und traegt deshalb den
   URL-Text, nicht mehr eine 16 px schmale Marke daneben — und sie ist
   unterstrichen, weil eine Fremdmarkenfarbe allein keine Klickbarkeit
   aussagt (Styleguide: Farbe allein traegt keine Aussage). Das Eingabefeld
   steht daneben nicht mehr gleichzeitig, sondern tritt hinter "aendern"
   zurueck; sichtbar ist immer genau eines von beiden. */
.evt-li-row { display: flex; align-items: center; gap: 4px; margin-top: 2px; }
.evt-li-open {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
  color: var(--color-fremd-linkedin);
  text-decoration: underline;
}
.evt-li-edit { flex: none; }
.evt-li-input {
  flex: 1 1 auto;
  min-width: 0;
  width: auto;
  min-height: 30px;
  padding: 3px 7px;
  font-size: 11.5px;
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-input);
  background: var(--color-paper);
  color: var(--color-ink-2);
}
.evt-li-input:focus { border-color: var(--color-red); outline: none; color: var(--color-ink); }
.evt-li-input.is-invalid { border-color: var(--color-red); }
.evt-li-save { flex: none; }

@media (max-width: 700px) {
  .evt-li-input, .evt-li-save, .evt-li-edit { min-height: 44px; }
  /* #807/R17: der Link ist ein Tippziel und braucht dieselben 44 px wie die
     Knoepfe daneben — die vorherige Marke war 16 px breit und 12 px hoch. */
  .evt-li-open { min-height: 44px; display: inline-flex; align-items: center; }
}

/* M3 — Ziel-Ringe (mobil), Grid um das inline-SVG-Donut. Die Kachel-Ansicht
   (.evt-goals) bleibt auf Desktop-Breiten die Darstellung; unter 560px
   uebernehmen die Ringe (Spec M3: "Fortschrittsringe statt Balkenwand"),
   beide werden serverseitig aus denselben zielKacheln gerendert (keine
   Duplikat-Logik in JS). */
.evt-rings {
  display: none;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-3);
}

@media (max-width: 560px) {
  #events-ziele .evt-goals { display: none; }
  #events-ziele .evt-rings { display: grid; }
}

.evt-ring {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-card);
  padding: 9px 10px;
  background: var(--color-paper);
}

.evt-ring svg { flex: none; }
.evt-ring .evt-ring-label { font-size: 11px; color: var(--color-slate); line-height: 1.3; }
.evt-ring .evt-ring-value { font-family: var(--font-mono); font-size: 15px; }

/* M6/M7 — Kontaktkarte (mobil) statt breiter Tabelle. */
.evt-mk {
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-card);
  background: var(--color-paper);
  padding: 10px var(--space-3);
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.evt-mk-top { display: flex; align-items: center; gap: var(--space-2); }
.evt-mk-top .evt-who-name { flex: 1; }
.evt-mk-actions { display: flex; gap: var(--space-2); flex-wrap: wrap; align-items: center; }

/* Kontaktliste: Tabelle (Desktop) <-> Kartenliste (Mobil, M6) — dieselben
   Daten, zwei serverseitig gerenderte Darstellungen, per Breakpoint getauscht
   (kein Duplikat in JS: events-vorher.js bindet ueber dieselben data-*-
   Attribute, egal welche Darstellung sichtbar ist). */
.evt-mk-list { display: none; flex-direction: column; gap: var(--space-3); }

@media (max-width: 700px) {
  .evt-kontakte-table { display: none; }
  .evt-mk-list { display: flex; }
}

/* S246 Erweiterung (Tickets #800-#803): Kontaktzeile/-karte oeffnet jetzt EIN
   Overlay statt des frueheren Inline-Aufklappens (Hook-Editor/Fakten/
   Veroeffentlichungen standen bislang direkt in der Zeile). Eigene Steuer-
   elemente IN der Zeile (Prio-Auswahl, LinkedIn-Feld, Statusformular) stoppen
   ihren Klick weiterhin selbst (events-vorher.js) — dieser Zeiger/Hover gilt
   fuer die Zeile als Ganzes. */
.evt-kontakt-row { cursor: pointer; }
.evt-kontakt-row:hover { background-color: var(--color-cream); }
.evt-kontakt-row:focus-visible { outline-offset: -2px; }
.evt-mk.evt-kontakt-row { cursor: pointer; }
.evt-mk.evt-kontakt-row:hover { background-color: var(--color-cream); }

/* S259 — Abschnittskopf der Aufgabenbloecke (#879 "Kontaktaufnahme, Leitfaeden,
   Material, … — alles einklappbar"). Gleiche Bauform wie die grossen
   Abschnittskoepfe darueber, eine Stufe leiser: kein Kartenrahmen, dafuer eine
   Trennlinie. 44px Mindesthoehe (R17). */
.evt-abschnittskopf {
  width: 100%;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 44px;
  padding: var(--space-2) var(--space-3);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--color-hairline);
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  color: var(--color-ink);
}
.evt-abschnittskopf:hover { background-color: var(--color-cream); }
.evt-abschnittskopf-titel { font-size: 15px; }
.evt-abschnittskopf-rechts {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--color-slate);
}

/* S259 — die Filterleiste der Kontaktliste (#878/#877/#876).
   EINE Leiste statt vier Chip-Zeilen; sie klebt beim Scrollen oben, damit die
   gesetzten Kriterien sichtbar bleiben, waehrend man durch die Liste geht
   (Auftrag #879: "sichtbar und sticky dann die Filter und die Spaltenheader").
   Der sticky Spaltenkopf darunter bleibt unveraendert (#801/B2): er ankert im
   Listenbehaelter, diese Leiste an der Seite — zwei Bezugsrahmen, kein Konflikt.

   R17 — WARUM SIE UNTERHALB 769px NICHT KLEBT: dort klebt bereits die
   Docu/Action-Leiste (.evt-docu, M1). Eine zweite klebende Leiste mit sechs
   umbrechenden Feldern naehme auf einem 812px-Schirm rund 40 % der Bildhoehe.
   Ein sticky Element, das am Telefon die halbe Hoehe frisst, ist schlimmer als
   keines. Das Ziel-Scrollen nach dem Filtern (#876) gilt am Telefon
   unveraendert — nur ohne Kleben. */
.evt-filter {
  /* Papier, nicht Creme: die Seite ist creme — eine creme Leiste waere nicht als
     Leiste zu erkennen, und klebend muss sie den durchlaufenden Text decken. */
  background-color: var(--color-paper);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-card);
  padding: var(--space-2) var(--space-3);
  margin-bottom: var(--space-2);
}
.evt-filter-felder {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
/* Kompakter als die Grundform (die ist fuer Formularspalten gebaut, nicht fuer
   eine Leiste): die 44px Mindesthoehe bleiben, die Breite richtet sich nach dem
   Inhalt statt nach der Spalte. */
/* Das Suchfeld wird ueber SEINE KLASSE angesprochen, nicht ueber
   `.evt-filter-felder input[type="text"]`: der Waechter in
   hidden-ist-unsichtbar.test.js wertet den letzten Teil eines Selektors aus und
   haette eine Regel auf `input[type="text"]` faelschlich auf jedes Textfeld des
   Portals bezogen. Eine Klasse ist hier ohnehin die genauere Aussage. */
.evt-filter-felder select,
.evt-filter-suche {
  display: inline-block;
  width: auto;
  font-size: 13px;
  padding: 9px var(--space-2);
}
.evt-filter-felder select {
  padding-right: var(--space-5);
  background-position: right 6px center;
}
.evt-filter-suche { flex: 1 1 220px; min-width: 180px; max-width: 340px; }
.evt-filter-trenner {
  width: 1px;
  align-self: stretch;
  min-height: 28px;
  background: var(--color-hairline);
}
.evt-filter-kasten {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  font-size: 12px;
  color: var(--color-ink-2);
  white-space: nowrap;
  cursor: pointer;
}
.evt-filter-kasten input[type="checkbox"] { width: 18px; height: 18px; }
.evt-filter-tat { margin-left: auto; display: flex; align-items: center; gap: var(--space-2); }
.evt-filter-ergebnis {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-2);
  padding-top: var(--space-2);
  border-top: 1px solid var(--color-hairline);
  font-size: 13px;
  color: var(--color-ink-2);
}
.evt-filter-laeufe {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.evt-filter-ergebnis .evt-pipe-filter { display: inline-flex; margin-bottom: 0; }
.evt-filter-hinweis {
  margin: 0 0 var(--space-2);
  font-size: 0.75rem;
  color: var(--color-ink-2);
}
/* Ein leerer Ergebnisbehaelter darf keinen Platz beanspruchen — sonst steht
   dauerhaft eine Luecke ueber der Liste fuer etwas, das meistens nicht da ist. */
#evt-hookregen-ergebnisse { margin-bottom: var(--space-3); }
#evt-hookregen-ergebnisse:empty { margin-bottom: 0; }

@media (min-width: 769px) {
  .evt-filter {
    position: sticky;
    top: var(--sticky-unterkante, 0px);
    z-index: 3;
  }
}

/* Am Telefon ist die Leiste eine Liste untereinander, kein Band: die senkrechten
   Trenner gruppieren dort nichts mehr (jede Gruppe steht ohnehin in einer eigenen
   Zeile), und `margin-left: auto` schiebt nur Loecher in die Zeilen, statt etwas
   rechtsbuendig zu setzen. Gemessen 13.09.2026 bei 375 px: die Feldzeilen
   brauchten dadurch vier Zeilen statt drei. */
@media (max-width: 768px) {
  .evt-filter-trenner { display: none; }
  .evt-filter-tat,
  .evt-filter-laeufe { margin-left: 0; }
  .evt-filter-suche { flex: 1 1 100%; max-width: none; }
}
/* Ende S259 Filterleiste — Schlussmarke fuer die Tests (#1294), nicht entfernen */

/* #801 — sticky Listenkopf: die Spaltenueberschriften bleiben beim Scrollen
   durch eine lange Kontaktliste sichtbar.

   B2 (Korrektur-Paket S246, 11.09., Prod-UI-Testlauf): "Sticky Spaltenkopf
   ueberlappt die erste Zeile beim Scrollen" — WAR mit `top:
   var(--sticky-unterkante, 0px)` gegen die SEITE gerechnet, stand aber in
   `.evt-kontakte-table`, das AUCH `.table-container` ist (dieselbe Datei,
   vorher.ejs). `.table-container` traegt `overflow-x: auto` — CSS macht
   daraus zwangslaeufig auch senkrecht einen Scroll-Behaelter (`visible` ist
   neben `auto` nicht moeglich, s. exakt dieselbe Diagnose einen Tag zuvor bei
   `.permission-matrix .table-container` oben in dieser Datei, Abschnitt 24,
   Martin 10.09.). DER BEZUGSRAHMEN FUER `position: sticky` IST DAMIT DER
   BEHAELTER, NICHT DIE SEITE — ohne eigene Hoehe hat der Behaelter aber
   keinen eigenen Scrollport, gegen den `top` verlässlich ankern kann; je
   nach Browser/Scroll-Timing lag die Kopfzeile dadurch neben statt ueber der
   ersten Zeile ("ueberlappt").
   FIX (dasselbe Muster wie Abschnitt 24): der Behaelter bekommt eine eigene
   Hoehe (`max-height`, hier grosszuegiger als die Rechte-Matrix, weil die
   Kontaktliste bis zu ~400 Zeilen zeigt) und scrollt jetzt IN SICH SELBST —
   `top: 0` ankert dann korrekt gegen genau diesen Behaelter, nicht mehr
   gegen die Shell-Kopfzeile (`--sticky-unterkante` entfaellt hier deshalb,
   bleibt aber fuer `.evt-docu` unten unveraendert in Kraft). */
.evt-kontakte-table.table-container {
  overflow: auto;
  max-height: min(75vh, 900px);
}

.evt-kontakte-table thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background-color: var(--color-ink-4);
  color: var(--color-cream);
  padding: var(--space-2) var(--space-4);
  font-size: 12px;
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  text-align: left;
}

/* S259 — WARUM DIE LISTE AUF BREITEN SCHIRMEN NICHT MEHR IN SICH SELBST SCROLLT.
   #801/B2 (direkt darueber) gab dem Behaelter eine eigene Hoehe, damit der
   Spaltenkopf einen Scrollport zum Ankern hat. Mit einer klebenden Filterleiste
   DARUEBER (#879) ist genau das der Fehler: beim Weiterscrollen der Seite wandert
   der Behaelter unter die Leiste — und der Spaltenkopf, der am Behaelter klebt
   und nicht an der Seite, wandert mit. Gemessen 13.09.2026 bei 1440x900:
   Behaelter-Oberkante 70 px, Unterkante der Leiste 181 px, Spaltenkopf bei 71 px,
   also vollstaendig hinter der Leiste. Genau der Kopf, der laut Auftrag sichtbar
   bleiben soll.
   Deshalb: auf breiten Schirmen scrollt die Liste mit der SEITE, und der
   Spaltenkopf klebt an der Seite, unterhalb der Filterleiste. --evt-filter-hoehe
   misst events-vorher.js — dasselbe Muster, mit dem shell.js seit #385 die
   Klebekante misst statt sie zu rechnen: die Hoehe entsteht erst im Layout (die
   Leiste bricht je nach Breite um), CSS kann sie nicht kennen.
   Diese Regel steht BEWUSST nach dem #801-Block: sie hat dieselbe Spezifitaet
   und gewinnt nur durch ihre Stellung. Unterhalb 769 px bleibt alles wie bisher —
   dort zeigt die Seite die Kartenliste und gar keine Tabelle. */
@media (min-width: 769px) {
  .evt-kontakte-table.table-container {
    overflow: visible;
    max-height: none;
  }
  .evt-kontakte-table thead th {
    top: calc(var(--sticky-unterkante, 0px) + var(--evt-filter-hoehe, 130px));
  }
}

/* S265 (6) — Adminbereich am PC raumsparender (Martin 27.09.). NUR am Schreibtisch
   (min-width: 769px) und NUR unter [data-bereich="admin"] (layout.ejs, aus dem Pfad):
   das Telefon behaelt die 44-px-Beruehrungsziele und die groesseren Abstaende.
   KEINE Aenderung an Auswahl/Reihenfolge der Reiter (Martins Frage, #1205: „Release"
   bleibt erster). Groesster Hebel ist die 17-Reiter-Subnav: am PC klickt eine Maus,
   die Reihen duerfen niedriger sein (min-height 44 -> 32, weniger Innenabstand); dazu
   die drei gestapelten Abstaende oben (section-label / subnav / page-header) enger. */
@media (min-width: 769px) {
  [data-bereich="admin"] .section-label { margin-bottom: var(--space-2); }
  [data-bereich="admin"] .section-subnav {
    padding-top: var(--space-2);
    margin-bottom: var(--space-4);
  }
  [data-bereich="admin"] .section-subnav-item {
    min-height: 32px;
    padding-top: var(--space-1);
    padding-bottom: var(--space-1);
  }
  [data-bereich="admin"] .page-header { margin-bottom: var(--space-4); }
  [data-bereich="admin"] .page-header h1 { margin-top: 0; }
  [data-bereich="admin"] .section-header { margin-bottom: var(--space-3); }
}

/* Ticket #802 — 720px Overlay (Design-Vorlage) statt der Standardbreite
   520px; der 600px-Umbruch der `.dialog`-Basisklasse (Bottom-Sheet, voll-
   flaechig) bleibt unveraendert wirksam. Generischer Modifizierer, nicht
   Events-spezifisch — ein Overlay mit mehr Inhalt als ein einfaches Formular
   braucht diese Breite auch ausserhalb dieses Moduls. */
.dialog-lg { max-width: 760px; }

/* Overlay-Sektionen (Wer / Ansprache / Recherche / Warum dieser Kontakt /
   Vor Ort — Design-Vorlage kontakt-overlay-flow.html): jede Sektion traegt
   ihre eigene Kopfzeile mit Titel + optionalem Aktions-Slot ("jede Aktion
   sitzt, wo sie wirkt", Auftrag) und eine Trennlinie zur naechsten. Fakten
   nutzen das bestehende `.detail-list` (kein neues dl/dt/dd-Muster). */
.evt-ov-section { padding: var(--space-4) 0; border-bottom: 1px solid var(--color-hairline); }
.evt-ov-section:last-child { border-bottom: 0; padding-bottom: 0; }
.evt-ov-section:first-child { padding-top: 0; }
.evt-ov-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); margin-bottom: var(--space-3); flex-wrap: wrap; }
.evt-ov-h { margin: 0; font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-slate); font-weight: var(--weight-medium); }
.evt-ov-headacts { display: flex; gap: var(--space-2); align-items: flex-start; flex-shrink: 0; flex-wrap: wrap; }

/* "Warum dieser Kontakt" — Relevanz-Balken je Ausrichtung (Consulting/MDG/
   Portal/Martin-Job, eventsFokusLinse.js-Kopf) + Ziel-Chips (bestehende
   `.chip`, nur lesend). */
.evt-ov-bar { display: grid; grid-template-columns: 96px 1fr 28px; gap: var(--space-3); align-items: center; font-size: 13px; margin-bottom: 6px; }
.evt-ov-bar:last-child { margin-bottom: 0; }
.evt-ov-bar-track { height: 6px; background: var(--color-hairline); border-radius: 3px; overflow: hidden; }
.evt-ov-bar-fill { height: 100%; background: var(--color-ink-3); }
.evt-ov-bar-v { font-family: var(--font-mono); font-size: 12px; text-align: right; color: var(--color-ink-2); }
.evt-ov-chips { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-3); }

/* M1 — Docu/Action: klebende Leiste, NUR auf schmalen Bildschirmen (Mobil-
   first, Spec: "jedem Mobil-Screen"). Sitzt unterhalb der bestehenden Shell-
   Kopf-/Pillenzeile (--sticky-unterkante, Ticket #385).

   INK STATT ROT (gemessen 10.09.2026 im Browser bei 375 px). Die Leiste war satt
   rot. Sie wird von VIER Vorlagen eingebunden (uebersicht, vorher, waehrend,
   assistent) — solange sie das Rot traegt, kann kein Events-Bildschirm mehr ein
   eigenes rotes Moment haben, ohne R1.3 zu verletzen. Auf /events/:key/waehrend
   standen dadurch sieben rote Elemente gleichzeitig im Markup, fuenf davon bei
   aufgeklappten Zeilen gleichzeitig im Bild.
   Sie ist Bedienrahmen, kein Bildschirminhalt: sie klebt oben, steht auf jedem
   Screen und loest dieselbe Handlung aus wie der rote CTA des jeweiligen
   Bildschirms (`data-docu-trigger` an beiden). Also Ink — dieselbe gefuellte
   Tintenflaeche wie `.btn-secondary`. Das eine Rot bleibt beim Bildschirm, nicht
   beim Rahmen. */
.evt-docu {
  display: none;
}

@media (max-width: 768px) {
  .evt-docu {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    position: sticky;
    /* #821: unter BEIDEN Reiterzeilen. Bis 05.10. lag die Leiste auf der
       Kante der ersten Reiterzeile und deckte sie (z-index 30) zur Haelfte ab —
       lokal gemessen bei 375 px im kompakten Kopf: Leiste 84–128, Reiter 84–145. */
    top: var(--sticky-unterkante-3, var(--sticky-unterkante, 0px));
    z-index: 30;
    padding: 10px var(--space-4);
    margin: 0 calc(-1 * var(--space-4)) var(--space-4);
    background: var(--color-ink);
    color: var(--color-white);
    font-size: 13.5px;
    border: 0;
    width: calc(100% + var(--space-8));
    min-height: 44px;
    cursor: pointer;
    text-align: left;
  }

  .evt-docu .evt-docu-ic {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.2);
    display: grid;
    place-items: center;
    font-size: 12px;
    flex: none;
  }

  .evt-docu .evt-docu-hint {
    margin-left: auto;
    font-size: 10.5px;
    color: rgba(255, 255, 255, 0.85);
  }
}

/* M2 — Assistent als "Wann -> Was"-Ablauf. */
.evt-ast { display: flex; flex-direction: column; gap: 10px; }

.evt-ast-row {
  display: grid;
  grid-template-columns: 96px 1fr;
  gap: var(--space-3);
  align-items: start;
}

@media (max-width: 560px) {
  .evt-ast-row { grid-template-columns: 78px 1fr; }
}

.evt-ast-when {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--color-white);
  background: var(--color-ink);
  border-radius: 6px;
  padding: 4px 5px;
  text-align: center;
  line-height: 1.3;
}

.evt-ast-when.is-live { background: var(--color-red); }

.evt-ast-row .evt-ast-titel { font-size: 13.5px; }
.evt-ast-row .evt-ast-nutzen { font-size: 12px; color: var(--color-slate); margin-top: 2px; }

/* M8 — "Heute"-Agenda: Schnellaktionen im aufgeklappten Detail. */
.evt-qa {
  display: flex;
  gap: 6px;
  margin-top: 9px;
  flex-wrap: wrap;
}

.evt-qa .btn { flex: 1 1 auto; min-height: 40px; }

/* #795 — der Weg weg vom Schnellweg: unter den Schnellaktionen steht der
   Verweis auf den vollen Erfassen-Bildschirm mit vorbelegtem Kontakt.
   Bewusst ein Textverweis statt eines vierten Knopfes: die Knopfreihe bleibt
   die Handlung vor Ort, dies ist Navigation. Farbe ink-2; 44 px hoch, weil auf
   diesem Screen das Telefon der Regelfall ist (R17).

   NACHTRAG 10.09.2026: hier stand "damit neben dem roten 'Erfassen' keine zweite
   rote Flaeche entsteht". Das war nie wahr — "● Erfassen" trug `btn-red`, und
   diese Klasse ist in dieser Datei nie definiert worden; der Knopf rendert
   farblos. Der Kommentar hat einen ZUSTAND behauptet, statt ihn zu pruefen —
   dieselbe Fehlerklasse wie der Knopf selbst, nur in Prosa: beides sah
   vollstaendig aus und war es nicht. "● Erfassen" ist
   jetzt `btn-secondary` (Ink), das eine Rot des Bildschirms traegt der CTA
   `#events-heute-erfassen-cta`. Gehuetet von
   test/unit/knopfklassen-sind-definiert.test.js (jede benutzte `btn-*`-Klasse
   muss es hier geben) und test/unit/events-waehrend-one-red.test.js.

   Dieselbe Auszeichnung tragen beide Wegfuehrungen des Bildschirms — "Ausfuehrlich
   erfassen →" und "im Detail bearbeiten →". Sie tun dasselbe (weg von hier, mit
   Bezug), also sehen sie gleich aus. */
.evt-detail-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-size: 13px;
  color: var(--color-ink-2);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.evt-detail-link:hover { color: var(--color-ink); }



/* #587 Lauf-Chip — /invoices, Zusammenfassung je Monat
   -----------------------------------------------------------------------------
   Martin sah den 2026-08-Lauf viermal und konnte die Zeilen nicht unterscheiden
   (gemessen: alle vier sind Probelaeufe von zwei Nutzern, ein echter Augustlauf
   fehlt ganz). Jetzt EIN Chip je Monat; die Einzelzeilen stehen aufgeklappt
   darunter. Gebaut auf dem portalweiten Chip-Muster (.chip-toggle /
   .contract-chip*, R1.7) — hier stehen nur die drei invoices-eigenen Textteile.
   Kein eigener Chip-Renderer, damit es nicht zwei Fassungen des Musters gibt.

   Eigener, markierter Block: an /invoices/lauf/:id arbeitet parallel eine zweite
   Sitzung (#588/#589). Wer spaeter zusammenfuehrt, soll die Stellen unterscheiden
   koennen.

   Keine Hex-Werte, nur Token (R1.1). Keine Farbe als einziges Signal (R12):
   „Echter Lauf" / „Kein echter Lauf · N Probelaeufe" steht als Wort im Chip.
   ----------------------------------------------------------------------------- */
.lauf-chip .lauf-chip-stand {
  /* .contract-chip-type setzt nowrap — richtig fuer eine Vertragsart, falsch fuer
     einen ganzen Satz wie „Kein echter Lauf · 4 Probelaeufe". Bei 375 px steht die
     Kopfzeile untereinander (Regel 26 @420px), und eine nicht umbrechbare Zeile
     waere dort seitlicher Ueberstand — genau das, was R17.2 verbietet. */
  white-space: normal;
  overflow-wrap: anywhere;
}

/* R21: sichtbarer Verweis darauf, dass der Chip mehr Zeilen zusammenfasst, als er
   einzeln zeigt. Zurueckgenommen im Ton, aber vorhanden — ein Fehlen sieht der
   Leser nie. nowrap, damit „+3 weitere" nicht zwischen Zahl und Wort bricht
   (R17.6). */
.lauf-chip .lauf-chip-weitere {
  font-size: 11px;
  color: var(--color-slate);
  white-space: nowrap;
}

/* Der Monat ist eine Zahl (R1.4: Zahlen in Sometype Mono — .mono bringt die
   Schrift, .contract-chip-no die Pille). Tabellenziffern, damit die Chips
   untereinander auf einer Kante stehen. */
.lauf-chip .contract-chip-no {
  font-variant-numeric: tabular-nums;
}

/* Der Link im aufgeklappten Bereich — eigene Klasse statt der geteilten
   .contract-chip-detail-link, aus zwei GEMESSENEN Gruenden:

   1. R1.3 One-Red-Rule. .contract-chip-detail-link ist rot („The one red element
      of the expanded panel", Abschnitt 26b). Auf /invoices ist das eine rote
      Element aber das Eyebrow im Seitenkopf — bei sechs Monatschips waeren es
      sieben rote Momente. Deshalb Ink, und das Ziel wird ueber Unterstreichung
      und Pfeil gelesen, nicht ueber Farbe (R12).
   2. R17.4 Tippziele. Am 17.08. bei 375 px gemessen: die geteilte Klasse ist
      24 px hoch (inline-block ohne Mindesthoehe). 44 px sind Pflicht.

   Die geteilte Klasse selbst bleibt unangetastet — sie steht in 16 weiteren
   Ansichten, und deren Messung gehoert nicht in dieses Ticket. */
.lauf-chip .lauf-chip-detail-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin-top: var(--space-2);
  color: var(--color-ink);
  font-weight: var(--weight-medium);
  text-decoration: none;
}

.lauf-chip .lauf-chip-detail-link:hover {
  text-decoration: underline;
}

/* #586 — der „Probelauf oeffnen"-Link verschwindet, wenn es keinen gibt.

   Die Falle: `hidden` wirkt ueber `display: none` aus dem Browser-Standard, und
   `.btn` setzt `display: inline-flex` — eine Klassenregel schlaegt die
   Standardregel. Ohne diese Zeile stuende der Link fuer JEDEN Monat da, auch fuer
   die ohne Probelauf, und zeigte auf href="#". Also genau das leere Versprechen,
   gegen das #586 geschrieben ist.

   Dieselbe Falle ist im Portal schon einmal aufgetreten und dort vermerkt:
   `.shell-header-pill[hidden]` in Abschnitt M4 („Bleibt eine Ausnahme: das
   hidden-Attribut der Tour-Pille muss weiter gewinnen"). Hier bewusst NUR auf das
   eine Element, nicht global auf `.btn[hidden]` — eine globale Regel waere ein
   Eingriff in alle Ansichten und gehoert nicht in dieses Ticket.

   Aufgeloest mit #807 (12.09.2026): `[hidden]` traegt in Abschnitt 2 global
   `display: none !important`. `#lauf-probe-oeffnen[hidden]` und das
   nachgereichte `.btn[hidden]` (03.09.2026, Kommentar unten) sind damit
   ueberfluessig und stehen nicht mehr da. */

/* ── `.btn[hidden]`, 03.09.2026 — der Schritt, der hier halbiert wurde ───────

   VIERTES AUFTRETEN DERSELBEN FALLE in dieser Datei. Der Kommentar direkt
   darueber schliesst sie fuer EIN Element, `.shell-header-pill[hidden]` in
   Abschnitt M4 fuer ein zweites, `input[type="file"][hidden]` weiter oben fuer
   ein drittes — und am 03.09. stand im Import-Bildschirm ein LEERER ROTER KNOPF
   neben „Datei ablegen", weil ein `.btn` mit `hidden` wieder sichtbar blieb.
   Martin hat ihn auf dem Handy gefunden: „was macht der rote Button neben dem
   DateiAblegen Knopf? ist mobil nicht lesbar".

   Der Verzicht auf die globale Regel war seinerzeit eine SCHNITT-Entscheidung
   („gehoert nicht in dieses Ticket"), kein fachlicher Einwand. Vier Fundstellen
   spaeter kostet der Verzicht mehr, als er spart: jeder kuenftige Knopf mit
   `hidden` ist stumm sichtbar, bis ihn jemand am Bildschirm entdeckt.

   WARUM DIE GLOBALE REGEL UNGEFAEHRLICH IST: sie kann nur verbergen, was das
   Attribut `hidden` TRAEGT — und das setzt immer der Autor der Ansicht, mit
   genau dieser Absicht. Ein Element, das `hidden` traegt und trotzdem sichtbar
   sein soll, waere ein Widerspruch in sich. Gemessen am Bestand (03.09.2026):
   zwei Fundstellen im ganzen Ansichtsbaum, beide sollen verborgen sein.

   NACHTRAG 12.09.2026 (#807). `.btn[hidden]` hiess hier „UND JETZT GLOBAL" und
   war es nicht: global fuer Knoepfe, weiter blind fuer alles andere. Neun Tage
   spaeter traf dieselbe Falle `input[type="text"]` — auf Prod sichtbar bei 22
   von 70 Kontaktzeilen, wo Link, Knopf und Eingabefeld nebeneinander standen.
   Die Regel steht jetzt ohne Elementart in Abschnitt 2 und traegt `!important`,
   damit auch die naechste Darstellungsregel sie nicht schlaegt.

   Der Satz „die drei Einzelregeln bleiben stehen, wer sie loescht, verliert die
   Geschichte" ist damit eingeloest und nicht verworfen: die Begruendungen stehen
   alle noch hier, nur die Regeln selbst sind weg. Eine redundante Regel ist
   keine Geschichte, sondern eine zweite Stelle, an der jemand nachdenken muss. */
/* Ende #587 Lauf-Chip — Schlussmarke fuer die Tests (#1294), nicht entfernen */

/* #588/#589 Rueckfragen-Chips ─────────────────────────────────────────────────
   Martin, 17.08.2026: „besser waere: die Liste direkt als Chip anlegen - dann kann man
   direkt editieren und korrigieren."

   R1: ausschliesslich Tokens, kein Hex. R1.3 (One-Red-Rule): KEIN rotes Element — das
   eine rote Moment des Lauf-Bildschirms gehoert dem Fehlerfall, und eine Rueckfrage ist
   kein Fehler. R12: der Chip traegt Rahmen, Wortlaut UND Richtungszeichen, nie Farbe
   allein. R17 (375 px zuerst): der Chip bricht um statt zu ueberstehen, Tippziel 44 px. */
.rf-zeile {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

.rf-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  /* 44 px Tippziel (R17) — die Hoehe kommt aus min-height, nicht aus Polsterung, damit
     der Text bei 375 px umbrechen darf, ohne das Ziel zu verkleinern. */
  min-height: 44px;
  max-width: 100%;
  padding: 6px var(--space-3);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-pill);
  background-color: var(--color-cream-3);
  color: var(--color-ink);
  font-family: inherit;
  font-size: 13px;
  line-height: 1.35;
  text-align: left;
  cursor: pointer;
}

.rf-chip:hover,
.rf-chip:focus-visible {
  border-color: var(--color-ink);
  background-color: var(--color-paper);
}

/* Der Name darf umbrechen statt die Zeile aufzureissen — bei 375 px ist ein Betreff wie
   „Test Liebherr mehrere Tage" laenger als die halbe Breite. */
.rf-chip-name { overflow-wrap: anywhere; }

/* Das Richtungszeichen ist der zweite Traeger neben dem Rahmen (R12): es sagt „das hier
   fuehrt woandershin", auch ohne jede Farbe. */
.rf-chip-pfeil { flex-shrink: 0; }

.rf-grund {
  font-size: 13px;
  color: var(--color-slate);
  overflow-wrap: anywhere;
}

/* Die Spur der automatisch entschiedenen Meldungen (R21). Zugeklappt, aber DA — was
   verschwindet, verschwindet sonst ohne Spur. */
.rf-spur { margin-top: var(--space-3); }

.rf-spur > summary {
  cursor: pointer;
  font-size: 13px;
  color: var(--color-slate);
}

/* Der Grund, warum eine Zeile nicht fakturierbar ist — in der Zuordnungsliste, unter der
   Kundenauswahl (Entscheid Martin 17.08.2026, oof-Automatik). Er steht klein und ruhig:
   er erklaert einen Zustand, er verlangt nichts. Ohne ihn saehen „Pause", „Geburtstag"
   und „ausser Haus ohne Kategorie" alle gleich aus — und genau darauf beruht, dass die
   Automatik vertretbar ist (R21). */
.sz-nb-grund {
  margin: 2px 0 0;
  font-size: 11px;
  line-height: 1.3;
  color: var(--color-slate);
  max-width: 34ch;
  overflow-wrap: anywhere;
}

/* #673: die Zeile hing an einem geloeschten Kalendertermin und steht nur noch, weil
   jemand sie von Hand bearbeitet hat. Sie soll auffindbar sein, aber nicht mitreden —
   deshalb dieselbe ruhige Machart wie .sz-nb-grund und bewusst KEIN Rot (R1.3): die
   Tages-Kopfzeile belegt das Rot schon bei ueber 9 Stunden, und ein geloeschter Termin
   ist ein Hinweis, kein Alarm. Ohne diesen Satz saehe die Zeile aus wie jede andere —
   genau das war der Fehler, den Maren gemeldet hat. */
.sz-verwaist {
  margin: 2px 0 0;
  font-size: 11px;
  line-height: 1.3;
  color: var(--color-slate);
  font-style: italic;
}

/* Die Zeile, auf die ein Chip gesprungen ist. Ein Rahmen, kein Farbfeld — und er
   verschwindet nach vier Sekunden von selbst. */
.rf-treffer > td {
  background-color: var(--color-cream-3);
  box-shadow: inset 0 0 0 1px var(--color-ink);
}
/* Ende #588/#589 Rueckfragen-Chips — Schlussmarke fuer die Tests (#1294), nicht entfernen */

/* -----------------------------------------------------------------------------
   Aufklappbare Seitenabschnitte (#629, Martin 20.08.2026)
   „als Überraschung sieht man unten noch weitere Listen … Default ist dann: alle
   eingeklappt - mit einem Status was sich drunter verbirgt darüber."

   NATIVES <details>, KEIN JAVASCRIPT. Der Grund ist nicht Bequemlichkeit: ein
   Inline-`onclick` waere hier die vierte Wiederholung desselben Fehlers (F88,
   AB-20, AB-22, AB-33) — `script-src 'self'` blockt ihn LAUTLOS, und es faellt
   erst auf, wenn jemand genau dieses Element anklickt. Was der Browser selbst
   kann, braucht kein Skript, das ausfallen kann.

   DIE KOPFZEILE TRAEGT DEN ZUSTAND, nicht nur den Namen. Ein zugeklappter
   Abschnitt, an dem „Freigaben" steht, ist schlechter als gar keiner: er
   verbirgt, dass drei Releases seit gestern offen sind. Deshalb steht rechts im
   Kopf, was darunter liegt — abgeleitet aus den Daten, die die Seite ohnehin
   hat (R13.5).
   ----------------------------------------------------------------------------- */
.abschnitt {
  padding: 0;
}

.abschnitt > summary {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding: var(--space-5);
  cursor: pointer;
  user-select: none;
  list-style: none;
  border-radius: var(--radius-card);
}

/* Der eigene Marker muss weg, sonst steht neben dem Caret noch ein Dreieck. */
.abschnitt > summary::-webkit-details-marker { display: none; }
.abschnitt > summary::marker { content: ''; }

.abschnitt > summary:hover {
  background-color: var(--color-cream-3);
}

/* Tastaturbedienung sichtbar machen — <summary> ist von sich aus fokussierbar,
   aber ohne Ring sieht man beim Durchtabben nicht, wo man ist. */
.abschnitt > summary:focus-visible {
  outline: 2px solid var(--color-ink);
  outline-offset: -2px;
}

.abschnitt-caret {
  font-size: 10px;
  color: var(--color-ink-4);
  transition: transform var(--motion-fast);
  /* Der Pfeil ist die Wiederholung der Information, die `open` ohnehin traegt —
     er zeigt sie nur an einer Stelle, an der das Auge hinsieht. */
}

.abschnitt[open] > summary .abschnitt-caret {
  transform: rotate(90deg);
}

.abschnitt-titel {
  font-size: 16px;
  font-weight: var(--weight-medium);
  margin: 0;
}

/* Der Zustand steht rechts und ruhig: er wird GELESEN, nicht angeklickt. Keine
   Ampelfarbe — was Aufmerksamkeit braucht, sagt das Wort, nicht der Farbton
   (R12.3), und das eine rote Moment der Seite gehoert dem Sektions-Eyebrow
   (R1.3). */
.abschnitt-stand {
  margin-left: auto;
  font-size: 13px;
  color: var(--color-slate);
  text-align: right;
}

.abschnitt-inhalt {
  padding: 0 var(--space-5) var(--space-5) var(--space-5);
}

/* ---------------------------------------------------------------------------
   DIE DRITTE TEXTEBENE: „wie das gerechnet wird" (#957/#961, S262-Mockup)
   ---------------------------------------------------------------------------
   Drei Textebenen, drei Orte: der FAKT immer sichtbar (Zahl zuerst) · der GRUND
   eine Zeile darunter, nur wenn er nicht offensichtlich ist · die
   FUNKTIONSWEISE hinter einem Fragezeichen, NIE im Lesefluss.

   Die dritte Ebene ist die, die den Bildschirm zuwuchert. Sie beantwortet eine
   Frage, die man EINMAL hat, und stand bisher ueber jeder Sitzung — auf der
   Lead-Akte (#947/#948) wie auf dem Radar (#961) und auf /admin/release (#957).

   EIN ZEICHEN UND KEIN TITLE-ATTRIBUT. Ein `title` erscheint am Telefon gar
   nicht (R17: 375 px ist der Regelfall, nicht die Ausnahme), ist mit der
   Tastatur nicht erreichbar und laesst sich nicht markieren.

   R13.1: das Zeichen selbst ist SICHTBAR, auch zugeklappt — ein Tour-Anker
   darf auf ihm sitzen. Genau das tut `#radar-nenner`.

   ─── BLATT AM RAND STATT AUFKLAPPER IM FLUSS (Entscheid Martin, 17.09.2026) ──

   Bis hierher war die Bauform ein `details/summary`, und der Text klappte IM
   LESEFLUSS auf. Der Grund dafuer stand an dieser Stelle und BLEIBT GUELTIG:
   ein Overlay ueber einer Tabellenzeile verdeckt genau die Zahl, zu der es
   gehoert — derselbe Fehler, den #961 (2) behebt.

   Der Fluss-Aufklapper hat den ANDEREN Fehler, und der ist gemessen: auf
   `/leadgen/radar?stufe=kandidaten` mit 31 Zeilen schiebt das oberste
   Fragezeichen die letzte Tabellenzeile um 98 px (1440 px) bzw. 155 px
   (375 px) nach unten; beim Nenner-Zeichen direkt ueber der Tabelle sind es
   165 px und 248 px. Der Leser verliert die Stelle, an der er war.

   DAS BLATT LIEGT DESHALB AM RAND: unten als Leiste bei 375 px, rechts als
   schmale Spalte bei 1440 px. Es verschiebt NICHTS — es steht in der obersten
   Ebene. Kosten: 0 px Layout, nachgemessen (Verschiebung der letzten
   Tabellenzeile beim Oeffnen: 0 px bei 375 px UND bei 1440 px).

   WAS DAS BLATT NICHT LOEST, und das gehoert hierher: „ausserhalb der
   Lesespalte" stimmt fuer Fliesstext und NICHT fuer eine Tabelle, die die
   volle Breite nutzt. `.shell-content` ist hoechstens 1200 px breit und sitzt
   neben 232 px Seitenleiste — bei einem 1440 px breiten Fenster bleibt rechts
   kein freier Streifen. Gemessen auf der Radar-Liste bei 1440 px: das Blatt
   beginnt bei x = 1085, die Tabelle reicht bis 1383, und die Spalten
   „Treffer", „Zuletzt" und „Score" liegen darunter. Bei 375 px liegt dieselbe
   Ueberdeckung am unteren Rand. Ein Platz, der bei DIESER Tabelle weder
   verschiebt noch verdeckt, existiert geometrisch nicht — die Wahl heisst
   verschieben ODER verdecken. Verdecken ist das kleinere Uebel, weil der Leser
   seine Stelle behaelt und das Blatt mit Escape oder einem Klick daneben
   wieder weg ist. Auf den Fliesstext-Bildschirmen (Lead-Akte, Herkunftsabzug)
   tritt der Fall nicht auf.

   NATIVES `popover`, KEINE ZEILE JAVASCRIPT. Escape schliesst, Klick daneben
   schliesst, oberste Ebene, Tastaturfuehrung — alles vom Browser. Das ist hier
   Risikovermeidung und keine Bequemlichkeit: ein handgebautes Overlay braucht
   einen Klick-Handler, und `script-src 'self'` ohne `unsafe-inline` blockt
   jedes Inline-`onclick` LAUTLOS. Diese Falle hat im Portal viermal einzeln
   zugeschlagen (F88, AB-20, AB-22, AB-33). Reines HTML kann sie nicht machen.

   KEIN CSS-ANCHOR-POSITIONING. Ans Fragezeichen anzudocken koennte heute
   praktisch nur Chrome. Fester Platz am Rand, ueberall gleich.

   ─── DIE RICHTUNG DES @supports IST DER GANZE TRICK ─────────────────────────

   Was hier steht, ist der GRUNDZUSTAND und zugleich der RUECKFALL: der Kasten
   im Lesefluss, immer sichtbar. Erst `@supports selector(:popover-open)` macht
   daraus das Blatt am Rand.

   Die naheliegende Gegenrichtung — `@supports not selector(:popover-open)` mit
   dem Rueckfall darin — faengt den Fall NICHT ab. Ein Browser, der `selector()`
   selbst nicht kennt (Safari 16.0–16.3), wertet die Bedingung als UNBEKANNT
   aus; `not unbekannt` ist falsch, der Block greift nicht, und die Blaetter
   blieben `position: fixed` am unteren Rand stehen — sechzehn uebereinander,
   immer sichtbar. Die positive Form hat diesen Ausgang nicht: wer die Bedingung
   nicht auswerten kann, bekommt den Grundzustand.

   Der Rueckfall zeigt den Text also IMMER, statt ihn hinter dem Zeichen zu
   halten. Das ist nicht der gewollte Zustand, aber es ist kein Verlust (R21) —
   und es ist die richtige Rangfolge. Das Zeichen selbst ist dort ausgeblendet:
   ein Knopf, der nichts tut, ist schlimmer als keiner.

   Gehuetet von `test/unit/wie-blatt-am-rand.test.js`. */

/* GRUNDZUSTAND = RUECKFALL. Ohne `popover` gibt es kein Zeichen zu druecken. */
.wie {
  display: none;
}

/* Der Text steht dann UNTER der Zeile, in voller Breite und im Fluss — die
   Fassung, die bis zum 17.09.2026 der Normalfall war.

   HIER STEHT KEIN `display`, UND DAS IST DER WICHTIGSTE VERZICHT IN DIESEM
   ABSCHNITT. Ein geschlossenes `popover` blendet die Browser-Vorgabe aus
   (`[popover]:not(:popover-open) { display: none }`) — aber das ist eine
   UA-Regel, und JEDE Autorenregel schlaegt sie, ganz gleich wie unspezifisch
   sie ist. Ein `display: block` an dieser Stelle (es stand hier, solange der
   Text in einem Klapp-Tag lag und dort nichts anrichtete) macht alle sechzehn
   Blaetter dauerhaft sichtbar, fest am Bildschirmrand, ohne dass jemand
   geklickt haette. Gemessen am 17.09.2026 im Browser:
   `getComputedStyle(blatt).display` war `block` bei null offenen Blaettern.
   Ein Minus-Tag ist ohnehin `display: block` — die Zeile war ueberfluessig und
   dann schaedlich. Dieselbe Falle wie bei `hidden` weiter oben in dieser
   Datei, nur mit einem anderen Attribut. */
.wie-text {
  margin: var(--space-2) 0 var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--color-cream-3);
  border-left: 2px solid var(--color-slate-3);
  border-radius: var(--radius-input);
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--color-ink-4);
  max-width: 72ch;
}

@supports selector(:popover-open) {

  /* 44 px Tippziel (R17) ohne 44 px Flaeche im Text: der sichtbare Ring bleibt
     klein, der klickbare Bereich wird ueber das Polster des Pseudo-Elements
     aufgezogen. `appearance: none` ist Pflicht — der Traeger ist jetzt ein
     `<button>` und braeuchte sonst die Knopf-Optik des Systems. */
  .wie {
    display: inline-grid;
    place-items: center;
    position: relative;
    vertical-align: middle;
    width: 18px;
    height: 18px;
    padding: 0;
    border: 1px solid var(--color-slate-3);
    border-radius: var(--radius-pill);
    font-family: inherit;
    font-size: 11px;
    line-height: 1;
    color: var(--color-slate);
    background: none;
    cursor: pointer;
    user-select: none;
    -webkit-appearance: none;
    appearance: none;
  }

  .wie::after {
    content: '';
    position: absolute;
    inset: -13px;
  }

  .wie:hover {
    border-color: var(--color-ink);
    color: var(--color-ink);
  }

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

  /* Solange das zugehoerige Blatt offen ist, bleibt sein Zeichen markiert —
     sonst weiss der Leser bei zwei Zeichen nebeneinander nicht, welches er
     gerade liest. Das Blatt steht immer unmittelbar hinter seinem Zeichen. */
  .wie:has(+ .wie-text:popover-open) {
    border-color: var(--color-ink);
    color: var(--color-ink);
  }

  /* ─── DAS BLATT: 375 px = Leiste unten ───────────────────────────────────
     Der Grundfall ist das Telefon (R17). `inset` setzt alle vier Kanten, weil
     die Browser-Vorgabe fuer ein `popover` `inset: 0` + `margin: auto` ist —
     wer nur `bottom` setzt, bekommt das Blatt mittig zentriert. */
  .wie-text {
    position: fixed;
    inset: auto 0 0 0;
    z-index: 1;
    width: auto;
    max-width: none;
    max-height: 60vh;
    overflow-y: auto;
    margin: 0;
    padding: var(--space-4) var(--space-5) var(--space-5);
    border: 1px solid var(--color-slate-3);
    border-width: 1px 0 0;
    border-radius: var(--radius-card) var(--radius-card) 0 0;
    box-shadow: var(--shadow-md);
  }

  /* NICHT abdunkeln. Ein Schleier ueber der Seite verdeckt genau die Zahl, zu
     der das Blatt gehoert — das ist der Fehler, den dieser Umbau vermeidet. */
  .wie-text::backdrop {
    background: none;
  }

  /* ─── 1024 px und breiter: schmale Spalte rechts ─────────────────────────
     Die Lesespalte des Portals endet lange vor dem rechten Fensterrand; dort
     ist Platz, den keine Zahl braucht. 340 px sind rund 45 Zeichen — genug
     fuer einen Absatz, zu wenig, um eine zweite Lesespalte zu werden. */
  @media (min-width: 1024px) {
    .wie-text {
      inset: 0 auto auto auto;
      right: 0;
      width: min(340px, 32vw);
      max-height: 100dvh;
      padding: var(--space-6) var(--space-5);
      border-width: 0 0 0 1px;
      border-radius: 0;
    }
  }
}

.wie-text > p { margin: 0 0 var(--space-2); }
.wie-text > p:last-child { margin-bottom: 0; }

/* ---------------------------------------------------------------------------
   DER SEITENRAHMEN (S264) — vier Zeilen, davon zwei nebeneinander
   ---------------------------------------------------------------------------
   Gezeichnet von src/views/_seite.ejs. Antwort · Grund · (Aktion + Hilfe).

   AKTION UND HILFE TEILEN SICH EINE ZEILE (Martins Vorgabe, Fassung 2 des
   Vorschlags). Zwei Zeilen kosten bei 375 px rund 44 px Hoehe, und die Hoehe
   ist genau die Groesse, um die es in R45 geht. Deshalb `.seite-reihe` und
   nicht zwei Bloecke.

   KEIN ZWEITES ROT (R1.3). Die Aktion ist eine INK-Pille (.btn-secondary). Das
   eine rote Moment je Bildschirm ist das Seitenlabel (.eyebrow) — ein roter
   CTA hier waere das zweite, und zwar auf JEDEM Bildschirm mit Rahmen.

   ALLE FARBEN AUS TOKEN (R1). Kein Hex-Wert. */

.seite-rahmen {
  margin: 0 0 var(--space-6);
}

/* Die ANTWORT. Groesser als der Fliesstext, aber kleiner als die Headline im
   Seitenkopf: sie ist die Aussage, nicht der Name der Seite. */
.seite-antwort {
  margin: 0;
  font-size: 20px;
  line-height: 1.25;
  font-weight: var(--weight-medium);
  color: var(--color-ink);
  max-width: 62ch;
}

/* Der NAME VOR DER ANTWORT (Entscheid Martin, 18.09.2026). Er steht in
   DERSELBEN Zeile und in DERSELBEN Groesse — woertlich: „die verschiedenen
   Font-Sizes in einer Zeile sehen nicht gut aus: nimm dieselbe — arbeite mit
   Schriftfarbe & nutze die Grossschreibung konsistent."

   DESHALB AUSDRUECKLICH OHNE `font-size` UND OHNE `font-weight`. Beides erbt
   von `.seite-antwort`. Eine Groesse hier waere genau der Fehler, der diesen
   Umbau ausgeloest hat: vorher stand die Ueberschrift mit 40 px ueber einer
   Antwort mit 20 px, und das lauteste Element der Seite war das inhaltsleerste.

   KEIN ROT (R1.3) — der Praefix ist eine Einordnung, keine Warnung. */
.seite-antwort-praefix {
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--color-slate);
}

/* Der GRUND — eine Zeile darunter, ruhiger, aber lesbar (R12.3: --color-slate
   traegt 5,4:1 auf Cream). */
.seite-grund {
  margin: var(--space-2) 0 0;
  font-size: 14px;
  line-height: 1.45;
  color: var(--color-slate);
  max-width: 68ch;
}

.seite-reihe {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

/* Aufgeklappt nimmt die Erklaerung eine eigene Zeile in voller Breite — sonst
   quetschte sie sich neben den Knopf und waere bei 375 px zwei Woerter breit.
   Zugeklappt bleibt sie ein Ring von 18 px neben der Aktion. */
.seite-reihe > .wie[open] {
  flex-basis: 100%;
}

/* Die Aktion bricht bei 375 px um, statt zu quetschen: `.btn` setzt
   `white-space: nowrap`, und ein langer Aktionstext schoebe die Zeile sonst
   waagerecht aus dem Bild. */
@media (max-width: 480px) {
  .seite-aktion {
    white-space: normal;
    text-align: center;
  }
}

/* Die BELEGZEILE eines `beleg`-Bildschirms: seine Zusage ist nicht ein Knopf,
   sondern dass jede Zahl zu den Zeilen fuehrt, aus denen sie besteht (R45.4).
   Eine Quelle, die man nicht oeffnen kann, ist eine Behauptung.

   Bewusst eine ZEILE und kein Block: sie steht direkt unter dem Rahmen und
   darf die Hoehe, die der Rahmen spart, nicht gleich wieder aufbrauchen. Bei
   375 px bricht sie um, statt waagerecht zu schieben. */
.seite-belege {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-4);
  margin: 0 0 var(--space-6);
  font-size: 14px;
}

/* Bezeichnung und Zahl gehoeren zusammen und brechen nicht auseinander. Ohne
   diese Huelle stand bei 375 px eine Zahl neben der naechsten Bezeichnung —
   im Browser gesehen, von keinem Test gefragt. */
.seite-belege > span {
  white-space: nowrap;
}

.seite-belege .mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--color-ink);
  margin-left: var(--space-1);
}

/* ---------------------------------------------------------------------------
   Grund-Chips (#574, S238 §4.2 Weg A)

   Sechs vorgegebene Gruende beim Verwerfen und Beobachten. Die Bauform ist
   ein natives Radio-Feld IM Label: der ganze Chip ist klickbar, ein Klick
   genuegt (#576 — „es sind zu viele Klicks"), und es braucht kein JavaScript.
   Das ist nicht nur bequem: `script-src 'self'` blockt jedes Inline-onclick
   lautlos, und dieser Fehler ist im Haus bereits viermal einzeln entdeckt
   worden (F88, AB-20, AB-22, AB-33).

   Das Radio bleibt SICHTBAR. Es zu verstecken und den Zustand nur ueber die
   Flaeche zu zeigen waere huebscher und schlechter: die Auswahl ist dann nur
   noch an einer Farbe zu erkennen, und wer Farben schlecht unterscheidet,
   sieht gar nicht mehr, was gewaehlt ist.

   R1.3 / Ampel-Verbot: kein Chip traegt Rot, und keiner faerbt nach Bedeutung.
   Der ausgewaehlte Chip wechselt auf Ink — EINE Akzentfarbe, dieselbe fuer
   alle sechs. Ein Grund, der rot einfaerbt, weil er „schlimmer" ist, waere
   eine Ampel ueber einer menschlichen Entscheidung.
   --------------------------------------------------------------------------- */
.grund-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border: 1px solid var(--color-hairline);
  border-radius: 999px;
  font-size: 12.5px;
  line-height: 1.3;
  color: var(--color-slate);
  cursor: pointer;
  background: var(--color-paper);
}

.grund-chip:hover {
  border-color: var(--color-ink);
  color: var(--color-ink);
}

.grund-chip input[type="radio"] {
  width: 14px;
  height: 14px;
}

/* Der gewaehlte Chip. `:has()` faellt in aelteren Browsern still aus — dann
   bleibt das sichtbare Radio-Feld die Auskunft, und die genuegt. Deshalb
   steht der Zustand hier NICHT allein an dieser Regel. */
.grund-chip:has(input:checked) {
  border-color: var(--color-ink);
  color: var(--color-ink);
  font-weight: 500;
}
/* Ende Grund-Chips #574 — Schlussmarke fuer die Tests (#1294), nicht entfernen */

/* ── S224-E1 §9: der Lebenslauf zum Bearbeiten im Portal ──────────────────────
   Entscheid Martin 17.09.2026 nach abgenommenem Mock-up. Der Bildschirm zeigt
   den Lebenslauf, wie er gelesen wird — KEINE Mechanik: kein Klammerpaar, keine
   Feldnamen. Das Feld sieht deshalb aus wie Text und nicht wie ein Formularfeld,
   bis man es anfasst.
   Keine Farbe ausser der einen roten fuer „zu lang" (R1.3: eine rote Stelle je
   Bildschirm, und sie steht nur da, wenn eine Grenze wirklich ueberschritten ist). */
.cv-af-abschnitt {
  font-size: 11px;
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--color-slate);
  margin: var(--space-4) 0 var(--space-2);
}
.cv-af-abschnitt:first-child { margin-top: 0; }

.cv-af-zeile { margin-bottom: var(--space-3); }

/* ─── #965: die Textvorschau neben den Feldern ──────────────────────────────
   Martin, 17.09.2026: "kann daneben gleich der CV gezeigt werden wie er aussehen
   wuerde". DANEBEN nur, wo Platz ist: unter 900 px stapeln sich beide Spalten,
   sonst waere jede zu schmal zum Lesen. Die Vorschau steht dann UNTEN, weil man
   erst tippt und dann nachsieht. */
.cv-af-spalten { display: grid; gap: var(--space-4); }
@media (min-width: 900px) {
  .cv-af-spalten { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: start; }
  /* Die Vorschau bleibt stehen, waehrend man in der linken Spalte scrollt — sonst
     muesste man nach jedem Feld hochscrollen, um die Wirkung zu sehen. */
  .cv-af-vorschau { position: sticky; top: var(--sticky-unterkante, 0px); }
}

.cv-af-vorschau {
  background: var(--color-cream);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-card);
  padding: var(--space-3);
}
.cv-af-vorschau-kopf {
  font-size: 11px; font-weight: var(--weight-medium); text-transform: uppercase;
  letter-spacing: .04em; color: var(--color-slate); margin: 0 0 var(--space-2);
}
.cv-af-vorschau-kopf span { text-transform: none; letter-spacing: 0; }
.cv-af-vorschau-text {
  font-family: var(--font-family);     /* der CV-Text, nicht Quelltext */
  font-size: 13px; line-height: 1.55; color: var(--color-ink);
  white-space: pre-wrap; word-break: break-word;
  margin: 0; max-height: 60vh; overflow-y: auto;
}
.cv-af-vorschau-fuss {
  font-size: 11px; color: var(--color-slate);
  margin: var(--space-3) 0 0; padding-top: var(--space-2);
  border-top: 1px solid var(--color-hairline);
}

.cv-af-label {
  font-size: 11px;
  color: var(--color-slate);
  margin-bottom: 2px;
}
.cv-af-mehrfach { margin-left: var(--space-2); font-style: italic; }

/* Das Feld traegt im Ruhezustand nur eine Grundlinie — es soll wie der
   Lebenslauf aussehen, nicht wie ein Formular. Erst im Fokus bekommt es einen
   Rahmen und eine eigene Flaeche. */
.cv-af-feld {
  font-size: 13px;
  line-height: 1.6;
  padding: 4px 6px;
  border: 1px solid transparent;
  border-bottom-color: var(--color-hairline);
  border-radius: var(--radius-card);
  min-height: 1.6em;
  white-space: pre-wrap;
  word-break: break-word;
}
.cv-af-feld:hover { border-bottom-color: var(--color-slate); }
.cv-af-feld:focus {
  outline: none;
  border-color: var(--color-ink);
  background: var(--color-cream);
}
/* Leere Felder tragen ihren Hinweis aus dem Feldnamen — er verschwindet beim
   ersten Zeichen (S224-E1 §10: Marens Speicher ist leer, und das ist richtig
   so; der Bildschirm muss das TRAGEN, nicht als Fehler zeigen). */
.cv-af-leer::before {
  content: attr(data-hinweis);
  color: var(--color-slate);
  font-style: italic;
}
.cv-af-feld:focus.cv-af-leer::before { content: ''; }

.cv-af-fuss { min-height: 14px; }
.cv-af-zaehler { font-size: 11px; color: var(--color-slate); }
/* KEINE FARBE FUER "ZU LANG" (R1.3). Auf diesem Bildschirm ist der
   LLM-Herkunftschip bereits rot; ein zweites rotes Element waere ein Verstoss
   gegen die One-Red-Rule. Die Ueberschreitung sagt stattdessen der TEXT
   ("3 Zeichen zu lang") - er traegt die Aussage genauer als eine Farbe, und er
   ist auch dann lesbar, wenn jemand die Farbe nicht unterscheiden kann. */
.cv-af-zulang { font-weight: var(--weight-medium); }

.cv-af-leerhinweis { font-size: 13px; color: var(--color-slate); }

/* S224-E1 §9.2: „auch als Standard merken" — nur bei je-Bewerbung-Token.
   Es steht klein im Fuss, nicht als eigener Abschnitt: es ist eine Beigabe zum
   Speichern, keine zweite Handlung. */
.cv-af-standard {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: var(--space-3);
  font-size: 11px;
  color: var(--color-slate);
  cursor: pointer;
}
.cv-af-standard input { margin: 0; }

/* -----------------------------------------------------------------------------
   28. News-Konfiguration — klebende Spaltenkoepfe (#1165)
   ----------------------------------------------------------------------------- */
/* Martin, 23.09.2026, beim Durchsehen von /news/config: „in der Tabelle:
   Spaltenkoepfe soll sticky sein wenn man scrollt". Die Themen-Tabelle traegt
   zehn Spalten mit Eingabefeldern; wer in Zeile neun das Feld „Deckbegriff (en)"
   vor sich hat, sieht nicht mehr, dass es der englische ist.

   DER BEZUGSRAHMEN IST DER BEHAELTER, NICHT DIE SEITE — dieselbe Diagnose wie
   in Abschnitt 24 (Rechte-Matrix, 10.09.) und bei `.evt-kontakte-table`
   (#801/B2, 11.09.). `.table-container` traegt `overflow-x: auto`, und CSS
   macht daraus zwangslaeufig auch senkrecht einen Scroll-Behaelter (`visible`
   ist neben `auto` nicht moeglich). `position: sticky; top: 0` klebt deshalb
   NUR innerhalb dieses Kastens — und solange der Kasten keine eigene Hoehe hat,
   scrollt er senkrecht nie, also klebt nichts. Ein Blick in den Quelltext haette
   „position: sticky steht da" gemeldet; geklebt haette es nicht.

   Die zehn Spalten brauchen die WAAGERECHTE Rollbarkeit — sie ist der Grund,
   weshalb die Seite hier nicht der Anker sein kann. Also bekommt der Behaelter
   eine eigene Hoehe und rollt in sich selbst.

   GEMESSEN, nicht gelesen: `scripts/klebt-der-spaltenkopf.mjs` laedt die echte
   View mit dieser Datei in echtem Chrome, rollt den Behaelter und vergleicht die
   Bildschirmkante der Kopfzeile vorher/nachher. */
.news-config-table.table-container {
  overflow: auto;
  max-height: min(70vh, 620px);
}

/* `position: sticky` gehoert an die ZELLEN; an `thead` oder `tr` wirkt es in
   mehreren Browsern nicht. Die Flaeche muss die Zelle selbst tragen — die
   Grundregel setzt sie auf `thead`, und unter einer fixierten Zelle ohne eigene
   Flaeche scheinen die Zeilen durch. Der Strich unten kommt als `box-shadow`,
   weil `border-collapse: collapse` die Zellgrenze unter der fixierten Zelle
   verschwinden laesst. */
.news-config-table thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background-color: var(--color-ink-4);
  box-shadow: inset 0 -1px 0 var(--color-ink-3);
}

/* -----------------------------------------------------------------------------
   29. Tabellen: klebender Kopf, klebende Namensspalte (#1165, Schritt 1)
   ----------------------------------------------------------------------------- */
/* Martin, 24.09.2026, nach der Messung im laufenden Portal: der Kopf soll ueberall
   kleben, nicht nur auf /news/config. Dazu die Namensspalte beim Rechtsrollen.

   DIE MESSUNG HAT DEN WEG UMGEDREHT (berichte/tabellen-sticky-gemessen-2026-09-24.md).
   Gezaehlt wurden 99 Tabellen auf 60 Routen: KEIN einziger Kopf klebte, auch nicht
   die beiden, die es laut Quelltext taten. Der Grund ist eine einzige Zahl —
   `scrollHeight - clientHeight` ist bei ALLEN 99 Behaeltern 0. Senkrecht rollt im
   Portal nur die SEITE. Ein `top: 0`, das am Behaelter ankert, ankert damit an
   etwas, das nie rollt; die Rechte-Matrix belegt es: sie traegt `max-height` UND
   `position: sticky`, und ihr Kopf wanderte die vollen 200 px mit.

   Also ankert der Kopf hier an der SEITE — an `--sticky-unterkante-2`, der Kante
   unter Pillenleiste UND Reiterband (shell.js misst beide, #385/#894). Unter
   `--sticky-unterkante` allein laege er hinter dem Reiterband.

   DER HAKEN, UND WIE ER GELOEST IST. `.table-container` traegt `overflow-x: auto`.
   CSS macht daraus zwangslaeufig auch senkrecht einen Scroll-Behaelter (`visible`
   ist neben `auto` nicht moeglich) — und damit ist WIEDER der Behaelter der Anker
   statt der Seite. Ein Behaelter, der waagerecht nichts zu rollen hat, braucht das
   `auto` aber gar nicht: er bekommt `overflow: clip`. `clip` schneidet (die
   abgerundete Ecke bleibt), macht aber KEINEN Scrollport auf — der Kopf ankert an
   der Seite.

   OB WAAGERECHT ETWAS ZU ROLLEN IST, WIRD GEMESSEN, NICHT GERATEN. Die Messung hat
   die Faustregel „ab 8 Spalten rollt es" widerlegt: 5 von 11 Tabellen mit >= 8
   Spalten rollen bei 1440 px NICHT, waehrend eine mit 8 Spalten in einem 918 px
   schmalen Behaelter rollt. Es haengt am Inhalt und an der Behaelterbreite, und
   beides entsteht erst im Layout. `shell.js` setzt darum `data-waagerecht` je
   Behaelter (ResizeObserver + resize) — dasselbe Muster wie --sticky-unterkante.

   OHNE SKRIPT bleibt alles, wie es heute ist: kein `data-waagerecht`, also
   `overflow-x: auto` und kein klebender Kopf. Keine Verschlechterung.

   BEHAELTER MIT EIGENER HOEHE FASST DIE REGEL NICHT AN — wer `max-height` traegt,
   hat sich fuer den eigenen Scrollport entschieden (Rechte-Matrix, Kontaktliste,
   .news-config-table aus Schritt 1). shell.js leitet das aus der Rechnung ab und
   pflegt dafuer KEINE Liste.

   Gemessen mit `scripts/klebt-der-spaltenkopf.mjs --achse beide`. */
.table-container[data-waagerecht="passt"] {
  overflow: clip;
}

.table-container[data-waagerecht="passt"] .table thead th {
  position: sticky;
  /* #821: unter BEIDEN Reiterzeilen — die zweite klebt jetzt auch. */
  top: var(--sticky-unterkante-3, var(--sticky-unterkante-2, var(--sticky-unterkante, 0px)));
  /* Unter dem Reiterband (z-index 5), ueber den Zeilen. */
  z-index: 2;
  background-color: var(--color-ink-4);
  /* `border-collapse: collapse` laesst die Zellgrenze unter einer fixierten Zelle
     verschwinden — der Strich kommt deshalb als Schatten. */
  box-shadow: inset 0 -1px 0 var(--color-ink-3);
}

/* ── Die Namensspalte ──────────────────────────────────────────────────────
   Martin, 24.09.2026, woertlich: „ich erkenne IMMER nach Name." Beim Rechtsrollen
   bleibt die Namenszelle stehen; was links davon steht (Kaestchen, laufende
   Nummer, Ziehgriff), laeuft normal weg und verschwindet UNTER ihr. Das spart
   Platz gegenueber „Spalte 1 und 2 fixieren" — und 86 der 94 Rollfaelle entstehen
   erst bei 375 px, wo jeder Millimeter zaehlt.

   UEBER EINE MARKIERUNG, NICHT UEBER `:first-child`. Drei gemessene Gruende:
   auf /admin/modules steht der Name in Spalte 3 (davor Ziehgriff und Schluessel);
   im Rechnungslauf traegt „Text" die Wiedererkennung, nicht „Datum"; und in
   nachlese.ejs gibt es die Kaestchenspalte nur manchmal — eine Positionslogik
   traefe dort mal das Kaestchen, mal die Kennung.

   DIE FLAECHE IST PFLICHT, nicht Schmuck: ohne sie laeuft der Text der Nachbarzelle
   sichtbar unter der stehenden Spalte durch. `inherit` holt sie aus der Zeile,
   damit Zebra und Hover erhalten bleiben. */
.table td.zeilen-name {
  position: sticky;
  left: 0;
  z-index: 1;
  background-color: inherit;
  box-shadow: inset 0 -1px 0 var(--color-hairline), 1px 0 0 var(--color-hairline);
}

.table thead th.zeilen-name {
  position: sticky;
  left: 0;
  /* Die Ecke gehoert beiden Fixierungen — sonst schiebt sich eine darueber. */
  z-index: 3;
  /* Ticket 1190: derselbe Farbton wie der Kopf daneben (vorher ink-hdr). */
  background-color: var(--color-ink-4);
}

/* ── Wer waagerecht rollt, bekommt einen eigenen Rollbereich (#1187) ─────────
   Martin, 05.10.2026: „bei breiten Tabellen kleben Kopfzeile und Namensspalte nie
   gleichzeitig." Richtig beobachtet, und es lag am Aufbau oben: der Kopf ankert
   an der SEITE (nur bei „passt"), die Namensspalte am BEHAELTER (nur bei
   „rollt"). Eine Tabelle ist immer nur eines von beiden.

   ENTSCHEID: nur Tabellen, die waagerecht rollen, bekommen einen eigenen
   Rollbereich — `overflow: auto` UND eine Hoehe, die ins Fenster unter der
   Klebekante passt. Dann ankern Kopf (`top: 0`) und Namensspalte (`left: 0`) am
   SELBEN Behaelter und kleben beide. Normale Tabellen („passt") bleiben, wie
   sie sind: Kopf an der Seite.

   Die Hoehe hat einen zweiten Ertrag (#914, /admin/release): der waagerechte
   Rollbalken sitzt an der Unterkante des Behaelters. War der Behaelter hoeher
   als das Fenster, lag der Balken ausser Sicht — „fehlende horizontale
   Rollbalken". Jetzt steht er im Bild, sobald der Behaelter oben ankommt.

   `16rem` als Untergrenze: am Telefon kann die Klebekante hoch liegen; ein
   Rollbereich von drei Zeilen waere schlechter als keiner.

   shell.js liest die Hoehe NICHT als „eigene Hoehe" (dort `dataset.waagerecht
   !== 'rollt'`) — sonst wuerfe es die Einstufung ab, aus der sie stammt. */
.table-container[data-waagerecht="rollt"] {
  overflow: auto;
  max-height: max(16rem, calc(100vh - var(--sticky-unterkante-2, var(--sticky-unterkante, 0px)) - var(--space-4)));
}

.table-container[data-waagerecht="rollt"] .table thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background-color: var(--color-ink-4);
  box-shadow: inset 0 -1px 0 var(--color-ink-3);
}

/* Die Regel darueber ist spezifischer als `.table thead th.zeilen-name` und
   setzte deren z-index 3 auf 2 zurueck. Die Ecke muss obenauf bleiben, sonst
   schiebt sich beim Rechtsrollen der Kopf daneben ueber sie. */
.table-container[data-waagerecht="rollt"] .table thead th.zeilen-name {
  z-index: 3;
}

/* ── Im rollenden Behaelter klebt die ERSTE Spalte als Vorgabe (B3) ──────────
   Gemessen am 06.10.2026 auf /admin/llm: die zehnspaltige Tabelle mit Kopf
   „Modul" rollt waagerecht, traegt aber keine `zeilen-name`-Marke — ihre erste
   Spalte stand auf `position: static` und lief beim Rechtsrollen weg. Die Marke
   setzen heute nur sieben Views; jede andere rollende Tabelle verlor damit genau
   die Spalte, an der man die Zeile erkennt.

   DIE MARKE BLEIBT DER ERSTE WEG, die Position nur die Vorgabe dahinter. Traegt
   eine Tabelle irgendwo `.zeilen-name`, greift diese Regel NICHT (`:has`) — dann
   klebt, was die View bestimmt hat (Name in Spalte 3 auf /admin/modules, „Text"
   im Rechnungslauf). Ohne Marke ist die erste Spalte die beste Vermutung, und
   eine laufende Spalte ist die schlechtere.

   ABSCHALTEN: `data-namensspalte="aus"` am Behaelter ODER an der Tabelle — fuer
   eine Tabelle, deren erste Spalte nur ein Kaestchen oder ein Ziehgriff ist und
   die keine Namensspalte hat.

   Ohne `:has()`-Unterstuetzung faellt die ganze Regel weg — dann bleibt es beim
   Stand davor, nichts wird schlechter.

   Die Flaeche ist Pflicht und deckend: `inherit` holt sie aus der Zeile
   (`--color-paper`/`--color-cream-3`, beim Ueberfahren `--color-cream`), damit
   Zebra und Hover bleiben; der Kopf traegt `--color-ink-4` wie daneben. Keine
   neue Farbe, kein Rot. */
.table-container[data-waagerecht="rollt"]:not([data-namensspalte="aus"])
  .table:not([data-namensspalte="aus"]):not(:has(.zeilen-name))
  > :is(tbody, tfoot) > tr > :first-child {
  position: sticky;
  left: 0;
  z-index: 1;
  background-color: inherit;
  box-shadow: inset 0 -1px 0 var(--color-hairline), 1px 0 0 var(--color-hairline);
}

/* Die Ecke: klebt nach oben (Kopfregel darueber, `top: 0`) UND nach links, und
   liegt obenauf — sonst schiebt sich beim Rechtsrollen der Kopf daneben ueber
   sie, und beim Abwaertsrollen die Zellen darunter. */
.table-container[data-waagerecht="rollt"]:not([data-namensspalte="aus"])
  .table:not([data-namensspalte="aus"]):not(:has(.zeilen-name))
  > thead > tr > :first-child {
  position: sticky;
  top: 0;
  left: 0;
  z-index: 3;
  background-color: var(--color-ink-4);
  box-shadow: inset 0 -1px 0 var(--color-ink-3), 1px 0 0 var(--color-ink-3);
}

/* #914: die Freigaben-Zeile auf /admin/release oeffnet beim Klick ein Overlay mit
   allen Angaben (admin-release.js). Der Zeiger und der Tastaturfokus sagen, dass
   die Zeile bedienbar ist. */
.release-zeile { cursor: pointer; }
.release-zeile:focus-visible { outline: 2px solid var(--color-ink); outline-offset: -2px; }
/* Ende 29. Tabellen klebender Kopf — Schlussmarke fuer die Tests (#1294), nicht entfernen */
/* S272 — gefuehrter Vorgang: Kundenname, Kette, Aktions-Pillen, Feldhinweise. */

/* ───────────────────────────────────────────────────────────────────────────
   Gefuehrter Vorgang — Kundenname, Aktions-Pillen, Feldhinweise (S272)
   Tickets #1277 (Name deutlicher), #1283 (Hinweis am Feld), #1286 (Pillen)

   KEINE neuen Farben. Alles aus den vorhandenen Token; die Pille unterscheidet
   „anlegen" von „aendern" durch FUELLUNG gegen LINIE, nicht durch Farbe — eine
   Farbkodierung waere eine Ampel und ist verboten (R1.3), und sie traegt die
   Aussage nur fuer Sehende.
   ─────────────────────────────────────────────────────────────────────────── */

/* #1277 — DER KUNDENNAME IST ROT. AUSNAHME ZU R1.3, ENTSCHIEDEN VON MARTIN AM
   06.10.2026, woertlich auf die vorgelegte Frage: „BEC-Name rot mit Ausnahme".

   DAS SIND ZWEI ROTE ELEMENTE AUF DIESEM BILDSCHIRM, und das ist der Punkt, den
   die Ausnahme traegt. Das andere ist der Rueck-Link im Sektionskopf
   (`.eyebrow.section-label` aus _section_header.ejs). R1.3 nimmt seit v2.36 nur
   die Elemente der Portal-SHELL aus (layout.ejs: Navigationspunkte,
   Hinweispunkte, Primaerknoepfe der Shell-Dialoge) und nennt das Eyebrow
   ausdruecklich als „1 aus dem Bildschirm". Der Kundenname ist damit das zweite
   — nicht aus Versehen.

   VORGESCHICHTE, damit niemand es fuer einen Rueckschritt haelt: 85c372a3 hat
   den Namen BEWUSST auf ink gesetzt, mit genau dieser Begruendung (der
   Rueck-Link ist schon rot). Das war richtig, solange niemand entschieden hatte.
   Martins Ticketkommentar vom 06.10. stellte die Wahl: „rot mit R1.3-Ausnahme
   oder gross und nicht rot" — und er hat die erste genommen. Die alte Fassung
   ist also nicht falsch gewesen, sie ist ersetzt.

   WARUM ER ES WILL: auf diesem Bildschirm entscheidet der Kundenname, WAS man
   gerade tut. Dasselbe Formular legt einen Kunden an oder pflegt einen
   vorhandenen; wer den Namen uebersieht, pflegt den falschen. Das Ticket sagt es
   knapp: „der BEC ist optisch zu klein - den auf diesem Screen in rot
   darstellen und deutlciher".

   KEINE AMPEL: der Name ist rot, WEIL er der Gegenstand des Bildschirms ist,
   nicht je nach Zustand. Er ist rot beim Anlegen wie beim Pflegen; es gibt
   keinen Schwellenwert und keine zweite Farbe daneben. Ein Name, der je nach
   Lage die Farbe wechselte, waere wieder ein Verstoss (s. #368, derselbe Satz).

   EINGETRAGEN in CLAUDE.md, Abschnitt „Wichtige Verbote", neben #368 und der
   ueberfaelligen Summe im Ausgangsrechnungs-Register. Ein Eintrag dort ist eine
   Entscheidung und kein Freibrief. */
.wf-kunde {
  font-size: 28px;
  font-weight: 500;
  line-height: 1.2;
  margin: 0 0 var(--space-5) 0;
  color: var(--color-red);
}

.pillen-reihe {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.pille {
  display: inline-block;
  padding: 6px 14px;
  border-radius: 999px;
  font-size: 13px;
  line-height: 1.4;
  text-decoration: none;
  cursor: pointer;
  border: 1px solid var(--color-ink);
  background: transparent;
  color: var(--color-ink);
  font-family: inherit;
}

/* „Anlegen" ist der Weg, den man im Regelfall nimmt, und traegt deshalb die
   Fuellung. „Aendern" und „Holen" stehen daneben als Linie. */
.pille-anlegen { background: var(--color-ink); color: var(--color-paper); }
.pille-aendern,
.pille-holen   { background: transparent; }

.pille:hover  { opacity: .85; }
.pille:focus-visible { outline: 2px solid var(--color-ink); outline-offset: 2px; }

/* Der Hinweis steht direkt unter seinem Feld und ist als Nebentext gesetzt —
   er erklaert das Feld, er warnt nicht. */
.wf-feldhinweis-text {
  margin: 4px 0 0 0;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--color-slate);
  max-width: 62ch;
}

/* Die Schrittkette des gefuehrten Vorgangs (S272).

   SIE HATTE BIS ZUM 01.10.2026 GAR KEIN LAYOUT — Nummer, Titel und Stand liefen als
   Fliesstext hintereinander („1 Wer ist es? erledigt"). Aufgefallen ist das beim
   ANSEHEN, nicht in einem Test: eine Liste ohne Spalten rendert fehlerfrei.

   Keine Farbe traegt hier eine Aussage. Der Stand steht in der Schrift (blass = fertig,
   kraeftig = faellig, kursiv = entfaellt) und im Haken — eine Farbkodierung waere eine
   Ampel (R1.3) und truege die Aussage nur fuer Sehende. */

.wf-kette-stand { color: var(--color-slate); max-width: 72ch; }
.wf-kette-stand strong { color: var(--color-ink); }

/* TABELLE STATT AUFZAEHLUNG (#1303). Die vorige Fassung war ein Raster auf einer Liste;
   Titel, Zustand und Grund standen in einer Spalte hintereinander. Die Regeln unten sind
   dieselben Aussagen, nur an Zeilen einer Tabelle statt an Listenpunkten — die
   data-lage-Merkmale sind unveraendert, damit die Aussage nicht am Bauteil haengt. */
.wf-kette { max-width: 72ch; font-size: 14px; }
.wf-kette .wf-nr  { color: var(--color-slate); font-size: 12px; }
.wf-kette .wf-was a { color: var(--color-ink); text-decoration: none; }
.wf-kette .wf-was a:hover { text-decoration: underline; }
.wf-kette .wf-lage { color: var(--color-slate); font-size: 12.5px; }

/* Erledigt tritt zurueck — man hat es hinter sich. */
.wf-schritt[data-lage="fertig"] .wf-was a,
.wf-schritt[data-lage="fertig"] .wf-was span { color: var(--color-slate); }
/* Faellig tritt hervor: er ist die Antwort auf „wo bin ich". */
.wf-schritt[data-lage="jetzt"] .wf-was a { font-weight: 500; }
.wf-schritt[data-lage="jetzt"] .wf-lage { color: var(--color-ink); }
/* Entfaellt: sichtbar MIT Grund, nie weggelassen (R21). */
.wf-schritt[data-lage="uebersprungen"] .wf-was span,
.wf-schritt[data-lage="uebersprungen"] .wf-lage { font-style: italic; color: var(--color-slate); }

@media (max-width: 560px) {
  /* Auf dem Telefon traegt die Tabelle vier Spalten, und „Wie weit" ist ein bis fuenf
     Zeichen breit — die Spalte „Was jetzt" ist die, die Platz braucht. Der GRUND eines
     entfallenen Schritts ist ein ganzer Satz; er bekommt die Breite, indem die
     Nummernspalte wegfaellt. Der Zustand selbst bleibt in seiner Spalte: eine eigene
     Zeile fuer einen Haken ist verschwendet, und acht erledigte Schritte warfen in der
     vorigen Fassung acht fast leere Zeilen (bei 400 px aufgefallen). */
  .wf-kette .wf-nr { display: none; }
  .wf-kette thead th:first-child { display: none; }
  .wf-kette .wf-tun .pille { padding-left: var(--space-2); padding-right: var(--space-2); }
}
/* Ende S272 Schrittkette #1303 — Schlussmarke fuer die Tests (#1294), nicht entfernen */

/* #1092: „zuletzt: Maren · 27.09. 14:05" an der Status-Auswahl — klein, Slate,
   eigene Zeile unter der Auswahl. Der Status ist geteilt; wer ihn setzte, soll
   man sehen, ohne dass es lauter wird als der Status selbst. */
.evt-status-zuletzt { display: block; font-size: 11px; color: var(--color-slate); margin-top: 2px; }
.evt-status-zuletzt:empty { display: none; }

/* #822: „Heute faellig" in der Uebersicht — Haken links, Titel als Knopf (oeffnet
   den Aufgaben-Dialog), Rueckgaengig rechts. Tippziele 44 px (R17). Erledigt:
   durchgestrichen und Slate, bleibt stehen bis zum Neuladen (mit Rueckgaengig). */
.evt-faellig-zeile { display: flex; align-items: center; gap: var(--space-2); padding: 0 var(--space-3) 0 var(--space-2); }
.evt-faellig-haken { display: inline-flex; align-items: center; justify-content: center; min-width: 44px; min-height: 44px; cursor: pointer; }
.evt-faellig-haken input { width: 20px; height: 20px; }
.evt-faellig-titel { flex: 1; display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); min-height: 44px; padding: var(--space-2) 0; background: none; border: 0; font: inherit; text-align: left; cursor: pointer; color: var(--color-ink); }
.evt-faellig-frist { font-size: 12px; color: var(--color-slate); }
.evt-faellig-zeile.is-erledigt .evt-acc-title { text-decoration: line-through; color: var(--color-slate); }

/* #817 Kontaktliste kompakter (Martin 12.09.): Firma in der Namenszeile, kleiner;
   LinkedIn nur als kurzer Link bzw. „+ LinkedIn"; Fokus als leiser Text; „neu"
   klein in Slate (kein Rot — R1.3). */
.evt-who-name .evt-who-firma { font-size: 12.5px; color: var(--color-ink-2); font-weight: inherit; }
.evt-li-kurz { display: inline-flex; align-items: center; min-height: 44px; }
.evt-fokus-dezent { display: block; font-size: 11px; color: var(--color-slate); margin-top: 2px; }
.evt-neu-flag { margin-left: 6px; font-size: 10.5px; vertical-align: middle; }

