/* ============================================================================
   COREFRAME · die gemeinsame Huelle der vier Unterseiten

   Die Landingpage behauptet in Akt 5 "Eine Marke. Durch jede Oberflaeche." und
   zeigt darunter vier Aufnahmen dieser Seiten. Damit das keine Behauptung
   bleibt, tragen sie dieselbe Handschrift: dieselbe Leiste, denselben Akzent,
   dieselbe Bahn.

   Bewusst NEBEN styles.css, nicht hinein. Das Blatt ist gewachsen, blockweise
   Eingriffe haben dort schon zweimal Regeln mitgerissen, und die Huelle laesst
   sich so in einer Zeile wieder abhaengen.

   Wird nach styles.css geladen, shell.js nach app.js.
   ========================================================================== */

:root {
  --header-h: 76px;
  --bahn: #c0603c;          /* dieselbe Bahn wie auf der Landingpage */
  --bahn-w: 4px;
  /* Terrakotta eine Stufe tiefer, fuer Schrift auf einer Terrakotta-Toenung.
     --terra selbst steht dort nur auf 4,14:1. Gleiche Farbe, nicht die
     zweite: 6,5:1 auf Ivory, 5,9:1 auf der Toenung. */
  --terra-tief: #8f3a20;
  /* Dasselbe Spiel wie bei --terra-tief, nur fuer die Nebenschrift: --muted
     ist ein warmes Taupe FUER IVORY und faellt auf einer Champagner-Toenung
     auf 4,42:1. Gemessen hat das check-kontrast.mjs auf modul-neue-website,
     an zwei Zeilen im hervorgehobenen Block des Seitengeruests. Eine Stufe
     tiefer, gleiche Farbe, kein zweiter Ton: 5,1:1 auf der Toenung. */
  --muted-tief: #655741;
}

/* Das hidden-Attribut hat in der UA-Tabelle nur display:none, und jede Regel
   im Blatt schlaegt das. `.estimate small { display: block }` hat genau das
   getan: die Budgetwarnung im Konfigurator stand von Anfang an da, auch ohne
   gewaehlten Umfang. */
[hidden] { display: none !important; }

/* overflow-x: hidden auf dem body macht den body zum Scrollcontainer, und
   position: sticky darin klebt an einem Kasten, der selbst nie scrollt. Die
   Leiste unten braucht sticky. clip beschneidet genauso, ohne einen
   Scrollcontainer aufzumachen. */
body { overflow-x: clip; }

/* styles.css setzt scroll-behavior: smooth. Zusammen mit dem eigenen Nachlauf
   in shell.js faellt natives Smooth-Scrollen jeden Frame in sich zusammen und
   Anker kommen nie an. Dieselbe Falle wie auf der Landingpage. */
html { scroll-behavior: auto; }


/* ============================================================== 1 · LEISTE ==
   76 Pixel, klebend, Wortmarke links, Ort in der Mitte, eine runde Pille
   rechts mit dem Terrakotta-Punkt statt des Pfeils. Der Pfeil bleibt den
   Knoepfen im Fliesstext, die wirklich woanders hinfuehren.

   Sie sitzt auf --pad, dem Gutter DIESER Seite, nicht auf dem der
   Landingpage: eine Leiste, die nicht auf derselben Kante steht wie der Text
   darunter, faellt staerker auf als ein paar Pixel Unterschied zum Nachbarn. */

.site-header {
  position: sticky;
  top: 0;
  min-height: var(--header-h);
  padding: 0 var(--pad);
  border-bottom: 1px solid transparent;
  background: color-mix(in oklab, var(--paper) 86%, transparent);
  backdrop-filter: blur(12px);
  transition: border-color 240ms cubic-bezier(0.4, 0, 0.1, 1),
              background-color 240ms cubic-bezier(0.4, 0, 0.1, 1);
}
.site-header.is-stuck { border-bottom-color: var(--line); }

.config-page .site-header { background: color-mix(in oklab, var(--paper) 86%, transparent); }

.wordmark-logo { height: 22px; width: auto; }

.header-context {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Die Pillen werden rund. Die Hauptpille traegt den Terrakotta-Punkt. */
.site-header .pill,
.site-header .icon-pill {
  min-height: 40px;
  padding: 0 18px;
  border-radius: 999px;
  font-size: 12.5px;
  font-weight: 550;
  letter-spacing: 0.01em;
}
.site-header .pill {
  gap: 9px;
  background: var(--ink);
  color: var(--paper);
  border-color: transparent;
}
.site-header .pill::after {
  content: "";
  width: 6px;
  height: 6px;
  flex: none;
  border-radius: 50%;
  background: var(--terra);
}
@media (hover: hover) and (pointer: fine) {
  .site-header .pill:hover { background: var(--ink-2); }
}

/* Der Menue-Knopf traegt keinen Punkt: er fuehrt nirgendwohin, er klappt auf. */
.site-header .icon-pill {
  gap: 8px;
  background: transparent;
  color: var(--ink);
  border-color: var(--line);
}
/* Ohne Gate klebt der Hover-Zustand auf Beruehrgeraeten nach dem Tippen. */
@media (hover: hover) and (pointer: fine) {
  .site-header .icon-pill:hover { background: color-mix(in oklab, var(--ink) 7%, transparent); }
}
.site-header .menu-dots {
  display: inline-grid;
  grid-auto-flow: column;
  gap: 3px;
  align-items: center;
}
.site-header .menu-dots i {
  display: block;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: currentColor;
  transition: transform 400ms cubic-bezier(0.4, 0, 0.1, 1),
              background-color 240ms cubic-bezier(0.4, 0, 0.1, 1);
}
.site-header .icon-pill[aria-expanded="true"] .menu-dots i:first-child {
  background: var(--terra);
  transform: scale(1.5);
}

/* Unter 560 Pixel passen Marke, Aktion und Menue nicht nebeneinander. Das
   Logo wird dann auf das Zeichen beschnitten. */
@media (max-width: 560px) {
  /* Deutsche Komposita brauchen auf 393 Pixel eine Trennstelle.

     "Allgemeine Geschäftsbedingungen" ist als Wort 434px breit und die
     Spalte 313 -- die Ueberschrift lief also 121px ueber den Rand, und weil
     das Layout-Viewport eines Telefons sich auf den Inhalt dehnt, scrollte
     das ganze Dokument seitwaerts: agb.html 474px breit bei 393, datenschutz
     446, widerruf 405. Auf allen drei Rechtstexten, und auf jeder von ihnen
     wanderte anschliessend auch die feste Kopfleiste mit.

     Gesucht hatte ich den Verursacher zuerst beim Menue-Overlay, das mit 474
     das breiteste Element war -- es steht aber auf `fixed; inset: 0` und
     folgte der Dehnung nur. Wer waagerechtes Scrollen sucht, misst
     `scrollWidth` und laesst alles Feste aus.

     `hyphens: auto` greift, weil die Seiten `lang="de"` tragen;
     `overflow-wrap` ist das Netz fuer Woerter ohne Trennstelle (URLs). */
  h1, h2, h3, .page-hero h1, .page-title {
    hyphens: auto;
    overflow-wrap: break-word;
  }


  .site-header { grid-template-columns: auto 1fr auto; gap: 10px; }
  .header-context { display: none; }
  .wordmark-logo { width: 26px; object-fit: cover; object-position: left center; }
  .site-header .icon-pill span:first-child { display: none; }
  .site-header .icon-pill { padding: 0 12px; }
  /* styles.css versteckt die Aktions-Pille hier ganz. Genau dafuer wird das
     Logo aber auf das Zeichen beschnitten: damit Marke, AKTION und Menue
     nebeneinander passen. Ohne die Aktion ist die Leiste auf dem Telefon nur
     noch Marke plus Menue, und der eine Weg der Seite steckt zwei Klicks
     tief. Gemessen passen die drei bei 390 Pixel in 233. */
  .header-actions .pill { display: inline-flex; }
  .site-header .pill { padding: 0 14px; font-size: 12px; }
}


/* ================================================ 2 · CLIENT UND ADMIN =====
   Keine Leiste, sondern Kopfzeile und Seitenspalte. Beide bekommen dieselbe
   Hoehe, damit Wortmarke und Aktionen auf einer Linie sitzen. Ohne das steht
   die Marke in der Spalte auf einer anderen Grundlinie als die Kopfzeile
   daneben, und genau das faellt an einer Arbeitsflaeche auf. */

.workspace-sidebar > .wordmark {
  display: flex;
  align-items: center;
  min-height: var(--header-h);
  margin: 0 0 18px;
}
.workspace-topbar {
  min-height: var(--header-h);
  align-items: center;
  margin-bottom: 44px;
}


/* ================================================================ 3 · BAHN ==
   Derselbe rundgekappte Terrakotta-Strich wie auf der Landingpage, in drei
   Formen, je nachdem was die Seite ist. Drei Formen, eine Grammatik: dieselbe
   Farbe, dieselbe runde Kappe, immer die Bedeutung "hier entlang". */

/* --- Shop: gezeichnet, laeuft beim Scrollen durch das ganze main ---------- */

.bahn-host { position: relative; }
.bahn-wrap {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: clip;
  pointer-events: none;
}
.bahn-host > *:not(.bahn-wrap) { position: relative; z-index: 1; }
.bahn { width: 100%; height: 100%; display: block; }
.bahn path {
  fill: none;
  stroke: var(--bahn);
  stroke-width: 22;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* Hinter dem Text, aber nicht gegen ihn: bei voller Deckkraft liest sich der
     Bogen als Strich quer durch die Ueberschrift statt als Band dahinter. */
  opacity: 0.72;
}

/* Auf dem Telefon tritt die gezeichnete Bahn ganz zurueck: eine einspaltige
   Seite hat keine freie Bahn, und Taupe auf Terrakotta ist nicht mehr lesbar. */
@media (max-width: 860px) {
  .bahn-wrap { display: none; }
}

/* --- Konfigurator: waagerecht als Fortschritt durch die vier Schritte ----- */

.config-progress { gap: 8px; }
.progress-segment {
  height: var(--bahn-w);
  border-radius: 999px;
  background: color-mix(in oklab, var(--ink) 11%, transparent);
}
/* scaleX statt width: width ist Layout, scaleX laeuft auf der GPU. Die runde
   Kappe kommt hier ohnehin vom Elternteil (overflow: hidden plus radius), also
   verzieht sich beim Skalieren nichts. */
.progress-segment::after {
  width: 100%;
  transform: scaleX(0);
  transform-origin: 0 50%;
  background: var(--bahn);
  transition: transform 500ms cubic-bezier(0.4, 0, 0.1, 1);
}
.progress-segment.is-complete::after,
.progress-segment.is-current::after { transform: scaleX(1); }

/* --- Client und Admin: senkrecht in der Seitenspalte ---------------------- */

.workspace-nav {
  position: relative;
  padding-left: 16px;
}
/* Die Spur laeuft ueber alle Eintraege, die Marke sitzt auf dem aktuellen.
   Ihre Lage kommt aus shell.js vom echten Eintrag, nicht aus einer
   gerechneten Zeilenhoehe: die Eintraege sind unterschiedlich hoch, sobald
   einer umbricht. */
.workspace-nav::before {
  content: "";
  position: absolute;
  left: 3px;
  top: 4px;
  bottom: 4px;
  width: var(--bahn-w);
  border-radius: 999px;
  background: color-mix(in oklab, var(--ink) 11%, transparent);
}
/* Bewegt wird ueber transform, nicht ueber top. Die Hoehe bleibt eine echte
   Hoehe und wird nicht animiert: die Eintraege sind gleich hoch, sie aendert
   sich also ohnehin nicht, und ueber scaleY wuerde die runde Kappe mitgezogen. */
.workspace-nav .bahn-mark {
  position: absolute;
  left: 3px;
  top: 0;
  width: var(--bahn-w);
  height: var(--mark-h, 0px);
  border-radius: 999px;
  background: var(--bahn);
  transform: translateY(var(--mark-t, 0px));
  transition: transform 420ms cubic-bezier(0.4, 0, 0.1, 1);
}


/* ============================================================ 4 · AKZENT ====
   Die Token standen nach der Umstellung warm, aber der Akzent kam auf den
   Unterseiten nirgends vor. Jetzt traegt ihn, was Aktion und Weg ist.

   Die Regel dahinter: Terrakotta traegt Aktion und Weg, Champagner traegt
   Flaeche. Terrakotta kommt nur dorthin, wo die Regel die Schriftfarbe gleich
   mitbestimmt; ein Austausch an der Wurzel braeche die Regeln, die dunkle
   Schrift auf helle Flaeche setzen. */

.cart-count,
.workspace-nav .is-active::before { background: var(--terra); }

/* Die Seitenspalte ist DUNKEL. var(--ink) darauf sind 1,26:1: der aktive
   Eintrag waere der einzige unlesbare in der ganzen Spalte gewesen. Aktiv
   heisst hier heller als der Rest, nicht dunkler. */
.workspace-nav a.is-active { color: var(--paper); font-weight: 550; }

.menu-nav a[aria-current="page"] { color: var(--terra); }


/* ============================================================= 5 · FOKUS ====
   app.js setzt beim Schrittwechsel den Fokus auf die Ueberschrift des aktiven
   Schritts (renderStep -> activeHeading.focus({ preventScroll: true })), damit
   ein Screenreader den neuen Schritt vorliest. Die h1 tragen dafuer
   tabindex="-1", stehen also NICHT in der Tabreihenfolge: niemand kann sie mit
   der Tastatur ansteuern, der Fokus kommt dort immer programmatisch an.

   Was der Browser darauf malt, kommt aus seiner eigenen Tabelle
   (outline: auto) und ist kraeftig blau: neben Terrakotta, Espresso und Ivory
   die einzige kalte Farbe auf der ganzen Seite, und sie liegt um Display-Type
   in 76 Pixel Groesse. Der Ring wird deshalb nicht entfernt, sondern
   uebersetzt: derselbe Terrakotta wie an jedem anderen fokussierten Element in
   styles.css, nur duenner und weiter abgesetzt, weil er nicht um einen Knopf
   liegt, sondern um eine Zeile.

   Ganz weglassen waere vertretbar (ein nicht tabbares Ziel muss keinen Ring
   tragen, WCAG 2.4.7 gilt der Tastaturbedienung), aber es kostet etwas: wer
   "Weiter" mit der Tastatur ausloest, sieht sonst nicht, wohin der Fokus
   gesprungen ist, und weiss beim naechsten Tab nicht, wo er anfaengt.

   :focus-visible, nicht :focus. Der Unterschied ist hier kein feiner: die
   Regel darf nur die Darstellung des Rings tauschen, nicht die
   Tastaturbedienung abschalten. Alles andere auf den vier Seiten behaelt
   seinen Ring ohnehin -- die Regel steht auf .config-step h1 und sonst
   nirgends.

   Nachgemessen an den drei Wegen zu Schritt 2, weil die Heuristik des
   Browsers hier nicht offensichtlich ist:

     echter Mausklick auf "Weiter"   :focus ja, :focus-visible NEIN, kein Ring
     Tab bis "Weiter", dann Enter    :focus-visible ja, Ring
     element.click() aus dem Skript  :focus-visible ja, Ring

   Der blaue Rahmen im Kontaktbogen lab/auswahl/gross-2-vorwahl.png stammt
   also aus der dritten Zeile: check-auswahl.mjs klickt im Dokument und nicht
   mit der Maus, und ohne echte Zeigereingabe zaehlt Chrome den
   programmatischen Fokus als sichtbar. Ein Kunde mit der Maus hat den Ring nie
   gesehen -- ein Kunde auf der Tastatur schon, und der bekommt jetzt
   Terrakotta. */

/* Dieselbe Uebersetzung fuer die Ueberschriften der zwoelf Ansichten in
   Client und Admin: shell.js setzt den Fokus beim Wechsel darauf, und der
   Browser malt darum genau denselben blauen Rahmen. Beim ersten Kontaktbogen
   der neuen Portale stand er auf sechs von zwoelf Aufnahmen. */
.config-step h1:focus-visible,
.ws-view h2[tabindex="-1"]:focus-visible {
  outline: 2px solid var(--terra);
  outline-offset: 10px;
  border-radius: 3px;
  /* Der Ring liegt sonst um den BLOCKKASTEN, und der ist 14ch breit. Bei
     "Was bauen wir?" sind das 94 Pixel Leerraum rechts vom Fragezeichen, und
     ein gerundetes Rechteck mit Luft darin liest sich auf einer Seite voller
     Formularfelder wie ein Eingabefeld. fit-content legt ihn um den Text.
     Der Umbruch bleibt, wo er war: max-width: 14ch deckelt beide Faelle, die
     Zeilenkaesten aller vier Ueberschriften stehen mit und ohne auf demselben
     Pixel (gemessen bei 1440, 900 und 390). Bei den drei langen Zeilen aendert
     sich ohnehin nichts, sie stossen an denselben Deckel. */
  width: fit-content;
}

/* Die 10 Pixel Abstand stimmen fuer die frei stehende h1 im Konfigurator.
   In Client und Admin sitzt ueber jeder Ueberschrift ein Label, und gemessen
   liegen zwischen den beiden Kaesten genau 4 Pixel: der Ring lief damit quer
   durch "WO WIR STEHEN", und zwar auf allen zwoelf Ansichten, weil der
   Abstand ueberall aus derselben Regel kommt. */
.ws-view h2[tabindex="-1"]:focus-visible {
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .site-header,
  .site-header .menu-dots i,
  .progress-segment::after,
  .workspace-nav .bahn-mark { transition: none; }
  .progress-segment.is-complete::after,
  .progress-segment.is-current::after { transform: scaleX(1); }
}


/* ============================================== 6 · EINWILLIGUNG UND VERSAND
   Was der Konfigurator braucht, seit er eine Anfrage wirklich absetzt: eine
   Einwilligung, einen Koeder gegen Bots und eine Zeile, die sagt, was gerade
   passiert.

   Hier und nicht in styles.css, aus demselben Grund wie der Rest der Huelle:
   das Blatt ist gewachsen und blockweise Eingriffe haben dort schon zweimal
   Regeln mitgerissen. */

.consent {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 12px;
  align-items: start;
  max-width: 680px;
  margin-top: 6px;
}

/* Nicht .field label: das ist Mono, 10px und Grossbuchstaben, also fuer eine
   Feldbeschriftung gedacht. Eine Einwilligung ist Fliesstext und muss sich
   lesen lassen, sonst wird sie weggeklickt statt gelesen. */
.consent label {
  color: var(--muted);
  font-size: 12.5px;
  line-height: 1.55;
  cursor: pointer;
}

.consent label a {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.consent input[type="checkbox"] {
  width: 18px;
  height: 18px;
  margin-top: 1px;
  accent-color: var(--terra);
  cursor: pointer;
}

/* Aus dem Bild geschoben, nicht display:none und nicht [hidden]: beides lesen
   einfache Bots, und ein Feld, das sie nicht sehen, fuellen sie auch nicht
   aus. Der Koeder soll fuer sie da sein und fuer Menschen nicht. Deshalb
   ausserdem aria-hidden und tabindex="-1" im Markup: wer die Seite hoert oder
   sich durchtabbt, stolpert sonst ueber eine Faxnummer. */
.honigtopf {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.config-status {
  max-width: 680px;
  margin: 24px 0 0;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-left-width: 3px;
  border-radius: 10px;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.5;
}

.config-status a {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* Fehler und Hinweis tragen die Bahnfarbe an der Kante, nicht als Flaeche:
   Terrakotta auf Ivory steht bei 5,1:1 und traegt Text, ein Fehlerkasten
   ganz in Terrakotta traegt ihn nicht mehr. Die Farbe zeigt hin, der Text
   erklaert. Kein Kaltrot: die Seite hat keins, und eins einzufuehren waere
   ein zweiter Akzent fuer einen einzigen Zustand. */
.config-status.is-fehler,
.config-status.is-hinweis { border-left-color: var(--terra); color: var(--ink); }
.config-status.is-ok { border-left-color: var(--terra); color: var(--ink); }
.config-status.is-warte { border-left-color: var(--muted); }

/* Ein Knopf, der gerade sendet, darf nicht so aussehen wie einer, der
   wartet. Kein Spinner: die Uebertragung dauert Bruchteile einer Sekunde,
   und ein Rad, das einmal aufblitzt, ist unruhiger als eine ruhige Flaeche. */
.config-nav button[disabled] {
  opacity: .55;
  cursor: default;
}


/* ================================== 7 · RECHTSTEXTE UND BESTAETIGUNGSSEITE
   Drei neue Seiten teilen sich diese Regeln: impressum, datenschutz, danke.
   Sie liegen bewusst NICHT im Router (PAGES kennt sie nicht): sie werden
   gelesen und nicht angeflogen, und ein Flug braucht ein Standbild in
   assets/pages/, das bei jeder Textaenderung veralten wuerde. */

.rechtstext,
.danke-shell {
  padding: 0 var(--pad) 120px;
}

/* Fliesstext bricht bei etwa 68 Zeichen. Die Seite hat sonst ein weites
   Raster, aber ein Rechtstext ueber 1400 Pixel Breite wird nicht gelesen,
   sondern ueberflogen, und das ist bei genau diesen beiden Texten der
   falsche Modus. */
.rechtstext > h2,
.rechtstext > p,
.rechtstext > .entwurf-hinweis {
  max-width: 68ch;
}

.rechtstext > h2 {
  margin: 46px 0 14px;
  font-size: clamp(19px, 2.1vw, 23px);
  letter-spacing: -.01em;
}

.rechtstext > p {
  margin: 0 0 14px;
  color: var(--muted);
  font-size: 15px;
  line-height: 1.68;
}

.rechtstext a {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* Das Muster-Widerrufsformular. Es steht als blockquote da, weil es genau
   das ist: ein woertlich uebernommener Text, den der Besucher abschreiben
   oder kopieren soll, nicht unsere eigene Prosa.

   Die Absaetze darin brauchen eigene Regeln: `.rechtstext > p` greift nur auf
   DIREKTE Kinder, und die Absaetze im Formular sind Enkel. Ohne das steht das
   Formular in Browser-Standardschrift mitten im gesetzten Text. */
.rechtstext > .widerruf-muster {
  max-width: 68ch;
  margin: 20px 0 28px;
  padding: 22px 24px;
  border-left: 3px solid var(--terra);
  background: var(--paper-2);
}

.rechtstext > .widerruf-muster p {
  margin: 0 0 10px;
  color: var(--ink);
  font-size: 14px;
  line-height: 1.62;
}

.rechtstext > .widerruf-muster p:last-child {
  margin-bottom: 0;
  color: var(--muted);
  font-size: 13px;
}

/* Die Einleitung sagt, fuer wen die Belehrung gilt. Sie steht vor der ersten
   Ueberschrift und darf deshalb nicht wie ein weiterer Absatz aussehen. */
.rechtstext > .rechtstext-intro {
  color: var(--ink);
  font-size: 16px;
}

.rechtstext-stand {
  margin-top: 52px !important;
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
}

/* Ein Platzhalter muss beim Ueberfliegen auffallen, sonst geht er live.
   Terrakotta traegt auf Ivory 5,1:1, also genug fuer Text; die gepunktete
   Unterkante macht ihn auch dort erkennbar, wo jemand die Farbe nicht sieht.
   Farbe allein ist nie das einzige Signal. */
/* Terrakotta traegt auf Ivory 5,1:1 -- aber nicht auf einer Toenung aus
   Terrakotta. Gemessen 4,14:1, also durchgefallen. Die Toenung bleibt, weil
   sie das ist, was einen Platzhalter beim Ueberfliegen einer langen Seite
   findbar macht; stattdessen geht die Schrift eine Stufe tiefer. Derselbe
   Ton, nur dunkler: kein zweiter Akzent, nur ein zweiter Wert davon. */
.platzhalter {
  padding: 1px 5px;
  border-bottom: 1px dotted var(--terra);
  border-radius: 3px;
  background: color-mix(in oklab, var(--terra) 9%, transparent);
  color: var(--terra-tief);
  font-family: var(--mono);
  font-size: .92em;
}

.entwurf-hinweis {
  display: grid;
  gap: 8px;
  margin: 34px 0 8px;
  padding: 18px 20px;
  border: 1px solid var(--terra);
  border-left-width: 3px;
  border-radius: 12px;
  background: color-mix(in oklab, var(--terra) 6%, transparent);
}

.entwurf-hinweis strong {
  color: var(--terra-tief);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .08em;
  text-transform: uppercase;
}

.entwurf-hinweis span {
  color: var(--ink);
  font-size: 13px;
  line-height: 1.6;
}

/* ---------------------------------------------------------- Bestaetigung --
   Die Referenz ist das Einzige, was der Besuch von dieser Seite mitnimmt,
   also bekommt sie das Gewicht einer Ueberschrift und nicht das einer
   Fussnote. */

.danke-referenz {
  display: grid;
  gap: 6px;
  max-width: 520px;
  margin: 8px 0 56px;
  padding: 26px 28px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  /* War var(--surface), und das Token gibt es nirgends: eine undefinierte
     Custom Property macht die Deklaration ungueltig, background-color faellt
     auf transparent zurueck, und der Kasten stand ohne Fuellung da. Ohne
     Konsolenfehler, und check-shell hat es nicht gesehen, weil der Pruefer
     bewusst gegen den ECHTEN Grund misst -- er hat also korrekt gemessen,
     nur eben den Grund dahinter. Sand heisst hier --paper-2. */
  background: var(--paper-2);
}

.danke-referenz strong {
  font-size: clamp(26px, 3.6vw, 36px);
  font-family: var(--mono);
  letter-spacing: .02em;
  color: var(--ink);
}

.danke-referenz small {
  color: var(--muted);
  font-size: 12px;
  line-height: 1.55;
}

.danke-h2 {
  margin: 0 0 20px;
  font-size: clamp(19px, 2.1vw, 23px);
}

.danke-schritte { margin-bottom: 56px; }

.danke-kontakt { max-width: 68ch; }

.danke-kontakt p {
  margin: 0 0 24px;
  color: var(--muted);
  font-size: 15px;
  line-height: 1.68;
}

/* :not(.button) ist hier keine Feinheit, sondern der ganze Punkt.

   Gemeint sind die beiden mailto-Adressen im Fliesstext. Ohne den Ausschluss
   traf die Regel auch die zwei Knoepfe darunter -- und weil `.danke-kontakt a`
   (0,1,1) spezifischer ist als `.button` (0,1,0), gewann sie gegen deren
   `color: var(--white)`. Der Hauptknopf stand damit auf `--ink` UND mit
   `--ink` als Grund: Schrift und Flaeche exakt dieselbe Farbe, Kontrast 1:1,
   ein schwarzes Rechteck ohne erkennbaren Text.

   Der zweite Knopf (.secondary) fiel nicht auf, weil er ohnehin dunkle
   Schrift tragen soll. Ein Fehler, der nur die Haelfte der Faelle sichtbar
   trifft, ueberlebt jede fluechtige Sichtpruefung. */
.danke-kontakt a:not(.button) {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* Impressum muss von jeder Seite aus erreichbar sein. Der Konfigurator hat
   keine Fusszeile und die beiden Arbeitsflaechen auch nicht, also haengt es
   dort an der Navigation: im Menue und unten in der Seitenspalte. */
.menu-legal {
  display: flex;
  gap: 18px;
  margin: 22px 0 0;
}
/* Beide Orte stehen auf DUNKLEM Grund: die Seitenspalte auf Espresso, das
   Menue auf der aufgezogenen Flaeche. --muted ist ein warmes Taupe fuer
   helle Flaechen und kam hier auf 2,70:1. Genommen wird derselbe Wert wie
   bei den Nachbarn (Profilzeile, Menuetext), also Weiss mit Deckkraft. */
.menu-legal a,
.workspace-legal a {
  color: rgba(255, 255, 255, .62);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .06em;
  text-transform: uppercase;
  text-decoration: none;
}
.workspace-legal {
  display: flex;
  gap: 16px;
  margin: 14px 0 0;
  padding-top: 14px;
  /* --line ist eine dunkle Linie fuer helle Flaechen und waere hier
     unsichtbar. */
  border-top: 1px solid rgba(255, 255, 255, .12);
}
@media (hover: hover) and (pointer: fine) {
  .menu-legal a:hover,
  .workspace-legal a:hover { color: #fff; }
}

/* Ohne gueltige Referenz steht dort ein Satz und keine Kennung. Der darf
   nicht im Schriftgrad einer Kennung stehen, sonst liest er sich wie eine. */
.danke-referenz strong.is-leer {
  font-family: inherit;
  font-size: clamp(17px, 2vw, 21px);
  color: var(--muted);
}


/* Der Richtwert-Block zeigt seit dem Entfernen der Preise einen SATZ statt
   einer Zahl. styles.css setzt dort bis zu 62px -- richtig fuer "ab 10.000 €",
   voellig falsch fuer "Den Rahmen legen wir gemeinsam fest.". Der Satz
   braucht Lesegroesse und Zeilenabstand, nicht Display-Type. */
.estimate-value {
  font-size: clamp(19px, 1.8vw, 24px);
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: -0.01em;
}

/* Kleintext auf DUNKLEN Panels.

   styles.css setzt dort --muted, und das ist ein warmes Taupe fuer helle
   Flaechen: auf Espresso gemessen 2,70:1. Aufgefallen ist es erst, als das
   Protokoll im Admin in ein dunkles Panel wanderte -- die Zeitangaben waren
   praktisch unsichtbar. Dieselbe Falle wie bei den Rechtliches-Links in der
   Seitenspalte, nur eine Stelle weiter.

   Weiss mit Deckkraft, wie bei allen anderen Nachbarn auf dunklem Grund. */
.dark-panel .activity-item small,
.dark-panel .task-item small,
.dark-panel .timeline-item small {
  color: rgba(255, 255, 255, .62);
}

/* Der Rahmen-Block folgt jetzt direkt auf die Zusammenfassung, statt per
   margin-top:auto ans untere Ende gedrueckt zu werden.

   Mit Preisen trug das Panel fuenf Zeilen und eine grosse Zahl, es war voll.
   Ohne sie sind es drei Zeilen und ein Satz -- dazwischen stand ein Loch von
   ueber 200 Pixeln, das sich als unfertig liest. Nebenbei war es die Stelle,
   an der Chromium und WebKit am weitesten auseinanderliefen: der Flug nach
   configurator.html meldete in WebKit 31,7% Bildunterschied gegen 5,7%
   vorher, weil ein frei stehender Block in zwei Engines nie auf demselben
   Pixel landet. Was aneinanderhaengt, verschiebt sich gemeinsam. */
.config-summary .estimate {
  margin-top: 44px;
  padding-top: 30px;
  border-top: 1px solid var(--line-light);
}


/* ========================================== 8 · DIE ZWEI ARBEITSFLAECHEN ====

   client.html und admin.html waren bis hierher dieselbe Seite mit anderen
   Woertern: vier Kennzahlkacheln, eine Zeitleiste, eine Aufgabenliste, eine
   Aktivitaetsliste, in genau dieser Reihenfolge, mit denselben Klassen. Wer
   beide nacheinander sah, sah zweimal dasselbe Bauteil.

   Getrennt werden sie jetzt nicht ueber die Beschriftung, sondern ueber die
   ZEIT. Der Client begleitet den Bau, der Admin den Betrieb danach:

     client.html   Waehrend wir bauen. Der Kunde liest und entscheidet.
                   Wenige grosse Bloecke, ein Phasenband als Rueckgrat,
                   Entscheidungskarten, ein Nachrichtenfaden. Erzaehlend.

     admin.html    Nachdem die Seite steht. Der Kunde betreibt sie.
                   Werkzeugleiste mit Filtern, dichte Tabellen, eine
                   Rechtematrix, Einstellungszeilen. Werkzeug.

   Die letzte Ansicht des Client heisst "Livegang" und ihr letzter Punkt
   uebergibt an den Admin. Damit sind die beiden eine Folge und keine
   Zwillinge, und die Landingpage kann in Akt 5 beide zeigen, ohne sich zu
   wiederholen.

   Jede Seite traegt sechs Ansichten in EINEM Dokument. Der Router taeuscht
   Seitenwechsel schon auf oberster Ebene; hier noch eine zweite Ebene
   daraufzusetzen hiesse, sechs Dateien je Arbeitsflaeche zu pflegen, sechs
   Standbilder in assets/pages/ zu halten und den Park des Routers mit zwoelf
   Koerpern zu fuellen. Ein Wechsel im Dokument kostet nichts davon.
   ========================================================================== */

/* --- Was die alte Fassung schief stehen liess ----------------------------- */

/* Die Kacheln waren so hoch wie die hoechste ihrer Reihe, ihr Inhalt aber
   nicht: unter der Kennzahl stand bis zu 200 Pixel Nichts, und im Admin
   endete die Rechte-Karte als leeres Rechteck neben dem Statusfluss. Eine
   Karte mit einem Loch liest sich als unfertig. Verschieden hohe Karten
   nebeneinander liest niemand als Fehler. */
.dashboard-grid { align-items: start; gap: 14px; }

/* Ein Rasterkind hat min-width: auto und kann damit nicht schmaler werden als
   sein Inhalt. Die Tabelle im Admin steht auf min-width: 540px, also stand
   bei 390 Pixel Bildschirm das GANZE Raster auf 578 und die Seite scrollte
   waagerecht: gemessen 600 statt 390, auf allen vier Ansichten mit Tabelle.
   Auffaellig war es nicht an der Tabelle, sondern an den Kennzahlkacheln
   darueber, die dieselbe Spalte teilen.

   body { overflow-x: clip } faengt das NICHT ab: es beschneidet, was
   herausragt, aber die Breite des Dokuments bleibt. min-width: 0 laesst die
   Spalte schrumpfen, und die Tabelle scrollt dann in ihrem eigenen Kasten,
   wofuer .ws-tab-wrap da ist. */
.dashboard-grid > * { min-width: 0; }

/* Display-Type gehoert auf die Landingpage. Eine Arbeitsflaeche, deren
   Ueberschrift 72 Pixel hoch ist, kostet einen halben Bildschirm, bevor
   irgendetwas dasteht, worauf man klicken koennte. */
.workspace-main .workspace-title {
  font-size: clamp(25px, 2.5vw, 36px);
  letter-spacing: -0.035em;
  line-height: 1.1;
  margin-bottom: 9px;
}
.workspace-main .workspace-subtitle {
  max-width: 68ch;
  margin-bottom: 24px;
  font-size: 14px;
}
/* Der Hinweis hing an margin-top: -20px und damit an der Unterkante der
   Unterzeile. Wer die Unterzeile kuerzt, zieht ihn in den Text hinein. */
.workspace-main .workspace-notice { margin: 0 0 26px; }

.workspace-main .metric-value { margin: 16px 0 3px; font-size: 32px; }
.workspace-main .panel h2 { font-size: 19px; letter-spacing: -0.025em; }
.workspace-main .panel-head,
.workspace-main .project-panel-head { margin-bottom: 20px; }

/* Die Kopfzeile bricht schmal in zwei Zeilen um, und der Ort stand dann
   neben den Knoepfen statt ueber ihnen. */
@media (max-width: 700px) {
  .workspace-topbar { flex-wrap: wrap; gap: 12px; }
  .workspace-topbar > div:first-child { flex: 1 0 100%; }
}


/* --- Die Ansichten -------------------------------------------------------- */

/* Ein Wechsel ohne Bewegung liest sich als Sprung, einer mit Bewegung auf
   Layout-Eigenschaften kostet Layout und Paint. Deshalb nur Deckkraft und
   ein kurzer Weg, und beides nur beim Ankommen: die gehende Ansicht ist im
   selben Frame weg, es gibt also nichts zu ueberblenden.

   Die Bewegung haengt an .ws-an und nicht an .ws-view, weil .ws-view im
   Markup steht: sonst laeuft sie auch beim AUFBAU der Seite, und dann liegt
   unter dem Ankunftsvorhang noch eine zweite Schicht, die sich bewegt. Beim
   Seitenaufbau erledigt der Vorhang die Ankunft, hier gibt es nichts
   einzublenden. shell.js setzt .ws-an nur beim Klick. */
.ws-view.ws-an { animation: ws-an 260ms cubic-bezier(0.23, 1, 0.32, 1) both; }
@keyframes ws-an {
  from { opacity: 0; transform: translateY(9px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .ws-view.ws-an { animation: none; }
}

/* Die Seitenspalte traegt jetzt Knoepfe statt Platzhalter-Links. Beide sehen
   gleich aus (styles.css stylt .workspace-nav a und button gemeinsam), aber
   ein Knopf ist ehrlich: er fuehrt nirgendwo hin, er schaltet um. Der Router
   sieht ihn gar nicht erst, und ohne data-cf-from wuerde ein Link sonst bei
   jedem Klick den Schleier des Flugs ausloesen. */
.workspace-nav button.is-active {
  background: rgba(255, 255, 255, 0.08);
  color: var(--paper);
  font-weight: 550;
}
.workspace-nav button.is-active::before {
  border-color: var(--terra);
  background: var(--terra);
}
/* Der Zaehler am Eintrag: die eine Zahl, die man in der Spalte sehen will. */
.workspace-nav .ws-zahl {
  margin-left: auto;
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--terra);
  color: #fff;
  font-family: var(--mono);
  font-size: 9px;
  line-height: 1.5;
}


/* --- Client: das Phasenband ---------------------------------------------- */

.phasen {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.phase { display: grid; gap: 9px; align-content: start; }
/* Dieselbe Grammatik wie ueberall: rundgekappter Terrakotta-Strich, Bedeutung
   "hier entlang". Im Shop gezeichnet, im Konfigurator waagerecht, in der
   Seitenspalte senkrecht, hier als Band durch die Phasen. */
.phase-bahn {
  height: var(--bahn-w);
  border-radius: 999px;
  background: color-mix(in oklab, var(--ink) 11%, transparent);
}
.phase.is-done .phase-bahn,
.phase.is-now .phase-bahn { background: var(--bahn); }
.phase strong { font-size: 13px; font-weight: 500; }
.phase small {
  display: block;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .03em;
}
.phase.is-now strong { color: var(--terra-tief); font-weight: 600; }
.phase:not(.is-done):not(.is-now) strong { color: var(--muted); }

@media (max-width: 860px) {
  .phasen { grid-auto-flow: row; gap: 14px; }
  .phase { grid-template-columns: 42px 1fr auto; align-items: center; gap: 12px; }
  .phase-bahn { height: var(--bahn-w); }
  .phase small { text-align: right; }
}


/* --- Client: der Zeitplan als senkrechte Spur ----------------------------- */

.phasenliste { margin: 0; padding: 0; list-style: none; }
.pl-item {
  display: grid;
  grid-template-columns: 22px 1fr;
  gap: 16px;
  padding-bottom: 26px;
}
.pl-item:last-child { padding-bottom: 0; }
.pl-spur { position: relative; }
.pl-spur::before {
  content: "";
  position: absolute;
  left: 9px;
  top: 18px;
  bottom: -26px;
  width: var(--bahn-w);
  border-radius: 999px;
  background: color-mix(in oklab, var(--ink) 11%, transparent);
}
.pl-item:last-child .pl-spur::before { display: none; }
.pl-item.is-done .pl-spur::before { background: var(--bahn); }
.pl-punkt {
  position: absolute;
  left: 5px;
  top: 4px;
  width: 12px;
  height: 12px;
  border: 2px solid color-mix(in oklab, var(--ink) 30%, transparent);
  border-radius: 50%;
  background: transparent;
}
.pl-item.is-done .pl-punkt { border-color: var(--bahn); background: var(--bahn); }
.pl-item.is-now .pl-punkt {
  border-color: var(--terra);
  box-shadow: 0 0 0 4px rgba(168, 72, 42, 0.16);
}
.pl-kopf {
  display: flex;
  gap: 12px;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 5px;
}
.pl-kopf strong { font-size: 15px; font-weight: 550; letter-spacing: -0.01em; }
.pl-item p { margin: 0 0 6px; max-width: 60ch; color: var(--muted); font-size: 13px; }
.pl-item .pl-meta {
  color: var(--muted);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .04em;
  text-transform: uppercase;
}


/* --- Client: Entscheidungskarten ----------------------------------------- */

/* Die Karten trennt nur ihr eigener Fuss. Ohne eine eigene Naht liest sich
   der Kopf der naechsten Freigabe wie eine Fortsetzung der vorigen. */
.entsch + .entsch {
  margin-top: 30px;
  padding-top: 30px;
  border-top: 1px solid var(--line);
}
.entsch-kopf {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 14px;
  align-items: flex-start;
  justify-content: space-between;
  margin-bottom: 10px;
}
/* Ohne nowrap bricht "Seit 3 Tagen" schmal mitten in der Pille um und die
   wird doppelt so hoch wie ihre Nachbarn. */
.entsch-kopf .status-badge { white-space: nowrap; }
.entsch-kopf h3 { margin: 3px 0 0; font-size: 19px; font-weight: 600; letter-spacing: -0.025em; }
.entsch > p { margin: 0 0 16px; max-width: 62ch; color: var(--muted); font-size: 13px; }
.entsch-optionen { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.entsch-opt {
  display: grid;
  grid-template-columns: 18px 1fr;
  gap: 11px;
  align-items: start;
  padding: 14px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--paper);
  cursor: pointer;
}
/* accent-color faerbt das native Bedienelement, ohne es nachzubauen. Ein
   nachgebautes Radio verliert Tastatur und Screenreader, und beides kostet
   hier mehr, als eine Terrakotta-Fuellung wert ist. */
.entsch-opt input { margin: 2px 0 0; accent-color: var(--terra); }
.entsch-opt strong { display: block; font-size: 13px; }
.entsch-opt small { display: block; margin-top: 3px; color: var(--muted); font-size: 11px; }
.entsch-opt:has(input:checked) { border-color: var(--terra); background: var(--acid); }
.entsch-opt:has(input:checked) small { color: var(--terra-tief); }
.entsch-fuss {
  display: flex;
  gap: 14px;
  align-items: center;
  justify-content: space-between;
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}
.entsch-fuss small {
  color: var(--muted);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .04em;
  text-transform: uppercase;
}
@media (max-width: 700px) {
  .entsch-optionen { grid-template-columns: 1fr; }
  .entsch-fuss { flex-direction: column; align-items: stretch; }
}


/* --- Client: Dateien ------------------------------------------------------ */

.dateien { margin: 0; padding: 0; list-style: none; }
.datei {
  display: grid;
  grid-template-columns: 46px 1fr auto;
  gap: 14px;
  align-items: center;
  padding: 13px 0;
  border-bottom: 1px solid var(--line);
}
.datei:last-child { border-bottom: 0; }
.datei-typ {
  display: grid;
  height: 30px;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: 7px;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: .04em;
}
.datei strong { display: block; font-size: 13px; }
.datei small { display: block; color: var(--muted); font-size: 11px; }


/* --- Client: der Nachrichtenfaden ---------------------------------------- */

.faden { display: grid; gap: 12px; margin: 0; padding: 0; list-style: none; }
.faden-msg {
  max-width: min(78%, 62ch);
  padding: 13px 16px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--paper);
}
.faden-msg.von-uns { justify-self: start; border-bottom-left-radius: 4px; }
.faden-msg.von-ihnen {
  justify-self: end;
  border-color: transparent;
  border-bottom-right-radius: 4px;
  background: var(--acid);
}
.faden-msg .faden-wer {
  display: block;
  margin-bottom: 5px;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: .05em;
  text-transform: uppercase;
}
/* --muted auf Champagner sind 4,09:1. Dieselbe Falle wie auf dem dunklen
   Panel, nur andersherum: das Taupe ist fuer Ivory gerechnet, nicht fuer
   jede helle Flaeche. --terra-tief traegt hier 4,98:1. */
.faden-msg.von-ihnen .faden-wer { color: var(--terra-tief); }
.faden-msg p { margin: 0; font-size: 13px; line-height: 1.55; }
.faden-eingabe {
  display: flex;
  gap: 10px;
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}
.faden-eingabe input {
  flex: 1;
  min-width: 0;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--paper);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
}


/* --- Client: die Checkliste zum Livegang ---------------------------------- */

.checkliste { margin: 0; padding: 0; list-style: none; }
.chk {
  display: grid;
  grid-template-columns: 20px 1fr auto;
  gap: 14px;
  align-items: center;
  padding: 14px 0;
  border-bottom: 1px solid var(--line);
}
.chk:last-child { border-bottom: 0; }
.chk-box {
  width: 18px;
  height: 18px;
  border: 2px solid color-mix(in oklab, var(--ink) 30%, transparent);
  border-radius: 5px;
}
.chk.is-done .chk-box {
  position: relative;
  border-color: var(--bahn);
  background: var(--bahn);
}
.chk.is-done .chk-box::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 5px;
  width: 4px;
  height: 8px;
  border: solid #fff;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
.chk.is-you .chk-box { border-color: var(--terra); }
.chk strong { display: block; font-size: 13px; }
.chk small { display: block; color: var(--muted); font-size: 11px; }


/* --- Admin: die Werkzeugleiste ------------------------------------------- */

.ws-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 20px;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--line);
}
.ws-filter { display: flex; flex-wrap: wrap; gap: 6px; }
.ws-chip {
  display: inline-flex;
  gap: 7px;
  align-items: center;
  padding: 7px 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: 12px;
}
.ws-chip span {
  font-family: var(--mono);
  font-size: 9px;
  opacity: .75;
}
.ws-chip.is-on {
  border-color: var(--terra);
  background: var(--terra);
  color: #fff;
}
@media (hover: hover) and (pointer: fine) {
  .ws-chip:not(.is-on):hover { border-color: var(--terra); color: var(--terra-tief); }
}
.ws-suche {
  min-width: 190px;
  padding: 8px 13px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--paper);
  color: var(--ink);
  font: inherit;
  font-size: 12px;
}
.ws-leer {
  padding: 26px 0;
  color: var(--muted);
  font-size: 13px;
  text-align: center;
}


/* --- Admin: die Tabelle --------------------------------------------------- */

/* Die Tabelle scrollt IN ihrem eigenen Kasten. Ohne das scrollt schmal die
   ganze Seite waagerecht, und dann wandert auch die Seitenspalte weg. */
.ws-tab-wrap { overflow-x: auto; }
.ws-tabelle {
  width: 100%;
  min-width: 540px;
  border-collapse: collapse;
  font-size: 13px;
}
.ws-tabelle th {
  padding: 0 12px 11px 0;
  border-bottom: 1px solid var(--line);
  color: var(--muted);
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 400;
  letter-spacing: .05em;
  text-align: left;
  text-transform: uppercase;
  white-space: nowrap;
}
.ws-tabelle td {
  padding: 13px 12px 13px 0;
  border-bottom: 1px solid var(--line);
  vertical-align: middle;
}
.ws-tabelle tr:last-child td,
.ws-tabelle tr:last-child th { border-bottom: 0; }
.ws-tabelle th:last-child,
.ws-tabelle td:last-child { padding-right: 0; text-align: right; }
.ws-tabelle strong { font-weight: 550; }
.ws-tabelle .ws-neben {
  display: block;
  margin-top: 2px;
  color: var(--muted);
  font-size: 11px;
}
.ws-tabelle .ws-datum {
  color: var(--muted);
  font-family: var(--mono);
  font-size: 11px;
  white-space: nowrap;
}


/* --- Admin: die Rechtematrix --------------------------------------------- */

/* Eine echte Kreuztabelle, weil "wer darf was" genau das ist. Als Liste
   gesetzt braucht dieselbe Aussage dreimal so viele Zeilen und man kann zwei
   Rollen nicht nebeneinander lesen. */
.ws-matrix th:not(:first-child),
.ws-matrix td:not(:first-child) { text-align: center; padding-right: 0; }

/* Die erste Zelle jeder Zeile ist ein Zeilenkopf und kein Wert: ohne
   <th scope="row"> liest ein Screenreader „ja, ja, nein" vor, ohne zu sagen,
   wozu. Aussehen soll sie trotzdem wie eine Zelle, denn .ws-tabelle th ist
   fuer den Spaltenkopf gesetzt (Mono, 10px, versalisiert). */
.ws-matrix tbody th {
  width: 42%;
  padding: 13px 12px 13px 0;
  border-bottom: 1px solid var(--line);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  white-space: normal;
}
.ws-recht {
  display: inline-block;
  width: 13px;
  height: 13px;
  border: 2px solid color-mix(in oklab, var(--ink) 26%, transparent);
  border-radius: 50%;
  vertical-align: -2px;
}
.ws-recht.ja { border-color: var(--terra); background: var(--terra); }


/* --- Admin: Einstellungen ------------------------------------------------- */

.einstell { margin: 0; }
.einstell-zeile {
  display: grid;
  grid-template-columns: 215px 1fr auto;
  gap: 18px;
  align-items: center;
  padding: 16px 0;
  border-bottom: 1px solid var(--line);
}
.einstell-zeile:last-child { border-bottom: 0; }
.einstell-zeile dt { font-size: 13px; font-weight: 550; }
.einstell-zeile dd { margin: 0; color: var(--muted); font-size: 13px; }
.einstell-zeile dd code {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--ink);
}
@media (max-width: 700px) {
  .einstell-zeile { grid-template-columns: 1fr; gap: 6px; }
  .einstell-zeile > :last-child { justify-self: start; margin-top: 6px; }
}


/* --- Die Uebergabe vom Client an den Admin -------------------------------- */

/* Der einzige Ort, an dem die eine Arbeitsflaeche auf die andere zeigt, und
   damit die Stelle, an der ihr Verhaeltnis sichtbar wird: hier hoert der Bau
   auf, dort faengt der Betrieb an.

   Das Standbild traegt data-cf-from, der Klick fliegt also wie von jeder
   anderen Oberflaeche. 1440x900 ist 16:10 wie der Bildschirm, der Flug
   quetscht auf dem Weg also nichts. */
.ws-uebergabe-text {
  margin: 0 0 18px;
  color: rgba(255, 255, 255, .72);
  font-size: 13px;
  line-height: 1.6;
}
.ws-uebergabe {
  display: block;
  color: inherit;
  text-decoration: none;
}
.ws-vorschau {
  display: block;
  overflow: hidden;
  aspect-ratio: 16 / 10;
  border: 1px solid var(--line-light);
  border-radius: 10px;
  background: var(--paper);
}
.ws-vorschau img { display: block; width: 100%; height: 100%; object-fit: cover; }
.ws-uebergabe-zeile {
  display: flex;
  gap: 10px;
  align-items: center;
  justify-content: space-between;
  margin-top: 14px;
  font-size: 13px;
  font-weight: 550;
}
.ws-pfeil { color: var(--acid); font-size: 15px; }
/* Kurzer Weg, also die schnappende Kurve, und nur auf Zeigergeraeten: sonst
   klebt der Zustand auf dem Telefon nach dem Tippen. */
@media (hover: hover) and (pointer: fine) {
  .ws-vorschau { transition: transform 220ms cubic-bezier(0.23, 1, 0.32, 1); }
  .ws-uebergabe:hover .ws-vorschau { transform: scale(1.02); }
}


/* --- Der Avatar in einer Liste ------------------------------------------- */

/* .activity-item ist ein Raster mit 12px erster Spalte, gerechnet fuer den
   Punkt. Ein Avatar ist 34 breit und lief damit UNTER den Namen daneben:
   genau die Ueberlappung, die auf beiden Arbeitsflaechen zu sehen war. Im
   alten Markup stand an jedem Avatar deshalb ein inline
   width:28px;height:28px;min-width:28px, und auch das ueberlappte noch um
   16 Pixel, nur weniger auffaellig.

   Die Spalte richtet sich jetzt nach ihrem Inhalt, statt den Inhalt in eine
   zu kleine Spalte zu zwingen. Damit koennen die zwoelf inline-Masse raus. */
.activity-item:has(.avatar) { grid-template-columns: 32px 1fr auto; }
.activity-item .avatar {
  width: 32px;
  height: 32px;
  flex-basis: 32px;
  font-size: 9px;
}

/* ============================ 12 · DREI KONTRASTE, GEMESSEN AM 31.08.2026
   Gefunden von tools/check-kontrast.mjs, der -- anders als check-shell.mjs --
   nicht eine Liste von Selektoren prueft, sondern JEDEN sichtbaren Text.
   Genau deshalb hat er sie gefunden: an keiner der drei Stellen haette
   jemand einen Selektor hingeschrieben.

   1 · Der Avatar hat eine eigene Flaeche, aber keine eigene Schriftfarbe.
        `.avatar` in styles.css setzt `background: var(--violet)` (das ist
        seit der Farbumstellung Terrakotta) und ueberlaesst `color` dem
        Kontext. In der dunklen Seitenspalte erbt er Weiss: 5,79:1, richtig.
        In einer hellen Karte erbt er --ink: 2,87:1, und dort steht er in
        client.html fuenfmal. Die Flaeche ist immer dieselbe, also gehoert
        die Schriftfarbe an das Element und nicht an seine Umgebung. */
.avatar {
  color: var(--white);
}

/* 2 · --muted ist ein Taupe fuer HELLE Flaechen, und Champagner ist keine.
       Auf .acid-panel steht es bei 3,77:1, waehrend Wert und Delta daneben
       laengst --ink tragen (10,17:1). Dieselbe Familie wie der bekannte
       Fall "--muted auf der dunklen Seitenspalte", nur andersherum.
       Gemessen: --ink mit 72% Deckkraft ergibt 5,06:1 und bleibt sichtbar
       eine Beschriftung und keine zweite Ueberschrift. */
.acid-panel .metric-label {
  color: rgba(36, 29, 22, 0.72);
}

/* 3 · Weiss mit 45% auf Espresso sind 4,40:1, also knapp darunter. Die
       Nachbarn in derselben Spalte stehen laut Hausregel auf .58 bis .65;
       .58 ergibt hier 6,40:1. Kein neuer Wert, sondern der, der ohnehin
       ueberall sonst steht. */
.config-summary .estimate-label {
  color: rgba(255, 255, 255, 0.58);
}


/* ================================ MINDESTSCHRIFTGROESSE AUF DEM TELEFON ==
   Steht bewusst am ENDE des Blattes.

   Der Block hat zuerst oben im 560px-Abschnitt gestanden und dort neun von
   siebzehn Regeln nicht durchgesetzt -- nicht wegen der Spezifitaet, die war
   gleich, sondern weil `.ws-tabelle th` und die anderen WEITER UNTEN in
   diesem selben Blatt stehen und bei Gleichstand die spaetere Regel gewinnt.
   Am Dateiende gewinnt diese hier, ohne dass irgendwo ein `!important`
   noetig waere. Wer hier etwas ergaenzt, ergaenzt es unten.
   ======================================================================== */
/* `pointer: coarse` steht bewusst NEBEN der Breite.

   Beide Regeln hier haengen nicht daran, wie breit der Bildschirm ist,
   sondern woran sie wirklich haengen: 44px braucht, wer mit dem FINGER
   zielt, und 11px braucht, wer ein Geraet in der Hand haelt. Ein iPad im
   Hochformat ist 768px breit und faellt damit durch jede Breitenregel --
   gemessen standen dort weiterhin 12 bis 44 Befunde je Seite, waehrend das
   Telefon laengst sauber war. */
@media (max-width: 560px), (pointer: coarse) {
  /* Nichts unter 11px, was gelesen werden soll.

     Auf dem Desktop sind 9 und 10px fuer eine Plakette oder eine
     Datumsangabe in Ordnung -- man sitzt einen halben Meter vom Schirm. Auf
     einem Telefon in der Hand ist 11px die Grenze, unter der Fliesstext
     nicht mehr sicher zu lesen ist (und die Vorgabe, an der sich iOS selbst
     orientiert). Gemessen standen hier Statusplaketten und Avatare auf 9px,
     Filterknoepfe, Tabellenkoepfe, Daten und der Warenkorb auf 10px.

     Die gesperrten Kapitaelchen-Zeilen sind ausgenommen und bleiben bei 10px:
     sie sind Beschriftung und tragen ihre Lesbarkeit ueber die Sperrung. */
  .status-badge, .avatar,
  .spec-value, .spec span, .product-spec span,
  .filter-button, .cart-trigger,
  .choice-meta, .workspace-notice,
  .small, .ws-datum, small, dt, th, .th,

  /* Ab hier die gemessenen Selektoren, nicht geratene.

     Der erste Entwurf stand auf `small` und `dt` als Tag -- und verlor
     stillschweigend gegen `.timeline-item small` und `.summary-row dt` aus
     styles.css, die mit (0,1,1) spezifischer sind. Die Regel stand da, sah
     richtig aus und tat nichts.

     Diese Liste stammt deshalb nicht aus dem Kopf, sondern aus einem Lauf
     ueber alle zehn Seiten, der zu jedem Text unter 11px die tatsaechlich
     GEWINNENDE Regel ausgelesen hat (CSS.getMatchedStylesForNode). Er lief
     mit aufgedeckten `hidden`-Aesten, sonst waeren zehn der zwoelf Ansichten
     von Client und Admin gar nicht dabei gewesen. */
  .timeline-item small, .task-item small, .activity-item small,
  .metric-label, .panel-label,
  .ws-tabelle th,
  .product-details span,
  .activity-item .avatar,
  .ws-chip span,
  .footer-bottom,
  .field label,
  .phase small,
  .pl-item .pl-meta,
  .datei-typ,
  .faden-msg .faden-wer,
  .summary-row dt,
  .workspace-nav .ws-zahl,
  .entsch-fuss small,
  .workspace-profile small,
  .estimate-label,
  /* Das kleine "ab" vor dem Betrag auf der Shopkarte. Es steht auf 10px und
     ist damit dieselbe Sorte gesperrte Beschriftung wie .overline daneben --
     nur dass der Betrag daneben die wichtigste Zahl der Karte ist und das
     "ab" die Aussage traegt, dass sie eine Untergrenze ist. Das darf auf
     einem Telefon nicht das sein, was man ueberliest. */
  .product-ab > span {
    font-size: 11px;
  }

  /* Und dieselbe Rechnung fuer die Tippziele im Inhalt.

     Steht aus demselben Grund hier unten: `.button` wird in styles.css
     definiert, und ein `min-height` weiter oben im Blatt haette gegen die
     dortige `height`-Angabe bei Gleichstand verloren. Gemessen standen die
     Knoepfe auf 42px, die Filterleiste des Shops auf 36 und der Warenkorb
     auf 40 -- alle knapp unter der Vorgabe aus WCAG 2.5.5, und auf einem
     Telefon sind gerade diese drei die meistbenutzten Ziele der Seite.

     `.button.small` steht ausdruecklich dabei: der Knopf "Auswaehlen" im
     Shop traegt beide Klassen, und `.button.small` (0,2,0) schlaegt ein
     blosses `.button` (0,1,0). Ohne den Zusatz stand die Regel im Blatt und
     der Knopf weiter auf 42px. */
  .button, .button.small, .filter-button, .cart-trigger {
    min-height: 44px;
  }

  /* Trefferflaechen auf 44px.

     Dieselbe Rechnung wie in der Leiste der Landingpage: die Marke war 22px
     hoch, Aktion und Menue 40px, die acht Verweise in der Fusszeile 23px.
     Alle vier liegen unter der Vorgabe aus WCAG 2.5.5, und alle vier haben
     den Platz laengst -- es fehlte nur die Hoehe am Ziel selbst. Sichtbar
     aendert sich nichts. */
  .site-header .wordmark { display: inline-flex; align-items: center; min-height: 44px; }
  .site-header .pill,
  .site-header .icon-pill { min-height: 44px; }
  .footer-col a {
    display: inline-flex; align-items: center; min-height: 44px;
    /* Ohne das reisst die Spalte um die gewonnenen Pixel auf. */
    margin-block: -6px;
  }
  .footer-brand .wordmark { display: inline-flex; align-items: center; min-height: 44px; }
}


/* ======================================== 13 · DIE KARTEN IM SHOP, ZWEITE FASSUNG

   Zwei Aenderungen, beide inhaltlich und keine am Aufbau: die Karte fuehrt
   jetzt auf eine Modulseite, und der Glyph ist auf jeder Karte ein anderer.

   Der Glyph war vorher auf allen sechs Karten identisch (drei ineinander
   gedrehte Quadrate, gezeichnet aus ::before und ::after). Sechs gleiche
   Bilder ueber sechs verschiedenen Ueberschriften sagen "das ist sechsmal
   dasselbe", und genau das ist der Satz, den die Seite nicht sagen soll.

   Der Ausweg ist der Markenname selbst: SELBER RAHMEN, ANDERER KERN. Die
   beiden aeusseren Quadrate bleiben auf allen sechs identisch, der Kern
   darin ist je Modul ein anderes Zeichen. Die Karten sehen als Reihe
   zusammengehoerig aus und einzeln unterscheidbar -- was die Marke behauptet,
   steht damit im Bild.

   Der Glyph ist SVG und nicht mehr CSS, weil ein Kern aus Kreisen, Boegen und
   Linien in ::before/::after nicht zu zeichnen ist. Die Regeln in styles.css
   fuer .package-glyph::before/::after werden hier stillgelegt statt dort
   geloescht: blockweise Eingriffe in styles.css haben schon zweimal Regeln
   mitgerissen.                                                             */

.package-glyph {
  width: clamp(150px, 16vw, 190px);
  height: clamp(150px, 16vw, 190px);
  border: 0;
  border-radius: 0;
  transform: none;
}
.package-glyph::before,
.package-glyph::after { content: none; }

.package-glyph svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}
.package-glyph .pg-frame { opacity: 0.5; }
.package-glyph .pg-core { opacity: 0.92; }

/* Die Karte als Ganzes fuehrt zur Beschreibung. Der Titel traegt den echten
   Link (damit eine Vorlesesoftware ihn mit seiner Ueberschrift bekommt), sein
   ::after deckt die Karte ab. Der Knopf darueber legt in die Auswahl und
   braucht deshalb einen eigenen Stapelplatz. */
.product-card { position: relative; }

.product-info h2 a.product-link {
  color: inherit;
  text-decoration: none;
  border: 0;
}
.product-link::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--radius-lg);
}
.product-buy { position: relative; z-index: 1; }

/* "Details ansehen" ist Beschriftung, kein zweiter Link: zwei Links
   nebeneinander auf dasselbe Ziel liest eine Vorlesesoftware zweimal vor. */
.product-more {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--terra);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
}
.product-more::after {
  content: "";
  width: 16px;
  height: 1.5px;
  background: currentColor;
  transition: width 240ms cubic-bezier(0.23, 1, 0.32, 1);
}
@media (hover: hover) and (pointer: fine) {
  .product-card:hover .product-more::after,
  .product-link:focus-visible ~ .product-more::after { width: 28px; }
}

/* Der Fokusring gehoert um die ganze Karte, nicht um die Ueberschrift: der
   Link deckt die Karte ab, und ein Ring nur um den Titel zeigt auf etwas
   anderes als das, was der Tastendruck auslaest. */
.product-link:focus-visible { outline: none; }
.product-card:has(.product-link:focus-visible) {
  outline: 2px solid var(--terra);
  outline-offset: 4px;
}

/* .cta-block traegt in styles.css `background: var(--ink)`. --muted ist ein
   warmes Taupe FUER HELLE FLAECHEN und steht auf Espresso bei 2,7:1 -- der
   Fehler, den dieses Projekt in der dunklen Seitenspalte und auf .dark-panel
   schon zweimal gemacht hat. Auf dunklem Grund gilt Weiss mit Deckkraft. */
.cta-lead {
  position: relative;   /* .cta-block::after ist eine 60vw grosse Zierform */
  z-index: 1;
  max-width: 56ch;
  margin: 20px 0 0;
  color: rgba(255, 255, 255, 0.72);
  font-size: 15.5px;
  line-height: 1.62;
}
.drawer-note {
  color: var(--muted);
  font-size: 12px;
  line-height: 1.55;
  margin: 12px 0 0;
}


/* ================================================ 14 · DIE SECHS MODULSEITEN

   Eine Seite je Modul, gleicher Aufbau, anderer Inhalt. Der Auftrag dahinter
   war ausdruecklich: jemand, der kein Fachwort kennt, soll nach dem Lesen
   wissen, was er bekommt -- nicht nur, wie lange es dauert.

   Daraus folgen drei Entscheidungen, die man dem Blatt ansieht:

   1 · SCHAUBILDER SIND HTML, NICHT SVG. Jede Beschriftung in einem Schaubild
       ist echter Text. Das kostet mehr CSS als ein gezeichnetes Bild, hat aber
       drei Gruende: check-kontrast.mjs misst JEDEN sichtbaren Text und wuerde
       Text in einem SVG nicht sehen; eine Vorlesesoftware liest ihn; und auf
       dem Telefon bricht er um, statt zu schrumpfen.

   2 · KEIN EIGENES JAVASCRIPT. Alles hier bewegt sich ueber CSS oder ueber
       .reveal, das app.js schon mitbringt. Der Router zaehlt Listener und
       rAF-Ketten und haelt sie gegen eine Grundlinie; sechs neue Seiten, die
       je eine eigene Schleife aufmachen, haetten diese Buchhaltung
       aufgeweicht, ohne dass eine einzige Bewegung es gebraucht haette.

   3 · MASSE STEHEN IN ch, NICHT IN px. Diese Seiten sind Lesestrecken. Eine
       Textspalte, die an der Bildschirmbreite haengt statt an der Zeilenlaenge,
       wird auf einem grossen Monitor unlesbar.                              */

.mod {
  --mod-sp: clamp(72px, 9vw, 130px);
  padding-bottom: 0;
}

.mod h2 {
  font-size: clamp(30px, 4.2vw, 54px);
  font-weight: 500;
  letter-spacing: -0.045em;
  line-height: 1.04;
  margin: 0;
}
.mod h3 {
  font-size: clamp(17px, 1.5vw, 20px);
  font-weight: 560;
  letter-spacing: -0.015em;
  margin: 0;
}
.mod p { margin: 0; }


/* -------------------------------------------------------------- 14.1 · KOPF */

.mod-hero {
  display: grid;
  grid-template-columns: minmax(0, 1.08fr) minmax(0, 0.92fr);
  gap: clamp(32px, 5vw, 80px);
  align-items: center;
  padding: calc(var(--header-h) + clamp(30px, 5vw, 64px)) var(--pad) var(--mod-sp);
}

.mod-back {
  display: flex;
  width: max-content;
  align-items: center;
  gap: 9px;
  margin-bottom: 26px;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  text-decoration: none;
}
.mod-back::before {
  content: "";
  width: 18px;
  height: 1.5px;
  background: var(--terra);
  transition: width 240ms cubic-bezier(0.23, 1, 0.32, 1);
}
@media (hover: hover) and (pointer: fine) {
  .mod-back:hover { color: var(--ink); }
  .mod-back:hover::before { width: 28px; }
}

.mod-hero .overline { display: block; }

.mod-hero h1 {
  margin: 10px 0 0;
  font-size: clamp(44px, 7vw, 92px);
  font-weight: 500;
  letter-spacing: -0.055em;
  line-height: 0.98;
}

.mod-lead {
  max-width: 34ch;
  margin: 22px 0 0 !important;
  font-size: clamp(17px, 1.7vw, 22px);
  line-height: 1.5;
  color: var(--ink);
}

/* Fuer wen das ist. Drei Zeilen, jede ein Satz, den jemand ueber sich selbst
   sagen wuerde. Der Haken davor ist gezeichnet und kein Zeichen aus der
   Schrift: ein Haken aus dem Zeichensatz faellt je nach Schriftschnitt
   unterschiedlich gross aus und steht dann nicht auf der Grundlinie. */
.mod-fuer {
  list-style: none;
  margin: 30px 0 0;
  padding: 0;
  display: grid;
  gap: 11px;
  max-width: 44ch;
}
.mod-fuer li {
  position: relative;
  padding-left: 30px;
  color: var(--muted);
  font-size: 14.5px;
  line-height: 1.5;
}
.mod-fuer li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 6px;
  width: 11px;
  height: 6px;
  border-left: 2px solid var(--terra);
  border-bottom: 2px solid var(--terra);
  transform: rotate(-45deg);
}

.mod-hero__tun {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 18px;
  margin-top: 38px;
}
.mod-hero__tun > span {
  color: var(--muted);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* Das Kopfbild. Derselbe Glyph wie auf der Shopkarte, nur gross und auf
   dunklem Grund -- damit die Modulseite und die Karte, ueber die man
   hergekommen ist, sichtbar dasselbe Ding sind. */
.mod-hero__bild {
  position: relative;
  display: grid;
  place-items: center;
  aspect-ratio: 1 / 1;
  margin: 0;
  border-radius: 24px;
  background: var(--ink);
  color: var(--white);
  overflow: hidden;
}
.mod-hero__bild.ist-hell {
  background: var(--acid);
  color: var(--ink);
}
.mod-hero__bild .package-glyph {
  width: 62%;
  height: 62%;
}
/* Ein weicher Schein hinter dem Glyph, damit die Flaeche nicht tot wirkt.
   Reines CSS, keine Schleife: der Verlauf dreht sich sehr langsam. */
.mod-hero__bild::before {
  content: "";
  position: absolute;
  inset: -30%;
  background: radial-gradient(closest-side, color-mix(in oklab, var(--terra-soft) 42%, transparent), transparent 72%);
  animation: mod-schein 22s cubic-bezier(0.4, 0, 0.1, 1) infinite alternate;
}
@keyframes mod-schein {
  from { transform: translate(-9%, -6%) scale(0.94); }
  to   { transform: translate(9%, 7%) scale(1.08); }
}
.mod-hero__bild .package-glyph { position: relative; }


/* ---------------------------------------------------- 14.2 · AUF EINEN BLICK

   Drei Kacheln direkt unter dem Kopf: Dauer, was der Kunde selbst tut, was
   am Ende dasteht. Die mittlere ist die, die sonst nirgends steht und nach
   der jeder als Zweites fragt. */

.mod-blick {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  background: var(--paper-2);
}
.mod-blick__k {
  padding: clamp(26px, 3.2vw, 42px) var(--pad);
  border-left: 1px solid var(--line);
}
.mod-blick__k:first-child { border-left: 0; }
.mod-blick__l {
  display: block;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.mod-blick__k strong {
  display: block;
  margin: 12px 0 8px;
  font-size: clamp(20px, 2.1vw, 27px);
  font-weight: 500;
  letter-spacing: -0.035em;
  line-height: 1.12;
}
.mod-blick__k p {
  color: var(--muted);
  font-size: 13.5px;
  line-height: 1.55;
}


/* ------------------------------------------------------------ 14.3 · SCHNITT

   Ein Abschnitt: schmaler Kopf links oder oben, Inhalt darunter. Die
   Abschnittsnummer ist dieselbe Grammatik wie die sieben Ebenen auf der
   Landingpage -- Zahl, Name, Nebensatz. */

.mod-s { padding: var(--mod-sp) var(--pad); }
.mod-s + .mod-s { padding-top: 0; }

.mod-s__kopf { max-width: 60ch; margin-bottom: clamp(34px, 4vw, 58px); }
.mod-s__kopf .overline { display: block; margin-bottom: 16px; }
.mod-s__kopf p {
  margin-top: 18px !important;
  max-width: 54ch;
  color: var(--muted);
  font-size: 15.5px;
  line-height: 1.62;
}


/* ------------------------------------------------------- 14.4 · KENNEN SIE DAS

   Drei Saetze in der Sprache des Kunden, jeder mit einer Zeile darunter, die
   sagt, was technisch dahintersteckt. Das ist die Uebersetzungsleistung, um
   die es auf diesen Seiten geht, und sie steht bewusst ganz vorn. */

.mod-faelle {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 270px), 1fr));
  gap: 18px;
}
.mod-fall {
  margin: 0;
  padding: 30px 28px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: rgba(255, 255, 255, 0.4);
}
.mod-fall p {
  font-size: clamp(17px, 1.6vw, 20px);
  line-height: 1.42;
  letter-spacing: -0.02em;
}
.mod-fall cite {
  display: block;
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: 13.5px;
  font-style: normal;
  line-height: 1.55;
}
.mod-fall cite b { color: var(--ink); font-weight: 560; }


/* ------------------------------------------------------------ 14.5 · UMFANG

   Die Liste dessen, was am Ende dasteht. Nummeriert, jeder Punkt mit einem
   Satz in normalem Deutsch und einer Zeile, die ihn greifbar macht.

   Die Nummer sitzt in einer eigenen Spalte und nicht als ::before im Text:
   bei zweizeiligen Ueberschriften rutscht sie sonst mit. */

.mod-liefer {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0;
  border-top: 1px solid var(--line);
}
.mod-liefer > li {
  display: grid;
  grid-template-columns: 74px minmax(0, 1fr);
  gap: 0 10px;
  padding: 28px 0;
  border-bottom: 1px solid var(--line);
}
.mod-nr {
  color: var(--terra);
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  padding-top: 3px;
}
.mod-liefer p {
  margin-top: 10px !important;
  max-width: 62ch;
  color: var(--muted);
  font-size: 15px;
  line-height: 1.62;
}
.mod-liefer__bsp {
  margin-top: 14px !important;
  padding: 12px 16px;
  border-left: 2px solid var(--terra-soft);
  background: color-mix(in oklab, var(--acid) 34%, transparent);
  color: var(--ink) !important;
  font-size: 13.5px !important;
  line-height: 1.55;
}
.mod-liefer__bsp b { font-weight: 560; }


/* ---------------------------------------------------------- 14.6 · SCHAUBILD

   Der gezeichnete Teil. Ein Kasten in Sand, darin je nach Modul eine Reihe
   von Stationen, zwei Spalten Vorher/Nachher oder ein Seitengeruest. Alle
   Beschriftungen sind echter Text. */

.sb {
  margin: clamp(34px, 4vw, 56px) 0 0;
  padding: clamp(26px, 3vw, 42px);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--paper-2);
}
.sb figcaption,
.sb-fuss {
  margin-top: 26px;
  padding-top: 18px;
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: 13px;
  line-height: 1.6;
}
.sb-titel {
  display: block;
  margin-bottom: 26px;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* Eine Reihe von Stationen mit durchgehender Bahn dahinter. Die Bahn ist
   dieselbe Farbe und dieselbe runde Kappe wie ueberall sonst im Build.
   Auf schmalen Bildschirmen kippt die Reihe in eine Spalte und die Bahn
   dreht sich mit. */
.sb-reihe {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 10px;
  position: relative;
}
.sb-reihe::before {
  content: "";
  position: absolute;
  left: 6%;
  right: 6%;
  top: 13px;
  height: var(--bahn-w);
  border-radius: 999px;
  background: color-mix(in oklab, var(--bahn) 34%, transparent);
}
.sb-station { position: relative; padding-top: 40px; }
.sb-station::before {
  content: "";
  position: absolute;
  left: 0;
  top: 5px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: var(--bahn-w) solid var(--bahn);
  background: var(--paper-2);
}
.sb-station.ist-ziel::before { background: var(--bahn); }
.sb-station b {
  display: block;
  font-size: 14.5px;
  font-weight: 560;
  letter-spacing: -0.01em;
}
.sb-station span {
  display: block;
  margin-top: 6px;
  color: var(--muted);
  font-size: 12.5px;
  line-height: 1.5;
}
.sb-station em {
  display: block;
  margin-top: 8px;
  color: var(--terra-tief);
  font-family: var(--mono);
  font-size: 10px;
  font-style: normal;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* Zwei Spalten: was heute passiert, was danach passiert. */
.sb-zwei {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  gap: 18px;
}
.sb-sp {
  padding: 24px 22px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: color-mix(in oklab, var(--paper) 70%, transparent);
}
.sb-sp--nach {
  border-color: color-mix(in oklab, var(--terra) 40%, transparent);
  background: color-mix(in oklab, var(--acid) 40%, transparent);
}
.sb-sp > span:first-child {
  display: block;
  margin-bottom: 16px;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.sb-sp--nach > span:first-child { color: var(--terra-tief); }
.sb-sp ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 11px; }
.sb-sp li {
  position: relative;
  padding-left: 22px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink);
}
.sb-sp li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 8px;
  width: 9px;
  height: 2px;
  border-radius: 2px;
  background: var(--muted);
}
/* Der Haken hatte zuerst 9x9 und wurde damit zu einem "v": ein gedrehtes
   QUADRAT hat zwei gleich lange Schenkel, ein Haken hat einen kurzen und
   einen langen. Dieselbe Geometrie wie in .mod-fuer, wo er stimmt. */
.sb-sp--nach li::before {
  width: 11px;
  height: 6px;
  top: 6px;
  background: none;
  border-radius: 0;
  border-left: 2px solid var(--terra);
  border-bottom: 2px solid var(--terra);
  transform: rotate(-45deg);
}

/* Ein Seitengeruest: der Aufbau einer Website als Bloecke untereinander. */
.sb-seite {
  display: grid;
  gap: 8px;
  padding: 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: color-mix(in oklab, var(--paper) 76%, transparent);
}
.sb-block {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 12px;
  padding: 14px 16px;
  border-radius: 6px;
  background: color-mix(in oklab, var(--ink) 6%, transparent);
}
.sb-block b { font-size: 14px; font-weight: 560; }
.sb-block span { color: var(--muted); font-size: 12.5px; }
.sb-block.ist-akzent {
  background: color-mix(in oklab, var(--acid) 52%, transparent);
  box-shadow: inset 2px 0 0 var(--terra);
}
.sb-block.ist-akzent span { color: var(--muted-tief); }

/* Eine Zaehlung: grosse Zahl, Einheit, Bezeichnung. Fuer "7 Handgriffe" und
   dergleichen. */
.sb-zahlen {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr));
  gap: 18px;
}
.sb-zahl {
  padding: 20px 18px;
  border-radius: var(--radius);
  background: color-mix(in oklab, var(--paper) 70%, transparent);
}
.sb-zahl b {
  display: block;
  font-size: clamp(28px, 3.4vw, 42px);
  font-weight: 500;
  letter-spacing: -0.05em;
  line-height: 1;
}
.sb-zahl span {
  display: block;
  margin-top: 10px;
  color: var(--muted);
  font-size: 12.5px;
  line-height: 1.5;
}
.sb-zahl.ist-akzent b { color: var(--terra-tief); }


/* ------------------------------------------------------------- 14.7 · ABLAUF

   Die Wochen oder Tage als Treppe. Jede Stufe sagt zusaetzlich, was der
   Kunde selbst tun muss -- das ist die Frage, die nach "wie lange" kommt
   und die sonst nirgends beantwortet wird. */

.mod-weg {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0;
}
.mod-weg > li {
  position: relative;
  display: grid;
  grid-template-columns: 128px minmax(0, 1fr);
  gap: 0 26px;
  padding: 0 0 34px 0;
}
.mod-weg > li:last-child { padding-bottom: 0; }
/* Die senkrechte Bahn zwischen den Stufen. */
.mod-weg > li::before {
  content: "";
  position: absolute;
  left: 5px;
  top: 20px;
  bottom: -6px;
  width: var(--bahn-w);
  border-radius: 999px;
  background: color-mix(in oklab, var(--bahn) 30%, transparent);
}
.mod-weg > li:last-child::before { display: none; }
.mod-weg__t {
  position: relative;
  padding-left: 28px;
  color: var(--terra-tief);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding-top: 2px;
}
.mod-weg__t::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--bahn);
}
.mod-weg p {
  margin-top: 10px !important;
  max-width: 60ch;
  color: var(--muted);
  font-size: 15px;
  line-height: 1.62;
}
.mod-weg__sie {
  margin-top: 14px !important;
  color: var(--ink) !important;
  font-size: 13.5px !important;
}
.mod-weg__sie b {
  color: var(--terra-tief);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin-right: 8px;
}


/* ------------------------------------------------------------ 14.8 · BEISPIEL

   Ein durchgerechneter Fall auf dunklem Grund. Dunkel, weil er die einzige
   Stelle der Seite ist, an der etwas Erfundenes steht: der Wechsel der
   Flaeche sagt "das hier ist ein Beispiel" deutlicher als jeder Hinweis.

   --muted steht auf Espresso bei 2,7:1 und ist hier verboten. Auf dunklem
   Grund gilt Weiss mit Deckkraft, wie ueberall sonst im Build.             */

.mod-bsp {
  padding: clamp(32px, 4vw, 60px);
  border-radius: var(--radius-lg);
  background: var(--ink-2);
  color: var(--white);
}
.mod-bsp__marke {
  display: inline-block;
  padding: 6px 12px;
  border-radius: 999px;
  background: var(--terra);
  color: var(--white);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.mod-bsp h2 { margin: 22px 0 0; max-width: 22ch; }
.mod-bsp__text {
  margin-top: 20px !important;
  max-width: 62ch;
  color: rgba(255, 255, 255, 0.72);
  font-size: 15.5px;
  line-height: 1.62;
}
.mod-bsp__liste {
  list-style: none;
  margin: 30px 0 0;
  padding: 0;
  display: grid;
  gap: 14px;
}
.mod-bsp__liste li {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr);
  gap: 16px;
  padding: 16px 0;
  border-top: 1px solid rgba(255, 255, 255, 0.16);
  font-size: 14.5px;
  line-height: 1.5;
}
.mod-bsp__liste b {
  color: var(--acid);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding-top: 3px;
}
.mod-bsp__fuss {
  margin-top: 30px !important;
  padding-top: 20px;
  border-top: 1px solid rgba(255, 255, 255, 0.16);
  color: rgba(255, 255, 255, 0.62);
  font-size: 12.5px;
  line-height: 1.6;
}


/* -------------------------------------------------------------- 14.9 · WORTE

   Das Fachwoerter-Verzeichnis. Es steht bewusst weit hinten: die Seite
   davor kommt ohne aus. Wer die Woerter trotzdem braucht, weil sie ihm in
   einem fremden Angebot begegnen, findet sie hier. */

.mod-worte {
  margin: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
  gap: 0 46px;
}
.mod-worte > div {
  padding: 20px 0;
  border-top: 1px solid var(--line);
}
.mod-worte dt {
  font-size: 15px;
  font-weight: 560;
  letter-spacing: -0.01em;
}
.mod-worte dd {
  margin: 8px 0 0;
  color: var(--muted);
  font-size: 14px;
  line-height: 1.6;
}


/* ------------------------------------------------------------- 14.10 · FRAGEN */

.mod-fragen { border-top: 1px solid var(--line); max-width: 78ch; }
.mod-frage { border-bottom: 1px solid var(--line); }
.mod-frage > summary {
  display: flex;
  align-items: baseline;
  gap: 18px;
  padding: 22px 0;
  cursor: pointer;
  list-style: none;
  font-size: clamp(16px, 1.4vw, 18.5px);
  font-weight: 500;
  letter-spacing: -0.015em;
}
.mod-frage > summary::-webkit-details-marker { display: none; }
.mod-frage > summary::after {
  content: "";
  flex: none;
  margin-left: auto;
  width: 13px;
  height: 13px;
  align-self: center;
  border-right: 2px solid var(--terra);
  border-bottom: 2px solid var(--terra);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform 240ms cubic-bezier(0.23, 1, 0.32, 1);
}
.mod-frage[open] > summary::after { transform: rotate(-135deg) translate(-3px, -3px); }
.mod-frage > summary:focus-visible {
  outline: 2px solid var(--terra);
  outline-offset: 3px;
}
.mod-frage p {
  max-width: 66ch;
  padding: 0 0 24px;
  color: var(--muted);
  font-size: 15px;
  line-height: 1.65;
}
.mod-frage p + p { padding-top: 14px; margin-top: -10px !important; }


/* ------------------------------------------------------- 14.11 · ANDERE MODULE

   Der Ausgang fuer den, bei dem dieses Modul nicht passt. Ohne den endet die
   Seite in einer Sackgasse, und jemand, der fast richtig gelandet ist, geht
   zurueck auf den Shop und faengt von vorn an. */

.mod-andere {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
  gap: 16px;
}
.mod-andere__k {
  display: grid;
  gap: 10px;
  padding: 26px 24px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: rgba(255, 255, 255, 0.4);
  color: var(--ink);
  text-decoration: none;
  transition: transform 240ms cubic-bezier(0.23, 1, 0.32, 1), border-color 240ms ease;
}
@media (hover: hover) and (pointer: fine) {
  .mod-andere__k:hover {
    transform: translateY(-4px);
    border-color: color-mix(in oklab, var(--terra) 46%, transparent);
  }
}
.mod-andere__k .package-glyph {
  width: 52px;
  height: 52px;
  color: var(--terra);
}
.mod-andere__k strong {
  font-size: 19px;
  font-weight: 550;
  letter-spacing: -0.025em;
}
.mod-andere__k > span:last-child {
  color: var(--muted);
  font-size: 13.5px;
  line-height: 1.55;
}


/* ------------------------------------------------------------ 14.12 · SCHMAL

   Auf dem Telefon fallen die dreispaltigen Raster in eine Spalte, und die
   waagerechte Stationenreihe wird zur senkrechten. Die Bahn dreht sich mit:
   eine Bahn, die quer liegt, waehrend die Stationen untereinander stehen,
   zeigt in die falsche Richtung.                                           */

@media (max-width: 900px) {
  .mod-hero {
    grid-template-columns: minmax(0, 1fr);
    gap: 40px;
  }
  .mod-hero__bild { order: -1; aspect-ratio: 16 / 10; }
  .mod-hero__bild .package-glyph { width: 42%; height: 82%; }
  .mod-blick { grid-template-columns: minmax(0, 1fr); }
  .mod-blick__k { border-left: 0; border-top: 1px solid var(--line); }
  .mod-blick__k:first-child { border-top: 0; }
}

@media (max-width: 700px) {
  .mod-liefer > li { grid-template-columns: 44px minmax(0, 1fr); }
  .mod-weg > li { grid-template-columns: minmax(0, 1fr); gap: 12px; }
  .mod-weg > li::before { display: none; }
  .mod-weg__t { padding-left: 24px; }
  .mod-bsp__liste li { grid-template-columns: minmax(0, 1fr); gap: 6px; }

  .sb-reihe {
    grid-auto-flow: row;
    grid-auto-columns: auto;
    gap: 22px;
    padding-left: 34px;
  }
  .sb-reihe::before {
    left: 8px;
    right: auto;
    top: 10px;
    bottom: 10px;
    width: var(--bahn-w);
    height: auto;
  }
  .sb-station { padding-top: 0; padding-left: 0; }
  .sb-station::before { left: -34px; top: 0; }
}


/* ================================================= 15 · ZUM ANFASSEN UND HILFE

   Zwei Bauteile, die aus derselben Ueberlegung entstanden sind: Die
   Modulseiten beschreiben sehr genau, was jemand bekommt, und Beschreibung
   bleibt Beschreibung. Vier der sechs Module lassen sich aber ANSEHEN, und
   zwar auf dieser Website selbst.

   Das ist kein Kunstgriff, sondern der Bestand: die Landingpage behauptet in
   Akt 5 "Eine Marke. Durch jede Oberflaeche." und zeigt vier gebaute
   Oberflaechen. Wer auf der Modulseite Kundenportal steht, kann eine Ebene
   tiefer genau so eine Arbeitsflaeche oeffnen und darin herumklicken.

   ACHTUNG, KOPPLUNG: die Standbilder in assets/pages/ haben damit einen
   zweiten Ort bekommen. Bisher wurden sie in Akt 5 und in der Klappe der
   Landingpage benutzt; jetzt auch hier. Wer eine Unterseite aendert, muss
   tools/shoot-pages.mjs laufen lassen, sonst zeigen VIER Stellen einen alten
   Stand statt zwei.                                                        */

.mod-probe {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: clamp(24px, 3vw, 44px);
  align-items: center;
  margin-top: clamp(28px, 3.4vw, 46px);
  padding: clamp(24px, 3vw, 40px);
  border: 1px solid color-mix(in oklab, var(--terra) 34%, transparent);
  border-radius: var(--radius-lg);
  background: color-mix(in oklab, var(--acid) 26%, transparent);
}
.mod-probe__bild {
  margin: 0;
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid var(--line);
  background: var(--paper);
  line-height: 0;
}
.mod-probe__bild img {
  width: 100%;
  height: auto;
  display: block;
  /* Der obere Ausschnitt: die Standbilder sind ganze Seiten im Format 16:10,
     und darunter steht in dieser Groesse nichts Lesbares mehr. */
  aspect-ratio: 16 / 10;
  object-fit: cover;
  object-position: top center;
}
.mod-probe__l {
  display: block;
  margin-bottom: 14px;
  color: var(--terra-tief);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.mod-probe h3 { margin-bottom: 12px; }
.mod-probe p {
  max-width: 46ch;
  color: var(--muted-tief);
  font-size: 14.5px;
  line-height: 1.6;
}
.mod-probe p + p { margin-top: 10px !important; }
.mod-probe .button { margin-top: 20px; }


/* Die Entscheidungshilfe im Shop. Sechs Karten nebeneinander beantworten die
   Frage "was gibt es" und nicht die Frage "was brauche ich". Die zweite ist
   die, mit der jemand ankommt.

   Bewusst als Liste von Saetzen in der ersten Person und nicht als
   Vergleichstabelle: eine Tabelle vergleicht Eigenschaften, und Eigenschaften
   kann nur vergleichen, wer die Begriffe schon kennt. Ein Satz, in dem man
   sich wiedererkennt, braucht keine Begriffe. */

.shop-hilfe {
  padding: clamp(56px, 7vw, 100px) var(--pad);
  border-top: 1px solid var(--line);
  background: var(--paper-2);
}
.shop-hilfe__kopf { max-width: 56ch; margin-bottom: clamp(28px, 3.4vw, 46px); }
.shop-hilfe__kopf .overline { display: block; margin-bottom: 14px; }
.shop-hilfe__kopf h2 {
  margin: 0;
  font-size: clamp(28px, 3.8vw, 46px);
  font-weight: 500;
  letter-spacing: -0.045em;
  line-height: 1.05;
}
.shop-hilfe__kopf p {
  margin: 16px 0 0;
  color: var(--muted-tief);
  font-size: 15.5px;
  line-height: 1.62;
}

.shop-hilfe__liste {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 330px), 1fr));
  gap: 14px;
}
.shop-hilfe__z {
  display: grid;
  gap: 12px;
  padding: 22px 24px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: color-mix(in oklab, var(--paper) 72%, transparent);
  color: var(--ink);
  text-decoration: none;
  transition: transform 240ms cubic-bezier(0.23, 1, 0.32, 1), border-color 240ms ease;
}
@media (hover: hover) and (pointer: fine) {
  .shop-hilfe__z:hover {
    transform: translateY(-3px);
    border-color: color-mix(in oklab, var(--terra) 46%, transparent);
  }
}
.shop-hilfe__z q {
  font-size: clamp(15.5px, 1.4vw, 17.5px);
  line-height: 1.45;
  letter-spacing: -0.015em;
  quotes: "„" "\201C";
}
.shop-hilfe__z span {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  color: var(--terra);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.shop-hilfe__z span::before {
  content: "";
  width: 16px;
  height: 1.5px;
  background: currentColor;
  transition: width 240ms cubic-bezier(0.23, 1, 0.32, 1);
}
@media (hover: hover) and (pointer: fine) {
  .shop-hilfe__z:hover span::before { width: 26px; }
}

@media (max-width: 820px) {
  .mod-probe { grid-template-columns: minmax(0, 1fr); }
  .mod-probe__bild { order: -1; }
}


/* ================================================== 16 · DIE SECHS AB-PREISE

   Am 29.08.2026 ist jeder Preis von dieser Seite verschwunden, und die
   Begruendung war gut: Richtwerte, Zuschlaege und ein Budgetregler haben eine
   Genauigkeit behauptet, die es bei einem zugeschnittenen Projekt nicht gibt.
   Was dabei aber mit wegfiel, war die Antwort auf die erste Frage, die jeder
   stellt. Eine Seite, die sechs Module ausfuehrlich erklaert und beim Betrag
   schweigt, verliert genau die Leute, die sich nicht trauen zu fragen.

   Der Ab-Preis ist der Mittelweg: eine Zahl, die haelt, weil sie nach unten
   begrenzt und nicht nach oben. Sie steht an vier Orten im Markup (Shopkarte,
   Rail der Landingpage, Kopf der Modulseite, die Frage "Was kostet das?") und
   an zwei Orten, an denen JavaScript sie aus einer Auswahl rechnet (Warenkorb,
   Uebersicht des Konfigurators). tools/check-module.mjs haelt alle sechs
   gegeneinander -- vorher stand dort Pruefung D "kein Preis im sichtbaren
   Text", jetzt steht dort das Gegenteil.

   Was ueberall dabeisteht: Umsatzsteuer wird nach Paragraph 19 UStG nicht
   erhoben. Das ist keine Formsache. Diese Seite spricht ausdruecklich auch
   Verbraucher an (daher widerruf.html), und ein Preis ohne die Angabe, ob
   Steuer darin steckt, ist nach der Preisangabenverordnung angreifbar. Wer
   sie weglaesst, laesst jeden Leser 19 Prozent dazurechnen, die es nicht
   gibt.                                                                    */

/* ---------------------------------------------------------- die Shopkarte */

/* `.product-info .product-ab` und nicht `.product-ab`: styles.css setzt
   `.product-info p { margin: 12px 0 20px; color: var(--muted) }`, und das ist
   mit (0,1,1) spezifischer als eine blosse Klasse. Ohne den Vorfahren stand
   der wichtigste Wert der Karte im Taupe der Beschreibung darueber, also bei
   5,4:1 statt 14,5:1, und mit einem Abstand, den nicht diese Regel bestimmt
   hat. Dieselbe Falle wie eine Zeile weiter unten bei `.mod .mod-ab`, und
   dieselbe wie bei `.timeline-item small` im Schriftgroessen-Block: die Regel
   steht da, sieht richtig aus und tut nichts. */
.product-info .product-ab {
  margin: 0 0 20px;
  color: var(--ink);
  font-size: clamp(20px, 1.7vw, 24px);
  font-weight: 550;
  letter-spacing: -0.035em;
  line-height: 1;
}

/* Das kleine "ab" traegt die ganze Aussage: ohne es waere die Zahl daneben
   eine Zusage. Es steht deshalb im Text und nicht in einem ::before, damit
   eine Vorlesesoftware es mitliest. */
.product-ab > span {
  margin-right: 5px;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 400;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  vertical-align: 0.35em;
}

.preis-hinweis {
  max-width: 74ch;
  margin: clamp(26px, 3vw, 38px) 0 0;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.6;
}

/* ------------------------------------------------------------ der Warenkorb */

/* Die zweite Zeile ist die Summe. Sie darf nicht so aussehen wie die erste,
   sonst liest sich der Drawer als Kasse. */
.cart-item span {
  color: var(--muted);
  font-size: 13px;
  white-space: nowrap;
}
.drawer-total--ab {
  margin-top: -8px;
  padding-top: 0;
  border-top: 0;
  color: var(--muted);
  font-size: 14px;
}
.drawer-total--ab strong { font-weight: 550; }

/* ------------------------------------------------------ der Kopf der Modulseite */

/* Der Preis steht zwischen "fuer wen ist das" und dem Knopf, also genau
   dort, wo entschieden wird. Der Kasten "Auf einen Blick" darunter bleibt
   bei drei Kacheln: eine vierte haette die Spalten auf 245px gedrueckt, und
   "Zwei Termine, rund 2 Stunden" passt in 27px dort nicht mehr hinein. */
/* `.mod .mod-ab` und nicht `.mod-ab`: eine Zeile weiter oben in diesem Blatt
   steht `.mod p { margin: 0 }`, und das ist mit (0,1,1) spezifischer als eine
   blosse Klasse. Gemessen stand der Abstand deshalb auf 0 und der Betrag
   klebte am letzten Aufzaehlungspunkt darueber -- dieselbe stille
   Spezifitaetsfalle wie bei `.timeline-item small` und `.summary-row dt`.
   Die Regel sah richtig aus und tat nichts. */
.mod .mod-ab {
  margin: 34px 0 0;
  font-size: clamp(30px, 3.2vw, 42px);
  font-weight: 500;
  letter-spacing: -0.045em;
  line-height: 1;
}
.mod-ab > span {
  margin-right: 9px;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  vertical-align: 0.6em;
}
.mod-ab > small {
  display: block;
  max-width: 52ch;
  margin-top: 14px;
  color: var(--muted);
  font-size: 12.5px;
  font-weight: 400;
  letter-spacing: 0;
  line-height: 1.55;
}
/* Ohne das stehen zwischen Betrag und Knopf 38 plus 14 Pixel und der Knopf
   loest sich vom Preis, zu dem er gehoert. */
.mod-ab + .mod-hero__tun { margin-top: 26px; }

/* ------------------------------------------------- die Uebersicht im Konfigurator */

/* Zwei Zustaende in einem Feld, und deshalb entscheidet der INHALT ueber die
   Groesse. styles.css setzt hier bis zu 62px: richtig fuer "ab 1.500 €",
   voellig falsch fuer "Den Rahmen legen wir gemeinsam fest." Seit dem
   Entfernen der Preise stand das pauschal auf Lesegroesse, und ein Betrag in
   19px in einer Spalte, die dafuer gebaut ist, sieht aus wie ein Versehen.
   app.js setzt die Klasse genau dann, wenn eine Zahl dasteht. */
.estimate-value.ist-betrag {
  font-size: clamp(34px, 3.4vw, 52px);
  font-weight: 500;
  line-height: 1.02;
  letter-spacing: -0.05em;
  font-variant-numeric: tabular-nums;
}
/* Dasselbe kleine "ab" wie auf der Shopkarte und im Kopf der Modulseite. Es
   in Display-Groesse mitlaufen zu lassen sah aus wie ein zweites Wort im
   Betrag. Farbe bleibt geerbt (Champagner auf Espresso): --muted waere hier
   die bekannte Falle, das warme Taupe steht auf einer dunklen Flaeche bei
   2,7:1. */
.estimate-value.ist-betrag > span {
  margin-right: 8px;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  vertical-align: 0.62em;
}


/* --------------------------------------------- die Spalte muss hineinpassen

   Gemeldet am 03.09.2026 mit einem Screenshot: mit allen sechs Funktionen
   angehakt lief der Hinweis zur Umsatzsteuer unten aus dem Bild.

   `.config-summary` ist `position: sticky` und traegt
   `height: calc(100dvh - var(--header-h))`, also eine FESTE Hoehe ohne
   Ueberlauf. Was nicht hineinpasst, steht ausserhalb und ist nicht
   erreichbar -- nicht abgeschnitten mit Scrollbalken, sondern weg. Gemessen
   passt es ab 900px Fensterhoehe, darunter fehlen bei 800px 49 und bei 700px
   149 Pixel. Ein MacBook 13" hat in Safari rund 760.

   Zwei Antworten, und beide werden gebraucht. Der Ueberlauf ist das Netz:
   nichts darf unerreichbar sein, auch nicht bei 600px und 200% Zoom. Die
   engere Staffelung ist die eigentliche Reparatur, damit das Netz im
   Normalfall gar nicht sichtbar wird -- ein Scrollbalken in einer
   Zusammenfassung liest sich, als waere sie zu lang geraten.

   Gemessen wird an der Fenster-HOEHE und nicht an der Breite: der Fehler
   haengt daran, wie viel senkrecht da ist, und ein breiter, flacher Bildschirm
   ist genau der Fall, in dem er auftritt. */
.config-summary {
  overflow-y: auto;
  overscroll-behavior: contain;
}

@media (max-height: 900px) {
  .config-summary { padding-top: 34px; padding-bottom: 34px; }
  .config-summary h2 { margin: 6px 0 26px; font-size: 27px; }
  .summary-row { padding: 11px 0; }
  .config-summary .estimate { margin-top: 26px; padding-top: 22px; }
  .estimate-value.ist-betrag { font-size: clamp(30px, 3vw, 40px); }
}

@media (max-height: 760px) {
  .config-summary { padding-top: 26px; padding-bottom: 26px; }
  .config-summary h2 { margin: 4px 0 20px; font-size: 23px; }
  .summary-row { padding: 9px 0; }
  .config-summary .estimate { margin-top: 20px; padding-top: 18px; }
  .estimate-value.ist-betrag { font-size: 34px; }
}

/* ==================================================== 17 · DER SHOP, LUFTIGER
   Pilot fuer den Premium-Durchgang, 07.09.2026.

   Der Anlass steht in CLAUDE.md unter "Der Seitenwechsel": nach dem Vergleich
   mit lusion.co war der Befund, dass das Gefuehl in den SEITEN steckt und
   nicht im Wechsel zwischen ihnen. Dort ist jeder Bildschirm ein Gedanke,
   randlos, grosse Type, und es bewegt sich immer etwas. Hier standen sechs
   Kacheln mit je sieben Bauteilen auf 5,6 Bildschirmen.

   Drei Eingriffe, und alle drei nehmen weg statt hinzuzufuegen:

     1. Der Auftakt bekommt einen GANZEN Bildschirm. Vorher teilte er sich
        einen mit der Werkzeugleiste.
     2. Die Karten verlieren ihren Kasten. Getrennt wird durch Luft, nicht
        durch Linien -- und es sind zwei Spalten statt drei, damit die Type
        Platz hat.
     3. Die drei Angaben je Karte werden EINE ruhige Zeile statt drei
        umrandeter Plaketten. Der Inhalt bleibt, die Unruhe geht.

   Was NICHT angetastet wird, weil Pruefer daran haengen: `.product-ab` und
   sein `span` (check-shell, check-module), die ERSTE Angabe in
   `.product-details` (die Dauer, check-module), `.product-link` mit href und
   Text, die beiden Glyph-Gruppen `.pg-frame` / `.pg-core`, und `.product-grid`
   selbst -- `tools/shoot-layers.mjs` fotografiert die Seite auf dessen Hoehe
   fuer Akt 4 der Landingpage. Wer hier etwas aendert, laesst danach
   shoot-layers.mjs und shoot-pages.mjs nachlaufen.                          */

/* ------------------------------------------------------------- der Auftakt */

.page-hero--weit {
  position: relative;
  min-height: calc(100svh - var(--header-h, 76px) - 40px);
  display: grid;
  align-content: center;
  gap: 0;
  padding-block: clamp(48px, 9vh, 120px);
  overflow: clip;                      /* die Marke darf nicht ueberlaufen */
}

.page-hero--weit h1 {
  max-width: 15ch;
  margin: 18px 0 0;
  font-size: clamp(40px, 6.4vw, 88px);
  line-height: 1.02;
  letter-spacing: -0.028em;
}

.page-hero--weit > p {
  max-width: 44ch;
  margin: clamp(20px, 3vh, 34px) 0 0;
  font-size: clamp(16px, 1.25vw, 19px);
  line-height: 1.62;
  color: var(--muted);
}

/* Der Einordnungs-Hinweis war ein Kasten mit Ueberschrift mitten im Auftakt.
   Als leise Fussnote sagt er dasselbe und nimmt dem Bild nichts weg. */
.page-hero--weit .concept-note {
  margin-top: clamp(26px, 4vh, 48px);
  padding: 0 0 0 14px;
  border: 0;
  border-left: 2px solid color-mix(in srgb, var(--acid) 55%, transparent);
  background: none;
  max-width: 52ch;
  display: block;
}
.page-hero--weit .concept-note strong {
  display: block;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted-tief, #655741);
  margin-bottom: 4px;
}
.page-hero--weit .concept-note span { font-size: 13.5px; line-height: 1.6; color: var(--muted); }

/* Die Marke als Koerper. Sehr gross, sehr blass, sehr langsam -- 140 Sekunden
   je Umdrehung heisst: man sieht sie nie sich bewegen und merkt trotzdem,
   dass die Seite lebt. Genau das ist der Unterschied zu einem Standbild.

   pointer-events: none ist Pflicht, sonst meldet check-mobil.mjs Block B
   jeden Text darunter als verdeckt -- der misst ueber elementFromPoint. */
.hero-mark {
  position: absolute;
  right: clamp(-140px, -6vw, -40px);
  top: 50%;
  width: clamp(360px, 46vw, 720px);
  aspect-ratio: 1;
  transform: translateY(-50%);
  color: var(--acid);
  opacity: 0.16;
  pointer-events: none;
  z-index: 0;
}
.hero-mark svg { width: 100%; height: 100%; display: block; animation: hero-dreh 140s linear infinite; }
@keyframes hero-dreh { to { transform: rotate(360deg); } }

.page-hero--weit > *:not(.hero-mark) { position: relative; z-index: 1; }

@media (prefers-reduced-motion: reduce) {
  .hero-mark svg { animation: none; }
}
@media (max-width: 860px) {
  /* Schmal steht die Marke hinter dem Text statt neben ihm, also blasser. */
  .hero-mark { right: -30%; width: 92vw; opacity: 0.10; }
}

/* ------------------------------------------------------------- die Karten */

.product-grid {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(28px, 4.6vw, 76px) clamp(28px, 4vw, 64px);
  margin-block: clamp(56px, 9vh, 132px);
}

/* Kein Kasten mehr: getrennt wird durch Luft. Der Rahmen war das, was die
   sechs Karten wie einen Katalog aussehen liess statt wie sechs Angebote. */
.product-grid .product-card {
  border: 0;
  border-radius: 0;
  background: none;
  padding: 0;
  overflow: visible;
}

.product-grid .product-visual {
  border: 0;
  border-radius: var(--r-lg, 18px);
  aspect-ratio: 16 / 10;
  margin-bottom: clamp(18px, 2.4vh, 28px);
}

.product-grid .product-info { padding: 0; }

.product-grid .product-info h2 {
  margin: 0;
  font-size: clamp(24px, 2.4vw, 34px);
  line-height: 1.1;
  letter-spacing: -0.016em;
}

.product-grid .product-info > p {
  margin: 10px 0 0;
  max-width: 38ch;
  font-size: 15.5px;
  line-height: 1.6;
  color: var(--muted);
}

/* Der Vorfahre steht mit im Selektor, sonst schlaegt `.product-info p`
   (0,1,1) die eigene Klasse und nimmt Abstand UND Farbe mit. Genau das
   ist hier schon einmal passiert, siehe CLAUDE.md. */
.product-grid .product-info .product-ab {
  margin: 16px 0 0;
  font-size: clamp(19px, 1.7vw, 24px);
  color: var(--ink);
}

/* Drei Plaketten werden eine Zeile. Der Inhalt bleibt vollstaendig -- die
   erste Angabe ist die Dauer, und check-module.mjs liest genau die. */
.product-grid .product-details {
  display: block;
  margin: 12px 0 0;
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--muted);
}
.product-grid .product-details span {
  border: 0;
  background: none;
  padding: 0;
  margin: 0;
  font-size: inherit;
  letter-spacing: 0;
  text-transform: none;
  color: inherit;
}
.product-grid .product-details span + span::before {
  content: "·";
  margin: 0 8px;
  color: color-mix(in srgb, var(--muted) 55%, transparent);
}

.product-grid .product-buy { margin-top: clamp(18px, 2.2vh, 26px); gap: 18px; align-items: center; }

/* ---------------------------------------------------- Luft zwischen allem */

.shop-toolbar { margin-block: clamp(24px, 4vh, 44px); }
.preis-hinweis { margin-block: clamp(20px, 3vh, 36px); }
.shop-hilfe { margin-block: clamp(64px, 11vh, 150px); }

/* Die sechs Saetze standen als sechs umrandete Kaesten im Dreierraster --
   dieselbe Unruhe wie bei den Karten, nur kleiner. Jetzt zwei Spalten, keine
   Rahmen, groessere Type, getrennt durch eine Haarlinie. */
.shop-hilfe__liste {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 clamp(32px, 5vw, 76px);
}
.shop-hilfe__z {
  border: 0;
  border-radius: 0;
  background: none;
  padding: clamp(20px, 3vh, 30px) 0;
  border-top: 1px solid var(--line);
}
.shop-hilfe__z q { font-size: clamp(15px, 1.4vw, 19px); line-height: 1.5; }
.shop-hilfe__kopf h2 { font-size: clamp(28px, 3.4vw, 46px); line-height: 1.08; max-width: 20ch; }
.shop-hilfe__kopf p { max-width: 46ch; }

@media (max-width: 860px) {
  .product-grid { grid-template-columns: 1fr; gap: clamp(34px, 6vh, 56px); }
  .shop-hilfe .hilfe-grid { grid-template-columns: 1fr; }
  .page-hero--weit { min-height: 0; padding-block: clamp(36px, 7vh, 64px); }
}

/* Die Bahn beginnt erst UNTER dem Auftakt.

   Mit der kleinen Ueberschrift lief sie daneben; mit einer, die einen ganzen
   Bildschirm fuellt, laeuft sie mitten hindurch -- Espresso auf Terrakotta
   ist zwar lesbar (5,6:1), sieht aber nach Versehen aus. Dieselbe Familie wie
   die Claim-Zeile, die auf der Landingpage durch die Wortmarke lief; dort war
   die Antwort ein Hof, hier ist sie eine Maske, weil ein Hof hinter vier
   Zeilen Display-Type ein grauer Klotz waere.

   Die Maske sitzt auf dem WRAPPER und nicht auf dem Pfad: auf dem Pfad
   selbst rechnet sie im viewBox-System und wandert beim Dehnen mit. */
.shop-page .bahn-wrap,
body:has(.page-hero--weit) .bahn-wrap,
body:has(.mod-hero) .bahn-wrap {
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, transparent 62vh, #000 96vh);
  mask-image: linear-gradient(to bottom, transparent 0, transparent 62vh, #000 96vh);
}

/* Und sie tritt auf dieser Seite zurueck.

   Die Bahn liegt hinter dem Inhalt, aber "DETAILS ANSEHEN" ist selbst
   Terrakotta -- Terrakotta auf Terrakotta ist rund 1:1. Dieselbe Falle wie
   auf dem Telefon, wo die Bahn deshalb ganz verschwindet (Taupe auf
   Terrakotta, 1,44:1).

   check-kontrast.mjs findet das NICHT: es misst Schrift gegen ihre eigene
   Flaeche, und die ist Papier. Zwei Dinge, die sich gegenseitig unlesbar
   machen, sind die dritte Art von Fehler, die kein Kontrastpruefer sieht --
   das steht so schon in CLAUDE.md ueber die Claim-Zeile und die Wortmarke.

   Mit dem luftigeren Raster hat die Bahn ohnehin mehr Platz. Bei 0,34 bleibt
   die Geste und der Streit hoert auf. */
body:has(.page-hero--weit) .bahn,
body:has(.mod-hero) .bahn { opacity: 0.34; }

/* Der Modulname unter jedem Satz steht jetzt auf PAPIER statt auf Sand --
   die Kaesten sind weg. Terrakotta trägt auf Sand 4,5:1 und auf Papier
   4,47:1, also knapp darunter, und check-kontrast.mjs hat beide Engines
   gemeldet. Das ist genau der Fall, für den --terra-tief da ist: dieselbe
   Farbe eine Stufe tiefer, kein zweiter Ton. Bei 10px und gesperrt gilt die
   4,5er-Grenze, nicht die 3er. */
.shop-hilfe__z span { color: var(--terra-tief); }

/* ------------------------------------- die Hervorhebung beim Darüberfahren

   Vorher lag auf `.product-card` ein Schatten (styles.css) auf einem Kasten
   mit Rundung und Polster. Als ich beides für den luftigen Aufbau entfernt
   habe, blieb der Schatten -- und zog seitdem eine hart geschnittene,
   eckige Kante direkt an der Schrift entlang. Genau das war zu sehen.

   Die Hervorhebung sitzt deshalb jetzt auf einem ::before, das nach AUSSEN
   ragt: sie bekommt Luft, ohne dass sich am Layout etwas verschiebt. Ein
   Polster auf der Karte selbst hätte das Raster verschoben und die
   Bildkacheln gegeneinander versetzt.

   isolation: isolate ist nötig, damit z-index: -1 innerhalb der Karte bleibt
   und die Fläche nicht hinter das Raster rutscht.

   Die Fläche ist Sand (--paper-2) und nicht Weiß: auf Papier ist Weiß fast
   unsichtbar. Nachgerechnet trägt --muted auf Sand 4,83:1, bleibt also über
   der Grenze -- und das muss man hier von Hand nachrechnen, weil
   check-kontrast.mjs Schrift gegen ihre EIGENE Fläche misst und eine Fläche
   dahinter gar nicht sieht. */
.product-grid .product-card { position: relative; isolation: isolate; }

.product-grid .product-card::before {
  content: "";
  position: absolute;
  inset: clamp(-32px, -2.4vw, -20px);
  border-radius: clamp(22px, 2.2vw, 34px);
  background: var(--paper-2);
  opacity: 0;
  transform: scale(0.986);
  transition: opacity 300ms var(--ease-out, cubic-bezier(0.23, 1, 0.32, 1)),
              transform 300ms var(--ease-out, cubic-bezier(0.23, 1, 0.32, 1));
  pointer-events: none;
  z-index: -1;
}

@media (hover: hover) and (pointer: fine) {
  .product-grid .product-card:hover::before { opacity: 1; transform: none; }
}
/* Mit der Tastatur muss dasselbe passieren, sonst weiß niemand, wo er ist. */
.product-grid .product-card:focus-within::before { opacity: 1; transform: none; }

/* Der Schatten und das Anheben aus styles.css sind damit erledigt: die
   Fläche macht die Arbeit, und ein Schatten ohne Rundung war ohnehin der
   Fehler. */
.product-grid .product-card:hover { transform: none; box-shadow: none; }

/* "Details ansehen" ist jetzt ein echter Verweis und kein Text mehr.

   Damit wird es zum Tippziel, und Tippziele müssen 44px hoch sein --
   check-mobil.mjs Prüfung E misst genau das. Die Schrift bleibt bei 10px
   gesperrt, die Höhe kommt aus der Mindesthöhe, nicht aus der Schrift. */
.product-grid .product-more {
  min-height: 44px;
  text-decoration: none;
  cursor: pointer;
}

/* ============================================ 18 · DIE MODULSEITEN, LUFTIGER
   Zweiter Teil des Premium-Durchgangs, 08.09.2026, nach demselben Muster wie
   der Shop.

   Der Ausgangspunkt: 13,1 Bildschirme, 12.999 Zeichen, 24 Ueberschriften, 60
   Absaetze -- und Ueberschriften, die kaum groesser sind als der Fliesstext.
   Dadurch liest sich alles gleich wichtig, und genau das ist das Gegenteil
   von premium.

   Angefasst wird KEIN Text und KEINE Struktur: check-module.mjs haelt 196
   Prueffragen an dieser Seite fest (die elf Abschnitte, die Glyph-Gruppen,
   den Betrag an Kopf und Frage, die Dauer, den erfundenen Beispielbetrieb).
   Was sich aendert, ist ausschliesslich Groesse und Abstand.               */

/* ------------------------------------------------------------- der Kopf -- */

.mod-hero {
  min-height: calc(100svh - var(--header-h, 76px) - 40px);
  align-content: center;
  padding-block: clamp(40px, 7vh, 96px);
}

.mod-hero h1 {
  font-size: clamp(44px, 7vw, 96px);
  line-height: 0.98;
  letter-spacing: -0.03em;
  margin: 14px 0 0;
}

.mod-hero .mod-lead {
  font-size: clamp(17px, 1.5vw, 22px);
  line-height: 1.55;
  max-width: 42ch;
  margin-top: clamp(18px, 2.6vh, 30px);
}

.mod-hero .mod-fuer { margin-top: clamp(20px, 3vh, 34px); }
.mod-hero .mod-fuer li { font-size: 15.5px; line-height: 1.6; }
.mod-hero__tun { margin-top: clamp(24px, 3.4vh, 40px); }

/* Die eine Sache, die auf einer Modulseite durchgehend lebt: ihr EIGENER
   Glyph. Auf dem Shop ist es die Marke, hier das Zeichen des Moduls -- der
   aeussere Rahmen dreht sich, der Kern steht still, damit das Symbol lesbar
   bleibt. 90 Sekunden je Umdrehung.

   Das Markup bleibt unberuehrt: check-module.mjs vergleicht das innerHTML von
   .pg-frame und .pg-core zwischen Shopkarte und Modulseite, und eine
   CSS-Animation aendert daran nichts. */
.mod-hero__bild .pg-frame {
  transform-box: fill-box;
  transform-origin: center;
  animation: mod-glyph 90s linear infinite;
}
@keyframes mod-glyph { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .mod-hero__bild .pg-frame { animation: none; }
}

/* ---------------------------------------------------------- die Abschnitte */

/* Luft zwischen den Abschnitten. Vorher stiessen sie aneinander, und elf
   gleich dichte Bloecke hintereinander lesen sich als eine einzige Wand. */
.mod-s { margin-block: clamp(72px, 13vh, 190px); }

.mod-s__kopf { margin-bottom: clamp(30px, 5vh, 62px); }

.mod-s__kopf h2 {
  font-size: clamp(30px, 4vw, 56px);
  line-height: 1.04;
  letter-spacing: -0.022em;
  max-width: 18ch;
  margin: 12px 0 0;
}

.mod-s__kopf p {
  font-size: clamp(16px, 1.3vw, 19px);
  line-height: 1.6;
  max-width: 52ch;
  margin-top: clamp(14px, 2vh, 22px);
}

/* Der Fliesstext in den Abschnitten stand auf 14px. Auf einer Seite, die
   13.000 Zeichen tragen soll, ist das die Groesse, an der Leute aufhoeren zu
   lesen. */
.mod-s p, .mod-s li { font-size: 16px; line-height: 1.68; }
.mod-s cite, .mod-s small { font-size: 14px; line-height: 1.6; }

@media (max-width: 860px) {
  .mod-hero { min-height: 0; padding-block: clamp(30px, 6vh, 56px); }
  .mod-s { margin-block: clamp(52px, 9vh, 96px); }
}

/* ================================== 19 · DER FOKUSSPRUNG UNTER DER LEISTE
   Gemessen am 08.09.2026: beim Wechsel auf Schritt 4 des Konfigurators stand
   die Ueberschrift bei -60px, die klebende Leiste endet bei 76px. "Wer sind
   Sie, und bis wann?" war also um 136px abgeschnitten -- man landete auf
   einem Schritt, dessen Frage man nicht lesen konnte.

   Ursache ist kein Fehler im Skript: app.js setzt den Fokus richtig auf die
   Ueberschrift, und der Browser scrollt sie pflichtgemaess ins Bild. Nur
   weiss er nichts von einer Leiste, die darueber klebt. Genau dafuer gibt es
   scroll-margin-top -- die Groesse, die einem gescrollten Ziel sagt, wieviel
   ueber ihm frei bleiben muss.

   Dieselbe Mechanik steckt in Client und Admin: shell.js setzt beim
   Ansichtswechsel den Fokus auf die h2, und dort klebt dieselbe Leiste. */
.config-step h1[tabindex="-1"],
.ws-view h2[tabindex="-1"],
.ws-view [tabindex="-1"] {
  scroll-margin-top: calc(var(--header-h, 76px) + 28px);
}

/* Die vier Rechtsverweise in der Seitenspalte brechen um.

   Sie standen in einer flex-Zeile ohne Umbruch: vier gesperrte Kennungen bei
   16px Abstand passen nicht in die Breite der dunklen Spalte, und "WIDERRUF"
   wurde an deren Kante abgeschnitten. Gemeldet vom Auftraggeber am
   08.09.2026 an client.html, und admin.html trägt dieselbe Spalte.

   Kein Prüfer hat das gesehen: check-mobil misst waagerechtes Scrollen am
   DOKUMENT, und hier lief nichts über -- der Text wurde von einem Vorfahren
   beschnitten. check-rechtliches prüft, ob die Verweise DA sind und wohin sie
   führen, nicht ob man sie lesen kann. Ein Verweis, den man nicht lesen kann,
   ist aber genau das, was in einer Fußzeile nicht passieren darf. */
.workspace-legal {
  flex-wrap: wrap;
  gap: 9px 14px;
}
