/* ============================================================
   LooPBack
   Der Hintergrund liegt konstant hinter allem. Was im Vordergrund
   steht, ist eine Karte: fast so breit wie der Schirm, nie ganz.
   Rundherum bleibt immer der Rand stehen.
   ============================================================ */
:root {
  /* konstanter Grund */
  --bg:        #e6e4e0;
  --bg-deep:   #dcd9d4;

  /* Karten */
  --card:      #f7f6f4;
  --card-alt:  #ffffff;
  --card-ink:  #1b1a18;

  /* ---------- Die drei Schriftfarben. Es gibt keine vierte. ----------
     --ink   Der Normalfall: Fliesstext, Ueberschriften, Beschriftungen.
             Auch dort, wo frueher ein blasses Grau Nebensaechlichkeit
             ausdrueckte — das tragen jetzt Groesse, Strichstaerke und
             Laufweite (10-11px, 600, Versalien, weite Sperrung).
     --gold  ROLLE: Mass und Menge. Kicker mit Abschnittsnummer, Zahlen,
             Einheiten, Zaehler, Fortschrittswerte. Alles, was misst.
     --rot   ROLLE: aktiv und hervorgehoben. Der aktive Navigationsknopf,
             das aktive Panelbild, der Fokusring, das hervorgehobene Wort.
             Nie dekorativ, nie fuer ruhende Elemente.
     Halbdurchsichtige Farben bleiben fuer Flaechen, Raender, Schatten
     und Schleier erlaubt — die Dreierregel gilt fuer Text. */
  --ink:       #1b1a18;
  --gold:      #7d5411;
  --rot:       #a32b1f;

  --edge:      rgba(27, 26, 24, .13);
  --hair:      rgba(27, 26, 24, .09);

  /* Flaechenfarben der beiden Rollen (kein Text) */
  --gold-wash: rgba(182, 130, 53, .10);
  --rot-wash:  rgba(163, 43, 31, .10);

  /* Auftakt: der Film traegt das Bild. Der Grund ist der Studioton des Videos,
     damit der Rueckfall (Film laedt nicht) genauso aussieht wie der Film selbst. */
  --hero-ink:   #e9e6e0;              /* Studioton: Rueckfallgrund, wenn der Film nicht laeuft */
  --hero-paper: #1b1a18;              /* der Schriftzug: dunkel, wie in der abgenommenen Studie */

  /* eine einzige, gewöhnliche Schrift */
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
          "Helvetica Neue", Helvetica, Arial, sans-serif;

  /* der Rand, der überall stehen bleibt */
  --frame: clamp(12px, 1.8vw, 30px);
  --pad:   clamp(20px, 4vw, 64px);
  --r:     4px;
  --navh:  58px;
  --ease:  cubic-bezier(.22, .61, .36, 1);

  /* Auftakt-Einfahrt: 0 = randlos bildschirmfuellend, 1 = Kartengeometrie.
     Der Kopfscript setzt ihn einmal auf documentElement, danach schreibt
     app.js ihn im Takt nur noch auf .hero (siehe dort).
     Ohne JS und bei prefers-reduced-motion bleibt dieser Grundwert stehen —
     der Auftakt steht dann sofort als Karte da. */
  --einfahrt: 1;

  /* Das Festhalten waehrend des Einfahrens: der Weg, den der Auftakt an Ort
     und Stelle stehenbleibt, waehrend nur er selbst schrumpft. app.js setzt
     die Zahl (eine halbe Bildschirmhoehe) und haelt den Inhalt darunter im
     selben Mass zurueck — .stack traegt dafuer ein translate3d, das genau
     dann verschwindet, wenn das Einfahren fertig ist.
     Ohne JS und bei prefers-reduced-motion bleibt der Grundwert 0px: kein
     Festhalten, kein zusaetzlicher Weg, die Seite steht sofort als Karte da. */
  --halt: 0px;
}

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

html { scroll-behavior: smooth; }

body {
  margin: 0;
  padding: var(--frame);
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img, video, svg { display: block; max-width: 100%; }
/* Der Symbolvorrat (index.html) nimmt keinen Platz ein */
.vorrat { position: absolute; width: 0; height: 0; overflow: hidden; }
figure { margin: 0; }   /* Browser geben figure sonst 1em/40px Rand */
::selection { background: var(--gold-wash); }
:focus-visible { outline: 2px solid var(--rot); outline-offset: 3px; }

/* ---------- Karten ---------- */
.stack {
  display: flex;
  flex-direction: column;
  gap: var(--frame);
  /* die Navigation schwebt darüber; darunter bleibt der Grund sichtbar */
  padding-top: calc(var(--navh) + var(--frame));
}
.card {
  position: relative;
  background: var(--card);
  border: 1px solid var(--edge);
  border-radius: var(--r);
  overflow: hidden;
}

/* ---------- Typo ---------- */
.kicker {
  margin: 0;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--gold);
  font-variant-numeric: tabular-nums;
}

.display {
  margin: 0;
  font-weight: 400;
  line-height: 1.06;
  letter-spacing: -.022em;
  text-wrap: balance;
}

/* ============================================================
   Reveal
   ============================================================ */
.js [data-reveal] {
  opacity: 0;
  transform: translate3d(0, 22px, 0);
  transition:
    opacity .8s var(--ease) calc(var(--d, 0) * 85ms),
    transform .8s var(--ease) calc(var(--d, 0) * 85ms);
}
.js [data-reveal].in { opacity: 1; transform: translate3d(0, 0, 0); }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .js [data-reveal] { opacity: 1 !important; transform: none !important; transition: none !important; }
}

/* ============================================================
   Navigation — Markenblock links, drei schwebende Kreise und Shop rechts

   Die Leiste selbst ist unsichtbar: kein Grund, kein Rand, kein
   Schatten, kein backdrop-filter, keine Rundung. Die Optik tragen
   allein der Markenblock und die einzelnen Knoepfe. Der reservierte
   Streifen (--navh) bleibt unangetastet stehen — beide schweben darin.
   ============================================================ */
.nav {
  position: fixed;
  top: var(--frame);
  left: var(--frame);
  right: var(--frame);
  z-index: 90;

  --s:   46px;   /* Durchmesser der Kreise, zugleich Hoehe des Markenblocks */
  --gap:  9px;   /* Abstand zwischen den Kreisen */
  --wmh: 20px;   /* Hoehe der Wortmarken-BOX im Markenblock. Die Box der
                    «Stitchin»-Marke reicht von der Nadeloese bis zur
                    Nadelspitze; die Buchstaben selbst fuellen nur gut die
                    Haelfte davon. Bei 20px stehen sie gut 10px hoch, so gross
                    wie die Buchstaben der frueheren Marke. */

  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap);
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;

  /* Ausblenden am Auftakt: der Kopfscript setzt --nav-auf (0…1) einmal auf
     documentElement, im Takt schreibt app.js die Deckkraft direkt als
     style.opacity auf .nav (die schlaegt diese Regel), dazu data-nav.
     Fehlt beides — kein Script, reduzierte Bewegung, Beruehrgeraet — greift der
     Rueckfallwert 1: die Leiste bleibt sichtbar und bedienbar. */
  opacity: var(--nav-auf, 1);
  transition: opacity .35s var(--ease);

  /* Die Leiste spannt jetzt von Rand zu Rand. Als Flaeche darf sie nichts
     abfangen — nur die Knoepfe selbst nehmen den Zeiger an. */
  pointer-events: none;
}
.nav__kreise, .nb { pointer-events: auto; }
html[data-nav="aus"] .nav { pointer-events: none; }
html[data-nav="aus"] .nav__kreise,
html[data-nav="aus"] .nb { pointer-events: none; }

/* die Symbolreihe rechts */
.nav__kreise {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--gap);
  flex: 0 1 auto;
  min-width: 0;
}

/* Die Kreise liegen ueber dem Abschnittskopf: beim Springen entsprechend
   frueher halten. Wirkt nur auf scrollIntoView, nicht auf das Layout. */
/* Alle Sprungziele tragen denselben Versatz. Fehlt einer, landet der Sprung
   nativ (ohne Lenis) um --navh + --frame zu hoch, also unter der Leiste.
   scroll-margin-top verschiebt nichts im Fluss. */
#atelier, #personen, #team, #bruecke { scroll-margin-top: calc(var(--navh) + var(--frame)); }

.nb {
  position: relative;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  height: var(--s);
  border-radius: 999px;
  text-decoration: none;
  color: var(--ink);
  background: rgba(255, 255, 255, .88);
  border: 1px solid var(--edge);
  box-shadow: 0 1px 1px rgba(27, 26, 24, .04), 0 10px 22px -16px rgba(27, 26, 24, .65);
  -webkit-backdrop-filter: blur(9px) saturate(1.25);
  backdrop-filter: blur(9px) saturate(1.25);
  transition:
    background-color .40s var(--ease),
    border-color .40s var(--ease),
    box-shadow .40s var(--ease),
    color .35s var(--ease);
}

/* Die Textspalte traegt die Breite. --tw wird beim Laden aus der wirklichen
   Textbreite gemessen (exakte Fahrt, kein Leerlauf); ohne Script greift der
   grosszuegige Rueckfallwert. */
.nb__t { flex: 0 0 auto; overflow: hidden; max-width: 0; transition: max-width .58s var(--ease); }
.nb__t > span {
  display: inline-block;
  white-space: nowrap;
  padding: 0 3px 0 17px;
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: .075em;
  text-transform: uppercase;
  color: var(--ink);
  opacity: 0;
  transform: translateX(7px);
  transition: opacity .34s var(--ease) .06s, transform .52s var(--ease), color .35s var(--ease);
}
.nb__i {
  flex: 0 0 auto;
  width: var(--s); height: var(--s);
  display: grid; place-items: center;
  color: var(--ink);
  transition: color .4s var(--ease);
}
.nb__i svg { width: 20px; height: 20px; display: block; overflow: visible; }

/* ausfahren bei Maus, Tastaturfokus und im Pruef-Zustand */
.nb:hover .nb__t,
.nb:focus-visible .nb__t,
.nb.is-open .nb__t { max-width: var(--tw, 260px); }
.nb:hover, .nb:focus-visible, .nb.is-open {
  background: var(--card-alt);
  border-color: rgba(27, 26, 24, .20);
  box-shadow: 0 2px 3px rgba(27, 26, 24, .05), 0 16px 30px -18px rgba(27, 26, 24, .75);
}
.nb:hover .nb__t > span,
.nb:focus-visible .nb__t > span,
.nb.is-open .nb__t > span { opacity: 1; transform: none; color: var(--ink); }
.nb:focus-visible { outline: 2px solid var(--rot); outline-offset: 3px; }
/* Das Eindruecken nur mit feinem Zeiger: auf dem Handy setzt schon das
   Ansetzen des Daumens zum Wischen :active, und der Knopf zuckte mit. */
@media (hover: hover) and (pointer: fine) {
  .nb:active { transform: translateY(.5px); }
}

/* aktiver Abschnitt: Gold und ein zarter Ring, KEINE dauerhaft offene Pille.
   Der Wechsel faellt genau auf die Abschnittsgrenzen (der Scrollspy schaltet
   dort um) — und war der Ruckler beim Ueberscrollen. Als Uebergang von
   Hintergrund, Rand und Schatten musste die Pille samt Weichzeichner-Schatten
   400 ms lang in JEDEM Bild neu gerastert werden, zwei Pillen zugleich.
   Gemessen (headless, 1440x900, Rad 300 px/150 ms): an den Grenzen 365 ms
   GPU-Raster je Sekunde, ohne diese Uebergaenge 242; Bilder ueber 33 ms 7-11
   gegen 4. Darum liegt das Rot jetzt auf einer eigenen Deckschicht, die nur
   ihre Deckkraft ueberblendet: die rastert einmal und laeuft dann auf dem
   Compositor. Die Pille selbst behaelt ihre Ruhewerte (sonst wuerde Hover
   den aktiven Zustand ueberstimmen). Die Deckschicht ist so gemischt, dass
   das Ergebnis dem frueheren Aktivzustand entspricht: Weiss mit 1/3 ueber
   den .88 der Pille ergibt genau die frueheren .92, darueber der rote Hauch,
   der 3-px-Ring als Schatten der Deckschicht. */
.nb[aria-current="true"] {
  border-color: var(--edge);
  background: rgba(255, 255, 255, .88);
  box-shadow: 0 1px 1px rgba(27, 26, 24, .04), 0 10px 22px -16px rgba(27, 26, 24, .65);
}
/* Eigener Stapelkontext, damit z-index:-1 der Deckschicht innerhalb der
   Pille bleibt — das backdrop-filter leistet das auch, aber nicht ueberall. */
.nb { isolation: isolate; }
.nb::after {
  content: "";
  position: absolute;
  inset: -1px;
  z-index: -1;             /* ueber Grund und Rand der Pille, unter Symbol und Schrift */
  border-radius: inherit;
  border: 1px solid rgba(163, 43, 31, .42);
  background: linear-gradient(0deg, var(--rot-wash), var(--rot-wash)), rgba(255, 255, 255, .3333);
  box-shadow: 0 0 0 3px var(--rot-wash);
  opacity: 0;
  pointer-events: none;
  transition: opacity .40s var(--ease);
}
.nb[aria-current="true"]::after { opacity: 1; }
.nb[aria-current="true"] .nb__i { color: var(--rot); }
.nb[aria-current="true"] .nb__t > span { color: var(--rot); }

/* ---------- Markenblock: Signet + Wortmarke ----------
   Dieselbe schwebende Flaeche wie ein Kreis, nur laenger: gleiche Fuellung,
   gleicher Rand, gleicher Schatten, gleiches backdrop-filter, dieselbe
   Rundung und Hoehe. Ohne diese Flaeche laege die Marke nackt auf dem
   Leopardenfilm und waere stellenweise unlesbar.
   Kein Ausfahren — der Block traegt keine .nb__t-Beschriftung. */
.nb--marke { flex: 0 1 auto; min-width: 0; }
.nb__i--sig svg { width: 27px; height: auto; }
.nb__w {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  padding: 0 17px 0 1px;
  overflow: hidden;
}
/* 1275.7 : 339.18 aus dem viewBox — Hoehe fuehrt, Breite folgt dem
   Verhaeltnis: 20 * 3.7611 = 75.2px gegen vorher 67.5px. Die knapp 8px
   mehr zehren am Breitenbudget der Leiste (siehe Kommentar weiter unten),
   dort bleibt genug Luft.
   Keine evenodd-Regel: der Illustrator-Export wickelt seine Loecher
   (Nadeloese, Punzen der Unterzeile) gegenlaeufig, nonzero stimmt. */
.nb__w svg {
  height: var(--wmh);
  width: calc(var(--wmh) * 3.7611);
  overflow: visible;
}
/* Statisch und gefuellt: kein Strich, kein Freilegen. */
.nb__w svg path { fill: currentColor; stroke: none; }
/* Die Bildmarke bringt ihr eigenes <style> mit (.st0 = #f6f5f0). Auf der
   hellen Flaeche waeren diese Flaechen weisse Schnitte quer durch den
   Panther — sie bleiben offen, so wie die Marke bisher aussah.
   Als Variable, nicht als .nb--marke .st0: das Signet kommt per <use> aus
   dem Vorrat, und in dessen Schattenbaum reicht nur Geerbtes hinein. */
.nb--marke { --sg0: none; }
/* Im Kopfbereich traegt der Markenblock die Aktiv-Markierung. Ring und
   Goldschimmer der Flaeche genuegen: Signet und Wortmarke bleiben in der
   Schriftfarbe, damit die Marke nicht die Farbe wechselt. */
.nb--marke[aria-current="true"] .nb__i { color: inherit; }

@media (max-width: 640px) {
  .nav { --s: 38px; --gap: 6px; --wmh: 17.3px; }   /* = 20px * 16.1/18.6, gleiche Verkleinerung wie bisher */
  .nb__i svg { width: 18px; height: 18px; }
  .nb__i--sig svg { width: 23px; }
  .nb__t > span { font-size: 11px; letter-spacing: .05em; padding-left: 13px; }
  .nb__w { padding-right: 13px; }
}
/* Der Markenblock kostet Breite, die es vorher nicht gab. Massgeblich ist
   der offene Zustand mit der breitesten Pille (frueher „DER LEOPARD“): bei 480 px
   braucht die Leiste dann rund 397 px von 456 verfuegbaren — es passt. Bei
   390 px waeren es 376 von 366: es passt nicht mehr. Darum faellt unter
   480 px zuerst die Wortmarke weg; das Signet bleibt als Kreis stehen und
   die Beschriftungen bleiben erhalten. */
@media (max-width: 479px) {
  .nb__w { display: none; }
}
/* Mit nur noch drei Beschriftungen (plus Shop) passt die ausgefahrene Pille auch auf
   dem Handy — nachgemessen bei 360 px. Die Kreise werden kleiner, die
   Beschriftung bleibt. */
@media (max-width: 479px) {
  .nav { --s: 36px; --gap: 5px; }
  .nb__i svg { width: 17px; height: 17px; }
  .nb__i--sig svg { width: 21px; }
}

/* Reduzierte Bewegung heisst „keine Animation“, nicht „keine Information“:
   die Beschriftung erscheint weiterhin, nur ohne Uebergang. */
@media (prefers-reduced-motion: reduce) {
  .nav, .nav *, .nav *::before, .nav *::after { transition: none !important; animation: none !important; }
  .nb__t > span { transform: none; }
}
/* Messhilfe (?still): schaltet die Nav-Uebergaenge ab, damit Standbilder den
   Endzustand zeigen — headless friert laufende Transitions sonst ein.
   .nav selbst behaelt ihre Animation: die einzige dort ist die scroll-
   gebundene Deckkraft (html.sda, Dateiende) — eine Position, keine Dauer,
   die gehoert zum Standbild dazu. */
html.still .nav, html.still .nav *, html.still .nav *::before, html.still .nav *::after {
  transition: none !important;
}
html.still .nav *, html.still .nav *::before, html.still .nav *::after {
  animation: none !important;
}

/* ============================================================
   00 — Auftakt: der Film als Grund, die freigelegte Wortmarke darüber

   Ein gewöhnlicher Abschnitt im Fluss, bildschirmfüllend in derselben
   Rahmung wie jede andere Karte. Man scrollt nach unten, er geht mit
   nach oben weg. Kein Pin, keine Maske, kein Zoom.
   ============================================================ */
/* Das Einfahren: bei Scrollstand 0 steht der Auftakt randlos von Kante zu
   Kante, beim Anscrollen wird er auf die Kartengeometrie zusammengezogen.
   Die Ausdehnung sitzt in negativen Raendern, nicht in einem transform —
   nur so bleibt der Film unverzerrt und der Eckradius echt.
     --ex-o  Zuwachs nach oben  (Body-Polster + der fuer die Leiste
             reservierte Streifen: --navh + 2 * --frame)
     --ex-s  Zuwachs seitlich und nach unten (je --frame)
   margin-bottom nimmt den Zuwachs nach unten wieder zurueck: der Fluss
   darunter (#atelier) bleibt dadurch bei jedem Stand unveraendert, und
   bei --einfahrt: 1 sind alle vier Zuwaechse exakt 0 — die Geometrie ist
   dann bitgenau die alte Karte, kein Sprung moeglich.
   Der obere Rand traegt zusaetzlich --halt: einen KONSTANTEN Zuwachs im
   Fluss, der den Weg des Festhaltens aufnimmt. Konstant heisst: er wird nie
   pro Bild veraendert, es entsteht kein Umbruch im Takt. Waehrend des
   Einfahrens haelt app.js den ganzen .stack per translate3d um genau diesen
   Betrag zurueck; beides zusammen ergibt einen Auftakt, der an Ort und
   Stelle schrumpft, waehrend alles darunter stillsteht. */
.hero {
  position: relative;
  --e:    calc(1 - var(--einfahrt));
  --ex-o: calc(var(--e) * (var(--navh) + 2 * var(--frame)));
  --ex-s: calc(var(--e) * var(--frame));
  margin: calc(var(--halt) - var(--ex-o)) calc(-1 * var(--ex-s)) calc(-1 * var(--ex-s)) calc(-1 * var(--ex-s));
  height: calc(100svh - var(--navh) - 3 * var(--frame) + var(--ex-o) + var(--ex-s));
  min-height: 380px;
  border-radius: calc(var(--r) * var(--einfahrt));
  border-color: rgba(27, 26, 24, calc(.13 * var(--einfahrt)));
}

/* Das Einfahren als Ausschnitt statt als Geometrie. Die Regel oben bleibt
   der Stand ohne Einfahren (kein JS, prefers-reduced-motion, Beruehrgeraete
   ohne feinen Zeiger — siehe Kopfscript in index.html): dort steht
   --einfahrt fest auf 1 und damit die ruhende Karte. Sobald der Kopfscript
   html.einfahrt setzt, liegt der Auftakt DAUERHAFT in der randlosen
   Endausdehnung im Layout — Raender und Hoehe sind konstant, der Fluss
   darunter bleibt wie vorher genau an der Kartenunterkante (margin-bottom
   nimmt den Zuwachs nach unten zurueck). Gewandert wird nur noch im
   Ausschnitt: clip-path zieht die Flaeche auf die Kartengeometrie ein.
   Vorher liefen margin, height und border-radius einer bildschirmgrossen
   Flaeche pro Bild durchs Layout — Umbruch in jedem Bild, CLS 0.436.
   Die Hoehe traegt die Mindesthoehe der Karte (380px) samt Zuwachs, damit
   jeder Stand hineinpasst. Der untere Einzug ist nicht einfach --frame:
   auf niedrigen Fenstern (Handy quer) griff frueher die Mindesthoehe schon
   waehrend des Einfahrens, --h-alt rechnet genau diese alte Hoehe nach,
   damit Karte und Film dort ebenfalls stehen wie vorher.
   Der Rand ist kein border mehr (der laege ausserhalb des Ausschnitts):
   Der Auftakt schneidet auf die Aussenkante der Karte, der Film 1px
   weiter innen — dazwischen bleibt ein Ring aus dem Grund des Auftakts,
   und der traegt dieselbe Randfarbe wie vorher der border. */
html.einfahrt .hero {
  --ex-o: calc(var(--navh) + 2 * var(--frame));
  --ex-s: var(--frame);
  --hero-h: max(100svh, calc(380px + var(--navh) + 3 * var(--frame)));
  /* Einzug oben und seitlich/unten, 0 = randlos, voll = Karte */
  --in-o: calc(var(--einfahrt) * var(--ex-o));
  --in-s: calc(var(--einfahrt) * var(--ex-s));
  /* die Hoehe, die die Karte bei diesem Stand frueher im Layout hatte */
  --h-alt: max(calc(100svh - (var(--navh) + 3 * var(--frame)) * var(--einfahrt)), 380px);
  --in-u: calc(var(--hero-h) - var(--in-o) - var(--h-alt));
  /* Versatz der Kartenmitte gegen die Mitte der randlosen Flaeche */
  --mitte: calc(var(--in-o) + var(--h-alt) / 2 - var(--hero-h) / 2);
  margin: calc(var(--halt) - var(--ex-o)) calc(-1 * var(--ex-s)) calc(-1 * var(--ex-s)) calc(-1 * var(--ex-s));
  height: var(--hero-h);
  min-height: 0;
  border: 0;
  border-radius: 0;
  background:
    linear-gradient(rgba(27, 26, 24, calc(.13 * var(--einfahrt))), rgba(27, 26, 24, calc(.13 * var(--einfahrt)))),
    var(--card);
  clip-path: inset(var(--in-o) var(--in-s) var(--in-u) var(--in-s) round calc(var(--r) * var(--einfahrt)));
}
html.einfahrt .auftakt__film {
  clip-path: inset(calc(var(--in-o) + 1px) calc(var(--in-s) + 1px) calc(var(--in-u) + 1px) calc(var(--in-s) + 1px)
                   round max(0px, calc(var(--r) * var(--einfahrt) - 1px)));
}
/* Film und Standbild liegen als Kasten im exakten Seitenverhaeltnis der
   Quelle (1920 x 1072), deckend fuer die randlose Flaeche. Mit object-fit
   allein ginge das nicht: das Verkleinern per scale verkleinerte dann auch
   die Kiste, und die Ecken des Ausschnitts laegen frei. app.js rechnet
   --film-k so, dass der Film genau so gross steht, wie ihn "cover" in der
   Karte des jeweiligen Stands zeigen wuerde, und um die Mitte der Karte. */
html.einfahrt .auftakt__standbild,
html.einfahrt .auftakt__bewegt {
  inset: auto;
  left: 50%; top: 50%;
  width: max(100%, calc(var(--hero-h) * 1920 / 1072));
  max-width: none;          /* der allgemeine Riegel img/video 100% kappte sonst */
  height: auto;
  aspect-ratio: 1920 / 1072;
  transform: translate(-50%, calc(-50% + var(--mitte))) scale(var(--film-k, 1));
  /* Dasselbe fuer den Film: auf eigener Ebene skaliert der Compositor,
     statt dass die Videoflaeche pro Bild neu gemalt wird (A/B: GPU-Arbeit
     0.67 s -> 0.07 s). */
  will-change: transform;
}
/* Die Wortmarke misst ihre Breite am Auftakt; der ist jetzt die randlose
   Flaeche. --wort-k (app.js) bringt sie auf das Mass der Karte zurueck. */
html.einfahrt .auftakt__wort {
  transform: translate(-50%, calc(-50% + var(--mitte))) scale(var(--wort-k, 1));
  /* Eigene Ebene, und das ist der teuerste Einzelposten der Einfahrt
     gewesen: ohne will-change rastert Chrome das SVG bei JEDEM Bild in
     der neuen Groesse neu (A/B mit GPU am 23.09.2026: 19 % der Bilder ueber
     16.7 ms, GPU-Arbeit 2.8 s in 9 s Scroll - mit will-change 0.9 % und
     0.05 s). Die Skalierung laeuft dann als Transform auf der Ebene. */
  will-change: transform;
}

/* Die Wortmarke steht mittig über dem Film, grosszügig: zwei Drittel
   der Bildbreite quer, hochkant etwas mehr, weil dort weniger Breite
   zur Verfügung steht. Das Seitenverhältnis der Marke ist flach
   (1275.71 : 377.05), sie schneidet auf keinem Format an. Die Breite
   bleibt bei 66%: der viewBox trägt 2% Rand je Seite, die Marke selbst
   steht damit 4% schmaler als die frühere — sie wirkt nicht breiter,
   nur kräftiger, weil die Schrift fetter ist.
   Sichtbar ist nur Füllung: geschrieben wird die Marke über Masken, deren
   Striche selbst nie zu sehen sind (siehe wm-schreiben). Ein sichtbarer
   Strich gäbe ihr bloss einen Rand. Die Farbe kommt über color, die
   Pfade erben sie per fill="currentColor". */
.auftakt__wort {
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: 66%;
  overflow: visible;
  color: var(--hero-paper);
}
@media (max-width: 640px), (max-aspect-ratio: 3/4) {
  .auftakt__wort { width: 82%; }
}
/* ---- Die Marke wird geschrieben -------------------------------------
   Jeder Maskenstrich ist ein Stift: dasharray 1 mit Luecke 2 auf der
   Laenge 1 (pathLength), dashoffset von 1.05 auf 0 laesst ihn in
   Schreibrichtung wachsen. 1.05 statt 1, weil ein Strich der Laenge null
   mit runder Kappe als Punkt am Anfang stehen bliebe — die Marke waere vor
   dem ersten Buchstaben schon betupft. Der Grundzustand ist offset 0: ohne
   die Klasse wm-schreibt (kein JS, reduzierte Bewegung, abgekuerzt) steht
   die Marke fertig da. Beginn und Dauer sind Anteile (--wm-t, --wm-d aus
   index.html) der Gesamtdauer --wm-dauer, die app.js aus HERO_DRAW setzt;
   so bleibt der Takt der Hand erhalten, wenn sich die Dauer aendert.
   Die Kurve setzt weich an und laeuft weich aus wie eine Hand, die zum
   Buchstaben ansetzt und absetzt. Nur die Unterzeile laeuft linear: sie
   wird nicht geschrieben, sondern in einem Zug nachgezogen. */
.auftakt__wort .wm-strich {
  fill: none;
  stroke: #fff;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 1 2;
  stroke-dashoffset: 0;
}
.auftakt__wort.wm-schreibt .wm-strich {
  animation: wm-schreiben calc(var(--wm-d) * var(--wm-dauer, 3400ms))
             cubic-bezier(.45, .05, .55, .95)
             calc(var(--wm-t) * var(--wm-dauer, 3400ms)) both;
}
.auftakt__wort.wm-schreibt .wm-strich--wipe { animation-timing-function: linear; }
@keyframes wm-schreiben {
  from { stroke-dashoffset: 1.05; }
  to   { stroke-dashoffset: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .auftakt__wort.wm-schreibt .wm-strich { animation: none; stroke-dashoffset: 0; }
}
/* ---- Der Film als Grund des Auftakts --------------------------------
   Er steht scharf; die Tiefe kommt aus Grösse und Kontrast der Marke,
   nicht aus Unschärfe. Fällt Film und Standbild aus, bleibt --hero-ink
   als Grund stehen. */
.auftakt__film {
  position: relative;
  width: 100%; height: 100%;
  overflow: hidden;
  background: var(--hero-ink);
  /* Kein filter mehr: saturate(0.94) auf der bildschirmgrossen, pro Bild
     skalierten Flaeche zwang den Film jedes Bild durch einen Filterpass.
     Der Unterschied von 6 % Saettigung ist im Bild nicht zu sehen, die
     Kosten waren es (23.09.2026). */
}
.auftakt__standbild,
.auftakt__bewegt {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  border: 0;
  pointer-events: none;
}
/* Schmal und hochkant: "cover" schnitte aus dem 16:9-Bild einen Streifen,
   in dem vom Leoparden nur noch eine Schulter steht — er ist dort nicht mehr
   lesbar, und lesbar ist er der ganze Grund, warum das Tier zur Marke gehört.
   Also das ganze Bild zeigen und die Studio-Hohlkehle nach oben und unten
   weiterführen. Die beiden Töne sind aus der obersten und untersten Zeile des
   Standbilds gemittelt, der Übergang bleibt dadurch unsichtbar. */
@media (max-width: 640px), (max-aspect-ratio: 3/4) {
  /* Der Grund führt die Hohlkehle weiter; --hero-ink bleibt darunter liegen. */
  .auftakt__film {
    background:
      linear-gradient(180deg, #dcd7cd 0%, #e0dbd1 46%, #e7e1d8 100%),
      var(--hero-ink);
  }
  /* Das Bild steht als Band in voller Breite, oben und unten weich
     auslaufend — die Vignette des Studios ist links dunkler als rechts,
     eine harte Kante gegen den Grund wäre dort sofort zu sehen. */
  .auftakt__standbild,
  .auftakt__bewegt {
    top: 50%; bottom: auto; left: 0; right: 0;
    height: auto;
    transform: translateY(-50%);
    -webkit-mask-image: linear-gradient(180deg, rgba(0,0,0,0) 0%, #000 16%, #000 95%, rgba(0,0,0,0) 100%);
            mask-image: linear-gradient(180deg, rgba(0,0,0,0) 0%, #000 16%, #000 95%, rgba(0,0,0,0) 100%);
  }
  /* Mit Einfahren: das Band spannt die randlose Breite, --film-k (app.js)
     bringt es auf die Kartenbreite des Stands. Die Hohlkehle des Grunds
     sitzt nur im Kartenausschnitt, wie vorher, als die Kiste die Karte war. */
  html.einfahrt .auftakt__film {
    background-size: 100% calc(var(--h-alt) - 2px), auto;
    background-position: 0 calc(var(--in-o) + 1px), 0 0;
    background-repeat: no-repeat;
  }
  html.einfahrt .auftakt__standbild,
  html.einfahrt .auftakt__bewegt {
    left: 0; top: 50%;
    width: 100%;
    height: auto;
    aspect-ratio: auto;
    transform: translateY(calc(-50% + var(--mitte))) scale(var(--film-k, 1));
  }
}
/* Der Schleier nimmt dem Bild die letzten 8 % — die 0.92 Deckkraft aus
   dem Bildaufbau. Er muss aufhellen, nicht abdunkeln, also der helle
   Studioton (--hero-ink) und NICHT die Schriftfarbe (--hero-paper, dunkel). */
.auftakt__schleier {
  position: absolute; inset: 0;
  background: var(--hero-ink);
  opacity: 0.08;
}
/* prefers-reduced-motion: app.js entfernt das <video> ganz, das Standbild
   bleibt stehen. Der Riegel hier ist die Sicherung, falls JS nicht läuft. */
@media (prefers-reduced-motion: reduce) {
  .auftakt__bewegt { display: none; }
}

/* ---- Die zwei Wege aus dem ersten Bild ------------------------------
   Der Knopf "Zum Shop" unter der Wortmarke und der Scroll-Hinweis unten
   mittig (Auftraggeber, 16.10.2026). Beide gehoeren zur Karte und fahren
   mit ihr: sie stehen auf denselben Groessen wie die Wortmarke.
     Knopf  Mitte der Karte (--mitte) plus halbe Hoehe der Wortmarke plus
            Luft. Die halbe Hoehe ist Breite x 377.05 / 1275.71 (viewBox)
            / 2: bei 66 % Breite 9.753 cqw, hochkant (82 %) 12.118 cqw.
            cqw meint die Breite des Auftakts (container-type unten), die
            Wortmarke steht mit --wort-k auf das Mass der Karte gerechnet -
            dieselbe Zahl hier.
     Pfeil  Unterkante der Karte: --in-o + --h-alt (html.einfahrt .hero).
   Gesetzt wird beides ueber `translate`, nie ueber top/left: im Takt
   aendern sich nur Custom Properties, kein Layout. Und `translate` statt
   `transform`, weil .bruecke__knopf beim Druecken transform: scale()
   setzt - die Lage bliebe sonst beim Klick nicht stehen.
   Ohne Einfahren (Beruehrgeraet, reduzierte Bewegung, kein JS) fehlen
   --mitte und --wort-k: dann gelten die Rueckfallwerte, die ruhende Karte. */
.auftakt__wege {
  position: absolute; inset: 0;
  container-type: inline-size;
  pointer-events: none;        /* die Flaeche faengt nichts ab, nur die Wege */
  --wm-halb: 9.753cqw;
  --wege-luft: clamp(18px, 3.4vh, 34px);
  --pfeil-luft: clamp(10px, 2.2vh, 22px);
}
@media (max-width: 640px), (max-aspect-ratio: 3/4) {
  .auftakt__wege { --wm-halb: 12.118cqw; }
}
.auftakt__shop,
.auftakt__pfeil { pointer-events: auto; }

.auftakt__shop {
  position: absolute;
  left: 50%; top: 50%;
  translate: -50% calc(var(--mitte, 0px) + var(--wm-halb) * var(--wort-k, 1) + var(--wege-luft));
  white-space: nowrap;
  padding-inline: 26px;
}

/* Der Hinweis: ein Winkel nach unten in einer kleinen Glaspille - dieselben
   Zutaten wie die Kreise der Leiste (Weiss .88, Haarrand, Weichzeichner),
   damit er auf jeder Stelle des Films lesbar bleibt und trotzdem nur
   andeutet. 44 px Antippflaeche. Er wippt langsam (2.6 s je Hub); bei
   reduzierter Bewegung steht er still. */
.auftakt__pfeil {
  position: absolute;
  left: 50%; bottom: var(--pfeil-luft);
  translate: -50% 0;
  display: grid; place-items: center;
  width: 44px; height: 44px;
  border-radius: 999px;
  color: var(--ink);
  background: rgba(255, 255, 255, .88);
  border: 1px solid var(--edge);
  box-shadow: 0 1px 1px rgba(27, 26, 24, .04), 0 10px 22px -16px rgba(27, 26, 24, .65);
  -webkit-backdrop-filter: blur(9px) saturate(1.25);
  backdrop-filter: blur(9px) saturate(1.25);
  animation: lb-wippen 2.6s ease-in-out infinite;
  transition: opacity .45s var(--ease), visibility 0s linear 0s;
}
.auftakt__pfeil svg { width: 18px; height: 10px; display: block; overflow: visible; }
.auftakt__pfeil:hover { background: var(--card-alt); }
.auftakt__pfeil:focus-visible { outline: 2px solid var(--rot); outline-offset: 3px; }
@keyframes lb-wippen {
  0%, 100% { transform: translateY(-3px); }
  50%      { transform: translateY(4px); }
}
/* Mit Einfahren: an der Unterkante des Ausschnitts, nicht der Flaeche. */
html.einfahrt .auftakt__pfeil {
  top: 0; bottom: auto;
  translate: -50% calc(var(--in-o) + var(--h-alt) - 100% - var(--pfeil-luft));
}
/* Sobald gescrollt wird (app.js, updatePfeil): ausblenden und aus der
   Tab-Reihenfolge nehmen - visibility erst nach der Blende. */
.auftakt__pfeil.is-weg {
  opacity: 0;
  visibility: hidden;
  transition: opacity .45s var(--ease), visibility 0s linear .45s;
}
@media (prefers-reduced-motion: reduce) {
  .auftakt__pfeil { animation: none; transition: none; }
}
html.still .auftakt__pfeil { animation: none; transition: none; }

/* ============================================================
   Abschnitte in Karten
   ============================================================ */
.section { padding: clamp(48px, 8vh, 110px) var(--pad); }
.section__head { margin-bottom: clamp(22px, 4vh, 44px); }

/* Ueber uns (16.10.2026, Auftraggeber): Kicker, Titel und die zwei Absaetze,
   sonst nichts — die vier Piktogramme sind gestrichen, ebenso die
   bildschirmhohe Karte, in der der Text an der Unterkante weit unter dem
   Titel stand. Die Karte ist jetzt genau so hoch wie ihr Inhalt.
   Breit stehen Titel und Text nebeneinander wie eine aufgeschlagene Seite:
   links der Titel in zwei Zeilen, rechts die beiden Absaetze untereinander,
   oben buendig; der Kicker spannt ueber beide. So steht neben dem Titel
   keine leere Haelfte und unter ihm kein Loch. Schmal alles untereinander. */
#atelier { padding-block: clamp(44px, 6vw, 88px); }
#atelier .section__head { margin-bottom: clamp(18px, 2.4vw, 32px); }
.atelier__title { font-size: clamp(40px, 7vw, 112px); max-width: 17ch; margin-bottom: clamp(24px, 4vw, 40px); }
.atelier__cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: clamp(18px, 3.4vw, 52px);
  align-items: start;
}
.atelier__cols p { margin: 0; color: var(--ink); font-size: clamp(16px, 1.3vw, 19px); line-height: 1.6; max-width: 46ch; }
@media (min-width: 900px) {
  #atelier {
    display: grid;
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
    column-gap: clamp(32px, 5vw, 96px);
    align-items: start;
  }
  #atelier .section__head { grid-column: 1 / -1; }
  #atelier .atelier__title { margin-bottom: 0; }
  /* Die erste Textzeile auf die Versalhoehe des Titels: dessen Zeile ist
     knapp (1.06), die des Lesetexts weit (1.6) - ohne Ausgleich stuende der
     Text sichtbar hoeher als der Titel. */
  #atelier .atelier__cols { grid-template-columns: minmax(0, 1fr); gap: 1em; padding-top: clamp(4px, .9vw, 14px); }
}

/* ============================================================
   02 — Ablauf (Klassen .people aus der Rollen-Fassung)
   ============================================================ */
/* GEWOLLTE ASYMMETRIE (nicht "korrigieren"): der versetzte Aufbau der
   Werkstatt — breites Standbild links, Textspalte in der Mitte, schmale
   Bildleiste rechts. Die drei Spalten sind absichtlich ungleich; gleich
   sind die Innenabstaende innerhalb jeder Spalte. */
.people { position: relative; }
.people__stage {
  position: sticky;
  top: calc(var(--navh) + 2 * var(--frame));
  height: calc(100svh - var(--navh) - 3 * var(--frame));
  min-height: 420px;
  display: grid;
  grid-template-columns: 42% 1fr clamp(84px, 10%, 140px);
}

.people__lead { position: relative; overflow: hidden; background: var(--bg-deep); }
.people__shot { position: absolute; inset: 0; opacity: 0; }
/* Eigene Ebenen dauerhaft. Frueher schaltete app.js sie per .nah erst
   anderthalb Bildschirmhoehen vor dem Abschnitt an — damit fiel die
   Ebenen-Anlage genau in den Grenzuebergang. Gemessen, drei Stellungen
   (Bilder > 33 ms im Grenzfenster): Umschalten 7-11, nie 10, dauerhaft 6.
   Das Umschalten war also nicht besser, und ausserhalb des Bildes rastert
   Chrome die zehn Ebenen ohnehin nicht, sie kosten dort kaum Speicher. */
.people__shot,
.people__panel { will-change: transform, opacity; }
.people__shot img { width: 100%; height: 100%; object-fit: cover; }
.people__shot::before {
  content: "";
  position: absolute; inset: 0 0 auto 0;
  height: 1px; background: var(--rot); z-index: 2;
}

.people__body {
  position: relative;
  padding: clamp(26px, 5vh, 56px) clamp(22px, 3.4vw, 64px);
  display: grid;
  align-content: center;
}
.people__panel { grid-area: 1 / 1; opacity: 0; max-width: 42ch; }
.people__no { margin: 0; font-size: 10.5px; font-weight: 600; letter-spacing: .2em; color: var(--gold); font-variant-numeric: tabular-nums; }
.people__role { margin: 12px 0 0; font-weight: 400; font-size: clamp(28px, 3.8vw, 60px); line-height: 1.05; letter-spacing: -.02em; }
.people__text { margin: 18px 0 0; color: var(--ink); font-size: 15px; }

.people__rail { position: relative; overflow: hidden; border-left: 1px solid var(--edge); background: var(--bg-deep); }
.people__railtrack {
  position: absolute;
  left: 0; right: 0; top: 50%;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 0 10px;
  will-change: transform;
}
.people__thumb {
  position: relative;
  aspect-ratio: 3 / 4;
  overflow: hidden;
  border-radius: var(--r);
  opacity: .30;
  filter: grayscale(1);
  transition: opacity .45s var(--ease), filter .45s var(--ease);
}
.people__thumb img { width: 100%; height: 100%; object-fit: cover; }
.people__thumb.on { opacity: 1; filter: grayscale(0); }
.people__thumb.on::after { content: ""; position: absolute; inset: 0; border: 1px solid var(--rot); border-radius: var(--r); }

.people__progress {
  position: absolute;
  left: 16px;
  bottom: 16px;
  z-index: 3;
  margin: 0;
  padding: 6px 11px;
  font-size: 10.5px; font-weight: 600; letter-spacing: .2em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  /* liegt auf dem Foto, braucht darum einen eigenen Grund */
  background: var(--card);
  border: 1px solid var(--edge);
  border-radius: var(--r);
  pointer-events: none;
}
.people__progress b { color: var(--gold); font-weight: 700; }

/* Reduzierte Bewegung: app.js laesst den Abschnitt dann ganz in Ruhe (kein
   Scrollweg, keine Ueberblendung). Bisher hiess das: alle Bilder und Texte
   blieben auf ihrer Grunddeckkraft 0, der Abschnitt stand leer da
   (gemessen 15.10.2026, alle fuenf Texte opacity 0). Jetzt liegen die fuenf
   Schritte als ruhige Liste untereinander, daneben das erste Bild; Leiste
   und Zaehler entfallen, sie gehoeren zur Bewegung. */
@media (prefers-reduced-motion: reduce) {
  .people__stage {
    position: relative;
    top: auto;
    height: auto;
    min-height: 0;
    grid-template-columns: 42% 1fr;
  }
  .people__shot:first-child { opacity: 1; }
  .people__body { align-content: start; row-gap: clamp(30px, 5vh, 56px); }
  .people__panel { grid-area: auto; opacity: 1; transform: none; }
  .people__rail, .people__progress { display: none; }
}

/* ============================================================
   02 — Team: waagrecht fahrender Streifen
   (Mechanik aus «Ausgewaehlte Arbeiten», Commit e7df36e; statt neun
   Outfits fahren sechs Teamkarten mit Portrait, Name, Position, Zitat)

   Die Bühne ist die Karte, die Section nur der Scrollweg. Fünf Phasen
   hintereinander: wachsen → ruhen → fahren → ruhen → lösen. Das Wachsen
   staucht die Platte per transform und blendet den Schlagschatten auf
   (.arb__schlag, Deckkraft), die Fahrt schiebt die Reihe nach links
   (--shift, --gx1, --prog). In den beiden Ruhephasen wird nichts
   geschrieben — kein Custom Property, kein Stil.
   Das Wachsen lief frueher ueber ein padding an der Buehne (--inset).
   Das kostete in JEDEM Bild einen Layoutlauf ueber die ganze Platte samt
   Drahtgitter-SVG und neun Kacheln: gemessen 83 ms je Bild im Median,
   ungedrosselt (gemessen mit den neun Outfits). Dieselbe Bewegung als transform kostet nur noch
   Zusammensetzung — 16.7 ms, also volle Bildrate. Die Buehne hat darum
   kein padding mehr, die Platte liegt immer in ihrer Endgroesse und wird
   waehrend des Wachsens um genau den Rahmenwert nach innen gestaucht.
   Die Werte unten auf .arb sind die Ruhelage — sie gelten ohne JS und
   bei prefers-reduced-motion. Im Takt schreibt app.js sie NICHT hierher,
   sondern an genau das Element, das sie liest: --shift an .arb__rail,
   --gx1 an .arb__ghost, --prog an .arb__meter i. Sonst zoege jede Zahl
   den ganzen Teilbaum durch die Stilberechnung. Stauchung und Schatten
   gehen an der Vererbung ganz vorbei: fertiger Stil, direkt auf
   .arb__plate und .arb__schlag.
   ============================================================ */
.arb {
  position: relative;
  --arb-gap: clamp(18px, 2.4vw, 36px);
  /* Breite einer Teamkarte. Das Portrait ist quadratisch, darunter stehen
     Name, Position und Zitat (rund 140-170 px). Die Hoehe der Karte muss in
     den Platz der Reihe passen, der von der Fensterhoehe abhaengt — darum
     haengt auch die Breite an ihr: 340 px bei 900 hoch, 290 px bei 800,
     246 px bei 720. Etwas breiter als die alten Outfitkarten (3:4 an 42vh,
     283 px bei 900), damit das Zitat Platz hat. */
  --karte-b: clamp(220px, calc(100svh * .55 - 150px), 340px);
  --rad:   var(--r);
  --edgeA: 0;
  --shift: 0px;
  --gx1:   0px;
  --prog:  0;
}
.arb__stage {
  position: sticky;
  top: calc(var(--navh) + 2 * var(--frame));
  height: calc(100svh - var(--navh) - 3 * var(--frame));
  min-height: 420px;
}
.arb__plate {
  position: relative;
  height: 100%;
  /* Ohne JS und bei prefers-reduced-motion steht hier gar nichts: die
     Platte liegt in ihrer Endgroesse, das ist der richtige Ruhezustand.
     app.js setzt waehrend des Wachsens ein scale() und raeumt es am Ende
     der Phase wieder ab.
     will-change haelt die Platte auf einer eigenen Ebene — dieselbe
     Behandlung wie Reihe und Drahtgitter. Ohne sie zeichnet der Browser
     die Karte bei jeder Aenderung des Massstabs neu; gemessen ueber die
     30 Bilder der Wachstumsphase, ungedrosselt: ohne will-change 13 Bilder
     ueber 33 ms (Spitze 83 ms), mit will-change kein einziges (16.9 ms). */
  transform-origin: 50% 50%;
  will-change: transform;
  background: var(--card);
  border-radius: var(--rad);
  overflow: hidden;
  overflow: clip;
  /* Ring statt Rand: so kann der Radius mitwandern, ohne dass die Karte springt */
  box-shadow: 0 0 0 1px var(--edge);
}

/* Der Schlagschatten der wachsenden Karte, auf einer eigenen Ebene.
   Als Teil des box-shadow der Platte kostete seine Alpha-Fahrt in JEDEM
   Bild der Wachstumsphase ein Neuzeichnen der ganzen Kartenflaeche samt
   70-px-Weichzeichnung: gemessen 12 von 30 Bildern ueber 33 ms, allein
   dafuer. Auf eigener Ebene faehrt stattdessen die Deckkraft, und die
   kostet nur Zusammensetzung — 0 von 30 Bildern ueber 33 ms.
   Der Kasten selbst ist leer; ein aeusserer box-shadow wird ausserhalb
   der Randbox gezeichnet und deckt darum nichts zu. Die Stauchung
   bekommt er von app.js gleich mitgeschrieben, damit der Schatten den
   Kanten der Karte folgt. */
.arb__schlag {
  position: absolute;
  inset: 0;
  border-radius: var(--r);
  box-shadow: 0 30px 70px -46px rgba(27, 26, 24, .55);
  opacity: var(--edgeA);
  pointer-events: none;
  transform-origin: 50% 50%;
  will-change: transform, opacity;
}

/* ---------- Drahtgitter im Grund ---------- */
/* Die Bewegung traegt der Ghost, nicht das Signet darin — und das Signet
   haelt die geerbten Werte darunter an. Grund ist die Vererbung: app.js
   schreibt --gx1 pro Bild auf den Ghost, Custom Properties erben, also
   liefen ohne Grenze pro Bild auch die 111 Formen des Signets durch die
   Stilberechnung. Gemessen (Zwangsdurchlauf in der Fahrphase, CPU 4x,
   1440x900): mit Grenze 1.1 ms, ohne Grenze 3.4 ms — der Wert eines ganz
   entfernten Drahtgitters. Das Bild bleibt gleich: der Ghost deckt die Platte
   deckungsgleich ab, geschnitten wird ohnehin an der Platte (overflow: clip),
   und das 93 % breite, mittige Signet erreicht die Ghost-Kanten nie. */
.arb__ghost {
  position: absolute; inset: 0;
  overflow: hidden; overflow: clip;
  pointer-events: none;
  z-index: 0;
  transform: translate3d(calc(var(--gx1) * .09), 0, 0);
  will-change: transform;
}
.arb__signet {
  position: absolute;
  left: 50%; top: 50%;
  width: 93%; height: auto;
  aspect-ratio: 423 / 180;
  transform: translate3d(-50%, -50%, 0);
  /* die Vererbungsgrenze: alles, was app.js pro Bild auf .arb schreibt,
     wird hier auf einen festen Wert gesetzt und geht nicht weiter nach unten */
  --gx1: 0px; --shift: 0px; --prog: 0; --edgeA: 0; --rad: var(--r);
  /* Die Konturen kommen per <use> aus dem Vorrat (index.html). Nachfahren-
     Regeln erreichen den Schattenbaum nicht; darum steht hier alles als
     geerbter Wert: Fuellungen ueber die Variablen des Vorrats aus, Strich
     direkt am svg. vector-effect erbt nicht und laeuft ueber --sg-ve. */
  --sg0: none; --sg1: none; --sg2: none; --sg3: none;
  --sg-ve: non-scaling-stroke;
  stroke: rgb(241, 240, 238);
  stroke: color-mix(in srgb, var(--card) 97.5%, #000);
  stroke-width: 1.4;
  stroke-linejoin: round;
  shape-rendering: geometricPrecision;
}

/* ---------- Inhalt ---------- */
.arb__inner {
  position: relative;
  z-index: 1;
  height: 100%;
  display: flex;
  flex-direction: column;
  /* nach Höhe bemessen, nicht nach Breite: die Bühne ist hier flacher
     als ein voller Schirm und der Platz muss für die Reihe reichen */
  padding: clamp(22px, 3.4vh, 48px) 0;
}
.arb__kicker { margin: 0 var(--pad) 14px; }
.arb__hd {
  margin: 0 var(--pad);
  font-size: clamp(30px, min(5.2vw, 9.2vh), 78px);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -.03em;
  max-width: 20ch;
}
.arb__hd span { display: block; }
/* «Sechs Koepfe, eine Marke.» ist kurz: am Schreibtisch auf einer Zeile,
   das gibt der Reihe die Hoehe fuer Portrait und Zitat. Schmaler wie die
   alte Ueberschrift auf zwei. */
@media (min-width: 1100px) {
  .arb__hd { max-width: none; }
  .arb__hd span { display: inline; }
}
.arb__hd em { font-style: normal; color: var(--rot); }

.arb__railwrap {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  padding: clamp(16px, 2.6vh, 34px) 0;
}
.arb__rail {
  display: flex;
  /* gleich hohe Karten, auch wenn ein Zitat eine Zeile mehr braucht */
  align-items: stretch;
  gap: var(--arb-gap);
  width: 100%;
  padding: 0 var(--pad);
  transform: translate3d(var(--shift), 0, 0);
  will-change: transform;
}

/* ---------- eine Teamkarte ----------
   Portrait (quadratisch) oben, darunter auf der Kartenflaeche Name,
   Position und Zitat — Text nie auf dem Foto. Alle sechs Karten gleich
   breit (--karte-b) und gleich hoch (align-items: stretch an der Reihe). */
.arb__item {
  position: relative;
  flex: 0 0 auto;
  width: var(--karte-b);
  display: flex;
  flex-direction: column;
  border-radius: var(--r);
  overflow: hidden;
  overflow: clip;
  background: var(--card-alt);
  box-shadow: 0 0 0 1px var(--edge);
  transition: transform .6s var(--ease), opacity .5s var(--ease), box-shadow .6s var(--ease);
}
.arb__bild {
  position: relative;
  flex: 0 0 auto;
  display: block;
  aspect-ratio: 1;
  overflow: hidden;
  overflow: clip;
  background: var(--bg-deep);
}
/* Kurze Handys (Handy-Durchgang 16.10.2026): bei 375 x 560 (iPhone SE mit
   Leisten) war die Karte 376 px hoch, die Buehne bot 466 — die Reihe schob
   sich ueber die zweite Titelzeile und deckte den Fortschrittsbalken zu.
   Dort wird das Portrait niedriger statt die Karte zu gross: hoechstens so
   hoch, wie 100svh nach Abzug von Leiste, Titel, Beschriftung und Balken
   (rund 450 px) hergibt, mindestens 120 px. Ab rund 670 px Hoehe greift die
   Grenze nicht mehr, das Portrait bleibt quadratisch. svh und nicht eine
   Hoehen-Abfrage: die aenderte sich mit der Adressleiste. Mit feinem
   Zeiger bleibt alles wie es war, ebenso bei reduzierter Bewegung (dort
   stehen die Karten als Raster und nicht in der Buehne). */
@media (prefers-reduced-motion: no-preference) {
  @media not all and (hover: hover) and (pointer: fine) {
    .arb__bild { aspect-ratio: auto; height: clamp(120px, calc(100svh - 450px), var(--karte-b)); }
    .arb__bild img { object-position: 50% 30%; }
  }
}
.arb__cap {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 12px 14px 14px;
  color: var(--ink);
}
.arb__name { font-size: 17px; font-weight: 600; line-height: 1.25; letter-spacing: -.012em; }
.arb__rolle {
  font-size: 11px;
  font-weight: 600;
  line-height: 1.5;
  letter-spacing: .14em;
  text-transform: uppercase;
}
/* Das Zitat: eigene Zeile, Schweizer Anfuehrungszeichen, in Tinte. Eine
   Haarlinie trennt es von Name und Position. */
.arb__zitat {
  display: block;
  margin-top: 9px;
  padding-top: 9px;
  border-top: 1px solid var(--hair);
  font-size: 15.5px;
  line-height: 1.45;
  font-style: italic;
  color: var(--ink);
  quotes: "«" "»";
  text-wrap: pretty;
}
.arb__item img {
  width: 100%; height: 100%;
  object-fit: cover;
  transform: scale(1.001);
  transition: transform 1.2s var(--ease);
}
/* Die uebrigen Karten treten zurueck, solange der Zeiger auf der Reihe steht
   oder eine Karte den Fokus hat (Klick, Tipp, Tastatur: die Karten tragen
   tabindex="0"). Die Karte unter dem Zeiger bzw. mit Fokus bleibt IMMER voll
   deckend und scharf.
   Ursache des frueheren «Bewoelkens» (Design-Review 15.10.2026): die Regel
   `.arb__rail:hover .arb__item` (Spezifitaet 0,3,0) schlug
   `.arb__item:hover` (0,2,0) — die eigene Karte stand mit auf .8. Die
   Ausnahme fuer die aktive Karte steht darum mit derselben Spezifitaet
   DAHINTER und gewinnt so ueber die Reihenfolge.
   Heben, Zoomen und Zuruecktreten NUR mit feinem Zeiger (16.10.2026): auf dem
   Handy fokussiert jeder Tipp die Karte (tabindex), und :focus hob sie dann
   um 12 px, zoomte das Portrait und blendete die anderen fuenf ab — bis zum
   naechsten Tipp irgendwo anders, der alles wieder zuruecknahm. Bisher
   verdeckte das nur der klebende Hover-Gegenschalter weiter unten; der hielt
   genau so lange, wie der Browser den Hover-Zustand stehen liess.
   Mit Beruehrung bleibt allein der Tastaturfokus (:focus-visible). */
@media (hover: hover) and (pointer: fine) {
  .arb__rail:hover .arb__item,
  .arb__rail:focus-within .arb__item { opacity: .8; }
  .arb__rail .arb__item:hover,
  .arb__rail .arb__item:focus,
  .arb__rail .arb__item:focus-visible {
    opacity: 1;
    transform: translate3d(0, -12px, 0);
    box-shadow: 0 0 0 1px var(--edge), 0 26px 46px -26px rgba(27, 26, 24, .42);
  }
  .arb__rail .arb__item:hover img,
  .arb__rail .arb__item:focus img,
  .arb__rail .arb__item:focus-visible img { transform: scale(1.075); }
}
@media not all and (hover: hover) and (pointer: fine) {
  .arb__rail:has(.arb__item:focus-visible) .arb__item { opacity: .8; }
  .arb__rail .arb__item.arb__item:focus-visible {
    opacity: 1;
    transform: translate3d(0, -12px, 0);
    box-shadow: 0 0 0 1px var(--edge), 0 26px 46px -26px rgba(27, 26, 24, .42);
  }
  .arb__rail .arb__item:focus-visible img { transform: scale(1.075); }
}
.arb__item:focus { outline: none; }
.arb__item:focus-visible { outline: 2px solid var(--rot); outline-offset: 3px; }

/* ---------- Fortschritt ---------- */
.arb__foot { display: flex; align-items: center; gap: 16px; margin: 0 var(--pad); }
.arb__meter {
  position: relative;
  width: clamp(110px, 13vw, 210px);
  height: 2px;
  background: var(--hair);
  border-radius: var(--r);
  overflow: hidden;
}
.arb__meter i {
  position: absolute; inset: 0;
  background: var(--gold);
  transform: scaleX(var(--prog));
  transform-origin: left center;
}
.arb__hint {
  font-size: 10.5px; font-weight: 600; letter-spacing: .18em; text-transform: uppercase;
  color: var(--ink);
}

/* ---------- Reduzierte Bewegung: stehendes Raster (3 Spalten, schmal 2) ---------- */
@media (prefers-reduced-motion: reduce) {
  .arb { height: auto !important; }
  .arb__stage { position: static; height: auto; min-height: 0; }
  .arb__plate { height: auto; border-radius: var(--r); box-shadow: 0 0 0 1px var(--edge); transform: none !important; }
  .arb__schlag { display: none; }
  .arb__inner { height: auto; padding: clamp(22px, 3.4vh, 48px) 0; }
  .arb__railwrap { display: block; padding: 0; margin-top: calc(var(--pad) * .9); }
  .arb__rail { transform: none !important; flex-wrap: wrap; align-items: stretch; }
  .arb__item { width: calc((100% - 2 * var(--arb-gap)) / 3); transition: none; }
  .arb__item img { transition: none; }
  .arb__ghost { transform: none !important; }
  .arb__foot { display: none; }
}
@media (max-width: 720px) and (prefers-reduced-motion: reduce) {
  .arb__item { width: calc((100% - var(--arb-gap)) / 2); }
}

/* ============================================================
   Fusszeile — ebenfalls eine Karte, kein Formular
   ============================================================ */
/* Seit 16.10.2026 (Auftraggeber) nur noch zwei Zeilen: oben die Wortmarke
   und die vier Wege, unter einer Haarlinie Copyright und Rechtliches.
   Signet, Begleitsatz und Adressblock sind weg. Jeder Link ist 44 px hoch
   (13 + 18 + 13), damit er sich auf dem Handy treffen laesst; die Zeilen
   brechen schmal von selbst um, die vier Wege passen bei 390 px in eine. */
.foot { padding: clamp(10px, 1.6vw, 22px) clamp(20px, 3vw, 44px); }
.foot__top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0 clamp(20px, 4vw, 60px);
}
.foot__mark { display: inline-flex; align-items: center; min-height: 44px; text-decoration: none; }
.foot__mark .wortmarke { width: 92px; height: auto; }
.foot__list {
  display: flex;
  flex-wrap: wrap;
  gap: 0 clamp(20px, 2.6vw, 36px);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 14px;
}
.foot__list a {
  display: inline-block;
  padding: 13px 0;
  line-height: 18px;
  color: var(--ink);
  text-decoration: none;
  transition: color .25s var(--ease);
}
.foot__bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0 18px;
  flex-wrap: wrap;
  margin-top: 4px;
  padding-top: 6px;
  border-top: 1px solid var(--hair);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .15em;
  text-transform: uppercase;
  color: var(--ink);
}
.foot__recht { display: inline-flex; align-items: center; gap: 12px; }
.foot__bar a {
  display: inline-block;
  padding: 13px 0;
  line-height: 18px;
  color: inherit;
  text-decoration: none;
}

/* ============================================================
   Stichzähler — schwebt im Rand, deckt nichts zu
   ============================================================ */
.stitches {
  position: fixed;
  right: calc(var(--frame) + 10px);
  bottom: calc(var(--frame) + 10px);
  z-index: 80;
  margin: 0;
  padding: 7px 12px;
  font-size: 10px; font-weight: 600; letter-spacing: .17em; text-transform: uppercase;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  background: var(--card);
  border: 1px solid var(--edge);
  border-radius: var(--r);
  pointer-events: none;
  transition: opacity .4s var(--ease);
}
.stitches b { color: var(--gold); font-weight: 700; }
/* Ziffernfächer (siehe app.js, Stichzähler): der Platzhalter setzt Breite
   und Grundlinie wie gewöhnlicher Text, das Band darüber wird nur
   verschoben. clip-path statt overflow, weil overflow:hidden die Grundlinie
   eines inline-block auf seine Unterkante legen und die Ziffern versetzen
   würde; clip-path schneidet bloss beim Malen ab. */
.st__fach { position: relative; display: inline-block; clip-path: inset(0); }
.st__fach[hidden], .st__trenner[hidden] { display: none; }
.st__platz { visibility: hidden; }
.st__band { position: absolute; left: 0; top: 0; width: 100%; }
/* Jede Zeile auf ganze Pixel aufgerundet: bei 10px Schrift und 1.65 Zeilen-
   höhe wäre der Schritt 16.5px, und jede ungerade Ziffer stünde nach dem
   Verschieben einen halben Pixel versetzt und damit unscharf bzw. eine
   Zeile tiefer gerastert. Die Zeilenhöhe selbst bleibt, damit die Grundlinie
   in der Zeile genau dort liegt wie beim Platzhalter. Kein will-change: eine
   eigene Ebene rastert die Schrift anders als der umgebende Text. */
.st__band > span { display: block; height: round(up, 1lh, 1px); }

/* ============================================================
   Preloader
   ============================================================ */
#pre {
  position: fixed;
  inset: 0;
  z-index: 9999;
  background: var(--bg);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 24px;
  pointer-events: none;
  animation: preOut .7s var(--ease) 2.75s forwards;
}
#pre svg { width: min(56vw, 660px); height: auto; overflow: visible; }
#pre path { fill: none; stroke: var(--gold); stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round; }
#pre .meta { display: flex; gap: 24px; font-size: 10px; font-weight: 600; letter-spacing: .2em; text-transform: uppercase; color: var(--ink); font-variant-numeric: tabular-nums; }
@keyframes stitchDraw { to { stroke-dashoffset: 0; } }
@keyframes preOut { to { opacity: 0; visibility: hidden; } }
/* Folgebesuch in derselben Sitzung: keine Blende (index.html nimmt sie aus
   dem Baum; die Regel hier ist nur die Sicherung). Die Wortmarke bleibt bis
   zum Beginn des Freilegens unsichtbar — sonst stuende sie fuer einen
   Augenblick fertig da und verschwaende dann, um sich neu freizulegen.
   app.js setzt wort-bereit, sobald das Freilegen beginnt oder abgekuerzt
   wird. */
html.ohne-blende #pre { display: none; }
html.ohne-blende:not(.wort-bereit) .auftakt__wort { visibility: hidden; }
@media (prefers-reduced-motion: reduce) {
  #pre { animation-delay: .2s; }
  #pre path { animation: none !important; stroke-dashoffset: 0 !important; }
}

/* ============================================================
   Schmale Bildschirme
   ============================================================ */
@media (max-width: 860px) {
  .people__stage { grid-template-columns: 1fr; grid-template-rows: 46% 1fr; }
  .people__rail { display: none; }
  .people__body { padding: clamp(18px, 3vh, 30px) var(--pad) clamp(40px, 6vh, 60px); }
  .arb__hd { max-width: none; }
  /* Der Stichzaehler laege sonst auf den Karten unten rechts */
  .stitches { display: none; }
}
/* Reduzierte Bewegung, schmal: die Buehne hat keine feste Hoehe mehr, die
   46 % der Bildzeile griffen ins Leere — das Bild bekommt sein eigenes Mass. */
@media (max-width: 860px) and (prefers-reduced-motion: reduce) {
  .people__stage { grid-template-rows: auto auto; }
  .people__lead { aspect-ratio: 4 / 3; }
}

/* Sichtbarkeit der Navigationsleiste waehrend des Einfahrens: Deckkraft
   (--nav-auf) und Klickbarkeit (data-nav) stehen einmalig oben im Nav-Block.
   app.js schreibt beide Signale auf <html>; fehlen sie (kein JS,
   prefers-reduced-motion, Beruehrgeraet), greifen die Rueckfaelle 1 bzw. data-nav="an" aus
   dem HTML und die Leiste steht sichtbar und klickbar da. Die hier frueher
   wiederholte Deckkraft-Regel war wortgleich mit der oberen. */

/* Lenis fuehrt das Scrollen selbst; das native weiche Scrollen wuerde
   dagegenhalten. Die Klasse setzt Lenis, sobald es laeuft. */
html.lenis { scroll-behavior: auto; }

/* ============================================================
   Feinschliff — Grund, Hover, Lesbarkeit
   Rein additiv ans Dateiende gehaengt. Nichts weiter oben wird
   angefasst; alles hier gewinnt ueber die Kaskade.
   ============================================================ */

/* ---------- Grundfarbschema und Bildlaufleiste ---------- */
:root { color-scheme: light; }

html {
  scrollbar-width: thin;
  scrollbar-color: rgba(27, 26, 24, .22) transparent;
  -webkit-tap-highlight-color: transparent;
  /* Wischen und Zwei-Finger-Zoom bleiben, nur das Doppeltippen zoomt nicht
     mehr: zwei schnelle Tipps, um eine Fahrt anzuhalten, zogen auf dem
     iPhone sonst die ganze Seite heran. */
  touch-action: manipulation;
}
/* Die Bilder der beiden Buehnen (Ablauf, Team) bewegen sich mit dem
   Scrollen. Bleibt der Daumen vor dem Wischen kurz darauf liegen, beginnt
   iOS die Vorschau des Bilds (es zieht sich zusammen und hebt sich ab) —
   auf einer Buehne, die gerade selbst faehrt, liest sich das als Sprung. */
.people__shot img,
.arb__bild img { -webkit-touch-callout: none; }

/* WebKit kennt scrollbar-width/-color nicht — dieselbe schmale Leiste
   noch einmal von Hand. Der durchsichtige Rand plus background-clip
   verschmaelert den Griff, ohne die Spur zu verengen. */
html::-webkit-scrollbar { width: 10px; height: 10px; }
html::-webkit-scrollbar-track { background: transparent; }
html::-webkit-scrollbar-thumb {
  background: rgba(27, 26, 24, .20);
  border-radius: 999px;
  border: 3px solid transparent;
  background-clip: content-box;
}
html::-webkit-scrollbar-thumb:hover {
  background: rgba(27, 26, 24, .34);
  background-clip: content-box;
}

/* ---------- Das Gewebe im Grund ----------
   Zwei gekreuzte Faedenraster, sehr schwach: der Grund liest sich als
   Stoff statt als tote Flaeche. Die Grundfarbe var(--bg) steht weiter
   oben und bleibt unberuehrt — hier nur das Bild darueber.
   Bewusst OHNE background-attachment: fixed. Das kostet auf dieser
   langen Seite bei jedem Bild neu Zeit. */
/* Die beiden ersten Schichten sind der Randschatten: die seitlichen
   Streifen, auf denen der Grund ueberhaupt zu sehen ist, laufen nach
   aussen dunkler aus. Dadurch LIEGEN die Karten auf dem Grund, statt in
   ihn hineingeschnitten zu sein.
   Warum als Verlauf im Fluss und nicht als fixierte Ebene: gemessen am
   28.08.2026 auf 10200 -> 11300 (Leopardenfilm), vier Zustaende rotiert,
   je rund 580 Bilder. Mit fixiertem body::before lag das 95. Perzentil
   bei 33.3 ms, ohne bei 17.5 ms — jedes zwanzigste Bild kostete eine
   ganze Bildperiode mehr. will-change: transform half nicht (8.3 %
   gegen 7.6 % Bilder ueber 20 ms, also eher schlechter). Das Gewebe
   dagegen ist gratis: nur Gewebe 4.2 %, gar nichts 4.5 %. */
body {
  background-image:
    linear-gradient( 90deg, rgba(27, 26, 24, .075) 0, rgba(27, 26, 24, 0) 34px),
    linear-gradient(270deg, rgba(27, 26, 24, .075) 0, rgba(27, 26, 24, 0) 34px),
    repeating-linear-gradient(90deg, rgba(27, 26, 24, .020) 0 1px, rgba(27, 26, 24, 0) 1px 3px),
    repeating-linear-gradient(0deg,  rgba(27, 26, 24, .020) 0 1px, rgba(27, 26, 24, 0) 1px 3px);
  background-repeat: no-repeat, no-repeat, repeat, repeat;
}

/* ---------- Wachsender Unterstrich in der Fusszeile ----------
   Als Hintergrundbild, nicht als text-decoration: die Linie waechst von
   0 auf volle Breite, ohne dass die Zeile dabei umbricht.
   .foot__bar a erbt seine Farbe (color: inherit weiter oben) —
   currentColor passt dadurch von selbst. Die Links sind 44 px hohe Kaesten
   (13 px Polster ueber und unter der 18-px-Zeile); der Strich sitzt darum
   nicht an der Kastenunterkante, sondern 12 px darueber, knapp unter der
   Schrift. */
.foot__list a, .foot__bar a {
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: 0 calc(100% - 12px);
  background-size: 0 1px;
  transition: background-size .3s var(--ease), color .25s var(--ease);
}

/* Der Tastaturfokus faehrt den Strich immer aus — auch auf Beruehrgeraeten,
   darum ausserhalb der Zeiger-Abfrage. */
.foot__list a:focus-visible,
.foot__bar a:focus-visible { background-size: 100% 1px; }

/* ---------- Tastfeedback statt Hover auf Beruehrung ---------- */
.foot__list a:active, .foot__bar a:active { opacity: .62; }

/* ---------- Hover nur dort, wo es einen Zeiger gibt ---------- */
@media (hover: hover) and (pointer: fine) {
  .foot__list a:hover, .foot__bar a:hover { background-size: 100% 1px; }
}

/* ---------- Gegenschalter fuer die bestehenden Hover-Zustaende ----------
   Ohne diesen Block klebt auf Beruehrgeraeten nach jedem Tipp der
   Hover-Zustand am zuletzt angetippten Element. Die Werte sind die
   RUHEwerte der Originalregeln weiter oben (.nb im Navigationsblock), nicht
   erfundene Rueckfaelle. Die Teamkarten brauchen keinen Gegenschalter mehr:
   ihr Heben steht seit 16.10.2026 ganz unter (hover: hover) and
   (pointer: fine), siehe Team-Abschnitt.
   Ausgenommen bleiben: .is-open (Messschalter ?open= aus app.js),
   [aria-current="true"] (der aktive Abschnitt aus dem Scrollspy — der
   traegt sein eigenes Rot und darf nie zurueckgenommen werden) und
   :focus-visible (Tastatur gibt es auch an Beruehrgeraeten). */
@media (hover: none), (pointer: coarse) {
  .nb:not(.is-open):not([aria-current="true"]):hover:not(:focus-visible) .nb__t { max-width: 0; }
  .nb:not(.is-open):not([aria-current="true"]):hover:not(:focus-visible) {
    background: rgba(255, 255, 255, .88);
    border-color: var(--edge);
    box-shadow: 0 1px 1px rgba(27, 26, 24, .04), 0 10px 22px -16px rgba(27, 26, 24, .65);
  }
  .nb:not(.is-open):not([aria-current="true"]):hover:not(:focus-visible) .nb__t > span { opacity: 0; }
}
/* Tippflaechen der Leiste auf Beruehrgeraeten (16.10.2026): die Kreise
   stehen schmal nur 36 bzw. 38 px gross. Ein unsichtbarer Rand fuehrt die
   Trefferflaeche auf 44 px Hoehe und seitlich in die halbe Luecke zum
   Nachbarn - sichtbar aendert sich nichts, und keine Flaeche reicht in die
   des Nachbarn hinein. Mit feinem Zeiger bleibt alles wie es war. */
@media not all and (hover: hover) and (pointer: fine) {
  .nb::before,
  .nx::after {
    content: "";
    position: absolute;
    /* gemessen ab der Polsterkante, darum je 1px fuer den Rand dazu */
    top: min(-1px, calc((44px - var(--s)) / -2 - 1px));
    bottom: min(-1px, calc((44px - var(--s)) / -2 - 1px));
    left: calc(var(--gap) / -2 - 1px);
    right: calc(var(--gap) / -2 - 1px);
  }
}

/* ---------- Lesbarkeit ----------
   text-wrap: pretty verhindert das einzelne Wort in der letzten Zeile. */
p, li, address, figcaption { text-wrap: pretty; }

/* Deutsche Silbentrennung nur schmal — das <html> traegt lang="de",
   die Trennung greift also. */
@media (max-width: 640px) {
  body { hyphens: auto; -webkit-hyphens: auto; }
}

/* Grosse Ueberschriften bleiben von der Trennung verschont, laufen aber
   trotzdem nie ueber die Kante hinaus. */
.display {
  hyphens: manual;
  -webkit-hyphens: manual;
  overflow-wrap: break-word;
}

/* ---------- Reduzierte Bewegung ----------
   Die Endzustaende bleiben (Strich bei Hover und Fokus), nur der Weg
   dorthin entfaellt. */
@media (prefers-reduced-motion: reduce) {
  .foot__list a, .foot__bar a { transition: none; }
}

/* ============================================================
   NACHTRAG — der Knopf "Shop" in der Navigationsleiste
   Wozu diese Regeln da sind: der Shop ist eine EIGENE Anwendung
   (Next.js, Port 3000). Sein Knopf ist der einzige der Leiste, der von
   dieser Seite wegfuehrt. Er traegt darum bewusst nicht die Klasse .nb —
   app.js sammelt alle .nb ein und erwartet zu jedem einen Abschnitt
   DIESER Seite (navLinks -> navSecs); ein Knopf ohne Abschnitt legte
   dort ein null ab. Statt app.js anzufassen, wird die Optik hier aus
   denselben Zutaten nachgebaut: gleiche Rundung, gleiche Fuellung,
   gleicher Rand, gleicher Schatten, gleiches Ausfahren der Beschriftung
   (die Innenteile .nb__i / .nb__t gelten weiter, sie haengen nicht an .nb;
   navMeasure misst --tw fuer diese Beschriftung mit).
   Der Unterschied "fuehrt hinaus" bleibt im vorhandenen System: ein
   Haarstrich als Trennung davor (--edge) und ein feiner Pfeil hinter der
   Beschriftung (--gold). Keine neue Farbe, keine neue Rundung.
   Zeigerdurchlass: .nx setzt bewusst KEIN pointer-events — es erbt von
   .nav__kreise und ist damit im Auftakt (data-nav="aus") genauso stumm
   wie die anderen Knoepfe.
   ============================================================ */
.nx {
  --pfeil: 11px;                 /* Kantenlaenge des Wegweiser-Pfeils */
  position: relative;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  height: var(--s);
  border-radius: 999px;
  text-decoration: none;
  color: var(--ink);
  background: rgba(255, 255, 255, .88);
  border: 1px solid var(--edge);
  box-shadow: 0 1px 1px rgba(27, 26, 24, .04), 0 10px 22px -16px rgba(27, 26, 24, .65);
  -webkit-backdrop-filter: blur(9px) saturate(1.25);
  backdrop-filter: blur(9px) saturate(1.25);
  transition:
    background-color .40s var(--ease),
    border-color .40s var(--ease),
    box-shadow .40s var(--ease),
    color .35s var(--ease);
  /* Die Trennung braucht Luft: zum letzten Kreis hin steht der doppelte
     Zwischenraum, der Strich sitzt genau in dessen Mitte. */
  margin-left: var(--gap);
}
.nx::before {
  content: "";
  position: absolute;
  left: calc(-1px - var(--gap));
  top: 50%;
  width: 1px;
  height: 20px;
  margin-top: -10px;
  background: var(--edge);
}
/* --tw kommt aus navMeasure() (offsetWidth des inneren span). Gemessen wird
   dort, wo .nb__t noch auf max-width:0 steht — und in dieser Zwangslage
   zaehlt der Browser den Pfeil, ein atomares Inline-Element, NICHT in die
   Mindestbreite des span: gemessen 63px statt der freien 74px, der Pfeil
   wurde vom overflow:hidden abgeschnitten (nachgemessen am 29.08.2026).
   Darum wird seine Kantenlaenge hier wieder dazugerechnet. Die .nb
   tragen keinen Pfeil und sind davon nicht betroffen. */
.nx:hover .nb__t,
.nx:focus-visible .nb__t { max-width: calc(var(--tw, 260px) + var(--pfeil)); }
.nx:hover, .nx:focus-visible {
  background: var(--card-alt);
  border-color: rgba(27, 26, 24, .20);
  box-shadow: 0 2px 3px rgba(27, 26, 24, .05), 0 16px 30px -18px rgba(27, 26, 24, .75);
}
.nx:hover .nb__t > span,
.nx:focus-visible .nb__t > span { opacity: 1; transform: none; color: var(--ink); }
.nx:focus-visible { outline: 2px solid var(--rot); outline-offset: 3px; }
@media (hover: hover) and (pointer: fine) {
  .nx:active { transform: translateY(.5px); }   /* wie .nb:active */
}
/* Der feine Pfeil steht IM Beschriftungs-span: damit misst app.js ihn in
   --tw mit und die Pille faehrt genau bis dahin aus. */
.nx__weg {
  display: inline-block;
  width: var(--pfeil); height: var(--pfeil);
  margin-left: 6px;
  vertical-align: -1px;          /* auf der Versalhoehe stehen, nicht auf der Grundlinie */
  color: var(--gold);
  overflow: visible;
}
@media (max-width: 640px) {
  .nx { --pfeil: 10px; }
  .nx::before { height: 17px; margin-top: -8.5px; }
  .nx__weg { margin-left: 5px; }
}
/* Seit 16.10.2026 steht die Beschriftung "Shop" IMMER ausgefahren, auf
   jeder Breite (Auftraggeber) - der eine Knopf, der hinausfuehrt, soll
   sagen, wohin. Darum hier kein --tw und kein Ausfahren mehr: die Spalte
   nimmt ihre natuerliche Breite (Wort plus Pfeil), auch beim Ueberfahren.
   Nachgemessen bei 390 und 360 px: die Leiste passt ohne Ueberlauf. Die
   anderen Knoepfe (.nb) fahren unveraendert erst beim Ueberfahren aus. */
.nx .nb__t,
.nx:hover .nb__t,
.nx:focus-visible .nb__t { max-width: none; transition: none; }
.nx .nb__t > span { opacity: 1; transform: none; }

/* ============================================================
   03 — Bruecke in den Shop

   Der ruhigste Abschnitt der Seite: er verkauft nicht, er zeigt den
   Weg. Deshalb keine eigene Buehne, kein Scrollweg, keine Bewegung
   ausser dem gewoehnlichen Reveal — der Abschnitt sitzt in derselben
   .card .section wie Ueber uns und benutzt dessen Masse.

   Die drei Karten baut app.js aus GET /api/produkte. Solange sie
   fehlen, traegt der Kopfteil den Abschnitt allein; das Raster steht
   dann gar nicht da (`hidden`).
   ============================================================ */
.bruecke__titel {
  font-size: clamp(28px, 4.2vw, 60px);
  max-width: 18ch;
  margin-bottom: clamp(26px, 4vh, 48px);
}

/* Text links, Wege und Versandzeile darunter. Eine zweite Spalte gaebe es
   nur um der Symmetrie willen — der Abschnitt hat nicht genug zu sagen,
   um sie zu fuellen. */
.bruecke__kopf { max-width: 62ch; }

.bruecke__text { margin: 0; color: var(--ink); }

/* Der Versandbetrag (GET /api/versand, Rueckfall im HTML). Er ist Mass,
   traegt also Gold wie jede andere Menge auf dieser Seite. */
.bruecke__betrag {
  color: var(--gold);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.bruecke__wege {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin: clamp(22px, 3vh, 32px) 0 0;
}

/* Der Knopf. Dieselben Zutaten wie ueberall: 44 px Antippflaeche, 11px
   Versalien in weiter Sperrung, Radius --r. Der ruhende Knopf traegt nur
   Rand, der aktive die gefuellte Flaeche in --rot — Weiss auf Rot gibt es
   auf dieser Seite sonst nirgends, hier traegt es die eine Handlung. */
.bruecke__knopf {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 10px 20px;
  border: 1px solid var(--edge);
  border-radius: var(--r);
  background: var(--card-alt);
  color: var(--ink);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  text-decoration: none;
  transition: background-color .2s var(--ease), border-color .2s var(--ease),
              color .2s var(--ease), transform .18s var(--ease);
}
.bruecke__knopf:hover { background: var(--bg-deep); }
/* Bewegung beim Druecken nur mit feinem Zeiger (wie .nb:active); auf dem
   Handy traegt der Farbwechsel des Hover-Zustands die Rueckmeldung. */
@media (hover: hover) and (pointer: fine) {
  .bruecke__knopf:active { transform: scale(.985); }
}
.bruecke__knopf:focus-visible { outline: 2px solid var(--rot); outline-offset: 2px; }

.bruecke__knopf--aktiv {
  border-color: var(--rot);
  background: var(--rot);
  color: var(--card-alt);
}
/* Beim Ueberfahren nicht heller, sondern eine Spur tiefer — dieselbe
   Entscheidung wie beim gefuellten Knopf des Shops. Der Wert steht hier
   direkt: die Portfolioseite fuehrt kein --rot-tief. */
.bruecke__knopf--aktiv:hover { border-color: #8a2419; background: #8a2419; }

.bruecke__versand {
  margin: 18px 0 0;
  font-size: 13px;
  line-height: 1.6;
  color: var(--ink);
}
.bruecke__versand a { color: inherit; }

/* ---------- Die drei Karten ----------
   Drei Spalten am Schreibtisch, zwei auf dem Handy. Nicht eine: bei einer
   Spalte steht die dritte Karte so weit unten, dass sie niemand mehr als
   Teil desselben Abschnitts liest. app.js baut immer bis zu drei Karten
   (Fotos aus der Verwaltung zuerst, dann die Werkstattbilder). */
.bruecke__karten {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--frame);
  margin: clamp(30px, 5vh, 56px) 0 0;
}
@media (min-width: 720px) {
  .bruecke__karten { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
/* Schmal zwei Spalten, also nur ZWEI Karten: die dritte stand allein in
   einer zweiten Reihe, der Auftraggeber will die Reihe symmetrisch
   (Design-Review 15.10.2026). app.js baut weiter drei; ab drei Spalten
   ist die dritte wieder da. */
@media (max-width: 719px) {
  .bruecke__karte:nth-child(n + 3) { display: none; }
}

.bruecke__karte {
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
  border: 1px solid var(--edge);
  border-radius: var(--r);
  background: var(--card-alt);
  color: inherit;
  text-decoration: none;
  transition: border-color .22s var(--ease), transform .22s var(--ease),
              box-shadow .22s var(--ease);
}
/* Heben und Eindruecken nur mit feinem Zeiger (16.10.2026): auf dem Handy
   setzt schon der Daumen, der auf einer Karte zum Wischen ansetzt, :active
   und :hover — die Karte zuckte dann unter dem Finger. Dort bleibt als
   Rueckmeldung nur der rote Rand beim Druecken. */
.bruecke__karte:focus-visible {
  border-color: var(--rot);
  transform: translateY(-2px);
  box-shadow: 0 18px 40px -22px rgba(27, 26, 24, .42);
}
.bruecke__karte:focus-visible { outline: 2px solid var(--rot); outline-offset: 2px; }
@media (hover: hover) and (pointer: fine) {
  .bruecke__karte:hover {
    border-color: var(--rot);
    transform: translateY(-2px);
    box-shadow: 0 18px 40px -22px rgba(27, 26, 24, .42);
  }
  .bruecke__karte:active { transform: scale(.99); }
}
@media not all and (hover: hover) and (pointer: fine) {
  .bruecke__karte:active { border-color: var(--rot); }
}

/* Feste Geometrie, damit beim Laden nichts springt — dieselbe 3:4 wie die
   Produktkarte des Shops. */
.bruecke__bild {
  display: block;
  position: relative;
  aspect-ratio: 3 / 4;
  overflow: hidden;
  background: var(--bg-deep);
}
.bruecke__bild img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform .75s var(--ease);
}
.bruecke__karte:focus-visible .bruecke__bild img { transform: scale(1.045); }
@media (hover: hover) and (pointer: fine) {
  .bruecke__karte:hover .bruecke__bild img { transform: scale(1.045); }
}

.bruecke__zeile {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 6px;
  padding: 14px 16px 16px;
  border-top: 1px solid var(--hair);
}
.bruecke__kicker {
  margin: 0;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--ink);
}
.bruecke__name { margin: 0; font-size: 17px; font-weight: 600; line-height: 1.2; }
/* Der Preis kommt aus der API und steht als Mass in Gold — die Rolle, die
   auf dieser Seite jede Zahl traegt. `margin-top:auto` haelt ihn an der
   Unterkante, damit die Preise dreier Karten auf einer Linie stehen, auch
   wenn ein Name zwei Zeilen braucht. */
.bruecke__preis {
  margin: auto 0 0;
  padding-top: 8px;
  font-size: 14px;
  font-weight: 600;
  color: var(--gold);
  font-variant-numeric: tabular-nums;
}

@media (prefers-reduced-motion: reduce) {
  .bruecke__karte,
  .bruecke__bild img,
  .bruecke__knopf { transition: none !important; transform: none !important; }
}

/* ============================================================
   Scrollgebundene Animationen (html.sda)

   Der Kopfscript in index.html setzt html.sda, wenn der Browser
   animation-timeline kann (und nicht ?alt, nicht reduzierte Bewegung).
   Dann rechnet der BROWSER alle Bewegungen aus dem Scrollstand, nicht
   mehr app.js im rAF. Grund: mit dem Finger scrollt der Compositor
   sofort, das Script zog bisher ein Bild spaeter nach — die klebenden
   Buehnen zitterten beim Wischen. transform und
   opacity mit scroll()-Zeitachse laufen in Chrome AUF dem Compositor,
   im selben Bild wie das Scrollen (gemessen per Trace, Chrome 151:
   auch mit var() in den Keyframes, Pixel-Bereichen und linear()).

   Jede Formel hier ist wortgleich die Rechnung, die app.js im Rueckfall
   im Takt macht: p = (y - von) / (bis - von), auf 0 … 1 begrenzt. Die
   Wege selbst (von, bis, Masse) misst app.js einmal je Groesse in
   messen() und legt sie als Custom Properties ab, im Takt nie.
   animation-range in Pixeln auf scroll(root) heisst genau das: der
   Fortschritt laeuft von Scrollstand «von» bis «bis».
   Die Ausgleichskurve von app.js fuer das Wachsen der Team-Platte (arbEase)
   steht hier als linear() mit 41 Stuetzstellen; Abweichung hoechstens
   0.0009 des Wegs. Die Buehne des Ablaufs laeuft linear.

   Was NICHT auf dem Compositor laeuft (clip-path, Custom Properties,
   stroke-dashoffset — Chrome 151 meldet «unsupported property»), ist
   ausschliesslich Form INNERHALB einer Flaeche, deren Lage selbst der
   Compositor fuehrt. Es kann darum ein Bild spaet kommen, aber nie die
   Flaeche gegen den Finger verschieben.
   ============================================================ */
@property --ein-t { syntax: '<number>'; inherits: false; initial-value: 1; }

html.sda {
  /* arbEase (easeInOutCubic) aus app.js */
  --lb-inaus: linear(0, 0.00006 2.5%, 0.0005 5%, 0.00169 7.5%, 0.004 10%, 0.00781 12.5%, 0.0135 15%, 0.02144 17.5%, 0.032 20%, 0.04556 22.5%, 0.0625 25%, 0.08319 27.5%, 0.108 30%, 0.13731 32.5%, 0.1715 35%, 0.21094 37.5%, 0.256 40%, 0.30706 42.5%, 0.3645 45%, 0.42869 47.5%, 0.5 50%, 0.57131 52.5%, 0.6355 55%, 0.69294 57.5%, 0.744 60%, 0.78906 62.5%, 0.8285 65%, 0.86269 67.5%, 0.892 70%, 0.91681 72.5%, 0.9375 75%, 0.95444 77.5%, 0.968 80%, 0.97856 82.5%, 0.9865 85%, 0.99219 87.5%, 0.996 90%, 0.99831 92.5%, 0.9995 95%, 0.99994 97.5%, 1);
}

/* ---------- Auftakt: Festhalten, Einfahren, Leiste ----------
   Nur unter html.sda.einfahrt, und einfahrt setzt der Kopfscript weiter
   NUR bei feinem Zeiger: auf Beruehrgeraeten bewusst kein Zoom, keine
   versteckte Leiste, kein Festhalten (Vorgabe des Auftraggebers) — dort
   gilt die ruhende Karte wie bisher.
   Alle drei auf demselben Weg: Scrollstand 0 bis --halt.
   Festhalten: wie updateEinfahrt(), translate3d(0, (p-1)*HALT, 0); danach
   (fill nur rueckwaerts) KEIN transform mehr — keine verschobene Bezugs-
   flaeche fuer die klebenden Buehnen. Laeuft auf dem Compositor. */
@keyframes lb-halten {
  from { transform: translate3d(0, calc(-1 * var(--halt)), 0); }
  to   { transform: translate3d(0, 0, 0); }
}
html.sda.einfahrt .stack {
  animation: lb-halten linear backwards;
  animation-duration: auto;
  animation-timeline: scroll(root);
  animation-range: 0px var(--halt);
}
/* Einfahren: dieselbe Zahl --einfahrt wie bisher, nur kommt sie jetzt aus
   der Zeitachse (--ein-t) statt aus dem Takt. Geometrie, Ring, Film und
   Wortmarke leiten sich daraus ab wie in html.einfahrt .hero oben.
   --wort-k und --film-k: wortgleich updateEinfahrt(); die Masse der
   randlosen Flaeche (--hf-*, unitless px) setzt app.js je Groesse.
   Bewusst als EINE Zahl auf dem Hauptfaden statt Film und Marke als
   eigene Compositor-Animationen: der Ausschnitt (clip-path) kann nicht auf
   den Compositor, und Film/Marke muessen zu IHM passen, nicht zum Finger —
   getrennt liefe der Film einem Ausschnitt voraus und legte am Rand den
   Grund frei. Die Lage der Flaeche fuehrt das Festhalten (Compositor). */
@keyframes lb-einfahrt {
  from { --ein-t: 0; }
  to   { --ein-t: 1; }
}
html.sda.einfahrt .hero {
  animation: lb-einfahrt linear both;
  animation-duration: auto;
  animation-timeline: scroll(root);
  animation-range: 0px var(--halt);
  --einfahrt: var(--ein-t);
  --hf-iw: calc(var(--hf-w) - 2 * var(--hf-f) * var(--ein-t) - 2);
  --hf-ih: calc(max(var(--hf-svh) - (var(--hf-o) + var(--hf-f)) * var(--ein-t), 380) - 2);
  --wort-k: calc(var(--hf-iw) / var(--hf-w));
  --film-k: calc(var(--hf-hoch) * var(--hf-iw) / var(--hf-w) + (1 - var(--hf-hoch)) *
    max(var(--hf-iw), var(--hf-ih) * 1920 / 1072) / max(var(--hf-w), var(--hf-h) * 1920 / 1072));
}
/* Leiste: navAuf = p * 1.25, also voll bei 80 % des Wegs. Klickbarkeit
   (data-nav) bleibt in app.js — ein Attribut, keine Bewegung. */
@keyframes lb-leiste {
  from { opacity: 0; }
  to   { opacity: 1; }
}
html.sda.einfahrt .nav {
  animation: lb-leiste linear both;
  animation-duration: auto;
  animation-timeline: scroll(root);
  animation-range: 0px calc(var(--halt) * .8);
}

/* ---------- Ablauf (.people) ----------
   Weg: --p-von … --p-bis (G.pTop … G.pTop + pH - pStageH), ein Schritt
   je Person (--p-schritt = Weg / (N-1)); --i ist die Nummer des Bildes
   bzw. Texts, --p-n1 = N-1 (beides app.js, einmal).
   Bild:  translate (i - p*(N-1)) * 100 %. Deckkraft fest 1: app.js
          blendet im Rueckfall jedes Bild aus, das mehr als eine Bildhoehe
          entfernt liegt — dort liegt es aber ganz ausserhalb von
          .people__lead (overflow: hidden) und ist ohnehin weggeschnitten.
          Als Animation waere dieses Ein/Aus eine konstante Deckkraft, die
          Chrome nicht auf den Compositor gibt (gemessen: «no effect») —
          also auf dem Hauptfaden, ein Bild spaeter als die Lage.
   Text:  translate (i - p*(N-1)) * 46 px, Deckkraft 1 - 2|o| (Zelt um
          den eigenen Schritt), greifbar nur bei |o| < .25.
   Das erste und das letzte Element halten ihren Randwert ueber den Weg
   hinaus — wie p auf 0 bzw. 1 begrenzt ist. */
@keyframes lb-p-bild {
  from { transform: translate3d(0, calc(var(--i) * 100%), 0); }
  to   { transform: translate3d(0, calc((var(--i) - var(--p-n1)) * 100%), 0); }
}
@keyframes lb-p-text {
  from { transform: translate3d(0, calc(var(--i) * 46px), 0); }
  to   { transform: translate3d(0, calc((var(--i) - var(--p-n1)) * 46px), 0); }
}
@keyframes lb-blende    { 0% { opacity: 0; } 50% { opacity: 1; } 100% { opacity: 0; } }
@keyframes lb-blende-aus { from { opacity: 1; } to { opacity: 0; } }
@keyframes lb-blende-ein { from { opacity: 0; } to { opacity: 1; } }
@keyframes lb-greifbar   { from, to { pointer-events: auto; } }
@keyframes lb-p-leiste {
  from { transform: translate3d(0, var(--rail-von), 0); }
  to   { transform: translate3d(0, var(--rail-bis), 0); }
}
html.sda .people__shot {
  opacity: 1;
  animation: lb-p-bild linear both;
  animation-duration: auto;
  animation-timeline: scroll(root);
  animation-range: var(--p-von, 0px) var(--p-bis, 1px);
}
html.sda .people__panel {
  pointer-events: none;
  animation: lb-p-text linear both, lb-blende linear none, lb-greifbar linear none;
  animation-duration: auto;
  animation-timeline: scroll(root);
  animation-range:
    var(--p-von, 0px) var(--p-bis, 1px),
    calc(var(--p-von, 0px) + (var(--i, 0) - .5) * var(--p-schritt, 1px)) calc(var(--p-von, 0px) + (var(--i, 0) + .5) * var(--p-schritt, 1px)),
    calc(var(--p-von, 0px) + (var(--i, 0) - .25) * var(--p-schritt, 1px)) calc(var(--p-von, 0px) + (var(--i, 0) + .25) * var(--p-schritt, 1px));
}
html.sda .people__panel:first-of-type {
  animation-name: lb-p-text, lb-blende-aus, lb-greifbar;
  animation-fill-mode: both, backwards, backwards;
  animation-range:
    var(--p-von, 0px) var(--p-bis, 1px),
    var(--p-von, 0px) calc(var(--p-von, 0px) + .5 * var(--p-schritt, 1px)),
    var(--p-von, 0px) calc(var(--p-von, 0px) + .25 * var(--p-schritt, 1px));
}
html.sda .people__panel:last-of-type {
  animation-name: lb-p-text, lb-blende-ein, lb-greifbar;
  animation-fill-mode: both, forwards, forwards;
  animation-range:
    var(--p-von, 0px) var(--p-bis, 1px),
    calc(var(--p-bis, 1px) - .5 * var(--p-schritt, 1px)) var(--p-bis, 1px),
    calc(var(--p-bis, 1px) - .25 * var(--p-schritt, 1px)) var(--p-bis, 1px);
}
/* Bildleiste rechts: ry = -railH/2 - weg/2 + p * weg (app.js rechnet die
   beiden Enden einmal je Groesse) */
html.sda .people__railtrack {
  animation: lb-p-leiste linear both;
  animation-duration: auto;
  animation-timeline: scroll(root);
  animation-range: var(--p-von, 0px) var(--p-bis, 1px);
}

/* ---------- Team-Schiene (.arb) ----------
   Wachsen: --a-von … + --a-wachs, Kurve arbEase; Platte und Schatten von
   scale(--a-sx, --a-sy) auf 1, der Schatten blendet dabei aus. Danach
   (fill nur rueckwaerts) kein transform mehr — wie arbStauchen().
   Fahren: --a-fahrt-von … + --a-fahrt, linear: Reihe, Drahtgitter
   (0.26 * 0.09 des Wegs) und Balken. */
@keyframes lb-a-stauch {
  from { transform: scale(var(--a-sx, 1), var(--a-sy, 1)); }
  to   { transform: scale(1, 1); }
}
@keyframes lb-a-schlag {
  from { transform: scale(var(--a-sx, 1), var(--a-sy, 1)); opacity: 1; }
  to   { transform: scale(1, 1); opacity: 0; }
}
@keyframes lb-a-reihe {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(calc(-1 * var(--a-fahrt, 0px)), 0, 0); }
}
@keyframes lb-a-geist {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(calc(var(--a-fahrt, 0px) * .0234), 0, 0); }
}
@keyframes lb-a-meter {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}
html.sda .arb__plate,
html.sda .arb__schlag {
  animation: lb-a-stauch linear backwards;
  animation-timing-function: var(--lb-inaus);
  animation-duration: auto;
  animation-timeline: scroll(root);
  animation-range: var(--a-von, 0px) calc(var(--a-von, 0px) + var(--a-wachs, 1px));
}
html.sda .arb__schlag { animation-name: lb-a-schlag; }
html.sda .arb__rail,
html.sda .arb__ghost,
html.sda .arb__meter i {
  animation: lb-a-reihe linear both;
  animation-duration: auto;
  animation-timeline: scroll(root);
  /* --a-fahrt-weg ist der Fahrweg, mindestens 1px: bei einer Reihe ohne
     Ueberstand (travel 0) springt sie dort, wo app.js t von 0 auf 1 setzt */
  animation-range: var(--a-fahrt-von, 0px) calc(var(--a-fahrt-von, 0px) + var(--a-fahrt-weg, 1px));
}
html.sda .arb__ghost   { animation-name: lb-a-geist; }
html.sda .arb__meter i { animation-name: lb-a-meter; }

/* Messen ohne laufende Bewegung: app.js setzt die Klasse fuer die Dauer
   EINER Messung je Groesse (Platte) — nie im Takt. Das Festhalten bleibt
   dabei an, messen() rechnet es ohnehin heraus. */
html.lb-mass .arb__plate,
html.lb-mass .arb__schlag { animation: none !important; }
