/* IBM Plex Sans und IBM Plex Mono, SIL Open Font License 1.1.
   Lizenztext: /assets/schrift/LICENSE.txt
   Lokal ausgeliefert — keine Google Fonts, kein CDN (MD4). */
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:400;font-display:swap;src:url(schrift/IBMPlexSans-Regular.ba711a30.woff2) format('woff2');}
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:600;font-display:swap;src:url(schrift/IBMPlexSans-SemiBold.f7804803.woff2) format('woff2');}
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:700;font-display:swap;src:url(schrift/IBMPlexSans-Bold.fa7130d8.woff2) format('woff2');}
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:400;font-display:swap;src:url(schrift/IBMPlexMono-Regular.ba204497.woff2) format('woff2');}
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:500;font-display:swap;src:url(schrift/IBMPlexMono-Medium.33faf307.woff2) format('woff2');}

/* =============================================================
   Designtokens — Dr.-Ing. Daniel Münning
   Fassung 3, 2026-08-16 · Kopfangabe berichtigt 2026-08-17

   Diese Datei ist die einzige Quelle für Farben, Größen und
   Abstände. Wer einen Wert ändern will, ändert ihn hier und
   nirgendwo sonst. Jede Farbe trägt ihren gerechneten
   Kontrastwert als Kommentar.

   Die Kommentarwerte sind keine Zusicherung, sondern eine
   Behauptung, die geprüft wird: `python3 kontrast.py` liest die
   Farben aus DIESER Datei, rechnet sie nach WCAG 2.1 nach und
   meldet jede Verletzung der drei Kontrastregeln. Bis zum
   2026-08-17 hielt das Werkzeug eigene, veraltete Kopien der
   Farben — es prüfte also etwas anderes, als ausgeliefert wurde.
   Deshalb liest es jetzt hier.
   ============================================================= */

:root {
  /* --- Flächen, Dunkelmodus (Regelfall) ------------------- */
  --bg:        #0B0F14;   /* Seitenhintergrund                 */
  --bg-2:      #111721;   /* abgesetzte Bänder                 */
  --card:      #161C25;   /* Karten, Hinweisboxen              */
  --line:      #1E2733;   /* feine Trennlinien                 */
  --line-2:    #2A3542;   /* betonte Trennlinien, Rahmen       */

  /* --- Schrift, Dunkelmodus ------------------------------- */
  --fg:        #EEF2F6;   /* 17,1 auf bg · 15,2 auf card       */
  --fg-2:      #98A5B3;   /*  7,7 auf bg ·  6,8 auf card       */
  --fg-3:      #778594;   /*  5,1 auf bg ·  4,5 auf card       */

  /* --- Akzent, Dunkelmodus -------------------------------- */
  --acc:       #E08A4B;   /*  7,2 auf bg ·  6,4 auf card       */
  --acc-hell:  #F0A96F;   /*  9,7 auf bg — Hover, Fokus        */
  --acc-tief:  #B96A2E;   /*  4,7 auf bg — Ränder, Flächen     */
  --auf-acc:   #0B0F14;   /*  7,2 auf Kupfer — Regel 1         */

  /* --- Schrift ------------------------------------------- */
  --schrift:   "IBM Plex Sans", -apple-system, BlinkMacSystemFont,
               "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono:      "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo,
               Consolas, "Liberation Mono", monospace;

  /* --- Schriftgrößen, fluid zwischen 360 px und 1200 px --- */
  --t-hero:    clamp(2.125rem, 1.05rem + 4.8vw, 3.9375rem);   /* 34 – 63 px */
  --t-titel:   clamp(1.5rem,  1.19rem + 1.4vw, 2.1875rem);    /* 24 – 35 px */
  --t-unter:   clamp(1.125rem, 1.03rem + 0.42vw, 1.375rem);   /* 18 – 22 px */
  --t-karte:   1.09375rem;                                     /* 17,5 px    */
  --t-text:    clamp(0.96875rem, 0.94rem + 0.13vw, 1.0625rem); /* 15,5 – 17 px */
  --t-klein:   0.875rem;                                       /* 14 px      */
  --t-zahl:    2rem;                                           /* 32 px      */
  --t-label:   0.65625rem;                                     /* 10,5 px    */
  --t-fuss:    clamp(0.65625rem, 0.6rem + 0.24vw, 0.75rem);    /* 10,5 – 12 px */

  /* --- Zeilenabstände ------------------------------------ */
  --z-hero:    1.04;
  --z-titel:   1.18;
  --z-text:    1.55;
  --z-eng:     1.35;

  /* --- Laufweiten ---------------------------------------- */
  --l-hero:    -0.021em;
  --l-titel:   -0.012em;
  --l-label:    0.14em;
  --l-marke:    0.055em;   /* Wortmarke, siehe WORTMARKE.md   */

  /* --- Abstände, Vielfache von 4 ------------------------- */
  --a-1:  4px;   --a-2:  8px;   --a-3: 12px;   --a-4: 16px;
  --a-5: 22px;   --a-6: 34px;   --a-7: 52px;   --a-8: 76px;   --a-9: 88px;

  /* --- Maße ---------------------------------------------- */
  --breite:      1080px;   /* maximale Inhaltsbreite          */
  --breite-text:   65ch;   /* maximale Lesezeile              */
  --rand:          30px;   /* Seitenrand, mobil --a-5         */
  --radius:         2px;   /* Karten, Buttons                 */
  --radius-gross:   3px;   /* Signet, Bildflächen             */

  /* --- Bewegung ------------------------------------------ */
  --zeit:      180ms;
  --kurve:     cubic-bezier(0.2, 0, 0.2, 1);
}

/* =============================================================
   Hellmodus — für Dokumente, Angebote, Druck.
   Wird über [data-modus="hell"] gesetzt, nicht über eine
   Medienabfrage: der Dunkelmodus ist der Regelfall der Marke,
   nicht eine Voreinstellung des Geräts.
   ============================================================= */

[data-modus="hell"] {
  --bg:        #FFFFFF;
  --bg-2:      #F6F8FA;
  --card:      #FFFFFF;
  --line:      #DDE4EB;
  --line-2:    #C6D0DA;

  --fg:        #0B0F14;   /* 19,2 auf Weiß                     */
  --fg-2:      #4A5866;   /*  7,3 auf Weiß ·  6,9 auf bg-2     */
  --fg-3:      #68727D;   /*  4,8 auf Weiß ·  4,5 auf flaeche-2
                             berichtigt 2026-08-17, siehe Regel 3 */

  --acc:       #7A4520;   /*  7,8 auf Weiß — Textakzent        */
  --acc-hell:  #5C3418;   /* 11,3 auf Weiß — Hover             */
  --acc-tief:  #E08A4B;   /*  2,7 — ausschließlich Fläche      */
  --auf-acc:   #FFFFFF;   /*  7,8 auf --acc                    */
}

/* -------------------------------------------------------------
   Regel 1 — Auf Kupferflächen steht dunkle Schrift, nie weiße.
             Weiß auf #E08A4B erreicht 2,7:1.
   Regel 2 — Im Hellmodus ist helles Kupfer keine Textfarbe.
             #E08A4B auf Weiß erreicht 2,7:1. Dort gilt #7A4520.
   Beide Regeln sind in den Tokens bereits abgebildet: --auf-acc
   und --acc wechseln mit dem Modus. Wer sie benutzt, kann den
   Fehler nicht machen.
   ------------------------------------------------------------- */

/* =============================================================
   Fassung 3 — Schärfung, 2026-08-16
   Additiv. Kein bestehender Wert wird geändert; die Tokens oben
   gelten unverändert. Alles hier dient einem einzigen Zweck:
   den Eindruck von Sorgfalt zu erzeugen, ohne mehr Elemente
   auf die Seite zu bringen.
   ============================================================= */

:root {
  /* --- Feinere Flächenstufen ------------------------------
     Sieben statt drei. Der Blauton bleibt erhalten, weil der
     Aufheller kühl ist (#AEC6E2) und nicht neutralweiß.
     Kontrast von --fg-3 in Klammern.                         */
  --flaeche-0: #0B0F14;   /* Seite            (5,1) */
  --flaeche-1: #10141A;   /* Band             (4,9) */
  --flaeche-2: #13191F;   /* Karte            (4,7) */
  --flaeche-3: #181E24;   /* Karte, gehoben   (4,5) */
  --flaeche-4: #1E242C;   /* Eingabefeld      (4,1) */
  /* --flaeche-5 am 2026-08-23 von #252C35 auf #1E252D abgesenkt.
     Der Kartenrahmen soll dunkler sein als die Flaeche daneben, nicht
     heller — dann traegt die helle Oberkante (--kante) die Tiefe
     allein. Keine Textfarbe, deshalb keine Kontrastfolge; mit
     kontrastaudit.py am 2026-08-23 gegengeprueft. */
  --flaeche-5: #1E252D;   /* Linie, betont          */
  --flaeche-6: #303842;   /* Rahmen, betont         */

  /* --- Licht und Kante ------------------------------------
     Tiefe entsteht durch Licht auf der Oberkante, nicht durch
     Schatten darunter. Das ist der Unterschied zwischen einer
     Fläche, die liegt, und einer, die gezeichnet ist.         */
  /* Am 2026-08-23 angehoben: 0,055 → 0,075 und 0,085 → 0,11.
     Zusammen mit dem dunkleren Rahmen (--flaeche-5) entsteht der
     Eindruck von Materialstaerke: Die Karte liegt auf der Flaeche,
     statt auf sie gezeichnet zu sein. Zwei Zahlen, groesste Wirkung
     je Zeichen von allem, was an diesem Tag geaendert wurde. */
  --kante:        rgba(255, 255, 255, 0.075);  /* Oberkante innen  */
  --kante-stark:  rgba(255, 255, 255, 0.11);   /* beim Überfahren  */
  --glanz-oben:   rgba(255, 255, 255, 0.10);   /* Verlaufsrahmen   */
  --glanz-unten:  rgba(255, 255, 255, 0.02);

  /* --- Lichtfeld ------------------------------------------
     Ein sehr weicher Kupferschein hinter der Kopfzeile.
     8 % im Kern. Gerechnet: Darüber fällt --fg-3 unter 4,5:1.
     Kontrast.                                                 */
  --schein: radial-gradient(60% 55% at 50% 0%,
              rgba(224, 138, 75, 0.08) 0%,
              rgba(224, 138, 75, 0.03) 40%,
              transparent 72%);

  /* --- Bewegung -------------------------------------------  */
  --zeit-schnell:  120ms;
  --zeit-normal:   200ms;
  --zeit-eintritt: 420ms;
  --kurve-aus:     cubic-bezier(0.16, 1, 0.3, 1);
}

[data-modus="hell"] {
  --flaeche-0: #FFFFFF;
  --flaeche-1: #F8FAFC;
  --flaeche-2: #F3F6F9;
  --flaeche-3: #EDF1F5;
  --flaeche-4: #E6EBF0;
  /* Hellmodus, dieselbe Absicht seitenverkehrt: Der Rahmen wird
     kraeftiger statt dunkler, die Oberkante bleibt der Traeger der
     Tiefe. Angepasst am 2026-08-23. */
  --flaeche-5: #D3DCE5;
  --flaeche-6: #C9D3DD;
  --kante:       rgba(11, 15, 20, 0.07);
  --kante-stark: rgba(11, 15, 20, 0.12);
  --glanz-oben:  rgba(11, 15, 20, 0.08);
  --glanz-unten: rgba(11, 15, 20, 0.02);
  --schein: radial-gradient(60% 55% at 50% 0%,
              rgba(122, 69, 32, 0.06) 0%,
              rgba(122, 69, 32, 0.02) 40%,
              transparent 72%);
}

/* -------------------------------------------------------------
   Regel 3 — Schrift auf Flächen.
   --fg-3 ist bis --flaeche-2 zugelassen. Ab --flaeche-3 gilt
   --fg-2. Gerechnet, nicht geschätzt — in BEIDEN Modi:

     Dunkelmodus   #778594 auf --flaeche-2 #13191F   4,69
     Hellmodus     #68727D auf --flaeche-2 #F3F6F9   4,51

   Berichtigung vom 2026-08-17. Die Regel war richtig, der
   Hellmodus hielt sie nicht ein: --fg-3 lautete dort #69737E
   und erreichte auf --flaeche-2 nur 4,45 — knapp unter der
   AA-Schwelle. In der Landingpage betraf das sechs Label auf
   Karten, im echten Browser gemessen.

   Berichtigt durch die kleinstmögliche Nachschärfung des Tons:
   #69737E → #68727D, ein Schritt je Kanal, Farbton und
   Sättigung unverändert (H 211,4° / S 0,091). Der Unterschied
   ist nicht sichtbar, der Kontrast steigt von 4,45 auf 4,51.

   Der Weg ist derselbe wie schon einmal: In Fassung 2 wurde
   --fg-3 im Dunkelmodus von #6B7885 auf #778594 nachgeschärft,
   statt eine Sonderregel einzuführen. Die Begründung von damals
   gilt weiter — Sonderregeln werden im Alltag nicht eingehalten.

   Warum es so lange unbemerkt blieb: kontrast.py hielt bis zum
   2026-08-17 eigene, veraltete Kopien der Farben und rechnete
   den Hellmodus mit einem Ton, den es gar nicht mehr gab. Das
   Werkzeug liest jetzt diese Datei und vergleicht die gemessene
   Grenze mit ERWARTETE_GRENZE.
   ------------------------------------------------------------- */

/* =============================================================
   Basis und Komponenten — Dr.-Ing. Daniel Münning
   Fassung 2, 2026-08-15
   Setzt tokens.css voraus. Keine externe Ressource, keine
   Abhängigkeit, kein Framework.
   ============================================================= */

*, *::before, *::after { box-sizing: border-box; }

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--schrift);
  font-size: var(--t-text);
  line-height: var(--z-text);
  font-feature-settings: "kern" 1, "liga" 1;
  text-rendering: optimizeLegibility;
}

/* --- Textbausteine ---------------------------------------- */

h1, h2, h3 { font-weight: 600; margin: 0; }

h1 { font-size: var(--t-hero);  line-height: var(--z-hero);  letter-spacing: var(--l-hero); }
h2 { font-size: var(--t-titel); line-height: var(--z-titel); letter-spacing: var(--l-titel); }
h3 { font-size: var(--t-karte); line-height: var(--z-eng);   letter-spacing: -0.003em; }

p { margin: 0 0 var(--a-4); max-width: var(--breite-text); }
p:last-child { margin-bottom: 0; }

a {
  color: var(--acc);
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--acc) 40%, transparent);
  transition: color var(--zeit) var(--kurve), border-color var(--zeit) var(--kurve);
}
a:hover { color: var(--acc-hell); border-bottom-color: var(--acc-hell); }

strong { font-weight: 600; color: var(--fg); }

/* Fokus wird nie entfernt — Anforderung 3 der Barrierefreiheit */
:focus-visible {
  outline: 2px solid var(--acc-hell);
  outline-offset: 2px;
  border-radius: 1px;
}

/* --- Gerüst ------------------------------------------------ */

.bahn { max-width: var(--breite); margin: 0 auto; padding: 0 var(--rand); }
@media (max-width: 640px) { .bahn { padding: 0 var(--a-5); } }

.band       { padding: var(--a-8) 0; }
.band--eng  { padding: var(--a-7) 0; }
.band--ab   { background: var(--bg-2); }
@media (max-width: 640px) { .band { padding: var(--a-7) 0; } }

/* Karten einer Reihe sind gleich hoch — der Vorgabewert `stretch`, hier
   ausgeschrieben, damit ihn niemand fuer ein Versehen haelt.

   Am 2026-08-23 war er kurzzeitig auf `align-items: start` geaendert,
   mit der Begruendung, das Loch unten in den kuerzeren Karten wirke
   unfertig. **Vom Inhaber am selben Tag am Bildschirm verworfen** und
   zurueckgebaut: Drei Karten, die auf drei Hoehen enden, lesen sich als
   Treppe, nicht als Reihe. Der Freiraum in der kuerzeren Karte ist der
   geringere Preis; er wird ausdruecklich in Kauf genommen.

   Wer das wieder aendern will, sehe zuerst am Bildschirm nach — beide
   Fassungen bestehen jeden Pruflauf, der Unterschied ist allein
   Gestaltung (Bauregel 9). */
.raster { display: grid; gap: var(--a-6); align-items: stretch; }
.raster--2 { grid-template-columns: repeat(2, 1fr); }
.raster--3 { grid-template-columns: repeat(3, 1fr); }
.raster--4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 860px) { .raster--3, .raster--4 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .raster--2, .raster--3, .raster--4 { grid-template-columns: 1fr; } }

/* --- Auszeichnungen ---------------------------------------- */

.label {
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: var(--l-label);
  text-transform: uppercase;
  color: var(--fg-3);
  margin: 0 0 var(--a-3);
  display: block;
}

.fuss  { font-size: var(--t-fuss); line-height: 1.45; color: var(--fg-3); }
.dick  { color: var(--fg-2); }
.zahl  { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* --- Kennzahlblock ----------------------------------------
   Eine Zahl ohne Quelle wird nicht gesetzt. Die Quellenzeile
   ist deshalb Teil der Komponente, nicht optional.
   ---------------------------------------------------------- */

/* Die Kachel trug bis zum 2026-08-23 nur eine Linie oben. Gedacht war
   ein Band mit drei Spalten; gelesen wurde es als drei Kaesten, denen
   der Rahmen unten fehlt — Befund des Inhabers am Bildschirm.
   Entscheidung vom 2026-08-23: voller Rahmen. Sie traegt dafuer
   dieselbe Form wie `.karte` und steht deshalb in deren Regel, statt
   die Werte ein zweites Mal zu fuehren. */
.kennzahl__wert {
  font-family: var(--mono);
  font-size: var(--t-zahl);
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  color: var(--acc);
  display: block;
}
.kennzahl__was    { font-size: var(--t-klein); color: var(--fg-2); margin-top: var(--a-2); display: block; }
.kennzahl__quelle { font-size: var(--t-fuss); color: var(--fg-3); margin-top: var(--a-2); display: block; }

/* --- Karte ------------------------------------------------- */

.karte, .kennzahl {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--a-5);
}
.karte h3 { margin-bottom: var(--a-3); }
.karte p  { color: var(--fg-2); }

/* --- Hinweisbox -------------------------------------------- */

/* Volle Bahnbreite, wie .karte--breit und .raster. Am 2026-08-23 trug
   der Kasten kurzzeitig `max-width: 70ch`, damit er gemeinsam mit
   seinem Text endet; **vom Inhaber am selben Tag verworfen**, zusammen
   mit den beiden gleichartigen Faellen. In diesem Projekt geht
   buendige Kante vor kurzer Zeile.

   Die Lesezeile bleibt trotzdem kurz: jedes `p` ist auf
   `--breite-text` gedeckelt (siehe oben). Der Freiraum liegt innen
   rechts und ist gewollt. */
.hinweis {
  background: var(--card);
  border-left: 2px solid var(--acc);
  padding: var(--a-4) var(--a-5);
  color: var(--fg-2);
}
.hinweis strong { color: var(--fg); }

/* --- Buttons ------------------------------------------------ */

.knopf {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;                /* Anforderung 4: 44 x 44 px */
  padding: var(--a-3) var(--a-5);
  font: inherit;
  font-weight: 500;
  font-size: var(--t-klein);
  letter-spacing: 0.01em;
  border: 1px solid var(--acc);
  border-radius: var(--radius);
  background: transparent;
  color: var(--acc);
  cursor: pointer;
  transition: background var(--zeit) var(--kurve), color var(--zeit) var(--kurve);
}
.knopf:hover  { background: var(--acc); color: var(--auf-acc); }   /* Regel 1 */
.knopf--stumm { border-color: var(--line-2); color: var(--fg-2); }
.knopf--stumm:hover { background: var(--line); color: var(--fg); }

/* --- Listen -------------------------------------------------- */

.liste { list-style: none; padding: 0; margin: 0; }
.liste li {
  padding: var(--a-3) 0;
  border-bottom: 1px solid var(--line);
  color: var(--fg-2);
  display: flex;
  gap: var(--a-3);
}
.liste li::before { content: "—"; color: var(--acc); flex: none; }

/* Negativliste: was ausdrücklich nicht angeboten wird.
   Farbe ist nie der einzige Träger — das Zeichen trägt mit. */
.liste--nein li::before { content: "\00D7"; color: var(--fg-3); }

/* --- Tabelle -------------------------------------------------- */

.tabelle { width: 100%; border-collapse: collapse; font-size: var(--t-klein); }
.tabelle th {
  text-align: left;
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: var(--l-label);
  text-transform: uppercase;
  color: var(--fg-3);
  padding: var(--a-2) var(--a-3);
  border-bottom: 1px solid var(--line-2);
}
.tabelle td {
  padding: var(--a-3);
  border-bottom: 1px solid var(--line);
  color: var(--fg-2);
  vertical-align: top;
}
.tabelle td:first-child { color: var(--fg); }
.tabelle .zahl { text-align: right; }

/* --- Formular -------------------------------------------------- */

.feld { display: block; margin-bottom: var(--a-4); }
.feld > span {
  display: block;
  font-size: var(--t-klein);
  color: var(--fg-2);
  margin-bottom: var(--a-2);
}
.feld input, .feld textarea, .feld select {
  width: 100%;
  min-height: 44px;
  padding: var(--a-3);
  font: inherit;
  font-size: var(--t-klein);
  color: var(--fg);
  background: var(--bg-2);
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
}
.feld textarea { min-height: 120px; resize: vertical; }
.feld input::placeholder, .feld textarea::placeholder { color: var(--fg-3); }

/* --- Trennung -------------------------------------------------- */

.strich { height: 1px; background: var(--line); border: 0; margin: var(--a-7) 0; }

/* --- Wortmarke im Seitenkopf ------------------------------------ */

.marke     { display: inline-flex; align-items: baseline; gap: 0.06em; text-decoration: none; border: 0; }
.marke__wort {
  font-weight: 700;
  letter-spacing: var(--l-marke);
  color: var(--fg);
  font-size: 1.25rem;
  line-height: 1;
}
.marke__punkt { color: var(--acc); font-weight: 700; font-size: 1.25rem; line-height: 1; }
.marke:hover .marke__wort { color: var(--fg); }

/* Hinweis: Im Seitenkopf der fertigen Website wird die SVG-Datei
   wortmarke_primaer.svg eingesetzt, nicht dieser Textnachbau.
   Der Nachbau existiert nur für Vorlagen, in denen kein Bild
   möglich ist — etwa in einer E-Mail-Signatur. */

/* --- Bewegung ----------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* =============================================================
   Fassung 3 — Schärfung, 2026-08-16, alle neun übernommen.
   Setzt die Tokens der Fassung 3 aus tokens.css voraus.
   ============================================================= */

/* --- 05 Korn ------------------------------------------------
   Löst Streifen in weichen Verläufen auf und gibt dunklen Flächen
   Material. Kein Gestaltungselement, sondern Dithering: Gemessen am
   2026-09-03 über den Hero verändert es 94,7 % der Pixel um im Mittel
   eine Helligkeitsstufe von 255. Einzeln unsichtbar — es verhindert,
   dass ein Verlauf auf 8-Bit-Bildschirmen in Bänder zerfällt.

   **Eigene Datei, keine data-URI — berichtigt am 2026-09-03.** Bis
   dahin stand die SVG hier als `data:`-URI im Stilblatt, und das war
   auf dem Server wirkungslos: `.htaccess` setzt `img-src 'self'`, und
   `data:` ist nicht `self`. Chrome hat die Ebene auf `/` und `/en/`
   blockiert und es zweimal in der Konsole gemeldet. **Gemerkt hat es
   niemand**, weil `technikpruefung.py` über einen eigenen kleinen
   Server ausliefert, der die `.htaccess` nicht kennt — das Werkzeug maß
   eine Seite ohne CSP, der Server lieferte eine mit. Derselbe Fehler
   wie in Bauregel 7, eine Ebene höher: nicht eine veraltete Kopie der
   Werte, sondern eine veraltete Kopie der Auslieferungsbedingungen.

   Die Quelle liegt in `02_DESIGN/assets/korn.svg` und wird von
   `03_CODE/bauen.py` nach `htdocs/assets/` kopiert. Der Pfad ist
   wurzelabsolut und trägt bewusst keine Prüfsumme: Die `.htaccess`
   gibt namenlosen SVG einen Tag Gültigkeit, `immutable` gilt nur für
   Namen mit Prüfsumme. Die Entwurfsdateien in `02_DESIGN/` finden
   dieselbe Datei, weil ihre Serverwurzel `02_DESIGN` ist.

   Nicht zurück auf `data:` ausweichen und dafür `img-src 'self' data:`
   setzen: Die harte CSP ist bei dieser Positionierung ein Argument,
   kein Formalismus.                                            */
:root {
  --korn: url("/assets/korn.svg");
}
.korn { position: relative; }
.korn::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  opacity: .28; mix-blend-mode: overlay;
  background-image: var(--korn); background-size: 160px 160px;
}

/* --- 04 Lichtfeld ------------------------------------------ */
.schein { position: relative; overflow: hidden; }
.schein::before {
  content: ""; position: absolute; inset: -30% -10% auto -10%; height: 200%;
  background: var(--schein); pointer-events: none;
}
.schein > * { position: relative; }

/* --- 02 Kantenlicht ---------------------------------------- */
.karte, .kennzahl {
  background: var(--flaeche-2);
  border-color: var(--flaeche-5);
  box-shadow: inset 0 1px 0 0 var(--kante);
}
.hinweis {
  background: var(--flaeche-2);
  box-shadow: inset 0 1px 0 0 var(--kante);
}

/* --- 03 Verlaufsrahmen — nur für hervorgehobene Karten ------ */
.karte--gehoben {
  position: relative; background: var(--flaeche-3); border: 0;
  box-shadow: inset 0 1px 0 0 var(--kante);
}
.karte--gehoben::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  pointer-events: none; padding: 1px;
  background: linear-gradient(180deg, var(--glanz-oben), var(--glanz-unten));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
}

/* --- 06 Typografische Genauigkeit --------------------------- */
h1 { letter-spacing: -0.026em; text-wrap: balance; }
h2 { letter-spacing: -0.018em; text-wrap: balance; }
h3 { text-wrap: balance; }
.zahl, .tabelle .zahl, .kennzahl__wert {
  font-variant-numeric: tabular-nums lining-nums;
}
p { text-wrap: pretty; }   /* verhindert einzelne Wörter auf der letzten Zeile */

/* --- 08 Zustände -------------------------------------------- */
.knopf {
  background: var(--flaeche-3);
  border-color: var(--flaeche-6);
  color: var(--fg);
  box-shadow: inset 0 1px 0 0 var(--kante);
  transition: background var(--zeit-schnell) var(--kurve-aus),
              border-color var(--zeit-schnell) var(--kurve-aus),
              box-shadow var(--zeit-schnell) var(--kurve-aus),
              color var(--zeit-schnell) var(--kurve-aus);
}
.knopf:hover {
  background: var(--flaeche-4);
  border-color: var(--acc-tief);
  box-shadow: inset 0 1px 0 0 var(--kante-stark);
}
.knopf--acc { background: transparent; border-color: var(--acc-tief); color: var(--acc); }
.knopf--acc:hover { background: var(--acc); border-color: var(--acc); color: var(--auf-acc); }
.knopf--stumm { background: transparent; border-color: var(--flaeche-5); color: var(--fg-2); }
.knopf--stumm:hover { background: var(--flaeche-3); border-color: var(--flaeche-6); color: var(--fg); }

.feld input, .feld textarea, .feld select {
  background: var(--flaeche-4);
  border-color: var(--flaeche-6);
  box-shadow: inset 0 1px 0 0 var(--kante);
  transition: border-color var(--zeit-schnell) var(--kurve-aus);
}
.feld input:hover, .feld textarea:hover { border-color: var(--flaeche-6); }

/* --- 07 Eintritt beim Scrollen ------------------------------
   Der Anfangszustand gilt nur, wenn JavaScript läuft. Ohne
   JavaScript ist alles sofort sichtbar — Inhalt darf niemals an
   einer Animation hängen.                                      */
.js .eintritt > * {
  opacity: 0; transform: translateY(8px);
  transition: opacity var(--zeit-eintritt) var(--kurve-aus),
              transform var(--zeit-eintritt) var(--kurve-aus);
}
.js .eintritt.sichtbar > * { opacity: 1; transform: none; }
.js .eintritt.sichtbar > *:nth-child(2) { transition-delay: 60ms; }
.js .eintritt.sichtbar > *:nth-child(3) { transition-delay: 120ms; }
.js .eintritt.sichtbar > *:nth-child(4) { transition-delay: 180ms; }
.js .eintritt.sichtbar > *:nth-child(n+5) { transition-delay: 220ms; }

@media (prefers-reduced-motion: reduce) {
  .js .eintritt > * { opacity: 1 !important; transform: none !important; }
}

/* --- 09 Bildmotiv ------------------------------------------- */
.motiv { display: block; width: 100%; height: 100%; }
.motivfeld { position: absolute; inset: 0; opacity: .55; pointer-events: none; }

/* --- 10 Leuchtkante ------------------------------------------
   Ein Licht, das dem Zeiger folgt und die Kante dort aufhellt,
   wo er steht. Der Mechanismus ist allgemein verbreitet; neu ist
   hier nur, worauf er angewendet wird — auf die eigenen Karten,
   in der eigenen Akzentfarbe.

   Wichtig: Die Kante ist ein zusätzlicher 1-px-Rahmen über dem
   vorhandenen, kein Ersatz. Fällt das Skript aus, sieht die
   Karte aus wie vorher. Bei Geräten ohne feinen Zeiger und bei
   prefers-reduced-motion entfällt der Effekt vollständig.      */

.leucht { position: relative; }
.leucht::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  padding: 1px; pointer-events: none; opacity: 0;
  /* Radius am 2026-08-23 von 230 auf 380 px erhoeht, die Stufen
     entsprechend gestreckt. 230 px wirkten wie ein Suchscheinwerfer,
     der ueber die Karte faehrt; 380 px wie eine grosse Lichtquelle im
     Raum, die die Kante streift. Der Unterschied ist nicht die
     Helligkeit, sondern die Groesse — davon haengt ab, ob es teuer
     oder nach Effekt aussieht. */
  background: radial-gradient(380px circle at var(--mx, 50%) var(--my, -30%),
              var(--acc) 0%,
              color-mix(in srgb, var(--acc) 55%, transparent) 26%,
              rgba(255, 255, 255, 0.18) 48%,
              transparent 76%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  transition: opacity var(--zeit-normal) var(--kurve-aus);
}
/* Ein zweiter, sehr schwacher Schein auf der Fläche selbst.
   Am 2026-08-23 von 9 auf 13 % angehoben und von 300 auf 420 px
   verbreitert. **Die Obergrenze bleibt 15 %** — darüber wird die
   Karte fleckig statt beleuchtet. Der Wert ist am Bildschirm
   ermittelt, nicht gerechnet; wer ihn erhöht, sehe selbst nach. */
.leucht::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  pointer-events: none; opacity: 0;
  background: radial-gradient(420px circle at var(--mx, 50%) var(--my, -30%),
              color-mix(in srgb, var(--acc) 13%, transparent) 0%, transparent 66%);
  transition: opacity var(--zeit-normal) var(--kurve-aus);
}
.leucht > * { position: relative; }

@media (hover: hover) and (pointer: fine) {
  .leucht:hover::after,
  .leucht:hover::before { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .leucht::after, .leucht::before { display: none; }
}

/* =============================================================
   Gerüst-CSS — ergänzt tokens.css und basis.css

   Enthält ausschließlich, was das Seitengerüst braucht: Kopf,
   Fuß, Sprungmarke, Rechtsseiten und die Darstellung leerer
   Felder. Keine Farbwerte, keine Größen — alles kommt aus
   tokens.css (Bauregel 1).

   Diese Datei wird nicht ausgeliefert. Sie wird von bauen.py mit
   schriften.css, tokens.css und basis.css zu einer Datei
   zusammengefasst und unter einem Namen mit Inhaltsprüfsumme
   abgelegt. Deshalb darf sie mit langem Cache ausgeliefert
   werden: Ändert sich der Inhalt, ändert sich der Dateiname.
   ============================================================= */

/* --- Sprungmarke zum Inhalt -------------------------------- */

.sprung {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: var(--a-3) var(--a-4);
  background: var(--card);
  color: var(--fg);
  border: 1px solid var(--acc-tief);
}

.sprung:focus { left: var(--rand); top: var(--a-3); }

/* --- Seitenkopf --------------------------------------------
   Übernommen aus dem Gestaltungsentwurf (02_DESIGN/landingpage.py),
   ohne die Entwurfsmarkierungen.                              */

.kopf {
  position: sticky;
  top: 0;
  z-index: 20;
  border-bottom: 1px solid var(--line);
  background: color-mix(in srgb, var(--bg) 90%, transparent);
  backdrop-filter: blur(8px);
}

.kopf .bahn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--a-4);
  padding: var(--a-4) var(--rand);
}

/* Die Wortmarke wechselt mit dem Modus, statt Farben zu überschreiben:
   die primäre Fassung bringt ihre dunkle Fläche als Vektorpfad mit.
   Siehe UEBERGABE_2026-08-16.md §3.4.                          */

.marke-svg { display: block; width: auto; }
/* dasselbe Tippziel-Mindestmaß wie für die Navigationslinks, siehe unten */
.marke { display: inline-flex; align-items: center; min-height: 24px; }
.nur-dunkel { display: block; }
.nur-hell   { display: none; }
[data-modus="hell"] .nur-dunkel { display: none; }
[data-modus="hell"] .nur-hell   { display: block; }

/* flex-wrap seit 2026-08-18: Ohne Umbruch war die Kopfzeile bei jeder
   Breite 546 px breit und schob die ganze Seite seitwärts — auf jedem
   Telefon, auf allen vier Seiten. Gefunden von 02_DESIGN/ansichten.py.
   Eigener row-gap, damit umgebrochene Zeilen nicht aneinanderkleben. */
.nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--a-3) var(--a-5);
  align-items: center;
}

/* min-height 24 px: WCAG 2.2, Erfolgskriterium 2.5.8 (Zielgröße,
   Minimum). Die Zeilenhöhe allein ergab 22 px. inline-flex, damit die
   zwei Pixel als Tippfläche zählen und nicht nur als Abstand. */
.nav a {
  font-size: var(--t-klein);
  color: var(--fg-2);
  border: 0;
  display: inline-flex;
  align-items: center;
  min-height: 24px;
}
.nav a:hover { color: var(--fg); }
.nav a[aria-current="page"] { color: var(--fg); }

.modusknopf {
  font-family: var(--mono);
  font-size: var(--t-fuss);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-3);
  background: transparent;
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  padding: var(--a-2) var(--a-3);
  cursor: pointer;
}

.modusknopf:hover { color: var(--fg-2); border-color: var(--flaeche-6); }

@media (max-width: 720px) { .nav a:not(.nav__recht) { display: none; } }

/* --- Seitenfuß ---------------------------------------------- */

.seitenfuss {
  border-top: 1px solid var(--line);
  background: var(--flaeche-1);
  padding: var(--a-7) 0 var(--a-6);
  margin-top: var(--a-8);
}

.seitenfuss .bahn > * + * { margin-top: var(--a-4); }
.seitenfuss .nav { flex-wrap: wrap; }

/* --- Standhinweis -------------------------------------------
   Solange nichts freigegeben ist, trägt jede Seite sichtbar,
   dass sie ein Gerüst ist. Der Balken verschwindet erst, wenn
   bauen.py mit --stand produktion läuft.

   @ungenutzt-erwartet-bei produktion standhinweis
   Genau deshalb: Im Produktivbau gibt es den Balken nicht, und
   das ist kein Verlust, sondern der Zweck des Schalters. Im
   Gerüstbau muss er dagegen erscheinen — fehlt er dort, ist es
   sehr wohl ein Befund, und die Prüfung meldet ihn weiterhin.

   Eingeführt am 2026-09-20 mit dem Probelauf des Go-live-Gates.
   Vorher brach `paket.py --stand produktion` ab: Die Prüfung
   meldete die Klasse als verlorene Gestaltung, es entstand kein
   Paket, und damit gab es nichts, was haette online gehen
   koennen. Eine Markierung ohne Stand loeste es nicht — sie
   waere im Geruestbau als "veraltete Ausnahme" angeschlagen.  */

.standhinweis {
  /* max-width zurücknehmen: basis.css begrenzt jedes p auf die
     Lesezeile. Für einen Balken über die volle Breite ist das falsch. */
  max-width: none;
  margin: 0;
  background: var(--acc);
  color: var(--auf-acc);          /* Regel 1: dunkle Schrift auf Kupfer */
  font-family: var(--mono);
  font-size: var(--t-fuss);
  letter-spacing: 0.06em;
  text-align: center;
  padding: var(--a-3) var(--a-4);
}

/* --- Sprachfassungen -----------------------------------------
   Neu am 2026-08-25. Die englische Fassung trägt bis auf Weiteres
   den deutschen Wortlaut als Platzhalter — Entscheidung des
   Inhabers: erst sehen, ob die Seite trägt, dann übersetzen.

   Kursiv, sonst nichts. Kein Rahmen, keine Karte, keine zweite
   Farbe: Das Layout soll sich zeigen, wie es später aussieht, und
   jede zusätzliche Auszeichnung würde genau das verfälschen.
   Die Schrägstellung genügt, um auf einen Blick zu sehen, was noch
   deutsch ist — und sie verschwindet Feld für Feld mit jeder
   Übersetzung, ohne dass hier etwas zu ändern wäre.              */

/* @ungenutzt-erwartet platzhalter
   Seit dem 2026-09-02 trägt kein Feld mehr den Status PLATZHALTER:
   alle 73 Startseiten- und 16 Datenschutzfelder liegen englisch vor.
   Die Regel bleibt, weil der Status bleibt — jedes neue deutsche Feld
   ist bis zu seiner Übersetzung wieder ein Platzhalter, und dann muss
   die Schrägstellung da sein, ohne dass jemand daran denkt.
   Kommt ein Platzhalter zurück, meldet bauen.py diese Markierung als
   veraltet; dann gehört sie weg.                                   */
.platzhalter { font-style: italic; }

/* Der Hinweisbalken der englischen Fassung. Bewusst dieselbe Form
   wie .standhinweis, aber ruhiger: Er sagt nicht "nicht
   veröffentlichen", sondern "hier steht noch Deutsch". Anders als
   der Gerüstbalken verschwindet er nicht mit --stand produktion --
   er hängt daran, ob tatsächlich noch ein Platzhalter auf der
   Seite steht.

   --fg-2 auf --flaeche-2: Regel 3 ist nicht berührt, --fg-3 wird
   hier nicht verwendet.

   @ungenutzt-erwartet sprachhinweis
   Der Balken hängt daran, ob noch ein Platzhalter auf der Seite
   steht. Seit dem 2026-09-02 steht keiner mehr, also erscheint er
   nicht — genau wie vorgesehen. Er kommt von selbst zurück, sobald
   ein unübersetztes Feld hinzukommt.                              */
.sprachhinweis {
  max-width: none;
  margin: 0;
  background: var(--flaeche-2);
  color: var(--fg-2);
  border-bottom: 1px solid var(--line);
  font-family: var(--mono);
  font-size: var(--t-fuss);
  letter-spacing: 0.04em;
  text-align: center;
  padding: var(--a-3) var(--a-4);
}

/* --- Sprachschalter ------------------------------------------
   Neu gefasst am 2026-08-25. Vorher ein einzelnes Wort als
   Textlink zwischen den Abschnittsmarken -- also in der Gestalt
   eines Inhaltsverweises. Ein Bedienelement, das aussieht wie ein
   Inhalt, ist falsch einsortiert; der Modusknopf daneben macht es
   richtig.

   Der Schalter uebernimmt deshalb dessen Formensprache: Rahmen,
   Radius, Mono, Versalien, dieselbe Schriftgroesse. Die beiden
   stehen als Paar rechts in der Kopfzeile und sind damit als
   Bedienung erkennbar, bevor man sie liest.

   Beide Kuerzel sind sichtbar -- man sieht, wo man ist UND wohin
   es geht. Reihenfolge fest DE, EN; sie folgt nicht der aktuellen
   Sprache.                                                       */

.sprachwahl {
  display: inline-flex;
  align-items: center;
  gap: var(--a-2);
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  padding: var(--a-2) var(--a-3);
  font-family: var(--mono);
  font-size: var(--t-fuss);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* Die aktive Sprache traegt volle Textfarbe und darunter eine
   kupferne Marke; die andere die zurueckgenommene Textfarbe.
   --fg-3 auf der Kopfflaeche ist nach Regel 3 zugelassen: Der Kopf
   liegt auf --bg, nicht ueber --flaeche-2.

   **Kupfer als Unterstrich, nicht als Schriftfarbe** -- Entscheidung
   des Inhabers vom 2026-08-25, nachdem er zunaechst die Schrift selbst
   kupfern wollte. Drei Gruende sprachen dagegen, und alle drei bleiben
   so umgangen:

   1. Kontrastregel 2 verbietet Kupfer im Hellmodus als Textfarbe.
      Als Flaeche ist es zugelassen -- hier ist es Flaeche.
   2. Diese Seite markiert "hier bin ich" bereits mit voller
      Textfarbe: `.nav a[aria-current="page"]` weiter oben. Zwei
      Zeichen fuer dieselbe Aussage waeren eines zu viel.
   3. Kupfer ist hier die Farbe der **Handlung** -- der einzige
      kupferne Knopf ist "Erstgespraech anfragen". Das aktive
      Segment ist ausgerechnet das einzige, das man nicht anklicken
      kann; Handlungsfarbe darauf laedt zum Klicken ein, wo nichts
      geschieht.

   Der Unterstrich sagt dasselbe, ohne eines davon zu brechen.       */
.sprachwahl__aktiv {
  color: var(--fg);
  border-bottom: 2px solid var(--acc);
  padding-bottom: 1px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
}

/* Tippziel: 24 x 24 px nach WCAG 2.2 SC 2.5.8. Zwei Zeichen in
   Mono bei --t-fuss ergeben rund 20 px Breite -- ohne min-width
   waere der Schalter auf dem Telefon zu klein, und ansichten.py
   meldet das zu Recht. */
.sprachwahl__ziel {
  color: var(--fg-3);
  border-bottom: 0;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  min-height: 24px;
}

.sprachwahl__ziel:hover { color: var(--fg); }

/* Haarlinie als Trenner. Sie ersetzt seit dem 2026-08-25 den
   kupfernen Punkt der Wortmarke, der hier kurz stand.

   Grund fuer den Rueckbau: Der Punkt ist das Signaturelement der
   Marke. Je oefter er auftaucht, desto weniger sagt er -- als
   Trenner in einem Bedienelement arbeitet er unter Wert. Die Linie
   trennt genauso gut und behauptet nichts.

   Sie traegt keine Bedeutung, die nicht schon in der Anordnung
   steckt; im Markup ist sie aria-hidden.                          */
.sprachwahl__strich {
  width: 1px;
  height: 12px;
  background: var(--line-2);
  flex: 0 0 auto;
}

/* Auf schmalen Schirmen bleibt der Schalter stehen -- dieselbe
   Ausnahme wie beim Kontaktknopf. Eine zweisprachige Seite, deren
   Umschalter auf dem Telefon verschwindet, ist einsprachig.
   `.nav a.sprachwahl__ziel` und nicht `.nav .sprachwahl__ziel`:
   Die Regel weiter oben, die Textlinks ausblendet, hat die
   Spezifitaet 0,2,1 -- eine Klasse weniger reicht nicht.         */
@media (max-width: 720px) { .nav a.sprachwahl__ziel { display: inline-flex; } }

/* --- Leere Felder --------------------------------------------
   Ein leeres Feld muss man sehen. Sonst geht die Lücke online.

   @ungenutzt-erwartet leerfeld
   @ungenutzt-erwartet leerfeld__kopf
   @ungenutzt-erwartet leerfeld__name
   @ungenutzt-erwartet leerfeld__status
   @ungenutzt-erwartet leerfeld__was
   Seit dem 2026-10-03 steht kein Feld mehr auf OFFEN — die
   Telefonnummer war das letzte. Die Regeln bleiben, weil jedes
   neue Feld als OFFEN beginnt und dann sofort als Lücke zu
   sehen sein muss. */

.feldliste { margin: 0; padding: 0; list-style: none; }
.feldliste > li + li { margin-top: var(--a-4); }

.leerfeld {
  border: 1px dashed var(--line-2);
  border-left: 2px solid var(--acc-tief);
  border-radius: var(--radius);

  background: var(--flaeche-2);
  padding: var(--a-4);

  /* Ein leeres Feld steht auch in einem h1. Ohne diese Rücknahme
     erbt es Hero-Größe, Hero-Zeilenhöhe und die negative Laufweite
     von h1 — und wird unleserlich. */
  font-family: var(--schrift);
  font-size: var(--t-text);
  font-weight: 400;
  line-height: var(--z-text);
  letter-spacing: normal;
  text-wrap: wrap;
}

.leerfeld__kopf {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--a-3);
  flex-wrap: wrap;
}

.leerfeld__name {
  font-family: var(--mono);
  font-size: var(--t-klein);
  color: var(--fg);
}

.leerfeld__status {
  font-family: var(--mono);
  font-size: var(--t-fuss);
  letter-spacing: 0.08em;
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  padding: 1px 6px;
  color: var(--fg-3);
  white-space: nowrap;
}

/* @ungenutzt-erwartet leerfeld__status--blockierend
   Zustandsklasse: erscheint nur, solange ein Feld auf BLOCKIEREND
   steht. Seit dem 2026-08-20 steht keines mehr so — die Regel bleibt,
   weil der Status jederzeit wiederkommen kann und ein blockierendes
   Feld dann sofort als solches zu sehen sein muss. */
.leerfeld__status--blockierend {
  color: var(--auf-acc);
  background: var(--acc);
  border-color: var(--acc);
}

.leerfeld__was {
  font-size: var(--t-klein);
  color: var(--fg-2);
  margin: var(--a-3) 0 0;
  max-width: var(--breite-text);
}

/* `.leerfeld__herkunft` und `.entwurf__herkunft` sind am
   2026-09-01 ENTFALLEN. Beide setzten interne Arbeitsangaben
   auf die Seite -- Dateipfade, Markdown-Sternchen,
   ASCII-Ersatzschreibung. Siehe rahmen.py, leerfeld().
   Die Kontrastregel dazu (--fg-3 auf --flaeche-2) ist damit
   gegenstandslos; sie galt nur fuer diese zwei Zeilen. */
/* --- Entwurfsfelder ------------------------------------------
   Ein Feld mit Wortlaut, das nicht freigegeben ist. Es muss
   lesbar sein wie fertiger Text und trotzdem auf den ersten
   Blick als Entwurf erkennbar — deshalb ruhige Fläche, aber ein
   Kupferschild in der Kopfzeile. Ein Entwurf, den man für
   fertig hält, ist gefährlicher als eine leere Lücke.
   Regel 3: --fg-3 ist bis --flaeche-2 zugelassen; die Karte
   steht auf --flaeche-1, also eine Stufe darunter.

   @ungenutzt-erwartet entwurf
   @ungenutzt-erwartet entwurf__kopf
   @ungenutzt-erwartet entwurf__name
   @ungenutzt-erwartet entwurf__status
   @ungenutzt-erwartet entwurf__text
   Seit dem 2026-09-02 steht kein Feld mehr auf ENTWURF: die
   deutschen Fassungen sind am 2026-09-01 freigegeben worden, die
   englischen am 2026-09-02 als freigegeben angenommen. Die
   Entwurfskarte ist damit vorübergehend gegenstandslos, nicht
   überflüssig — sie ist der Weg, auf dem jeder neue Wortlaut
   sichtbar wird, bevor er entschieden ist. Ohne diese Markierung
   meldete bauen.py fünf verlorene Klassen und man gewöhnte sich
   daran, den Befund wegzusehen.
   Kommt ein ENTWURF-Feld zurück, meldet bauen.py die Markierung
   als veraltet; dann gehören diese fünf Zeilen weg.           */

.entwurf {
  border: 1px solid var(--line);
  border-left: 2px solid var(--acc-tief);
  border-radius: var(--radius);
  background: var(--flaeche-1);
  padding: var(--a-4);

  /* Wie beim Leerfeld: Ein Entwurf kann in einem h1 stehen und
     erbt sonst Hero-Größe und negative Laufweite. */
  font-family: var(--schrift);
  font-size: var(--t-text);
  font-weight: 400;
  line-height: var(--z-text);
  letter-spacing: normal;
  text-wrap: wrap;
}

.entwurf__kopf {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--a-3);
  flex-wrap: wrap;
}

.entwurf__name {
  font-family: var(--mono);
  font-size: var(--t-klein);
  color: var(--fg-2);
}

\* Die Plakette darf umbrechen.

   Bis zum 2026-08-26 stand hier `white-space: nowrap`. Damit war
   "ENTWURF · nicht freigegeben" ein unteilbarer Block von 233 px, und
   weil die Mindestbreite eines Gitterfeldes seinem Inhalt folgt
   (`repeat(3, 1fr)` heisst implizit `min-width: auto`), sprengte jede
   Entwurfskarte in einem Dreierraster die Bahn: gemessen 313 px in
   einer 299 px breiten Spalte, macht 14 px waagerechtes Ueberlaufen
   auf `en/index.html` bei 1024 px.

   Aufgefallen ist es erst, als 72 englische Felder gleichzeitig auf
   ENTWURF standen. Die Regel selbst ist aelter -- auf der deutschen
   Seite stand nur nie eine Entwurfskarte in einem Dreierraster.

   Umbrochen wird an den Leerzeichen, nicht in den Woertern: Die
   Plakette wird dann zweizeilig, statt die Seite zu verschieben. */
.entwurf__status {
  font-family: var(--mono);
  font-size: var(--t-fuss);
  letter-spacing: 0.08em;
  border-radius: var(--radius);
  padding: 1px 6px;
  color: var(--auf-acc);          /* Regel 1: dunkle Schrift auf Kupfer */
  background: var(--acc);
}

.entwurf__text { margin-top: var(--a-4); color: var(--fg); }
.entwurf__text > * { max-width: var(--breite-text); }
.entwurf__text > * + * { margin-top: var(--a-3); }


/* --- Rechtsseiten -------------------------------------------- */

/* basis.css setzt bei h1/h2 margin:0 — für Fließtextseiten braucht es
   wieder Luft, sonst klebt der Vorspann an der Überschrift. */
.recht { padding: var(--a-8) 0; }
.recht h1 { margin-bottom: var(--a-5); }
.recht h2 { margin: var(--a-7) 0 var(--a-4); }
.recht > .bahn > p { max-width: var(--breite-text); }

/* --- Fehlerseiten -------------------------------------------- */

.fehler { padding: var(--a-9) 0; text-align: center; }
.fehler__code {
  font-family: var(--mono);
  font-size: var(--t-hero);
  color: var(--acc);
  line-height: 1;
}
.fehler p { color: var(--fg-2); margin-top: var(--a-4); }

/* Die englische Zeile der Fehlerseite steht ruhiger als die deutsche —
   sie ist dieselbe Aussage, nicht die naechste. --fg-3 auf --bg ist
   nach Regel 3 zugelassen. */
.fehler__en { font-size: var(--t-klein); color: var(--fg-3); }
.fehler .nav { justify-content: center; margin-top: var(--a-6); }

/* `.zahl { overflow-wrap: anywhere }` ist am 2026-09-01 ENTFALLEN.

   Sie fing lange Dateinamen ohne Trennstelle ab —
   "02_IMPRESSUM_UND_DATENSCHUTZERKLAERUNG.md" schob die
   Datenschutzseite auf dem Telefon um 42 px seitwaerts, gefunden von
   ansichten.py am 2026-08-18.

   Solche Pfade standen ausschliesslich in den festen Vorspaennen der
   Rechtsseiten. Die sind entfallen (seiten.py, geruesthinweis) —
   **interne Dateipfade gehoeren nicht auf eine oeffentliche Seite.**
   Damit hat die Regel keinen Gegenstand mehr; die Gestaltungspruefung
   hat sie am selben Tag als ungenutzt gemeldet.

   Nicht als @ungenutzt-erwartet markiert, sondern geloescht: Sie ist
   nicht zustandsabhaengig, sie ist ueberfluessig geworden. Kehren
   lange Bezeichner je auf eine Seite zurueck, kommt sie mit ihnen
   zurueck — dann aber mit neuem Anlass.

   `.zahl` traegt weiterhin Mono und tabular-nums aus basis.css; das
   ist unberuehrt. */

/* =============================================================
   Gestaltungsebene — übertragen am 2026-08-19

   Aus 02_DESIGN/landingpage.py (Abschnitt EXTRA). Bis zu diesem
   Tag erzeugte seiten.py für jedes Feld nur eine Zeile in einer
   schlichten Liste; die Bausteine des Gestaltungsentwurfs fehlten
   im Markup. Die meisten Regeln — .karte, .raster, .knopf,
   .kennzahl, .liste, .hinweis, .label, .leucht — standen bereits
   in basis.css und wurden immer mitgeliefert.

   NICHT übernommen wurde die Ausstattung des Prüfdokuments:
   .mk, .nurmk, .schalter, .fuss-band, .fuss-links. Das Gerüst hat
   mit .entwurf und .leerfeld eine eigene, strengere Kennzeichnung.

   Keine Farbwerte, keine Größen — alles aus tokens.css
   (Bauregel 1).
   ============================================================= */

/* --- Hero ---------------------------------------------------- */

.hero {
  padding: var(--a-9) 0 var(--a-8);
  position: relative;
  overflow: hidden;
  background: var(--flaeche-0);
}

/* Lichtfeld und Korn liegen hinter dem Inhalt, nie darauf.

   Der Entwurf zog das Lichtfeld waagerecht um je 10 Prozent über
   die Kante hinaus und schnitt es mit overflow:hidden wieder ab.
   Genau diese zehn Prozent hat ansichten.py am 2026-08-19 auf
   allen sechs Breiten als abgeschnittenen Inhalt gemeldet — 39 px
   bei 390, 144 px bei 1440. Der Befund war richtig gemessen und
   gestalterisch gewollt.

   Statt dem Prüfwerkzeug eine Ausnahme beizubringen, ist der
   Überstand weggenommen — waagerecht wie senkrecht: Ein Prüfstand
   mit Ausnahmeliste verliert genau die Fälle, für die es ihn gibt.

   Die Wirkung bleibt. Der Verlauf ist bei 50% 0% verankert und
   verlief ohnehin nach oben aus dem Bild; seine Größe kommt jetzt
   aus background-size: 100% 190% statt aus einem Kasten, der über
   die Kante ragt. Die Deckkraft ist unverändert — 8 Prozent im
   Kern, gerechnet, siehe tokens.css. */
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--schein);
  background-size: 100% 190%;
  background-repeat: no-repeat;
  pointer-events: none;
}

.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.26;
  mix-blend-mode: overlay;
  background-image: var(--korn);
  background-size: 160px 160px;
}

/* Feines Linienraster, weich ausmaskiert. Die Schärfung 09 mit
   Passkreuzen ist am 2026-08-16 auf Wunsch zurückgenommen worden. */
.hero__raster {
  position: absolute;
  inset: 0;
  opacity: 0.5;
  pointer-events: none;
  background-image: linear-gradient(var(--line) 1px, transparent 1px),
                    linear-gradient(90deg, var(--line) 1px, transparent 1px);
  background-size: 88px 88px;
  mask-image: radial-gradient(120% 90% at 78% 12%, #000 0%, transparent 68%);
}

.hero .bahn { position: relative; }

/* Feste Zeilenumbrüche in der Überschrift gelten nur auf breiten
   Schirmen. Auf dem Telefon bricht der Satz von selbst besser. */
@media (max-width: 720px) { .nur-breit { display: none; } }

.hero__aktionen {
  display: flex;
  flex-wrap: wrap;
  gap: var(--a-3);
  margin-top: var(--a-6);
}

/* Die Schaltflächen sind <a>-Elemente. `a` traegt in basis.css einen
   `border-bottom` als Unterstreichung — deshalb stand hier bis zum
   2026-08-23 `border-bottom: 0`.

   **Das war falsch und sichtbar:** `.knopf` setzt in basis.css die
   Kurzform `border: 1px solid` und gewinnt gegen `a` schon ueber die
   Spezifitaet. Die Zeile hat also nicht die Unterstreichung entfernt,
   sondern die **untere Rahmenkante der Schaltflaeche** — beide Knoepfe
   standen offen. Vom Inhaber am 2026-08-23 am Bildschirm gefunden;
   kein Werkzeug misst Rahmen.

   `text-decoration: none` bleibt, obwohl `a` es ebenfalls setzt: Es
   kostet nichts und haelt die Absicht fest. */
.hero__aktionen .knopf { text-decoration: none; }

/* --- Vorspann ------------------------------------------------
   Zwei Formen: hero.vorspann bringt sein <p class="vorspann">
   selbst mit, die übrigen Abschnitte werden von seiten.py in ein
   <div class="vorspann"> gefasst. Beide sollen gleich aussehen. */

.vorspann { margin-top: var(--a-5); }

/* Der Nutzensatz im Kopfbereich, neu am 2026-08-28.
   Steht zwischen Überschrift und Vorspann und trägt dieselbe Größe
   wie der Vorspann, aber die **volle** Textfarbe statt --fg-2. Das
   ist der ganze Unterschied: Er soll als Fortsetzung der Überschrift
   gelesen werden, nicht als deren Erläuterung.

   Keine zweite Farbe, kein Kupfer, kein größerer Grad — die Marke
   hat für Betonung genau ein Mittel, und das ist der Grad. Ein
   drittes Textgewicht im Kopfbereich stünde in Konkurrenz zur
   Wortmarke und zum Kontaktknopf. */
.hero__nutzen {
  margin-top: var(--a-5);
  font-size: var(--t-unter);
  line-height: 1.45;
  color: var(--fg);
  max-width: 62ch;
}

p.vorspann,
div.vorspann p {
  font-size: var(--t-unter);
  line-height: 1.45;
  color: var(--fg-2);
  max-width: 62ch;
}

/* **Eine Spezifitaetsfalle, gefunden am 2026-08-23 am Bildschirm.**
   `div.vorspann p` hat die Spezifitaet 0,1,1 und schlaegt damit
   `.fuss` mit 0,1,0. Jede Fussnote innerhalb eines Vorspanns wurde
   dadurch auf Vorspanngroesse hochgezogen — der Warnsatz in
   Abschnitt 07 stand bei 22 px und damit genauso gross wie der Satz
   darueber, den er nicht ueberdecken darf.

   Kein Werkzeug meldet so etwas: Die Geometrie stimmt, der Kontrast
   stimmt, die Klasse ist vorhanden. Nur die Rangfolge stimmt nicht.  */
div.vorspann p.fuss {
  font-size: var(--t-klein);
  line-height: 1.5;
  color: var(--fg-3);
  max-width: 62ch;
}

/* --- Kennzahlenband ------------------------------------------ */

.zahlen {
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  padding: var(--a-6) 0;
}

/* Ein Wort statt einer Ziffer: kleiner gesetzt, damit die Kachel
   nicht aus der Reihe fällt, aber weiterhin in Mono. */
.kennzahl__wert--wort { font-size: 1.55rem; letter-spacing: -0.02em; }

/* --- Karten und Hinweise in den Bändern ---------------------- */

.raster { margin-top: var(--a-7); }
.karte .label, .raster > div > .label { display: block; margin-bottom: var(--a-3); }
/* Volle Rasterbreite — die breite Karte endet rechts auf derselben
   Linie wie die Kartenreihe darueber.

   Am 2026-08-23 trug sie kurzzeitig `max-width: 74ch`, damit Kasten und
   Text gemeinsam enden. **Vom Inhaber am selben Tag verworfen**, aus
   demselben Grund wie die ungleich hohen Karten: Eine Kante, die vor
   der darueberliegenden endet, liest sich als Versehen. Die kurze
   Zeile ist der geringere Preis.

   Die Lesezeile bleibt trotzdem kurz: `basis.css` deckelt jedes `p` auf
   `--breite-text`. Der Kasten wird breit, der Text nicht — der Freiraum
   liegt innen rechts und ist genau der, der hier in Kauf genommen wird. */
.karte--breit { margin-top: var(--a-6); }
.hinweis { margin-top: var(--a-6); }
.hinweis h3 { margin-bottom: var(--a-3); }

/* --- Porträtspalte ------------------------------------------- */

.person {
  display: grid;
  grid-template-columns: 300px 1fr;
  gap: var(--a-7);
  align-items: start;
}

@media (max-width: 760px) { .person { grid-template-columns: 1fr; } }

.portraet {
  aspect-ratio: 4 / 5;
  background: var(--flaeche-1);
  border: 1px solid var(--flaeche-5);
  border-radius: var(--radius);
  overflow: hidden;
  position: relative;
  box-shadow: inset 0 1px 0 0 var(--kante);
}

/* Der Rahmen bleibt im Hellmodus dunkel — neu am 2026-09-01.
   ============================================================

   Es gibt **ein** Portraet, vor dunklem Grund aufgenommen. Auf der
   hellen Seite stand es bis heute ohne passenden Rahmen: ein dunkles
   Bild, das unvermittelt auf Weiss beginnt.

   Geplant war zunaechst eine zweite Aufnahme vor hellem Grund. Das
   ist am 2026-09-01 verworfen worden — **weder der Inhaber noch die
   Fotografin haben ein Studio**, und ein gleichmaessig ausgeleuchteter
   heller Grund setzt genau den Aufbau voraus, den es nicht gibt. Die
   dunkle Fassung dagegen entsteht durch Licht und Abstand in jedem
   Raum. FOTOBRIEFING.md §3 und §9.

   Deshalb traegt der Rahmen die Aufgabe: Er bleibt dunkel und gibt
   dem Bild eine eigene Flaeche, statt es auf die helle Seite
   aufzusetzen. Ein dunkles Bild in dunkler Fassung auf hellem Grund
   ist eine gewoehnliche Loesung — und konsistenter als zwei Fotos
   derselben Person, die nie ganz gleich aussehen.

   Die Werte sind die des Dunkelmodus, hier fest eingetragen statt
   ueber Tokens: Im Hellmodus zeigen `--flaeche-1` und `--flaeche-5`
   auf helle Werte, und genau die sollen hier NICHT gelten. Bauregel 1
   verlangt Farben aus tokens.css — diese kommen von dort, nur aus dem
   anderen Modus. Wer die Dunkelwerte in tokens.css aendert, aendert
   sie hier mit.

   **Der Bildunterschrift gilt das nicht:** Sie steht ausserhalb des
   Rahmens und behaelt die Textfarbe der hellen Seite. */
[data-modus="hell"] .portraet {
  background: #10141A;              /* --flaeche-1 dunkel */
  border-color: #1E252D;            /* --flaeche-5 dunkel */
  box-shadow: inset 0 1px 0 0 rgba(231, 237, 243, 0.06);
}

.portraet img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* --- Abgrenzungstabelle, Abschnitt 03 ------------------------ */

.gegen {
  display: grid;
  grid-template-columns: 180px 1fr;
  gap: var(--a-4) var(--a-5);
  border-top: 1px solid var(--line);
  padding: var(--a-4) 0;
}

/* Regel 3: --fg-3 ist bis --flaeche-2 zugelassen. Das Band steht
   auf --bg beziehungsweise --bg-2, also darunter. Nachgemessen
   mit kontrastaudit.py in beiden Modi. */
.gegen > div:first-child { color: var(--fg-3); font-size: var(--t-klein); }
.gegen--schluss { border-bottom: 1px solid var(--line); }

@media (max-width: 620px) {
  .gegen { grid-template-columns: 1fr; gap: var(--a-2); }
}

/* --- Navigation: Abschnittsmarken ---------------------------- */

.nav__abschnitt { text-decoration: none; }

/* Auf schmalen Schirmen bleiben nur die Rechtslinks stehen —
   dieselbe Regel wie oben, hier ausdrücklich auch für die
   Abschnittsmarken. */
@media (max-width: 720px) { .nav a.nav__abschnitt { display: none; } }

/* Der Kontaktknopf im Kopf bleibt auch auf schmalen Schirmen stehen —
   er ist der einzige Weg zur Kontaktaufnahme.

   @ungenutzt-erwartet nav__kontakt
   Zustandsklasse: rahmen.kopf() gibt sie nur dem Abschnitt 07, und der
   erscheint in der Kopfnavigation erst, wenn seine Markerzeile
   freigegeben ist. Solange es kein Feld 07.label gibt, steht kein
   Kontaktknopf im Kopf — dann ist diese Regel zu Recht ungenutzt. */
.nav__kontakt { border-bottom: 0; text-decoration: none; }
@media (max-width: 720px) { .nav a.nav__kontakt { display: inline-flex; } }

/* --- Die wandernde Abschnittsmarke ---------------------------
   Neu am 2026-08-28. Rücklauf Alex Maus: „dünner Kupfer-Strich
   oben unter die Wörter — also wie bei EN/DEU, aber dünner."

   **Ein** Strich für die ganze Kopfzeile, nicht einer je Verweis.
   Er übernimmt Ort und Breite des Abschnitts, in dem der Leser
   gerade steht, und gleitet dorthin. Ein Strich je Verweis, der
   ein- und ausgeblendet wird, könnte nicht gleiten — und genau
   das Gleiten war der Wunsch.

   Ort, Zeile und Breite kommen als drei Zahlen aus eintritt.js
   über CSS-Variablen. Dasselbe Verfahren wie bei der Leuchtkante
   (--mx/--my): Das ist kein Inline-Style aus dem Markup und wird
   von `style-src 'self'` nicht blockiert.

   **1 px, nicht 2.** Der Sprachschalter trägt 2 px (.sprachwahl__aktiv).
   Die Abstufung ist Absicht: Die Sprache ist eine Entscheidung des
   Besuchers, der Abschnitt nur seine Position.

   **Kupfer ist hier die Ausnahme, nicht die Regel.** Die Notiz zum
   Sprachschalter oben hält fest, dass Kupfer die Farbe der Handlung
   ist. Der Sprachschalter durchbricht das seit dem 2026-08-25 für
   den aktiven Zustand; diese Marke folgt derselben Ausnahme, damit
   der Besucher zwei Mal dieselbe Aussage in derselben Form liest.
   Eine dritte kupferne Fläche kommt ohne erneute Entscheidung
   nicht dazu.

   Bewegung: 180 ms, nur beim Wechsel. CLAUDE.md §8 Regel 5 ist am
   2026-08-28 dafür erweitert worden — vorher war ausschließlich der
   einmalige Eintritt zugelassen.

   Bei `prefers-reduced-motion: reduce` springt der Strich. Das
   nimmt die Medienabfrage weiter unten weg, **nicht das Skript**:
   Die Markierung selbst bleibt, weil sie die Information ist; nur
   ihr Übergang fällt aus. Das ist der Unterschied zur
   Eintrittsanimation, die in diesem Fall ganz entfällt — dort ist
   die Bewegung Zierrat, hier ist sie Zugabe zu einer Aussage.     */

.nav { position: relative; }

.nav__marke {
  position: absolute;
  left: 0;
  top: 0;
  height: 1px;
  width: var(--marke-b, 0px);
  background: var(--acc);
  transform: translate(var(--marke-x, 0px), var(--marke-y, 0px));
  opacity: 0;
  pointer-events: none;
  transition: transform 180ms ease, width 180ms ease, opacity 180ms ease;
}

/* @ungenutzt-erwartet nav__marke--an
   Zustandsklasse: Sie entsteht erst im Browser, sobald eintritt.js
   einen Abschnitt im Blickfeld hat. Im Erzeugnis steht sie deshalb
   nie — und das ist der Sollzustand, nicht ein verlorener Baustein.
   Ohne JavaScript bleibt der Strich unsichtbar; die Kopfzeile
   funktioniert vollständig ohne ihn (CLAUDE.md §8 Regel 4). */
.nav__marke--an { opacity: 1; }

@media (prefers-reduced-motion: reduce) { .nav__marke { transition: none; } }

/* Unter 721 px sind die Abschnittsmarken ausgeblendet — dann gibt es
   nichts zu markieren. Der Strich verschwindet mit ihnen, sonst
   stünde er unter dem Sprachschalter oder dem Modusknopf. */
@media (max-width: 720px) { .nav__marke { display: none; } }
