/* ==========================================================================
   Layout and components.

   Not one colour, font family or radius literal lives in this file — every
   value comes from tokens.css. That is what makes a theme switch complete
   rather than mostly complete.
   ========================================================================== */

/* --------------------------------------------------------------- 1. reset */
*, *::before, *::after { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-height) + var(--sp-4));
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--c-bg);
  color: var(--c-ink);
  font-family: var(--font-body);
  font-weight: var(--body-weight);
  font-size: var(--step-0);
  line-height: 1.65;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: clip;
}

img, svg, video { display: block; max-width: 100%; height: auto; }
button, input, select, textarea { font: inherit; color: inherit; }

/* Focus is never removed, only styled. Someone navigating by keyboard has to
   be able to see where they are. */
:focus-visible {
  outline: 2px solid var(--c-focus);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}
:focus:not(:focus-visible) { outline: none; }

.skip-link {
  position: absolute;
  left: var(--sp-4);
  top: var(--sp-2);
  z-index: 100;
  transform: translateY(-160%);
  background: var(--c-surface);
  color: var(--c-ink);
  border: var(--border-width) solid var(--c-line-strong);
  border-radius: var(--radius-sm);
  padding: var(--sp-2) var(--sp-4);
  text-decoration: none;
  transition: transform var(--dur-fast) var(--ease);
}
.skip-link:focus { transform: none; }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---------------------------------------------------------- 2. typography */
h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: var(--display-weight);
  font-variation-settings: var(--display-variation);
  letter-spacing: var(--display-tracking);
  line-height: 1.1;
  text-wrap: balance;
  margin: 0;
}
h1 { font-size: var(--step-5); }
h2 { font-size: var(--step-3); }
h3 { font-size: var(--step-1); line-height: 1.22; }
h4 { font-size: var(--step-0); line-height: 1.3; letter-spacing: 0; }

p, li { text-wrap: pretty; }
p { margin: 0 0 var(--sp-4); max-width: var(--measure); }
p:last-child { margin-bottom: 0; }

strong, b { font-weight: var(--body-weight-strong); }

a { color: var(--c-accent-ink); text-underline-offset: 0.22em; text-decoration-thickness: 1px; }
a:hover { text-decoration-thickness: 2px; }

/* Prices, percentages and the retracement marks must line up in columns. */
.num, table, .stat__value, .price__value {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.eyebrow {
  font-family: var(--font-body);
  font-size: var(--step--1);
  font-weight: var(--body-weight-strong);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--c-gold-ink);
  margin: 0 0 var(--sp-3);
}
.lede {
  font-size: var(--step-1);
  line-height: 1.5;
  color: var(--c-ink-muted);
  max-width: var(--measure-tight);
}

/* Die Unterzeile der H1. Sie erbt sonst --step-5 und die Display-Schrift von
   der h1-Regel oben - deshalb wird beides ausdrücklich zurückgesetzt, nicht
   nur die Größe. Die Werte sind absichtlich dieselben wie bei .lede: die
   Unterzeile IST der frühere Lede-Satz, nur eine Ebene höher gezogen. */
.h1__sub {
  display: block;
  font-family: var(--font-body);
  font-size: var(--step-1);
  font-weight: 400;
  font-variation-settings: normal;
  letter-spacing: normal;
  line-height: 1.5;
  color: var(--c-ink-muted);
  max-width: var(--measure-tight);
  margin-block-start: var(--sp-3);
}

/* ------------------------------------------------------------- 3. layout */
.wrap { width: min(100% - 2 * var(--gutter), var(--wrap)); margin-inline: auto; }
.wrap--narrow { width: min(100% - 2 * var(--gutter), var(--wrap-narrow)); margin-inline: auto; }

.section { padding-block: var(--sp-7); }
.section--tight { padding-block: var(--sp-6); }
.section--band { background: var(--grad-band); border-block: var(--border-width) solid var(--c-line); }
.section--surface { background: var(--c-surface); border-block: var(--border-width) solid var(--c-line); }

.section__head { margin-bottom: var(--sp-5); }
.section__head p { color: var(--c-ink-muted); }

/* Logo neben der Überschrift statt darüber: nebeneinander liest es sich als
   Marke zum Abschnitt, gestapelt als Bild mit Bildunterschrift.
   align-items: center, damit die Schnecke zur Höhe des Textblocks mittig steht -
   sie ist hochkant und ragt sonst unter die Überschrift. */
.section__head--logo {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
}
/* Zielte auf ein <picture>, das es seit dem SVG nicht mehr gibt - ohne
   flex: none wäre die Marke im Flexkasten geschrumpft. */
.section__head--logo > img { display: block; flex: none; }

/* Das VOLLE Logo, anders als die Schnecke: es trägt die Wortmarke, und die ist
   klein nicht mehr lesbar. An drei gerenderten Größen abgelesen - bei 120 px
   gedrängt, ab 200 px angenehm. Der Abschnittskopf schrumpft unter 48 rem auf
   88 px, deshalb ein eigenes Element statt des Kopfplatzes. */
.markenbild { display: block; width: min(12.5rem, 60%); height: auto; margin-inline: auto; }
.site-footer .markenbild { width: 11rem; margin-inline: 0; }
.section__logo {
  display: block;
  width: 88px;
  height: auto;
}
@media (min-width: 48rem) {
  .section__logo { width: 120px; }
}

.rule { height: 1px; border: 0; background: var(--grad-rule); margin-block: var(--sp-5); opacity: .7; }

/* Two columns where there is room, one where there is not. Used on the one-pager
   to put running text next to an offer block. */
.split { display: grid; gap: var(--sp-5); align-items: start; }
@media (min-width: 56rem) {
  .split { grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr); gap: var(--sp-6); }
}
.split > div > h3 { margin-bottom: var(--sp-3); }

/* Running text at reading width, but flush left inside a wide .wrap instead of
   centred - for a section whose text is narrow and whose cards below are wide. */
.prose--column { max-width: var(--wrap-narrow); }
.pricing--below { margin-top: var(--sp-6); }

.prose > * + * { margin-top: var(--sp-4); }
.prose h3 { margin-top: var(--sp-5); }
.prose ul { padding-left: 0; list-style: none; display: grid; gap: var(--sp-3); max-width: var(--measure); }
.prose ul li { position: relative; padding-left: var(--sp-4); }
.prose ul li::before {
  content: "";
  position: absolute; left: 0; top: 0.62em;
  width: 0.45rem; height: 0.45rem;
  border-radius: var(--radius-pill);
  background: var(--c-gold);
}

/* -------------------------------------------------------------- 4. header */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: color-mix(in oklab, var(--c-bg) 88%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: var(--border-width) solid var(--c-line);
}
@supports not (backdrop-filter: blur(1px)) {
  .site-header { background: var(--c-bg); }
}
.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  min-height: var(--header-height);
}

.brand { display: inline-flex; align-items: center; gap: var(--sp-3); text-decoration: none; color: var(--c-ink); }
.brand svg, .brand img { height: 2.1rem; width: auto; }
.brand__name { font-family: var(--font-display); font-weight: var(--display-weight); font-size: var(--step-0); letter-spacing: var(--display-tracking); line-height: 1.05; }
.brand__name span { display: block; font-family: var(--font-body); font-size: var(--step--2); font-weight: var(--body-weight); letter-spacing: 0.12em; text-transform: uppercase; color: var(--c-gold-ink); }

.nav { display: flex; align-items: center; gap: var(--sp-4); }
.nav__list { display: flex; gap: var(--sp-4); list-style: none; margin: 0; padding: 0; }
.nav__list a {
  color: var(--c-ink-muted);
  text-decoration: none;
  font-size: var(--step--1);
  padding-block: var(--sp-2);
  border-bottom: 2px solid transparent;
}
.nav__list a:hover { color: var(--c-ink); }
/* "page" marks the current document, "true" the section currently in view on
   the one-pager. Both mean "you are here" and look the same. */
.nav__list a[aria-current="page"],
.nav__list a[aria-current="true"] { color: var(--c-ink); border-bottom-color: var(--c-gold); }

.nav__toggle { display: none; }

@media (max-width: 60rem) {
  .nav__toggle {
    display: inline-flex; align-items: center; gap: var(--sp-2);
    background: none; border: var(--border-width) solid var(--c-line-strong);
    color: var(--c-ink); border-radius: var(--radius-sm);
    padding: var(--sp-2) var(--sp-3); cursor: pointer;
  }
  .nav__list {
    position: absolute;
    inset: var(--header-height) 0 auto 0;
    flex-direction: column;
    gap: 0;
    background: var(--c-surface);
    border-bottom: var(--border-width) solid var(--c-line);
    padding: var(--sp-2) var(--gutter) var(--sp-4);
    box-shadow: var(--shadow-2);
  }
  .nav__list a { display: block; padding-block: var(--sp-3); border-bottom: var(--border-width) solid var(--c-line); }
  .nav__list a[aria-current="page"],
  .nav__list a[aria-current="true"] { border-bottom-color: var(--c-gold); }

  /* [data-enhanced] is the whole point of this line. Collapsing the menu is
     only safe once site.js is there to open it again; without the attribute
     the list stays a plain, visible list. Written as .nav:not([data-open])
     this hid the entire navigation whenever JavaScript was unavailable, and
     left a visible button that could not do anything. */
  .nav[data-enhanced]:not([data-open]) .nav__list { display: none; }
}

/* ---------------------------------------------------------------- 5. hero */
/* Der obere Abstand richtet sich nach der FENSTERHOEHE, nicht nach einer festen
   Stufe. Mit var(--sp-8) - 8,5rem, der obersten der Skala - lagen bei 1280x720
   gemessene 145 px leere Fläche zwischen Kopfbereich und erster Zeile; mit den
   4,5rem des Kopfes zusammen ein Fünftel des Fensters, bevor ein Wort kommt.
   Auf einem großen Schirm ist derselbe Wert richtig, auf einem Laptop
   verschenkt er die Hälfte dessen, was ohne Scrollen zu sehen wäre.

   Die Fibonacci-Skala bleibt unangetastet: clamp hält sich zwischen zwei ihrer
   Stufen und nimmt dazwischen 9 % der Fensterhöhe - 65 px bei 720, 97 px bei
   1080, die vollen 136 px erst jenseits von 1500. Also genau dort, wo der
   großzügige Abstand gemeint war. */
.hero { position: relative; padding-block: clamp(var(--sp-5), 9svh, var(--sp-8)) var(--sp-7); overflow: hidden; }
.hero::before {
  content: "";
  position: absolute;
  inset: -40% -15% auto -15%;
  height: 150%;
  background: var(--grad-hero);
  pointer-events: none;
  z-index: 0;
}
.hero > * { position: relative; z-index: 1; }
.hero__grid { display: grid; gap: var(--sp-6); align-items: center; }
@media (min-width: 56rem) {
  .hero__grid { grid-template-columns: minmax(0, 1.25fr) minmax(0, 0.75fr); gap: var(--sp-6); }
}
/* --c-gold-display, not --c-gold-ink. This word starts at 2.60rem - 41.6 px -
   and WCAG 1.4.3 asks 3:1 of large-scale text rather than 4.5:1. On the lighter
   ground the gold cannot reach 4.5 and --c-gold-ink had to go near-white, which
   took the gold out of the one place the brand actually shows it. Here it stays
   gold at 4.00:1, comfortably past the 3:1 that applies at this size. Anything
   smaller keeps --c-gold-ink; the threshold is lower because the text is
   bigger, not because the rule was in the way. */
.hero h1 em { font-style: italic; color: var(--c-gold-display); }
/* Die zwei Zeilen unter dem Namen. Gegenüber den Grundregeln sind die Größen
   VERTAUSCHT, und das ist Absicht: seit dem 2026-09-06 trägt die Unterzeile der
   h1 den Anspruch ("Fibonacci, Regelwerk und Klarheit") und ist deshalb die
   größere, während das lede darunter die Leistung benennt und sich unterordnet.
   Ohne diese zwei Zeilen käme das Gegenteil heraus - .h1__sub ist in der
   Grundregel die kleinere von beiden.

   Drei Stufen, damit die Rangfolge zu sehen ist und nicht nur zu ahnen:
   Anspruch --step-2 in voller Tinte, Leistung --step-1 in voller Tinte,
   Fließtext --step-1 gedämpft. Die 24ch sind der Umbruch aus der früheren
   Fassung: zwei Zeilen, die zweite kürzer. */
.hero .h1__sub { font-size: var(--step-2); color: var(--c-ink); max-width: 24ch; }

/* Der Leitsatz, seit 2026-09-10 zwischen Anspruch und Leistung. Er ist bewusst
   KEINE vierte Stufe der Leiter darüber: vier Größen hintereinander sind keine
   Rangfolge mehr, sondern eine Rutsche, und die drei Stufen sind oben
   ausdrücklich als Rangfolge begründet.
   Stattdessen ein anderes REGISTER - klein, gesperrt, Versalien, Goldtinte,
   mit einer kurzen Linie davor. Damit liest er sich als Motto und nicht als
   weitere Verkaufszeile, und Anspruch → Motto → Leistung bleibt lesbar.
   Es ist dasselbe Register wie .eyebrow, das die Seite schon führt - nur ohne
   dessen Rolle als Vorzeile einer Überschrift.
   text-transform ändert nur die Darstellung: Vorlesesoftware bekommt weiterhin
   den geschriebenen Satz, nicht die Versalien. */
.hero .claim {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
  margin-top: var(--sp-4);
  font-family: var(--font-body);
  font-size: var(--step--1);
  font-weight: var(--body-weight-strong);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--c-gold-ink);
  max-width: var(--measure-tight);
}
.hero .claim::before {
  content: "";
  flex: none;
  width: var(--sp-5);
  height: var(--border-width);
  background: var(--c-gold);
  /* baseline-Ausrichtung greift bei einem leeren Kasten nicht - die Linie
     wird deshalb von Hand auf die Mitte der Versalhöhe gesetzt. */
  transform: translateY(-0.32em);
}
.hero .lede { margin-top: var(--sp-4); color: var(--c-ink); }
.hero__text p { color: var(--c-ink-muted); max-width: var(--measure-tight); }

.portrait {
  margin: 0;                    /* <figure> carries a browser default margin */
  border-radius: var(--radius-card);
  border: var(--border-width) solid var(--c-line-strong);
  overflow: hidden;
  background: var(--c-surface);
  aspect-ratio: 4 / 5;
  box-shadow: var(--shadow-2);
}
.portrait img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 22%; }
/* HIER STAND ein Deckel von 16rem auf das Porträt in "Über mich", und die
   Begründung war in sich schlüssig: einspaltig stand das Bild über dem Text,
   und ohne Deckel hätte es die volle .wrap gefüllt - bei 895px Fenster 814px
   breit. Nur war die Folge, dass es auf dem Telefon das EINZIGE kleine Bild der
   Seite war; das Hero-Foto daneben gibt 88vw an und füllt die Breite.
   Michael ist der Unterschied am 2026-09-11 aufgefallen.
   Der Deckel war das Symptom, nicht die Ursache: das Bild stand oben, WEIL es
   im Quelltext vor der Überschrift stand. Seit die Überschrift davor liegt,
   braucht es ihn nicht mehr - und beide Porträts verhalten sich gleich.

   Kein float, unverändert: dessen Zusammenspiel mit ".prose > * + *" ist ohne
   Browser nicht nachprüfbar, und ein Raster sitzt nachrechenbar richtig. Der
   Preis ist sichtbar und ehrlich - neben der unteren Hälfte des Textes bleibt
   die rechte Spalte leer. Ein Umfluss hätte das nicht, aber eben auch keine
   Gewissheit. */

/* Der Zeilenabstand des Rasters ist hier --sp-4 statt der --sp-5/--sp-6 von
   .split, und zwar aus einem nachrechenbaren Grund: zweispaltig stehen
   Überschrift und Fließtext als ZWEI Zeilen übereinander, wo sie vorher ein
   einziger Fluss waren. Dort galt zwischen h2 und erstem Absatz ".prose > * + *",
   also --sp-4. Mit row-gap: --sp-4 sieht die zweispaltige Ansicht deshalb
   unverändert aus. Die Spaltenlücke bleibt --sp-6, sie wird nicht angefasst. */
.split--portrait { row-gap: var(--sp-4); }
.split__intro > * + * { margin-top: var(--sp-4); }

/* Text links, Bild groß rechts - dieselben Anteile wie .hero__grid, damit das
   Porträt unten genauso breit steht wie oben: bei .wrap an ihrem Maximum von
   76rem sind das 436px.
   Die Platzierung ist ausdrücklich, weil die drei Kinder im Quelltext in
   LESEreihenfolge stehen (Überschrift, Bild, Text - fürs Telefon, siehe
   index.html) und zweispaltig anders liegen sollen: die beiden Textblöcke
   untereinander links, das Bild rechts über beide Zeilen.
   align-items: start kommt schon von .split - die Figur wird also nicht auf
   Texthöhe gestreckt, sondern steht oben. */
@media (min-width: 56rem) {
  .split--portrait { grid-template-columns: minmax(0, 1.25fr) minmax(0, 0.75fr); }
  .split--portrait > .split__intro { grid-column: 1; grid-row: 1; }
  .split--portrait > .prose { grid-column: 1; grid-row: 2; }
  .split--portrait > .portrait { grid-column: 2; grid-row: 1 / span 2; }
}

/* Dasselbe für die Wortmarke bei "Warum Fibonacci", seit 2026-09-10: Text
   links, Marke groß rechts. Bewusst dieselben Anteile wie .split--portrait und
   .hero__grid - dann stehen Foto oben, Marke in der Mitte und Foto unten in
   EINER Spaltenflucht, statt jedes für sich zu sitzen.
   Sie wird dabei von 200px auf dieselben 436px groß. Das kostet nichts: die
   Marke ist ein SVG, dieselbe Datei in jeder Größe.
   Anders als beim Porträt braucht es KEINE ausdrückliche Platzierung - das Bild
   steht im Quelltext nach dem Text, die automatische Platzierung setzt es damit
   von selbst in die zweite Spalte.
   Unterhalb von 56rem greift nichts davon: .split ist dort einspaltig, und die
   Marke behält ihre min(12.5rem, 60%) mittig unter dem Text. */
@media (min-width: 56rem) {
  .split--marke { grid-template-columns: minmax(0, 1.25fr) minmax(0, 0.75fr); }
  .split--marke > .markenbild { width: 100%; }
}

.portrait--placeholder {
  display: grid; place-items: center; text-align: center;
  border-style: dashed;
  color: var(--c-ink-muted);
  font-size: var(--step--1);
  padding: var(--sp-5);
}

/* ------------------------------------------------------------- 6. buttons */
.btn {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-5);
  border-radius: var(--radius-pill);
  border: var(--border-width) solid transparent;
  font-size: var(--step-0);
  font-weight: var(--body-weight-strong);
  text-decoration: none;
  cursor: pointer;
  /* Each variant says which colour it glows in; the hover rule below is then a
     single rule for all of them, instead of one per variant fighting over the
     same box-shadow at equal specificity. */
  --btn-glow: var(--c-ink);
  transition: box-shadow var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}

/* Hover glows, it does not move. The earlier translateY(-1px) shifted the
   painted box by a subpixel amount while the glyphs inside stayed snapped to
   the pixel grid until they crossed a threshold, so the box slid and the label
   followed a beat later - in both directions. Same lesson as the floating
   WhatsApp button: a hover state that changes geometry has to keep every part
   of the element in step, and light has nothing to keep in step.

   A plain shadow alone was measured at 0.017 RMSE against the resting state on
   the gold button - the client could not see it at all, and rightly so. Two
   glows, one tight and one wide, in the button's own colour, come to 0.091:
   five times as much, and still nothing that could be mistaken for the focus
   ring, which is a hard outline rather than diffuse light. */
.btn:hover {
  box-shadow: 0 0 10px 1px color-mix(in oklab, var(--btn-glow) 85%, transparent),
              0 0 30px 7px color-mix(in oklab, var(--btn-glow) 55%, transparent),
              var(--shadow-1);
}
/* Pressed without geometry either - the light goes out and the button sits back
   down on the page. */
.btn:active { box-shadow: none; }

.btn--gold { background: var(--c-gold); color: var(--c-on-gold); --btn-glow: var(--c-gold); }
.btn--gold:hover { background: color-mix(in oklab, var(--c-gold) 82%, var(--c-ink)); }

.btn--accent { background: var(--c-accent-ink); color: var(--c-on-accent); --btn-glow: var(--c-accent-ink); }
.btn--accent:hover { background: color-mix(in oklab, var(--c-accent-ink) 82%, var(--c-ink)); }

.btn--ghost { background: transparent; color: var(--c-ink); border-color: var(--c-line-strong); }
/* The outline brightens too. Filling it solid was measured at 0.29 RMSE and is
   plenty visible, but it turns an outline button into a filled one - a change
   of kind rather than of state, and it would no longer read as the quieter of
   the two buttons standing next to each other. */
.btn--ghost:hover { background: var(--c-surface); border-color: var(--c-ink); }

.btn-row { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin-top: var(--sp-5); }

/* --------------------------------------------------------------- 7. cards */
.cards { display: grid; gap: var(--sp-4); grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr)); }
.card {
  display: flex; flex-direction: column; gap: var(--sp-3);
  padding: var(--sp-5);
  background: var(--c-surface);
  border: var(--border-width) solid var(--c-line);
  border-radius: var(--radius-card);
  position: relative;
  overflow: hidden;
}
.card::after {
  content: "";
  position: absolute; inset: 0 0 auto 0; height: 2px;
  background: var(--grad-accent-line);
  opacity: 0; transition: opacity var(--dur) var(--ease);
}
.card:hover::after, .card:focus-within::after { opacity: 1; }
.card h3 { margin: 0; }
/* :not(.card__badge) is not cosmetic. ".card p" has specificity (0,1,1) and
   beats ".card__badge" at (0,1,0), so the muted ink won over --c-on-gold and
   the badge read at about 1.2:1 on the gold - declared correctly, painted
   wrongly. Excluding it here removes the conflict; raising the badge's
   specificity would only have won the next round of the same argument. */
.card p:not(.card__badge) { color: var(--c-ink-muted); margin: 0; font-size: var(--step--1); }
.card__index {
  font-family: var(--font-display);
  font-size: var(--step-2);
  color: var(--c-gold-ink);
  line-height: 1;
}

/* Cards that are entirely clickable: the link fills the card, so the whole
   surface is a target without nesting interactive elements. */
.card--link a::after { content: ""; position: absolute; inset: 0; }

/* The one card that is meant to be picked first. */
.card--featured { border-color: var(--c-gold); box-shadow: var(--shadow-1); }
.card__badge {
  align-self: start;
  margin: 0;
  padding: 0.2rem 0.7rem;
  border-radius: var(--radius-pill);
  background: var(--c-gold);
  color: var(--c-on-gold);
  font-size: var(--step--2);
  font-weight: var(--body-weight-strong);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* --------------------------------------------------------------- 8. steps */
.steps { display: grid; gap: var(--sp-4); counter-reset: step; list-style: none; padding: 0; margin: 0; }
.step {
  counter-increment: step;
  display: grid; grid-template-columns: auto 1fr; gap: var(--sp-4);
  padding-bottom: var(--sp-4);
  border-bottom: var(--border-width) solid var(--c-line);
}
.step:last-child { border-bottom: 0; padding-bottom: 0; }
.step::before {
  content: counter(step, decimal-leading-zero);
  font-family: var(--font-display);
  font-size: var(--step-1);
  color: var(--c-gold-ink);
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
}
.step h3 { margin-bottom: var(--sp-1); }
.step p { color: var(--c-ink-muted); margin: 0; }

/* ------------------------------------------------------------ 9. timeline */
.timeline { list-style: none; margin: 0; padding: 0 0 0 var(--sp-5); position: relative; display: grid; gap: var(--sp-5); }
.timeline::before {
  content: ""; position: absolute; left: 0.32rem; top: 0.5rem; bottom: 0.5rem;
  width: 2px; background: var(--c-line);
}
.timeline li { position: relative; }
.timeline li::before {
  content: ""; position: absolute; left: calc(-1 * var(--sp-5) + 0.05rem); top: 0.5rem;
  width: 0.75rem; height: 0.75rem; border-radius: var(--radius-pill);
  background: var(--c-gold); border: 2px solid var(--c-bg);
}
.timeline h3 { margin-bottom: var(--sp-1); }
.timeline p { color: var(--c-ink-muted); margin: 0; }
.timeline__when { display: block; font-size: var(--step--1); color: var(--c-gold-ink); font-weight: var(--body-weight-strong); }

/* ------------------------------------------------------------- 10. stats */
.stats { display: grid; gap: var(--sp-4); grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr)); }
.stat { padding: var(--sp-4) 0; border-top: 2px solid var(--c-gold); }
.stat__value { font-family: var(--font-display); font-size: var(--step-3); line-height: 1; display: block; }
.stat__label { color: var(--c-ink-muted); font-size: var(--step--1); display: block; margin-top: var(--sp-2); }

/* ------------------------------------------------------------- 11. quote */
/* ------------------------------------------------------------ 11b. Fragen
   Eine Definitionsliste statt sieben h3: Frage und Antwort gehören paarweise
   zusammen, und die Seite hat schon 23 h3 - sieben weitere verwässern die
   Gliederung, ohne dass eine Frage je Sprungziel wäre.

   JEDES PAAR STECKT IN EINEM <div>, und das ist keine Zutat, sondern die
   Bedingung für die zweite Spalte: nur ein gemeinsamer Kasten lässt sich am
   Umbrechen hindern. Vorher sollten das break-after auf dt und break-before
   auf dd leisten - beide sind in Chromium für Spaltenumbrüche nie zuverlässig
   umgesetzt worden, während break-inside seit Januar 2019 Baseline ist. Der
   <div> ist dafür ausdrücklich vorgesehen: der HTML-Standard nennt als Grund
   wörtlich "or just for styling purposes", und Adrian Roselli hat gemessen,
   dass Screenreader ihn nicht anders vorlesen als die nackte Liste.

   DER ABSTAND HÄNGT AM div UND WIRKT NACH UNTEN. Zwei Gründe, beide zählen:
   Ein margin-top am ersten Kasten der zweiten Spalte würde diese gegenüber
   der ersten nach unten versetzen, ein margin-bottom am letzten Kasten der
   ersten Spalte sieht dagegen niemand. Und er darf nicht mehr am dt hängen -
   "dt:first-of-type" traf früher genau das erste dt der Liste, jetzt wäre
   jedes dt das erste seines <div> und der Abstand fiele überall weg. */
.faq { margin: 0; max-width: var(--wrap-narrow); }
.faq > div { margin-bottom: var(--sp-5); }
.faq > div:last-child { margin-bottom: 0; }
.faq dt {
  font-weight: var(--body-weight-strong);
  color: var(--c-ink);
}
.faq dd {
  margin: var(--sp-2) 0 0;
  color: var(--c-ink-muted);
  max-width: var(--measure);
}
/* Zwei Spalten ab 62rem, weil sieben kurze Paare untereinander eine sehr lange
   Kolonne ergeben. Mehrspaltigkeit setzt einen Block-Container voraus - "Applies
   to: block containers except table wrapper boxes" -, deshalb darf auf .faq
   kein display: grid oder flex stehen. Genau daran ist es vorher gescheitert. */
@media (min-width: 62rem) {
  .faq { columns: 2; column-gap: var(--sp-6); max-width: none; }
  .faq > div { break-inside: avoid; }
}

.quote { border-left: 3px solid var(--c-gold); padding: var(--sp-2) 0 var(--sp-2) var(--sp-5); margin: 0; }
.quote p { font-family: var(--font-display); font-size: var(--step-2); line-height: 1.3; letter-spacing: var(--display-tracking); max-width: 30ch; }
.quote footer { color: var(--c-ink-muted); font-size: var(--step--1); margin-top: var(--sp-3); }

/* ----------------------------------------------------------- 12. pricing */
.pricing { display: grid; gap: var(--sp-4); grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr)); }
.price {
  display: flex; flex-direction: column; gap: var(--sp-3);
  padding: var(--sp-5);
  border: var(--border-width) solid var(--c-line);
  border-radius: var(--radius-card);
  background: var(--c-surface);
}
.price--featured { border-color: var(--c-gold); box-shadow: var(--shadow-1); }
/* baseline, not center: the unit is meant to sit on the same line the price
   stands on, the way a currency suffix does in a price list. Centring it would
   float it halfway up a number more than twice its size. */
.price__value { font-family: var(--font-display); font-size: var(--step-2); line-height: 1;
                display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--sp-2); }
.price__unit { font-family: var(--font-body); font-size: var(--step--1); font-weight: var(--body-weight);
               letter-spacing: 0; color: var(--c-ink-muted); }
.price__note { font-size: var(--step--2); color: var(--c-ink-muted); }
/* Steht unter einem Preisblock, nicht in einer Karte: die Aussage gilt für
   alle Preise darüber, nicht für einen. */
.price__hint { font-size: var(--step--2); color: var(--c-ink-muted); margin-top: var(--sp-4); }
.price ul { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-2); font-size: var(--step--1); color: var(--c-ink-muted); }
.price ul li { padding-left: var(--sp-4); position: relative; }
.price ul li::before { content: ""; position: absolute; left: 0; top: 0.6em; width: 0.4rem; height: 0.4rem; border-radius: var(--radius-pill); background: var(--c-gold); }
.price .btn { margin-top: auto; align-self: flex-start; }

/* ------------------------------------------------------------- 12b. video
   The tile is a LINK, not a button - see the comment in index.html. It has to
   look like something you press either way, hence the button-ish treatment.
   -------------------------------------------------------------------------- */
.videokachel {
  display: block;
  margin-top: auto;
  border: var(--border-width) solid var(--c-line);
  border-radius: var(--radius-card);
  overflow: hidden;
  text-decoration: none;
  color: var(--c-ink);
  background: var(--c-surface-2);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.videokachel:hover, .videokachel:focus-visible {
  border-color: var(--c-gold);
  box-shadow: 0 0 12px 1px color-mix(in oklab, var(--c-gold) 55%, transparent), var(--shadow-1);
}

/* 16:9, the shape of the video it stands for. The still and the play button
   share one grid cell rather than being stacked with position:absolute - same
   result, and the box keeps sizing itself from its contents. */
.videokachel__flaeche {
  display: grid;
  place-items: center;
  aspect-ratio: 16 / 9;
  background: var(--grad-accent-line), var(--c-surface);
  border-bottom: var(--border-width) solid var(--c-line);
  overflow: hidden;
}
/* picture is an inline element and would otherwise sit in the grid as a text
   box with a baseline gap under it, so it gets the cell and the img fills it. */
.videokachel__flaeche > * { grid-area: 1 / 1; }
.videokachel__flaeche picture { display: block; width: 100%; height: 100%; }
.videokachel__flaeche img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--dur) var(--ease), filter var(--dur) var(--ease);
}
/* Slightly dimmed so the play button keeps its contrast against whatever frame
   the video happens to open on - a still is not a background anyone chose. */
.videokachel:hover .videokachel__flaeche img,
.videokachel:focus-visible .videokachel__flaeche img { filter: brightness(1.06); }
.videokachel__flaeche svg { filter: drop-shadow(0 2px 8px rgb(0 0 0 / 0.45)); }

/* The YouTube red, and the ONLY place a literal colour is right outside
   tokens.css - the same reasoning as the WhatsApp green: it is a foreign
   brand's mark, not part of this palette, and it must not shift when the theme
   does. A viewer recognises the shape by its colour. */
.videokachel__knopf { fill: #F61C0D; }
.videokachel:hover .videokachel__knopf { fill: #FF0033; }

.videokachel__text {
  display: block;
  padding: var(--sp-3) var(--sp-4);
  font-weight: var(--body-weight-strong);
  font-size: var(--step--1);
}
.videokachel__hinweis {
  font-size: var(--step--2);
  color: var(--c-ink-muted);
  margin: var(--sp-2) 0 0;
}

/* The overlay. Height first, not width: a landscape video in a portrait window
   is limited by width, in a landscape one by height - and the second is the
   case that overflows, because a 16:9 box at 92vw is taller than the screen on
   any normal laptop. */
.videobox {
  width: min(92vw, calc((100svh - 8rem) * 16 / 9));
  max-width: 68rem;
  padding: 0;
  border: var(--border-width) solid var(--c-line-strong);
  border-radius: var(--radius-card);
  background: var(--c-bg);
  overflow: visible;
  color: var(--c-ink);
}
.videobox::backdrop { background: rgb(0 0 0 / 0.72); }
.videobox__rahmen { aspect-ratio: 16 / 9; }
.videobox__rahmen iframe { display: block; width: 100%; height: 100%; border: 0; }

/* Outside the top-right corner, because inside it would sit on the video's own
   controls. On a narrow screen there is no room outside, so it moves in. */
.videobox__zu {
  position: absolute;
  top: -0.5rem;
  right: -3.25rem;
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  border: var(--border-width) solid var(--c-line-strong);
  border-radius: var(--radius-pill);
  background: var(--c-surface);
  color: var(--c-ink);
  cursor: pointer;
}
.videobox__zu:hover { background: var(--c-surface-2); border-color: var(--c-gold); }
@media (max-width: 52rem) {
  .videobox__zu { top: 0.5rem; right: 0.5rem; }
}

/* 13 war eine zweite, ältere .faq - ein Aufklapp-Akkordeon aus <details>. Die
   sieben Regeln dafür waren tot, im ganzen HTML steht kein einziges <details>.
   Die achte war es nicht: "display: grid" traf sehr wohl die Liste in 11b, und
   weil sie später in der Datei stand als das @media dort, hat sie bei gleicher
   Spezifität gewonnen - Medienabfragen zählen für die Spezifität nicht mit. Ein
   Grid-Container ist kein Block-Container, also galt "columns: 2" nicht mehr:
   die zweite Spalte war geschrieben und hat nie funktioniert.

   Die Nummer bleibt frei. Sie neu zu vergeben hieße, alle folgenden Abschnitte
   umzunummerieren, und das wäre ein großer Diff für nichts. */

/* -------------------------------------------------------------- 14. form */
.form { display: grid; gap: var(--sp-4); max-width: 38rem; }
.field { display: grid; gap: var(--sp-2); }
.field label { font-size: var(--step--1); font-weight: var(--body-weight-strong); }
.field input, .field textarea, .field select {
  width: 100%;
  padding: var(--sp-3);
  background: var(--c-bg);
  color: var(--c-ink);
  border: var(--border-width) solid var(--c-line-strong);
  border-radius: var(--radius-sm);
}
.field textarea { min-height: 9rem; resize: vertical; }
.field input::placeholder, .field textarea::placeholder { color: var(--c-ink-muted); opacity: .8; }
.field__hint { font-size: var(--step--2); color: var(--c-ink-muted); }
.field--check { grid-template-columns: auto 1fr; align-items: start; gap: var(--sp-3); }
.field--check input { width: 1.1rem; height: 1.1rem; margin-top: 0.25rem; accent-color: var(--c-gold); }
.field--check label { font-weight: var(--body-weight); font-size: var(--step--1); }
/* The hint belongs under the label, not under the checkbox column. */
.field--check .field__hint { grid-column: 2; }

/* The honeypot. A real visitor never sees it; a naive bot fills it in and the
   submission can be discarded server-side. Hidden from assistive technology
   too, otherwise a screen reader user would be asked to fill in a trap. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* -------------------------------------------------------- 15. risk notice */
.risk {
  border: var(--border-width) solid var(--c-line-strong);
  border-left: 4px solid var(--c-gold);
  border-radius: var(--radius-sm);
  background: var(--c-surface);
  padding: var(--sp-4);
  max-width: 74ch;
  font-size: var(--step--1);
  color: var(--c-ink);
}
.risk__label {
  display: block;
  font-size: var(--step--2);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-weight: var(--body-weight-strong);
  color: var(--c-gold-ink);
  margin-bottom: var(--sp-2);
}
.risk p { margin: 0; max-width: none; }
.risk--prominent { font-size: var(--step-0); }

.note {
  border: var(--border-width) dashed var(--c-line-strong);
  border-radius: var(--radius-sm);
  padding: var(--sp-4);
  color: var(--c-ink-muted);
  font-size: var(--step--1);
  max-width: 74ch;
}
.note strong { color: var(--c-ink); }

/* A placeholder that has to stay visible until it is filled in. */
.todo {
  background: color-mix(in oklab, var(--c-gold) 20%, transparent);
  color: var(--c-ink);
  border-bottom: 2px dotted var(--c-gold-ink);
  padding: 0 0.2em;
  font-weight: var(--body-weight-strong);
}

/* ---------------------------------------------------------------- 16. cta */
.cta {
  background: var(--grad-band);
  border-block: var(--border-width) solid var(--c-line);
  padding-block: var(--sp-7);
  text-align: center;
}
.cta h2 { max-width: 22ch; margin-inline: auto; }
.cta p { margin-inline: auto; color: var(--c-ink-muted); }
.cta .btn-row { justify-content: center; }

.whatsapp { display: inline-flex; align-items: center; gap: var(--sp-2); }
.whatsapp svg { width: 1.15em; height: 1.15em; flex: none; }

/* -------------------------------------------------- 16b · floating WhatsApp
   A plain green circle in the bottom right on every page, the shape everybody
   already knows. The ring is not decoration: no single WhatsApp green clears
   3:1 against all seven page backgrounds, and --c-ink does so in every theme by
   definition, so the ring carries the boundary contrast the fill cannot.

   Deliberately no visible label. White text on the brand green measures 1.98:1
   and would be unreadable; the logo itself is exempt from that rule, a word is
   not. The link's accessible name carries the meaning instead. */
.wa-float {
  position: fixed;
  right: clamp(0.9rem, 2.5vw, 1.75rem);
  bottom: clamp(0.9rem, 2.5vw, 1.75rem);
  z-index: 60;

  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3.5rem;
  height: 3.5rem;

  background: var(--c-whatsapp);
  color: var(--c-on-whatsapp);
  border-radius: 50%;
  box-shadow: 0 0 0 2px var(--c-ink), var(--shadow-2);
  text-decoration: none;

  transition: filter var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.wa-float svg { width: 2rem; height: 2rem; display: block; flex: none; }

/* Hover glows and brightens, and moves nothing. A translate or a scale would
   shift the button's edge against the fixed page corner - this is the one
   element whose distance to the viewport edge the eye actually measures.

   Brightness alone was too little, exactly as on the offer buttons: measured
   at 0.016 RMSE against the resting state, against 0.038 with the glow added.
   (Those figures compare candidates for THIS button only - RMSE is weighted by
   area, and a 56 px circle fills its frame quite differently from a wide
   button.)

   The ring stays first in the list and unchanged, in both states: it carries
   the 1.4.11 boundary contrast that the green manages against only three of the
   seven page backgrounds. The glow is light around it, not a replacement. */
.wa-float:hover {
  filter: brightness(1.1);
  box-shadow: 0 0 0 2px var(--c-ink),
              0 0 12px 2px color-mix(in oklab, var(--c-whatsapp) 85%, transparent),
              0 0 34px 8px color-mix(in oklab, var(--c-whatsapp) 55%, transparent),
              var(--shadow-2);
}
.wa-float:focus-visible { outline: 3px solid var(--c-focus); outline-offset: 3px; }

/* A floating button that covers the last line of the footer is a nuisance on a
   phone, so the page reserves room for it. */
.site-footer { padding-bottom: calc(var(--sp-5) + 4.5rem); }
@media (min-width: 40rem) { .site-footer { padding-bottom: var(--sp-5); } }

@media print { .wa-float { display: none !important; } }

/* ------------------------------------------------------------- 17. footer */
.site-footer {
  border-top: var(--border-width) solid var(--c-line);
  padding-block: var(--sp-6) var(--sp-5);
  font-size: var(--step--1);
  color: var(--c-ink-muted);
}
/* Die 11rem sind gemessen und nicht gegriffen: die längste Beschriftung im
   Fußbereich hat 21 Zeichen ("Handeln NUR nach Plan", "Telefon: 0163 7740257")
   und braucht bei --step--1 rund 170px. 11rem sind 176px - die schmalste Spur,
   in der noch jede Beschriftung einzeilig steht.

   Vorher standen hier 14rem, und daran ist "Direkt" gescheitert: fünf Spuren
   zu 14rem plus vier Abstände zu 2rem fordern 1248px, die .wrap auch auf einem
   breiten Schirm nicht hergibt. auto-fit legte deshalb nur vier Spuren an, das
   Logo belegte die erste, und "Direkt" rutschte in die zweite Reihe. Zu sehen
   war das Gegenteil von Platzmangel: vier sehr breite Spalten mit viel Luft
   dazwischen. Genau so sieht diese Fehlerklasse aus - nicht zu wenig Platz,
   sondern eine Mindestbreite, die mehr fordert als der Inhalt braucht.

   Eine Zahl statt einer Medienabfrage, und das ist der Punkt: die Regel gilt
   für ALLE Seiten. Impressum und Datenschutz haben vier Fußspalten und kein
   Logo - ein fest auf fünf Spuren geschriebenes Raster hätte sie beschädigt,
   auto-fit legt dort weiterhin vier an. Und wird der Seite je eine Spalte
   hinzugefügt, richtet es sich von allein.

   Wird es trotzdem eng, bricht auto-fit in eine zweite Reihe um - so gewollt. */
.site-footer__grid { display: grid; gap: var(--sp-5); grid-template-columns: repeat(auto-fit, minmax(min(100%, 11rem), 1fr)); }
.site-footer h2 { font-family: var(--font-body); font-size: var(--step--1); letter-spacing: 0.12em; text-transform: uppercase; color: var(--c-ink); margin-bottom: var(--sp-3); font-weight: var(--body-weight-strong); }
.site-footer ul { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-2); }
.site-footer a { color: var(--c-ink-muted); text-decoration: none; }
.site-footer a:hover { color: var(--c-ink); text-decoration: underline; }
.site-footer__legal { margin-top: var(--sp-5); padding-top: var(--sp-4); border-top: var(--border-width) solid var(--c-line); }

/* Der Eigentumsnachweis für impact.com. Eine Stufe kleiner als der übrige
   Fußbereich, sonst nichts: die FARBE bleibt --c-ink-muted, und eine zusätzliche
   Deckkraft bekommt er nicht. Ihn blasser zu machen wäre zweimal falsch - es
   nähme ihm den Kontrast, den WCAG verlangt, und es wäre der Versuch, etwas zu
   verstecken, das man einem Partnerprogramm gerade zeigen will.
   overflow-wrap statt word-break: der Wert ist 36 Zeichen ohne Leerzeichen und
   liefe auf einem schmalen Telefon sonst aus der Spalte; break-all würde dafür
   auch dort trennen, wo Platz wäre. */
.site-footer__nachweis { font-size: var(--step--2); overflow-wrap: anywhere; }

/* --------------------------------------------------- 18. pinned scrolling */
/* The base state is an ordinary list: every step visible, nothing pinned,
   nothing dimmed. That is what a visitor gets without JavaScript, with reduced
   motion switched on, on a phone, and in a short browser window. The pinned
   behaviour is added on top and only where there is genuinely room for it.

   Building it the other way round — dim by default, undim by script — was the
   original mistake here, and it fails in exactly the cases nobody tests. */
.pinned { --pin-steps: 6; }
.pinned__track { position: relative; }
.pinned__stage { display: grid; gap: var(--sp-5); }

/* No aspect-ratio here. The charts all measure 1280 x 710 and carry that ratio
   in their own width/height attributes, so the browser reserves the right box
   before the file arrives. Repeating the ratio on the frame would only be a
   second place to get it wrong the day a chart is replaced. */
.pinned__figure {
  border: var(--border-width) solid var(--c-line);
  border-radius: var(--radius-card);
  background: var(--c-surface);
  padding: var(--sp-2);
  margin: 0 0 var(--sp-4);
  overflow: hidden;
}
.pinned__figure picture { display: block; }
.pinned__figure img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: calc(var(--radius-card) - var(--sp-2));
}

.pinned__steps { display: grid; gap: var(--sp-6); list-style: none; margin: 0; padding: 0; }
.pinned__step {
  border-left: 2px solid var(--c-gold);
  padding: var(--sp-2) 0 var(--sp-2) var(--sp-4);
}
.pinned__step h3 { font-size: var(--step-1); margin-bottom: var(--sp-1); }
.pinned__step p { margin: 0; color: var(--c-ink-muted); font-size: var(--step--1); }

.pinned__count { display: none; }

/* --- the enhancement -------------------------------------------------------
   data-enhanced is set by site.js, and only when the viewport is wide AND tall
   enough and the visitor has not asked for reduced motion. Six steps of prose
   never fit on one screen together, so the pinned stage shows exactly one at a
   time — trying to show all six was what pushed the content out of its own box
   and over the heading above it.

   align-content uses "safe": a grid with a fixed height whose content grows too
   tall overflows in BOTH directions when it is centred, which is invisible and
   lands on top of whatever precedes it. "safe" falls back to start alignment
   instead of overflowing upwards. */
/* 62rem x 52rem, raised from 56 x 44 when the charts moved in. The old floor
   was set for six paragraphs of prose, which fit anywhere; a 1280x710 picture
   does not. Measured at 1280x720 - which passed the old test - the chart came
   out 618px wide and the content was taller than the stage, so "safe center"
   fell back to start alignment and every step sat at a different height: 290px
   of drift between steps.
   Below the new floor the section is an ordinary list, and there each chart is
   full-width and larger than the pinned version could ever make it. The
   enhancement is not worth having where it makes the content smaller. */
@media (min-width: 62rem) and (min-height: 52rem) {
  /* --pin-dwell is how far you scroll per step. Set it per section: on a
     one-pager where this block is one of six, a shorter dwell keeps the page
     from turning into a corridor. svh, not vh - vh jumps on phones when the
     address bar slides away. */
  .pinned[data-enhanced] .pinned__track {
    height: calc(var(--pin-steps) * var(--pin-dwell, 82svh));
    /* Wider than --wrap on purpose, and only here. The charts carry Michael's
       own annotations in small type; at the 76rem the rest of the page uses,
       the picture column came out 665px wide and the price labels were at the
       edge of legible. Measured: the height cap allows 666px, the column was
       only letting through 369 - the width was the bottleneck, not the box. */
    width: min(100% - 2 * var(--gutter), 96rem);
  }

  .pinned[data-enhanced] .pinned__stage {
    position: sticky;
    top: var(--header-height);
    height: calc(100svh - var(--header-height));
    align-content: safe center;
    gap: var(--sp-4);
    padding-block: var(--sp-4);
  }

  /* Every step in the same grid cell, so the block is as tall as the tallest
     one and never taller. visibility keeps the hidden ones out of the tab order
     and away from screen readers, which opacity alone would not. */
  .pinned[data-enhanced] .pinned__steps { display: grid; gap: 0; }

  /* Text under the chart, not beside it - the same order the base state uses.
     Beside it was the first attempt and it wasted the shape: a 16:9 picture in
     a half-width column came out 810px wide and left a 200px band of empty blue
     above it, because the step was centred in a stage twice its height.

     Stacked, the height becomes the limit instead of the width, and the height
     is what there is plenty of: 900px window minus header minus the ~16rem this
     leaves for heading, text and counter allows a 570px-tall chart - 1028px
     wide, a good 30 % more picture than the column gave.

     It also deletes the two-column rule and both of its undo blocks further
     down. One layout for every state is one thing that cannot drift apart. */
  .pinned[data-enhanced] .pinned__step {
    grid-area: 1 / 1;
    /* Full width, NOT fit-content. fit-content was here so the text would share
       the chart's left edge - a reason that disappeared when the text became
       centred, and it brought a defect with it: as soon as the picture is
       narrower than the prose (measured at 1280x720: image 472px, text 620px)
       the TEXT sets the width, and that varies per step. The chart then shifted
       8px sideways from one step to the next. Every step the same width, and
       nothing can shift. */
    width: 100%;
    opacity: 0;
    visibility: hidden;
    border-left-color: var(--c-line);
    transition: opacity var(--dur) var(--ease), border-color var(--dur) var(--ease),
                visibility 0s linear var(--dur);
  }
  .pinned[data-enhanced] .pinned__step[data-active] {
    opacity: 1;
    visibility: visible;
    border-left-color: var(--c-gold);
    transition-delay: 0s;
  }

  /* NOTHING here moves. The step used to slide up 0.75rem as it faded in, and
     in this section that reads as a fault rather than a flourish: all six
     pictures are the SAME chart in six stages, so during the crossfade the eye
     sees one graphic twice, 12px apart. The rise was then kept for the text
     alone - and that wobbled too, because six paragraphs of different length
     under a fixed picture already shift the eye enough without being animated.
     A pure crossfade turns the transition into what the section is about: the
     chart being built up, stage by stage, in place. */

  /* Centred, and without the gold rule down the left. The rule marks a list
     item, and in the pinned state there is no list to mark - one step fills the
     stage. It also pushed the whole block off-centre against the picture above
     it, which is the one thing that must not happen when the picture is the
     only fixed point on the screen. */
  .pinned[data-enhanced] .pinned__step {
    border-left: 0;
    padding-left: 0;
    text-align: center;
  }
  .pinned[data-enhanced] .pinned__text p { margin-inline: auto; }
  .pinned[data-enhanced] .pinned__figure { margin-inline: auto; width: fit-content; }

  /* The bar and the counter belong under the chart, not across the full track:
     at 96rem they ran a good 350px wider than the picture and read as a rule
     belonging to the section rather than to the step. */
  .pinned[data-enhanced] .pinned__meta {
    width: 100%;
    max-width: 64rem;
    margin-inline: auto;
    text-align: center;
  }

  /* The cap is what keeps the picture inside the pinned viewport. Without it a
     1280x710 image at full track width is 772px tall, which alone overflows the
     826px stage before a word of text is placed.

     27rem, not the 16rem this started at - raised three times, each time
     against a measurement rather than a guess. At 1440x900: 16rem put the text
     at y=892 of 900 and the "Schritt x von 6" line off the screen; 21rem fitted
     the text but not the line; 24rem still clipped the line. At 27rem the
     lowest lit pixel sits at y=880 with 17px to spare, and that holds at
     1000x850, 1200x880, 1600x1000 and 1920x1080 as well - checked across all
     six steps, because the reserve has to cover the TALLEST paragraph, not the
     average one.

     Checked that the line renders at all before making room for it: at
     1440x1600 the bar sits at y=1357, 1024px wide. Had it been missing there,
     more space would have been the wrong fix entirely. */
  .pinned[data-enhanced] .pinned__figure { margin-bottom: var(--sp-3); }
  .pinned[data-enhanced] .pinned__figure img {
    max-height: calc(100svh - var(--header-height) - 27rem);
    width: auto;
    max-width: 100%;
  }

  /* Tighter than the standalone spacing. Every pixel the counter gives back is
     a pixel of chart, and the chart is the content here. */
  .pinned[data-enhanced] .pinned__meta .progress { margin-top: var(--sp-3); }
  .pinned[data-enhanced] .pinned__count { margin-top: var(--sp-2); }

  .pinned[data-enhanced] .pinned__count {
    display: block;
    font-variant-numeric: tabular-nums;
    font-size: var(--step--1);
    color: var(--c-ink-muted);
    margin-top: var(--sp-4);
  }
  .pinned[data-enhanced] .pinned__count b { color: var(--c-gold-ink); font-weight: var(--body-weight-strong); }
}

.progress { height: 3px; background: var(--c-line); border-radius: var(--radius-pill); overflow: hidden; margin-top: var(--sp-5); }
.progress > i { display: block; height: 100%; width: 100%; background: var(--grad-accent-line); transform-origin: left; }

/* --------------------------------------------------------------- 19. motion
   Two layers. position: sticky does the pinning and works everywhere. The
   scroll-driven animations below are an enhancement on top; where they are
   missing, nothing disappears — the progress bar simply stays full rather
   than empty, because an empty bar reads as broken rather than unsupported.
   -------------------------------------------------------------------------- */
@supports (animation-timeline: scroll()) {
  .progress > i {
    transform: scaleX(0);
    animation: progress-grow linear both;
    animation-timeline: scroll(nearest block);
  }
  .reveal {
    animation: reveal-rise linear both;
    animation-timeline: view();
    animation-range: entry 6% cover 30%;
  }
  .reveal--slow { animation-range: entry 2% cover 42%; }
}

@keyframes progress-grow { to { transform: scaleX(1); } }
@keyframes reveal-rise {
  from { opacity: 0; transform: translateY(1.75rem); }
  to   { opacity: 1; transform: none; }
}

/* Reduced motion means motion goes away, not content. Every element that an
   animation would have revealed has to be fully visible and readable. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .reveal { animation: none !important; opacity: 1 !important; transform: none !important; }
  .progress > i { animation: none !important; transform: none !important; }
  /* No .btn:hover override needed any more - the button stopped moving on
     hover altogether, so there is nothing here to take back. */

  /* site.js does not set data-enhanced under reduced motion, so this should
     never be needed. It is here anyway: if the attribute ever survives — a
     cached page, a setting changed after load — the steps would otherwise sit
     on top of each other with five of them invisible. */
  .pinned[data-enhanced] .pinned__track { height: auto; }
  .pinned[data-enhanced] .pinned__stage { position: static; height: auto; display: grid; grid-template-columns: minmax(0, 1fr); }
  .pinned[data-enhanced] .pinned__steps { gap: var(--sp-6); }
  .pinned[data-enhanced] .pinned__step {
    grid-area: auto;
    /* fit-content and the centring belong to the pinned stage. Here the steps
       are an ordinary list again and take the full width. */
    width: auto;
    margin-inline: 0;
    opacity: 1 !important;
    visibility: visible !important;
    border-left-color: var(--c-gold);
  }
  .pinned[data-enhanced] .pinned__text { transform: none !important; }
  .pinned[data-enhanced] .pinned__figure { margin-bottom: var(--sp-4); }
  .pinned[data-enhanced] .pinned__figure img { max-height: none; width: 100%; }
}

/* Print: the legal pages are the ones people actually print. */
@media print {
  .site-header, .site-footer, .cta, .btn-row, .skip-link { display: none !important; }
  body { background: var(--c-print-bg); color: var(--c-print-ink); font-size: 11pt; }

  /* These need the [data-enhanced] prefix for the same reason the
     reduced-motion block does: .pinned__stage scores 0,1,0 and loses to
     .pinned[data-enhanced] .pinned__stage at 0,3,0, and specificity beats
     source order. Without it, printing in landscape - where the enhancement
     media query still matches - drops five of six steps off the page.
     visibility has to be reset too; opacity alone leaves them hidden. */
  .pinned[data-enhanced] .pinned__track { height: auto; }
  .pinned[data-enhanced] .pinned__stage { position: static; height: auto; display: block; }
  .pinned[data-enhanced] .pinned__steps { gap: var(--sp-4); }
  .pinned[data-enhanced] .pinned__step {
    grid-area: auto;
    width: auto;
    margin-inline: 0;
    opacity: 1 !important;
    visibility: visible !important;
    border-left-color: var(--c-print-ink);
  }
  .pinned[data-enhanced] .pinned__text { transform: none !important; }
  .pinned__track { height: auto; }
  .pinned__stage { position: static; height: auto; display: block; }

  /* Six full-width charts would be six pages of ink. Capped, and kept with the
     text they belong to - a chart on its own page explains nothing. */
  .pinned__figure { max-height: 8cm; margin-bottom: 0.4cm; break-inside: avoid; }
  .pinned__figure img { max-height: 8cm; width: auto; max-width: 100%; }
  .pinned__step { break-inside: avoid; }

  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 9pt; word-break: break-all; }
}
