/* ============================================================
   Editorial Sheets v2: Website-Motion/UI-Stilsystem
   Referenz: greymac.com (Graham McDonnell), live vermessen am
   29.08.2026, plus Reel @detailsdotso. Dependency-frei.
   Kern-Mechanik: helle Karten wachsen auf blauem Grund heran und
   schieben sich uebereinander. Der Grund ist reines Blau mit weissen
   Schleiern; die Schrift zoomt nie mit.
   ============================================================ */

/* ---------- 1. Tokens ---------- */
:root {
  /* Karten: reines Weiss, die Zweitfarbe traegt einen Blauhauch,
     damit abwechselnde Karten sich noch unterscheiden. */
  --es-canvas:  #EDF1FF;              /* Seiten-Grund heller Seiten */
  --es-sheet:   #FFFFFF;              /* die Karte */
  --es-sheet-2: #F3F6FF;              /* die zweite Karte, Blauhauch */
  --es-ink:     #121417;              /* Tinte */
  --es-ink-2:   rgba(18, 20, 23, 0.82);
  --es-ink-3:   rgba(18, 20, 23, 0.64);
  --es-rule:    rgba(18, 20, 23, 0.14); /* feine Zeilenlinien, keine Kartenrahmen */

  /* Grund-Register: mehr Weiss als Blau. Der Grund ist ein weisses
     Blau-Weiss, das Blau kommt nur noch als Waesche an den Raendern.
     Weil der Grund hell ist, ist "paper" (die Schrift darauf) dunkel:
     die Rollen der beiden Register sind getauscht, nicht die Namen. */
  --es-ground:  #FFFFFF;              /* Weiss, der Grund */
  --es-ground-2:#0000FF;              /* das reine Blau, nur fuer Akzentflaechen */
  --es-glow-1:  rgba(26, 44, 240, 0.62);    /* die blauen Baender */
  --es-glow-2:  rgba(48, 84, 250, 0.56);
  --es-glow-3:  rgba(12, 26, 214, 0.46);
  /* Der Grund traegt kein Korn mehr: das Korn sass auf beiden Ebenen,
     Grund und Karte, und hat den Kontrast zwischen ihnen weggenommen.
     Es bleibt nur noch auf den Karten. Variablen fuer den Grund: */
  --es-bg-over: 30vmax;                     /* Ueberstand, damit der Weichzeichner die Bildraender nicht aufhellt */
  /* Der Weichzeichner steht in vmax, nicht in px: die Verlaeufe sind in
     vmax gebaut, ein fester px-Wert waescht sie im schmalen Fenster
     weg und laesst sie im breiten hart wirken. */
  --es-bg-soft: 0.55vmax;                      /* Weichzeichner der grossen Boegen: weich, aber die Form bleibt lesbar */
  --es-bg-edge: 0.4vmax;                    /* Weichzeichner der kleineren Boegen */
  --es-glass-hi:   0.72;                    /* helle Ecke der Scheibe */
  --es-glass-lo:   0.18;                    /* dunklere Ecke */
  --es-glass-blur: 20px;
  --es-grain-img: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  --es-paper:   #0A0E1A;                    /* Schrift auf dem hellen Grund */
  --es-paper-2: rgba(10, 14, 26, 0.84);
  --es-paper-3: rgba(10, 14, 26, 0.66);
  --es-rule-d:  rgba(10, 14, 26, 0.16);

  /* Der eine Akzent: Rot, nur in Case-Highlights */
  --es-red:     #D22B20;

  /* Typo (greymac: Hedvig Letters Serif + Neue Montreal; hier Geist) */
  --es-serif: "Hedvig Letters Serif", "Newsreader", "Georgia", serif;
  --es-sans:  "Geist", -apple-system, "Helvetica Neue", sans-serif;

  /* Form: innen kleine Radien (greymac: 10px), aussen grosse Sheet-Ecken */
  --es-r:      10px;
  --es-r-l:    clamp(22px, 3.5vw, 40px);
  --es-shadow: 0 18px 50px rgba(0, 0, 60, 0.16), 0 3px 10px rgba(0, 0, 60, 0.07);
  --es-shadow-up: 0 -26px 64px rgba(0, 0, 120, 0.16), 0 2px 10px rgba(0, 0, 120, 0.06);

  /* Motion */
  --es-t: 0.6s;
  --es-ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* Case-Overlap: wie lange der Hero gepinnt bleibt, waehrend
     das naechste Sheet ueber ihn faehrt */
  --es-dwell: 60vh;
}

/* ---------- 2. Basis ---------- */
.es-body {
  margin: 0;
  background: var(--es-ground);
  color: var(--es-ink);
  font-family: var(--es-sans);
  font-size: 15px;
  /* Grundgewicht 450 statt 400: auf dem hellen Grund liest sich 400
     grau und duenn, die Buchstaben verlieren gegen die Flaeche. */
  font-weight: 450;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
/* Der Grund: weiche blaue Flecken auf Weiss, kein Korn.

   Vorgeschichte: erst Ring-Verlaeufe plus Filmkorn, dann Ringe mit
   Kontur. Beide Male lasen sich die Boegen als Baender oder Schlaufen,
   die quer durchs Bild laufen und hinter der Schrift Muster machen.
   Jetzt Kreise mit einer Kante: aussen ein harter Schnitt, nach innen
   ein langer Verlauf ins Nichts. Die Farbe sitzt also am Rand und
   loest sich zur Mitte hin auf, nicht umgekehrt. Deshalb liegt hier
   kein filter: blur() mehr, das wuerde die Kante wieder aufweichen;
   die Weichheit steckt in den Farbstopps.

   ::before  grosse, sehr weiche Flecken am Bildrand. Sie geben den
             Grund seine Faerbung, ohne eine Form zu behaupten.
   ::after   kleinere, dichtere Flecken in denselben Ecken. Sie geben
             den grossen Wolken einen Kern, damit die Farbe nicht
             ueberall gleich duenn ist.

   Beide Ebenen liegen in den Ecken und am Rand, die Bildmitte bleibt
   weiss. Dort steht der Text, und dort darf keine Farbe stehen.

   filter: blur() ist hier erlaubt, weil beide Ebenen Pseudo-Elemente
   des Bodys sind und kein Vorfahre der Karten. Ein filter auf einem
   Vorfahren wuerde deren backdrop-filter abschalten, ein filter
   daneben nicht.

   Beide Ebenen stehen um --es-bg-over ueber den Bildrand hinaus,
   sonst zieht der Weichzeichner transparente Raender ins Bild und der
   Grund bekommt einen hellen Rahmen. Weil die Ebene groesser ist als
   das Fenster, werden die Zentren aus dem Ueberstand heraus gerechnet:
   calc(var(--es-bg-over) + Position im Fenster). */
.es-body::before,
.es-body::after {
  content: "";
  position: fixed;
  inset: calc(-1 * var(--es-bg-over));
  z-index: 0;
  pointer-events: none;
}
/* Geometrie der Kreise steht einmal in Variablen, damit Flaeche und
   spaetere Aenderungen denselben Kreis benutzen. */
.es-body::before {
  --es-k1: circle 42vmax at calc(var(--es-bg-over) - 14vw) calc(var(--es-bg-over) - 16vh);
  --es-k2: circle 33vmax at calc(var(--es-bg-over) + 108vw) calc(var(--es-bg-over) + 2vh);
  --es-k3: circle 30vmax at calc(var(--es-bg-over) - 16vw) calc(var(--es-bg-over) + 68vh);
  --es-k4: circle 38vmax at calc(var(--es-bg-over) + 18vw) calc(var(--es-bg-over) + 116vh);
  --es-k5: circle 44vmax at calc(var(--es-bg-over) + 112vw) calc(var(--es-bg-over) + 92vh);
  /* Ein weicher Schleier ueber allem: die Boegen verschwimmen ein
     Stueck, bleiben aber sichtbar. Kein Maskenschnitt mehr, die
     harte Kante ist damit bewusst wieder weg. */
  filter: blur(var(--es-bg-soft));
  background:
    radial-gradient(var(--es-k1),
      rgba(26,44,240,0) 0 20vmax, rgba(26,44,240,0.38) 42vmax, transparent 42vmax),
    radial-gradient(var(--es-k2),
      rgba(48,84,250,0) 0 16vmax, rgba(48,84,250,0.34) 33vmax, transparent 33vmax),
    radial-gradient(var(--es-k3),
      rgba(26,44,240,0) 0 15vmax, rgba(26,44,240,0.30) 30vmax, transparent 30vmax),
    radial-gradient(var(--es-k4),
      rgba(26,44,240,0) 0 19vmax, rgba(26,44,240,0.32) 38vmax, transparent 38vmax),
    radial-gradient(var(--es-k5),
      rgba(12,26,214,0) 0 22vmax, rgba(12,26,214,0.32) 44vmax, transparent 44vmax);
}
.es-body::after {
  --es-k6: circle 18vmax at calc(var(--es-bg-over) + 8vw) calc(var(--es-bg-over) + 6vh);
  --es-k7: circle 15vmax at calc(var(--es-bg-over) + 94vw) calc(var(--es-bg-over) + 18vh);
  --es-k8: circle 17vmax at calc(var(--es-bg-over) + 88vw) calc(var(--es-bg-over) + 78vh);
  filter: blur(var(--es-bg-edge));
  background:
    radial-gradient(var(--es-k6),
      rgba(16,30,225,0) 0 8vmax, rgba(16,30,225,0.28) 18vmax, transparent 18vmax),
    radial-gradient(var(--es-k7),
      rgba(30,60,244,0) 0 7vmax, rgba(30,60,244,0.24) 15vmax, transparent 15vmax),
    radial-gradient(var(--es-k8),
      rgba(10,20,200,0) 0 8vmax, rgba(10,20,200,0.24) 17vmax, transparent 17vmax);
}
/* Der Grund atmet: beide Ebenen driften kaum merklich und ungleich
   schnell (90s gegen 70s), damit die Boegen langsam gegeneinander
   wandern statt als ein Bild zu schwimmen. Nur transform, die Ebenen
   bleiben auf dem Compositor und die Weichzeichner rechnen nicht neu.
   Der Ueberstand (--es-bg-over, 30vmax) ist um ein Vielfaches groesser
   als die Drift, es kommt also nie ein Rand ins Bild. */
@keyframes es-bg-drift-a {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  50%      { transform: translate3d(4.5vmax, -3.2vmax, 0) scale(1.08); }
}
@keyframes es-bg-drift-b {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  50%      { transform: translate3d(-3.8vmax, 2.8vmax, 0) scale(1.06); }
}
.es-body::before { animation: es-bg-drift-a 70s ease-in-out infinite; }
.es-body::after  { animation: es-bg-drift-b 52s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
  .es-body::before, .es-body::after { animation: none; }
}

.es-body--light { background: var(--es-canvas); }
.es-body--light::before, .es-body--light::after { display: none; }
.es-wrap { max-width: 1200px; margin: 0 auto; padding: 0 clamp(18px, 4vw, 56px); }

/* Serif-Display */
.es-display {
  font-family: var(--es-serif);
  font-weight: 400;
  letter-spacing: -0.01em;
  line-height: 1.04;
  margin: 0;
}
.es-display em { font-style: italic; }
.es-h1 { font-size: clamp(42px, 7.5vw, 110px); }
.es-h2 { font-size: clamp(28px, 4.2vw, 56px); }
.es-h3 { font-size: clamp(20px, 2.4vw, 30px); }

/* Caps-Display fuer Case-Titel und Hero-Namen (grosse Grotesk-Caps) */
.es-caps {
  font-family: var(--es-sans);
  font-weight: 640;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  line-height: 0.96;
  margin: 0;
  font-size: clamp(40px, 7vw, 104px);
}
.es-caps em { font-family: var(--es-serif); font-style: italic; font-weight: 400; text-transform: none; letter-spacing: -0.01em; }

/* Mikro-Label: ( INTRO ), greymac setzt runde Klammern */
.es-label {
  font-family: var(--es-sans);
  font-size: 10.5px;
  font-weight: 580;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--es-ink-3);
  margin: 0;
}
.es-label::before { content: "( "; }
.es-label::after  { content: " )"; }

/* Serif-Eyebrow ueber Sektionstiteln: "CAREER &" klein serif, darunter gross */
.es-eyebrow {
  font-family: var(--es-serif);
  font-size: 13px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--es-ink-2);
  margin: 0 0 6px;
}

.es-lead { font-size: clamp(15px, 1.6vw, 18px); line-height: 1.62; color: var(--es-ink-2); }
.es-meta { font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--es-ink-3); }

/* Caps-Fliesstext der Case-Beschreibungen (klein, eng, laut greymac) */
.es-smallcaps {
  font-size: 10.5px;
  font-weight: 540;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  line-height: 1.85;
  color: var(--es-paper-2);
  margin: 0;
}

/* ---------- 3. Slab: die helle Box, die klein hochkommt ----------
   Das Leitmotiv. Eine Slab betritt das Bild als kleinere gerundete
   Box und waechst beim Ankommen auf Vollformat. es-motion.js schreibt
   zwei Regler:
     --e  Eintritt, 0 (noch klein, unten) bis 1 (angekommen, voll)
     --x  Verdeckung, 0 (frei) bis 1 (die naechste Slab liegt drauf)
   --x macht die hintere Slab kleiner und dunkler, wenn sich Karten
   ueberlagern. Beides zusammen in EINEM transform, sonst gewinnt der
   letzte und der andere Effekt faellt aus. */
.es-slab {
  --e: 1;
  --x: 0;
  /* Wie weit die sichtbare Flaeche vom Kartenrand einrueckt. Elemente,
     die in der Ecke der Flaeche sitzen sollen, rechnen damit. */
  --es-inset-x: 0px;
  --es-inset-y: 0px;
  position: relative;
  z-index: 2;
  background: none;             /* die Flaeche liegt im ::before */
  color: var(--es-ink);
  /* KEIN filter hier. Ein Filter am Elternteil hebt backdrop-filter im
     Kind auf, und die Kartenflaeche ist Glas. Das Abdunkeln der
     verdeckten Karte uebernimmt deshalb eine Verlaufsebene im ::before.

     Das Zurueckweichen dagegen skaliert die ganze Karte, Schrift und
     Bildfelder eingeschlossen: nur wenn der Inhalt mitgeht, liest es
     sich als Tiefe und nicht als eingefahrener Rand. Der Ursprung liegt
     oben mittig, damit die sichtbare Kopfzeile an ihrem Platz bleibt
     und die Karte nach hinten weg schrumpft.
     Nicht zu verwechseln mit dem Eintritt (--e): der laesst die Schrift
     ausdruecklich in Ruhe und bewegt nur die Flaeche. */
  transform: scale(calc(1 - 0.05 * var(--x)));
  transform-origin: 50% 0;
}
/* Nur die Flaeche waechst, nie die Schrift. Deshalb kein transform auf
   der Slab (das wuerde den Text mitzoomen), sondern ein Hintergrund-
   Layer, dessen Raender einfahren. Der Inhalt liegt darueber und steht
   die ganze Zeit still. */
.es-slab::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset:
    calc((1 - var(--e)) * 22px)
    calc((1 - var(--e)) * 5.5vw);
  /* Vier Ebenen, von oben nach unten:
     1. Korn, eingeblendet statt aufgelegt
     2. Verdeckungsschleier, dessen Deckkraft an --x haengt
     3. die harte Glanzlinie an der Oberkante (macht Glas aus Blur)
     4. der Glasverlauf von heller zu dunklerer Ecke */
  background-image:
    var(--es-grain-img),
    linear-gradient(
      rgba(10, 18, 52, min(0.30, calc(0.14 * var(--x)))),
      rgba(10, 18, 52, min(0.30, calc(0.14 * var(--x))))),
    linear-gradient(180deg,
      rgba(255, 255, 255, 0.92) 0, rgba(255, 255, 255, 0.92) 2px,
      rgba(255, 255, 255, 0) 20px),
    linear-gradient(155deg,
      rgba(255, 255, 255, var(--es-glass-hi)),
      rgba(255, 255, 255, var(--es-glass-lo)));
  background-blend-mode: soft-light, normal, normal, normal;
  -webkit-backdrop-filter: blur(var(--es-glass-blur)) saturate(180%);
  backdrop-filter: blur(var(--es-glass-blur)) saturate(180%);
  border: 1px solid rgba(255, 255, 255, 0.72);
  border-radius: var(--es-r-l);
  box-shadow:
    0 20px 48px -18px rgba(16, 28, 90, 0.30),
    inset 0 1px 1px rgba(255, 255, 255, 0.92),
    inset 0 -8px 14px -10px rgba(10, 20, 60, 0.16);
}
.es-slab.es-sheet--white { background: none; }
/* Die zweite Kartenvariante ist dichteres Glas, nicht eine andere Farbe. */
.es-slab.es-sheet--white::before { --es-glass-hi: 0.86; --es-glass-lo: 0.34; }
.es-slab-pad { position: relative; padding: clamp(34px, 6vh, 92px) clamp(24px, 6vw, 96px); }

/* Drei Endzustaende, damit nicht jede Karte gleich ankommt: */

/* a) Standard (oben): waechst bis genau an die Raender.

   b) Schwebend: bleibt auch am Ende eingerueckt, beruehrt die Raender
      nie. Fuer Karten, die als Objekt auf dem blauen Grund liegen
      sollen statt als Seitenflaeche. */
.es-slab--float { --es-inset-x: 6vw; --es-inset-y: 2.5vh; }
.es-slab--float::before {
  inset:
    calc(2.5vh + (1 - var(--e)) * 14px)
    calc(6vw + (1 - var(--e)) * 3vw);
}
.es-slab--float .es-slab-pad { padding-inline: clamp(44px, 11vw, 170px); }

/* c) Uebernahme: waechst ueber die eigenen Kanten hinaus und wird zum
      Hintergrund. Der Radius laeuft dabei auf 0, damit aus der Karte
      eine Flaeche wird und keine grosse Karte bleibt. */
.es-slab--takeover::before {
  /* Seitlich genau bis zur Kante, nie darueber hinaus: ein seitlicher
     Ueberstand erzeugt Querscrollen, weil overflow-x nur auf dem Body
     sitzt. Der Ueberstand nach oben und unten macht die Flaeche. */
  inset:
    calc((1 - var(--e)) * 26px - var(--e) * 14vh)
    calc((1 - var(--e)) * 6vw);
  border-radius: calc((1 - var(--e)) * var(--es-r-l));
}

/* d) Vollbild-Zoom: startet als schwebende Karte auf dem Grund und
      waechst beim Scrollen bis an alle vier Kanten.
      Unterschied zu --takeover: die Flaeche geht nicht ueber die
      eigenen Kanten hinaus. Fuer sehr hohe Karten ist das noetig, ein
      Ueberstand nach oben legte sich sonst ueber den Abschnitt davor.
      Und der Radius bleibt: die Karte wird randlos, hoert aber nicht
      auf, eine Karte zu sein. In den vier Ecken steht deshalb auch am
      Ende ein Stueck Grund. Wer eine echte Flaeche will, nimmt
      --takeover, das laesst den Radius auslaufen.
      Beide Einrueckungen laufen ueber --es-inset-x/-y, damit die
      Variablen auch waehrend des Zooms stimmen: alles, was sich an der
      Kante der Flaeche ausrichtet (Eck-Pfeil), rechnet damit. */
.es-slab--zoom {
  --es-inset-x: calc((1 - var(--e)) * 6vw);
  --es-inset-y: calc((1 - var(--e)) * 2.5vh);
  /* Notwerte, falls das Skript nicht laeuft: die Karte steht dann
     sofort auf Vollformat, statt gar nicht zu erscheinen. */
  --zsx: 1;
  --zsy: 1;
}
/* Der Zoom faehrt ueber transform, nicht ueber die Kanten.

   Die Kanten (inset) sind Geometrie: jede Aenderung kostet Layout und
   Neuzeichnen, und auf dieser Flaeche liegt ein backdrop-filter. Bei
   der Werdegang-Karte sind das 1280 mal 1851 Pixel, die in jedem Bild
   neu durch den Weichzeichner muessen. Gemessen allein fuer Stil und
   Layout, ohne das Zeichnen: 0.79ms pro Schritt gegen 0.17ms bei einer
   normalen Karte, und das Zeichnen ist der teurere Teil. Ein transform
   dagegen laeuft im Compositor, ohne Layout und ohne neu zu zeichnen.

   Die Skalierfaktoren kommen aus es-motion.js (measureZoom), weil CSS
   die eigene Pixelbreite nicht kennt. Die Rundung und der 1px-Rahmen
   werden dabei mitskaliert, unterwegs sind die Ecken also um wenige
   Pixel elliptisch; am Ziel (--e: 1) steht alles exakt.

   Der Rest der Karten bleibt bei inset: sie sind klein, ihr Eintritt
   ist kurz, und dort faellt das nicht ins Gewicht. */
.es-slab--zoom::before {
  inset: 0;
  transform-origin: 50% 50%;
  transform:
    scaleX(calc(1 - (1 - var(--zsx)) * (1 - var(--e))))
    scaleY(calc(1 - (1 - var(--zsy)) * (1 - var(--e))));
  /* Kein will-change: der backdrop-filter hebt die Flaeche ohnehin auf
     eine eigene Compositor-Ebene, und ein will-change auf genau so
     einer Flaeche laesst den Hintergrund in manchen Chrome-Versionen
     aus einer alten Aufnahme lesen. Der Gewinn waere null, das Risiko
     ein stehengebliebener Hintergrund. */
}

/* Stapel: Slabs pinnen und schieben sich uebereinander. Die hintere
   bleibt stehen und schrumpft (--x), die vordere kommt drueber. */
/* Stapel: jede Karte pinnt eine Stufe tiefer als die vorige. Bei
   gleicher Hoehe deckte die neue die alte vollstaendig zu und man
   konnte immer nur eine lesen. Mit der Stufe bleibt von jeder
   darunterliegenden Karte Kopfzeile, Titel und Zeile darunter stehen.
   Am Ende des Stapels loesen sich alle gleichzeitig und fahren
   gemeinsam raus. */
.es-slab-stack {
  /* Ausgangswert, falls das Skript nicht laeuft. Im Betrieb misst
     es-motion.js die Stufe am Titel der ersten Karte. */
  --es-stack-step: clamp(84px, 11vh, 132px);
  /* Halteraum unter der letzten Karte: die Lesezeit, in der alle
     Karten gemeinsam stehen. Er haengt als Rand an der letzten Karte,
     nicht als Innenabstand am Stapel. Ein Innenabstand vergroessert
     den Inhaltskasten nicht, in dem ein Sticky-Element leben darf,
     die letzte Karte wuerde also weiter hinausgeschoben, bevor sie
     ueberhaupt pinnt. */
  /* Halteraum am Stapelende: nur so lang, dass die letzte Karte
     sichtbar einrastet, danach faehrt der Stapel geschlossen raus.
     Er muss ein eigenes leeres Element sein (siehe ::after unten): ein
     Innenabstand am Stapel liegt ausserhalb des Kastens, in dem ein
     Sticky-Element leben darf, und ein Rand an der letzten Karte
     schiebt ihre Grenze gleich mit. Beides gemessen, beides wirkungslos. */
  --es-stack-hold: clamp(80px, 11vh, 150px);
  /* Ausfahrt: die Strecke, auf der der eingerastete Stapel geschlossen
     nach oben geht. Ueberliesse man das der Sticky-Mechanik, loesten
     sich die Karten nacheinander (die unterste zuerst, weil ihr im
     Kasten am wenigsten Platz bleibt) und die oberste schoebe sich
     dabei noch einmal ueber die anderen. Deshalb schiebt es-motion.js
     alle Karten um denselben Betrag hoch, solange sie gepinnt sind.
     Der Wert hier ist nur die Notbremse, falls das Skript nicht
     laeuft. Im Betrieb setzt measureStackRoom() ihn auf genau den Weg,
     den der Stapel zurueckzulegen hat: Strecke gleich Weg heisst eins
     zu eins mit dem Scroll, also dasselbe Tempo wie der Rest der
     Seite. Ein kuerzerer Wert laesst den Stapel davonschiessen und
     bricht das Tempo, wenn die Karten sich am Ende loesen. */
  --es-stack-exit: clamp(200px, 33vh, 340px);
  display: flex;
  flex-direction: column;
  gap: clamp(16px, 2.5vh, 30px);
}
.es-slab-stack::after { content: ""; display: block; flex: 0 0 auto; height: calc(var(--es-stack-hold) + var(--es-stack-exit)); }
.es-slab-stack > .es-slab {
  position: sticky;
  top: var(--es-stack-top-1, clamp(34px, 6vh, 72px));
  /* Eigene translate-Eigenschaft, damit sie sich nicht mit dem
     transform: scale() des Zurueckweichens ueberschreibt. */
  translate: 0 var(--es-exit-y, 0px);
}
.es-slab-stack > .es-slab:nth-child(2) { top: calc(clamp(34px, 6vh, 72px) + var(--es-stack-step)); }
.es-slab-stack > .es-slab:nth-child(3) { top: calc(clamp(34px, 6vh, 72px) + 2 * var(--es-stack-step)); }
.es-slab-stack > .es-slab:nth-child(4) { top: calc(clamp(34px, 6vh, 72px) + 3 * var(--es-stack-step)); }
.es-slab-stack > .es-slab:nth-child(5) { top: calc(clamp(34px, 6vh, 72px) + 4 * var(--es-stack-step)); }
@media (max-height: 760px) {
  .es-slab-stack { --es-stack-step: clamp(50px, 6.4vh, 72px); }
}

/* Slab zieht sich ueber den Vorgaenger (dunkler Streifen davor). */
.es-slab--over { margin-top: calc(-1 * var(--es-dwell)); }

/* Master: alte Klasse, jetzt Alias fuer eine Slab ohne Eintrittszoom
   (fuer Unterseiten, die einfach nur eine helle Flaeche brauchen). */
.es-master {
  position: relative;
  z-index: 2;
  background: var(--es-sheet);
  color: var(--es-ink);
  border-radius: var(--es-r-l);
  box-shadow: var(--es-shadow-up);
}
.es-master--over { margin-top: calc(-1 * var(--es-dwell)); }
.es-master-pad { padding: clamp(40px, 7vh, 110px) clamp(20px, 5vw, 72px); }

/* Sektionen im Sheet: viel Luft dazwischen, dicht im Inhalt */
.es-sect { padding: clamp(44px, 9vh, 130px) 0; }
.es-sect + .es-sect { border-top: 1px solid var(--es-rule); }

/* Zweispalter: links schmale Sektions-Einleitung, rechts die Daten */
.es-cols { display: grid; grid-template-columns: minmax(200px, 30%) 1fr; gap: clamp(24px, 5vw, 80px); align-items: start; }
.es-cols > .side { position: sticky; top: 24px; }
.es-cols .side p { font-size: 12px; line-height: 1.7; color: var(--es-ink-2); margin: 14px 0 0; max-width: 300px; }
@media (max-width: 760px) { .es-cols { grid-template-columns: 1fr; } .es-cols > .side { position: static; } }

/* ---------- 4. Roll: die grosse Serif-Liste (Career) ----------
   Eine Station pro Zeile, riesig, zentriert, mit winziger
   Caps-Anmerkung. Faded gestaffelt ein (es-motion.js). */
.es-roll { list-style: none; margin: 0; padding: 0; text-align: center; }
.es-roll li {
  font-family: var(--es-serif);
  font-size: clamp(30px, 5vw, 64px);
  line-height: 1.42;
  letter-spacing: -0.01em;
  white-space: nowrap;
}
.es-roll .note {
  font-family: var(--es-sans);
  font-size: 9.5px;
  font-weight: 560;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--es-ink-3);
  vertical-align: super;
  margin-left: 10px;
  white-space: nowrap;
}
@media (max-width: 640px) {
  .es-roll li { white-space: normal; font-size: clamp(26px, 8vw, 40px); }
  .es-roll .note { display: block; margin: 2px 0 10px; }
}

/* ---------- 5. Rows: dichte Datenzeilen (Awards, Projekt-Index) ----------
   Kapitälchen links, Beschreibung mittig gedimmt, Jahr rechts.
   Duenne Linien, enge Zeilen: die Dichte kommt aus der Menge. */
.es-rows { margin: 0; }
.es-rows .row {
  display: grid;
  grid-template-columns: minmax(140px, 34%) 1fr auto;
  gap: 18px;
  align-items: baseline;
  padding: 13px 0;
  border-top: 1px solid var(--es-rule);
  text-decoration: none;
  color: inherit;
}
.es-rows .row:first-child { border-top: 0; }
.es-rows .t { font-size: 11px; font-weight: 620; letter-spacing: 0.12em; text-transform: uppercase; color: var(--es-ink); }
.es-rows .d { font-size: 10.5px; font-weight: 500; letter-spacing: 0.10em; text-transform: uppercase; color: var(--es-ink-3); }
.es-rows .y { font-size: 10.5px; letter-spacing: 0.08em; color: var(--es-ink-3); font-variant-numeric: tabular-nums; white-space: nowrap; }
a.row { transition: opacity var(--es-t) var(--es-ease); }
a.row:hover { opacity: 0.55; }
@media (max-width: 640px) { .es-rows .d { display: none; } }

/* ---------- 6. Cloud: Wordmark-Wolke (Notable Partners) ---------- */
.es-cloud { display: flex; flex-wrap: wrap; gap: clamp(18px, 3.5vw, 44px) clamp(24px, 5vw, 72px); align-items: baseline; justify-content: center; padding: 10px 0; }
.es-cloud span { font-family: var(--es-serif); font-size: clamp(17px, 2.2vw, 26px); letter-spacing: -0.01em; white-space: nowrap; }
.es-cloud span:nth-child(3n)   { font-family: var(--es-sans); font-weight: 640; text-transform: uppercase; font-size: clamp(12px, 1.5vw, 17px); letter-spacing: 0.08em; }
.es-cloud span:nth-child(4n+1) { font-style: italic; }

/* ---------- 7. Case: dunkler Streifen, gepinnt ----------
   Nicht mehr Vollbild als Regel: --es-band setzt die Hoehe, Standard
   ist 78vh. Nur der Hero (.es-case--hero) nimmt den ganzen Schirm.
   .es-case ist der Scrollraum, .es-case-pin pinnt den Inhalt. */
.es-case {
  --es-band: 78vh;
  position: relative; z-index: 1;
  min-height: calc(var(--es-band) + var(--es-dwell));
  background: none;              /* der blaue Grund scheint durch */
  color: var(--es-paper);
}
.es-case--solid { background: var(--es-ground-2); }
.es-case-pin {
  position: sticky; top: calc((100vh - var(--es-band)) / 2);
  min-height: var(--es-band);
  display: grid;
  align-content: center;
  overflow: hidden;
  padding: clamp(20px, 5vw, 72px);
}
.es-case--hero { --es-band: 100vh; }
/* Der Hero sitzt in dem Streifen, der ueber der Box frei bleibt,
   und nicht in der ganzen Fensterhoehe: sonst liegt der Name auf
   kleineren Bildschirmen unter der Kartenkante, weil die Karte
   dort weiter oben klebt. Die Streifenhoehe misst es-motion.js
   (Klebepunkt der Box), hier steht nur der Notwert.
   box-sizing, damit der Innenabstand in der Hoehe steckt und die
   Rechnung fuer die Ueberlappung nicht daneben liegt. */
.es-case--hero .es-case-pin {
  top: 0;
  box-sizing: border-box;
  min-height: var(--es-hero-strip, 100vh);
}
.es-case .es-label { color: var(--es-paper-3); }

/* Meta-Zeile CLIENT : X   COMPANY : Y   YEAR : Z */
.es-case-meta { display: flex; flex-wrap: wrap; gap: 8px 34px; margin: 22px 0 0; }
.es-case-meta span { font-size: 9.5px; font-weight: 560; letter-spacing: 0.16em; text-transform: uppercase; color: var(--es-paper-3); }
.es-case-meta b { font-weight: 640; color: var(--es-paper); }

/* Der eine rote Akzent */
.es-case-kicker { font-family: var(--es-serif); font-size: clamp(13px, 1.5vw, 17px); letter-spacing: 0.28em; text-transform: uppercase; color: var(--es-red); margin: 0 0 14px; }
.es-panel--tilt { transform: rotate(5deg); box-shadow: 0 40px 90px rgba(0, 0, 70, 0.55), 0 0 0 6px var(--es-red); }

/* ---------- 8. Panel: gerundetes Bild ---------- */
.es-panel { border-radius: var(--es-r); overflow: hidden; box-shadow: var(--es-shadow); background: var(--es-ground-2); }
.es-panel img { display: block; width: 100%; height: 100%; object-fit: cover; }
.es-panel--type { display: grid; place-items: center; min-height: 220px; background: var(--es-ground-2); color: var(--es-sheet); }
.es-panel--type span { font-family: var(--es-serif); font-size: clamp(72px, 10vw, 150px); line-height: 1; }

/* Die 3D-Mulde: das Objekt sitzt frei in der Karte, ohne Kasten und
   ohne Rahmen. Das Canvas fuellt die Buehne, Groesse kommt von aussen.
   Ein weicher Schein darunter setzt das Objekt auf die Karte, statt es
   auf eine Flaeche zu kleben. */
.es-object {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  opacity: 0;
  transition: opacity 0.6s var(--es-ease);
}
.es-object.is-ready { opacity: 1; }
.es-object::before {
  content: "";
  position: absolute; inset: 12% 12% 8%;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(26, 44, 240, 0.16), rgba(26, 44, 240, 0) 72%);
  filter: blur(10px);
}
.es-object canvas { position: relative; display: block; width: 100%; height: 100%; }
@media (prefers-reduced-motion: reduce) { .es-object { transition: none } }

/* ---------- 9. Sheet-Karte im Sheet (Hebung), Pill, Tag ---------- */
.es-sheet { background: var(--es-sheet); border-radius: var(--es-r); box-shadow: var(--es-shadow); padding: clamp(24px, 3.5vw, 48px); }
.es-sheet--white { background: var(--es-sheet-2); }

.es-pill {
  display: inline-flex; align-items: center; gap: 9px;
  padding: 12px 26px;
  border: 0; border-radius: 999px;
  background: var(--es-ink); color: var(--es-sheet);
  font-family: var(--es-sans);
  font-size: 10.5px; font-weight: 620; letter-spacing: 0.14em; text-transform: uppercase;
  text-decoration: none; cursor: pointer;
  transition: transform var(--es-t) var(--es-ease), opacity var(--es-t) var(--es-ease);
}
.es-pill:hover { transform: translateY(-2px); opacity: 0.88; }
.es-pill--ghost { background: transparent; color: inherit; box-shadow: inset 0 0 0 1.5px currentColor; }

.es-tag { display: inline-block; padding: 5px 12px; border-radius: 999px; background: rgba(18, 20, 23, 0.06); font-size: 9.5px; font-weight: 580; letter-spacing: 0.1em; text-transform: uppercase; color: var(--es-ink-2); }

/* CTA-Zeile am Sektionsende: Frage links, Pill rechts */
.es-cta-row { display: flex; justify-content: space-between; align-items: center; gap: 18px; flex-wrap: wrap; margin-top: clamp(26px, 4vh, 44px); padding-top: 18px; border-top: 1px solid var(--es-rule); }
.es-cta-row p { font-size: 10.5px; font-weight: 580; letter-spacing: 0.14em; text-transform: uppercase; color: var(--es-ink-3); margin: 0; }

/* ---------- 10. Dunkel-Register (Hero, Kontakt, Fuss) ----------
   Kein eigener Grund: der blaue Schleier der Seite traegt diese
   Abschnitte. --solid deckt ihn ab, wo Ruhe noetig ist. */
.es-dark { background: none; color: var(--es-paper); }
.es-dark--solid { background: var(--es-ground-2); }
.es-dark .es-lead   { color: var(--es-paper-2); }
.es-dark .es-meta   { color: var(--es-paper-3); }
.es-dark .es-label  { color: var(--es-paper-3); }
.es-dark .es-eyebrow{ color: var(--es-paper-2); }
.es-dark .es-pill   { background: var(--es-paper); color: var(--es-ground); }
/* Die Umriss-Variante muss den Register-Fuellstil wieder abraeumen,
   sonst gewinnt .es-dark .es-pill und alle Knoepfe sehen gleich aus. */
.es-dark .es-pill--ghost { background: transparent; color: var(--es-paper); }
.es-dark .es-tag    { background: rgba(10, 14, 26, 0.07); color: var(--es-paper-2); }
.es-dark .es-rows .row { border-top-color: var(--es-rule-d); }
.es-dark .es-rows .t { color: var(--es-paper); }
.es-dark .es-rows .d, .es-dark .es-rows .y { color: var(--es-paper-3); }
.es-dark .es-cta-row { border-top-color: var(--es-rule-d); }
.es-dark .es-cta-row p { color: var(--es-paper-3); }

/* ---------- 11. Prose (Case-Seiten, Posts, Legal) ---------- */
.es-prose h3 { font-family: var(--es-serif); font-weight: 400; font-size: 20px; margin: 1.9em 0 0.55em; }
.es-prose h3:first-child { margin-top: 0; }
.es-prose p, .es-prose li { font-size: 14.5px; line-height: 1.72; color: var(--es-ink-2); }
.es-prose ul { padding-left: 1.15em; }
.es-prose li { margin: 0.35em 0; }
.es-prose strong { color: var(--es-ink); font-weight: 600; }
.es-prose code { font-size: 0.88em; background: rgba(18, 20, 23, 0.06); padding: 0.1em 0.4em; border-radius: 6px; }

/* Der ausgeschriebene Case zieht die Textflaeche weiter ueber den
   Kopf als eine Sektion auf der Startseite. Mit der normalen
   Ueberdeckung endete die Fahrt genau auf der Oberkante des
   Kopfblocks (gemessen Flaechenoberkante 389 gegen Kopf 373): der
   Name lag die ganze gepinnte Strecke halb angeschnitten da, und
   das war der Endzustand, nicht ein Moment im Vorbeigehen. Jetzt
   faehrt die Flaeche 252px weiter und der Kopf ist am Ende ganz
   dahinter. Die Ueberdeckung haengt am Wert selbst: die Flaeche
   startet unabhaengig davon immer an der Unterkante des Cases
   minus Ueberdeckung, aber die gepinnte Strecke waechst mit. */
.es-detail { --es-dwell: 88vh; }
/* Der Streifen muss auf einer Case-Seite hoeher sein als in einer
   Sektion. Die Textflaeche beginnt immer genau eine Streifenhoehe
   unter der Oberkante des Cases, der Kopfblock haengt aber oben fest
   und waechst nach unten: bei 78vh endete die Zeile mit Kunde,
   Status und Datum 26px ueber der Kante und wurde angeschnitten.
   Der Wert muss auf .es-detail .es-case stehen und nicht auf
   .es-detail: --es-band ist in .es-case selbst gesetzt, eine Vorgabe
   weiter oben im Baum kommt dagegen nicht an. */
.es-detail .es-case { --es-band: 88vh; }
@media (max-width: 720px) {
  .es-detail { --es-dwell: 50vh; }
}

/* ---------- 12. Kamera: Zoom im gepinnten Case ----------
   es-motion.js schreibt --p (0..1) auf jede .es-case. Der Inhalt
   faehrt von 0.92 auf 1.02 heran (Push-in), das Panel laeuft leicht
   versetzt mit (Parallaxe ueber die translate-Property, damit der
   rotate des Tilt-Panels unberuehrt bleibt). */
.es-case { --p: 0; }
.es-case .case-inner {
  translate: 0 calc((0.5 - var(--p)) * 46px);
  /* Der Boden liegt bei 0.8 und nicht bei 0.45. Beim Hereinscrollen
     war 0.45 nur ein kurzer Moment, aber ein Case, den man aus der
     Uebersicht anklickt, startet bei --p = 0 und bleibt dort stehen:
     die Ueberschrift stand dann dauerhaft halb durchsichtig ueber den
     blauen Baendern. Die Fahrt bleibt, sie faengt nur lesbar an. */
  opacity: min(1, calc(0.8 + var(--p) * 1.2));
}
/* Das Bild laeuft langsamer als der Text: Tiefe ohne Skalierung. */
.es-case .case-inner .es-panel { translate: 0 calc((0.5 - var(--p)) * -34px); }

/* Hero-Variante: liegt UNTER dem ersten Sheet und weicht nach hinten,
   indem sie abblendet und leicht steigt. Kein Zoom. */
.es-case--hero { background: none; }
.es-case--hero .case-inner {
  translate: 0 calc(var(--p) * -44px);
  /* Die Fahrt endet, wenn die Box einrastet, und der Hero bleibt
     danach in diesem Zustand stehen. Deshalb darf sie ihn nicht
     ganz wegblenden: 0.55 statt 0.85 laesst den Namen lesbar. */
  opacity: calc(1 - var(--p) * 0.55);
}
.es-case--hero .case-inner .es-panel { translate: none; }

/* ---------- 13. Intro-Overlay: einmal durchzoomen ---------- */
.es-intro {
  position: fixed; inset: 0; z-index: 99;
  background: var(--es-ground);
  display: grid; place-items: center;
  pointer-events: none;
}
.es-intro span {
  font-family: var(--es-serif);
  font-size: clamp(44px, 9vw, 130px);
  color: var(--es-paper);
  opacity: 0;
}
.es-intro.go { animation: esIntroFade 1.6s cubic-bezier(0.7, 0, 0.3, 1) forwards; }
.es-intro.go span { animation: esIntroZoom 1.6s cubic-bezier(0.7, 0, 0.3, 1) forwards; }
@keyframes esIntroZoom {
  0%   { transform: scale(0.72); opacity: 0; }
  30%  { opacity: 1; }
  55%  { transform: scale(1); opacity: 1; }
  100% { transform: scale(14); opacity: 0; }
}
@keyframes esIntroFade {
  0%, 72% { opacity: 1; }
  100%    { opacity: 0; visibility: hidden; }
}

/* ---------- 14. Reveal-Zustaende (es-motion.js) ----------
   Weicher als v2: mehr Weg, leichter Blur, laengere Kurve. */
[data-es-reveal], .es-roll li, .es-cloud span {
  opacity: 0;
  transform: translateY(36px);
  filter: blur(7px);
  transition:
    opacity 1.05s cubic-bezier(0.16, 1, 0.3, 1) var(--es-delay, 0s),
    transform 1.05s cubic-bezier(0.16, 1, 0.3, 1) var(--es-delay, 0s),
    filter 1.05s cubic-bezier(0.16, 1, 0.3, 1) var(--es-delay, 0s);
}
.es-in { opacity: 1 !important; transform: none !important; filter: none !important; }

@media (prefers-reduced-motion: reduce) {
  [data-es-reveal], .es-roll li, .es-cloud span { opacity: 1; transform: none; filter: none; transition: none; }
  .es-case { min-height: auto; }
  .es-case-pin { position: static; min-height: auto; }
  .es-master--over { margin-top: 0; }
  .es-case .case-inner, .es-case--hero .case-inner { translate: none; opacity: 1; }
  .es-case .case-inner .es-panel { translate: none; }
  .es-slab { filter: none; transform: none; }
  .es-slab::before { inset: 0; }
  .es-slab-stack > .es-slab { position: static; }
  .es-intro { display: none; }
  .es-pill, .es-pill:hover { transform: none; transition: none; }
}

/* ---------- 15. Schmale Breite ---------- */
@media (max-width: 720px) {
  :root { --es-dwell: 34vh; }
  .es-case { --es-band: 82vh; }
  .es-panel--tilt { transform: rotate(3deg); }
  /* Schmal faehrt die Flaeche weniger ein, sonst wird die Box winzig. */
  .es-slab::before {
    inset:
      calc((1 - var(--e)) * 12px)
      calc((1 - var(--e)) * 3.5vw);
  }
  .es-slab--float { --es-inset-x: 3.5vw; --es-inset-y: 1.4vh; }
  .es-slab--float::before {
    inset:
      calc(1.4vh + (1 - var(--e)) * 10px)
      calc(3.5vw + (1 - var(--e)) * 2vw);
  }
  .es-slab--float .es-slab-pad { padding-inline: clamp(26px, 8vw, 60px); }
  .es-slab--takeover::before {
    inset:
      calc((1 - var(--e)) * 14px - var(--e) * 8vh)
      calc((1 - var(--e)) * 4vw);
  }
  /* Der Vollbild-Zoom faehrt schmal aus einer kleineren Einrueckung
     auf, sonst waere die Startkarte fast schon randlos. Die Regel muss
     hier ausdruecklich stehen: die allgemeine .es-slab::before-Regel
     weiter oben in diesem Block hat dieselbe Gewichtung und steht
     spaeter als der Zoom-Block, sie wuerde sonst still uebernehmen. */
  .es-slab--zoom {
    --es-inset-x: calc((1 - var(--e)) * 3.5vw);
    --es-inset-y: calc((1 - var(--e)) * 1.4vh);
  }
  .es-slab--zoom::before { inset: var(--es-inset-y) var(--es-inset-x); }
}

/* ---------- 17. Navigation ----------
   Glas wie die Karten, nur dichter: sie liegt ueber dunklem Grund
   genauso wie ueber hellem, deshalb traegt sie mehr Weiss als eine
   Karte, sonst saeuft die Schrift ueber dem Hero ab.

   Zwei Zustaende. Oben auf der Seite steht sie voll da, alle Ziele
   lesbar. Sobald gescrollt wird, faellt die Schrift in sich zusammen
   und uebrig bleibt eine Blase mit dem Kuerzel. Beim Zeigen faehrt
   sie aus der Blase wieder auf, das Kuerzel bleibt dabei stehen:
   so wandert die Leiste zwischen den Zustaenden, statt zu wechseln.

   Der Einzug laeuft ueber grid-template-columns 1fr -> 0fr. Das ist
   die einzige Art, eine Breite von auto auf null zu fahren, ohne sie
   vorher zu messen. */
.es-nav {
  position: fixed;
  top: clamp(12px, 2vh, 22px);
  left: 50%;
  transform: translateX(-50%);
  /* Ohne diese zwei Zeilen erstickt die Leiste an ihrer eigenen
     Ausrichtung. Ein fest gesetzter Kasten mit left: 50% bekommt als
     verfuegbare Breite nur den Rest rechts der Kante, also die halbe
     Fensterbreite; das translate schiebt ihn danach nur optisch in
     die Mitte, die Breite ist da laengst gerechnet. Auf 375px waren
     das 188 statt der noetigen 333, und die Ziele standen als
     "Portf...", "Proje...", "Abou..." da. max-content nimmt die
     natuerliche Breite, die Deckelung haelt sie im Fenster. */
  width: max-content;
  max-width: calc(100vw - 2 * clamp(10px, 3vw, 24px));
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 0;
  padding: 5px;
  border-radius: 999px;
  isolation: isolate;
  transition: gap var(--es-t) var(--es-ease), padding var(--es-t) var(--es-ease);
}

/* Die Scheibe selbst. Liegt hinter der Schrift, damit backdrop-filter
   nur den Grund unter der Leiste greift und nicht die Beschriftung. */
.es-nav::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  /* Kein Korn. Die Kachel ist dieselbe wie auf den grossen Karten, auf
     44px Hoehe liest sie sich aber nicht als Textur, sondern als
     schmutzige Scheibe. Die Leiste lebt hier von Unschaerfe,
     Saettigung und Kante. */
  background-image:
    linear-gradient(180deg, rgba(255,255,255,0.85) 0, rgba(255,255,255,0.45) 3px, rgba(255,255,255,0) 18px),
    linear-gradient(155deg, rgba(255,255,255,0.50), rgba(255,255,255,0.40));
  /* Der Ursprung muss die Rahmenkante sein. Sonst liegt die Kachel auf
     dem Innenkasten, der 1px breite Streifen darueber zeigt per
     background-repeat das untere Ende derselben Kachel, und das ist
     das dunkle Ende des Verlaufs: eine schwarze Kappe oben. */
  background-origin: border-box;
  backdrop-filter: blur(18px) saturate(180%);
  -webkit-backdrop-filter: blur(18px) saturate(180%);
  border: 1px solid rgba(255,255,255,0.58);
  box-shadow:
    0 12px 30px -14px rgba(16, 28, 90, 0.30),
    0 2px 6px -2px rgba(16, 28, 90, 0.12),
    inset 0 1px 1px rgba(255,255,255,0.70);
}


/* Das Kuerzel. Oben auf der Seite ist es nicht da, es gehoert zur
   eingefahrenen Blase. Feste Breiten auf beiden Seiten, damit der
   Kreis rund bleibt und nicht ueber den Text seine Groesse holt. */
.es-nav-mark {
  display: grid;
  place-items: center;
  width: 0;
  height: clamp(30px, 2.9vw, 34px);
  opacity: 0;
  overflow: hidden;
  box-sizing: border-box;
  border-radius: 999px;
  /* Dunkles Glas: der Verlauf laesst den Grund durch, die Glanzkante
     oben und der helle Rand geben der Scheibe eine Dicke. Ein zweiter
     backdrop-filter bringt hier nichts Sichtbares mehr, der Grund
     unter der Blase ist schon durch die Leiste gegangen; die Tiefe
     kommt aus Durchsicht und Kante, nicht aus einer zweiten Unschaerfe. */
  background-image:
    linear-gradient(180deg, rgba(255,255,255,0.34) 0, rgba(255,255,255,0) 9px),
    linear-gradient(160deg, rgba(10, 14, 26, 0.58), rgba(10, 14, 26, 0.82));
  background-origin: border-box;
  border: 1px solid rgba(255,255,255,0.30);
  box-shadow:
    inset 0 1px 1px rgba(255,255,255,0.38),
    0 6px 14px -8px rgba(16, 28, 90, 0.50);
  color: #fff;
  font-family: var(--es-sans);
  font-size: 10.5px;
  font-weight: 640;
  letter-spacing: 0.1em;
  white-space: nowrap;
  transition:
    width var(--es-t) var(--es-ease),
    opacity var(--es-t) var(--es-ease);
}

.es-nav a {
  display: grid;
  grid-template-columns: 1fr;
  padding: 8px 13px;
  border-radius: 999px;
  font-size: 9.5px;
  font-weight: 620;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--es-paper-3);
  white-space: nowrap;
  transition:
    grid-template-columns var(--es-t) var(--es-ease),
    padding var(--es-t) var(--es-ease),
    letter-spacing var(--es-t) var(--es-ease),
    opacity var(--es-t) var(--es-ease),
    color var(--es-t) var(--es-ease),
    background-color var(--es-t) var(--es-ease);
}
.es-nav a > span { overflow: hidden; min-width: 0; }

.es-nav a:hover { color: var(--es-paper); background: rgba(10, 14, 26, 0.06); }
.es-nav a[aria-current] { color: var(--es-paper); background: rgba(10, 14, 26, 0.08); }

/* Voller Zustand: Zeigen laesst die Leiste etwas atmen. */
.es-nav:not(.is-compact):hover, .es-nav:not(.is-compact):focus-within { gap: 5px; padding: 7px; }
.es-nav:not(.is-compact):hover a,
.es-nav:not(.is-compact):focus-within a { padding: 9px 19px; letter-spacing: 0.2em; }

/* Eingefahren: nur die Blase. Die Ziele bleiben im Markup und im
   Fokuslauf, sie sind nur auf null gefahren. */
.es-nav.is-compact .es-nav-mark { width: clamp(30px, 2.9vw, 34px); opacity: 1; }
.es-nav.is-compact a {
  grid-template-columns: 0fr;
  padding-inline: 0;
  opacity: 0;
  pointer-events: none;
}
.es-nav.is-compact:hover, .es-nav.is-compact:focus-within { gap: 4px; }
.es-nav.is-compact:hover a,
.es-nav.is-compact:focus-within a {
  grid-template-columns: 1fr;
  padding-inline: 13px;
  opacity: 1;
  pointer-events: auto;
}

@media (max-width: 620px) {
  .es-nav a { padding: 7px 9px; font-size: 9px; letter-spacing: 0.08em; }
  .es-nav:not(.is-compact):hover a,
  .es-nav:not(.is-compact):focus-within a { padding: 8px 12px; letter-spacing: 0.12em; }
  .es-nav.is-compact:hover a,
  .es-nav.is-compact:focus-within a { padding-inline: 9px; }
}


@media (prefers-reduced-motion: reduce) {
  .es-nav, .es-nav a, .es-nav-mark { transition: none; }
}

/* ---------- 18. Kapitel ----------
   Ein Abschnitt, der den Grund uebernimmt: ab hier ist die Seite in
   der Farbe der Karten, die blauen Baender hoeren auf. Der Uebergang
   laeuft ueber einen Verlauf oberhalb der Kante, sonst schneidet die
   Farbe hart ins Bild. Inhalte darin brauchen keine eigene Flaeche
   mehr, sie stehen direkt auf dem Kapitel. */
.es-chapter {
  position: relative;
  z-index: 2;
  background: var(--es-chapter-bg, var(--es-sheet));
  padding: clamp(56px, 11vh, 130px) 0 clamp(64px, 13vh, 150px);
}
.es-chapter::before {
  content: "";
  position: absolute;
  left: 0; right: 0;
  top: calc(-1 * var(--es-chapter-fade, 160px));
  height: var(--es-chapter-fade, 160px);
  background: linear-gradient(to bottom, rgba(255, 255, 255, 0), var(--es-chapter-bg, var(--es-sheet)));
  pointer-events: none;
}

/* ---------- 19. Station ----------
   Der Karteneintrag ohne Karte. Auf einem Kapitel waere eine Glaskarte
   unsichtbar, weil Grund und Karte dieselbe Farbe haben. Stattdessen
   trennt eine Haarlinie, und die Spalten tragen die Ordnung:
   links wann, mitte was, rechts die Marken. */
.es-station {
  display: grid;
  grid-template-columns: minmax(120px, 17%) 1fr minmax(0, 22%);
  gap: clamp(16px, 3vw, 48px);
  align-items: start;
  padding: clamp(24px, 4vh, 46px) 0;
  border-top: 1px solid var(--es-rule);
}
.es-station:first-of-type { border-top: 0; }
.es-station .when { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
.es-station .marks { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end; }
@media (max-width: 900px) {
  .es-station { grid-template-columns: 1fr; gap: 12px; }
  .es-station .when { flex-direction: row; align-items: baseline; gap: 12px; }
  .es-station .marks { justify-content: flex-start; }
}

/* ---------- 20. Eck-Pfeil ----------
   Der Knopf in der oberen rechten Ecke einer Karte. Im Ruhezustand
   zeigt der Pfeil nach rechts, beim Zeigen kippt er auf 45 Grad nach
   oben rechts: die Geste fuer "fuehrt woanders hin". Gedreht wird die
   eigene rotate-Eigenschaft, nicht transform, damit sie sich nicht mit
   anderen Transformationen der Karte ins Gehege kommt. */
.es-arrow {
  /* Ein Abstandswert fuer beide Seiten, damit der Pfeil gleich weit
     von Ober- und Seitenkante der Flaeche wegsteht. Getrennte Werte in
     vh und vw ergeben je nach Fensterformat ungleiche Abstaende. */
  --es-arrow-gap: clamp(26px, 2.6vw, 42px);
  position: absolute;
  top: calc(var(--es-inset-y, 0px) + var(--es-arrow-gap));
  right: calc(var(--es-inset-x, 0px) + var(--es-arrow-gap));
  z-index: 3;
  width: clamp(40px, 4vw, 50px);
  height: clamp(40px, 4vw, 50px);
  display: grid;
  place-items: center;
  border-radius: 999px;
  background: rgba(10, 14, 26, 0.06);
  color: var(--es-paper);
  text-decoration: none;
  transition: background-color var(--es-t) var(--es-ease), color var(--es-t) var(--es-ease);
}
.es-arrow svg {
  width: 44%;
  height: 44%;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: rotate var(--es-t) var(--es-ease);
}
.es-arrow:hover, .es-arrow:focus-visible { background: var(--es-paper); color: #fff; }
.es-arrow:hover svg, .es-arrow:focus-visible svg { rotate: -45deg; }

@media (prefers-reduced-motion: reduce) {
  .es-arrow, .es-arrow svg { transition: none; }
  .es-arrow:hover svg { rotate: none; }
}

/* Im Stapel ist die ganze Karte der Klickweg (Handler in es-motion.js),
   wie bei den Kacheln der Projektuebersicht: der Pfeil springt beim
   Ueberfahren der Karte auf schwarz und kippt nach oben, egal wo der
   Zeiger steht. */
.es-slab-stack > .es-slab:hover .es-arrow { background: var(--es-paper); color: #fff; }
.es-slab-stack > .es-slab:hover .es-arrow svg { rotate: -45deg; }
@media (prefers-reduced-motion: reduce) {
  .es-slab-stack > .es-slab:hover .es-arrow svg { rotate: none; }
}

/* Derselbe Pfeil ohne den Knopf: fuer eine Zeile statt fuer eine
   Ecke. Kein Kreis, keine Flaeche, nur das Zeichen im Text.

   Die Richtung, in die er beim Zeigen kippt, sagt, wohin die Zeile
   fuehrt. Deshalb zwei Varianten, und nicht eine mit fester Drehung:
   --up fuehrt aus der Karte heraus, --down klappt darunter auf. */
.es-inline-arrow {
  display: inline-flex;
  align-items: center;
  margin-left: 0.7em;
  /* An der Versalhoehe ausgerichtet, nicht an der Zeilenhoehe: die
     Zeile ist gesperrt und in Versalien gesetzt, der Pfeil saesse
     sonst sichtbar zu tief. */
  translate: 0 -0.05em;
}
.es-inline-arrow svg {
  width: 1.25em;
  height: 1.25em;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
  rotate: 0deg;
  transition: rotate var(--es-t) var(--es-ease);
}

/* a) Fuehrt woanders hin. Kippt nach oben rechts, dieselbe Geste wie
      der Eck-Pfeil auf den Karten. */
a:hover .es-inline-arrow--up svg,
a:focus-visible .es-inline-arrow--up svg { rotate: -45deg; }

/* b) Klappt darunter auf. Kippt nach unten rechts, und aufgeklappt
      steht er senkrecht auf dem, was gerade aufgegangen ist. Beim
      Zeigen im offenen Zustand geht er denselben Weg zurueck: eine
      Bewegung ersetzt damit das Plus und das Minus. */
summary:hover .es-inline-arrow--down svg,
summary:focus-visible .es-inline-arrow--down svg { rotate: 45deg; }
details[open] .es-inline-arrow--down svg { rotate: 90deg; }
details[open] summary:hover .es-inline-arrow--down svg,
details[open] summary:focus-visible .es-inline-arrow--down svg { rotate: 45deg; }

@media (prefers-reduced-motion: reduce) {
  .es-inline-arrow svg { transition: none; }
}

/* ============================================================
   21. Uebersichtsraster: alle Arbeiten auf einen Blick
   Acht Kacheln statt acht ausgeschriebener Cases. Die Kachel
   traegt nur, was zum Auswaehlen reicht; alles Weitere liegt
   hinter dem Klick in .es-detail.
   ============================================================ */
/* Zwei Gruppen, Auftragsarbeit und Eigenes. Die Trennung laeuft
   ueber die Ueberschriftszeile, nicht ueber eine Linie oder einen
   Kasten: das Raster soll als ein Feld lesen und nicht als zwei
   getrennte Bloecke. */
.es-tiles-group + .es-tiles-group { margin-top: clamp(22px, 3.4vh, 40px); }
.es-tiles-head {
  display: flex;
  align-items: baseline;
  gap: clamp(10px, 1.6vw, 22px);
  flex-wrap: wrap;
  margin: 0 0 clamp(10px, 1.4vh, 16px);
  padding-bottom: 8px;
  border-bottom: 1px solid var(--es-rule-d);
}
.es-tiles-head .es-label { margin: 0; }
.es-tiles-note {
  margin: 0;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--es-paper-3);
}

.es-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: clamp(12px, 1.4vw, 20px);
}
.es-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: clamp(17px, 1.7vw, 23px);
  padding-right: clamp(52px, 4.6vw, 64px);
  border-radius: clamp(16px, 1.8vw, 24px);
  text-decoration: none;
  color: inherit;
  overflow: hidden;
  isolation: isolate;
  /* Der Sprung laeuft ueber die eigene translate-Eigenschaft und
     nicht ueber transform: die Reveal-Regel setzt beim Auftauchen
     `transform: none !important` und wuerde ein Anheben per
     transform stillschweigend ausloeschen.
     Zurueck geht es ruhig, hoch mit Federung: die schnellere Kurve
     steht deshalb im :hover und nicht hier. */
  translate: 0 0;
  /* Die eigene Regel schlaegt die Reveal-Regel (gleiche Spezifitaet,
     steht spaeter), der Auftritt muss hier also mitgenannt werden,
     sonst schnappt die Kachel ohne Blende ins Bild. */
  transition:
    translate 0.32s var(--es-ease),
    box-shadow 0.32s var(--es-ease),
    opacity 1.05s cubic-bezier(0.16, 1, 0.3, 1) var(--es-delay, 0s),
    transform 1.05s cubic-bezier(0.16, 1, 0.3, 1) var(--es-delay, 0s),
    filter 1.05s cubic-bezier(0.16, 1, 0.3, 1) var(--es-delay, 0s);
}
/* Dieselbe Scheibe wie bei .es-slab, nur kleiner dosiert: der
   Verlauf liegt auf einer eigenen Ebene, damit backdrop-filter
   im Kind nicht von einem filter am Elternteil abgeschaltet wird. */
.es-tile::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image:
    var(--es-grain-img),
    linear-gradient(180deg, rgba(255,255,255,0.92) 0, rgba(255,255,255,0.92) 1.5px, rgba(255,255,255,0) 16px),
    linear-gradient(155deg, rgba(255,255,255,0.66), rgba(255,255,255,0.16));
  background-blend-mode: soft-light, normal, normal;
  backdrop-filter: blur(14px) saturate(170%);
  border: 1px solid rgba(255,255,255,0.66);
  border-radius: inherit;
  box-shadow: 0 14px 34px -16px rgba(16,28,90,0.28), inset 0 1px 1px rgba(255,255,255,0.9);
  transition: background-color var(--es-t) var(--es-ease);
}
.es-tile:hover, .es-tile:focus-visible {
  /* Springt hoch und bleibt oben, solange der Zeiger draufsteht.
     Die Kurve schiesst leicht ueber (1.56) und faellt zurueck. */
  translate: 0 -9px;
  /* Ohne Verzoegerung und kurz: der Sprung soll unter dem Zeiger
     passieren, nicht nach ihm. */
  transition: translate 0.16s cubic-bezier(0.2, 1.5, 0.5, 1) 0s;
  outline: none;
}
.es-tile:hover::before, .es-tile:focus-visible::before {
  box-shadow: 0 30px 56px -18px rgba(16,28,90,0.38), inset 0 1px 1px rgba(255,255,255,0.95);
}

@media (prefers-reduced-motion: reduce) {
  .es-tile, .es-tile:hover, .es-tile:focus-visible { translate: 0 0; transition: none; }
}
.es-tile .t {
  font-family: var(--es-serif);
  font-size: clamp(17px, 1.65vw, 22px);
  line-height: 1.12;
  letter-spacing: -0.015em;
  color: var(--es-paper);
}
.es-tile .d {
  font-size: 12px;
  line-height: 1.5;
  color: var(--es-paper-2);
  margin: 0;
}
.es-tile .m {
  margin-top: auto;
  padding-top: 4px;
  font-size: 9px;
  font-weight: 560;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--es-paper-3);
}
.es-tile .es-arrow {
  width: clamp(34px, 3vw, 40px);
  height: clamp(34px, 3vw, 40px);
  top: clamp(14px, 1.6vw, 20px);
  right: clamp(14px, 1.6vw, 20px);
  pointer-events: none; /* die ganze Kachel ist der Link, der Pfeil zeigt ihn nur an */
}
/* Der Pfeil reagiert auf die Kachel, nicht auf sich selbst: die
   ganze Flaeche ist anklickbar, also muss auch die ganze Flaeche
   den Zustand schalten. Schneller als der Sprung, damit die Farbe
   schon steht, waehrend die Kachel noch steigt. */
.es-tile .es-arrow { transition: background-color 0.1s linear 0s, color 0.1s linear 0s; }
.es-tile .es-arrow svg { transition: rotate 0.14s cubic-bezier(0.2, 1.5, 0.5, 1) 0s; }
.es-tile:hover .es-arrow,
.es-tile:focus-visible .es-arrow {
  background: var(--es-paper);
  color: #fff;
}
.es-tile:hover .es-arrow svg,
.es-tile:focus-visible .es-arrow svg { rotate: -45deg; }

/* ============================================================
   22. Detail hinter dem Klick
   Ein Case liegt bis zum Klick im Markup, aber nicht im Layout.
   Umgeschaltet wird ueber eine Klasse, nicht ueber :target:
   der Smooth-Scroll faengt Sprungmarken ab, ein :target-Aufbau
   waere davon abhaengig, wer den Klick zuerst sieht.
   ============================================================ */
.es-detail { display: none; }
.es-detail.is-open { display: block; }
.es-detail-open .es-overview { display: none; }
.es-back {
  position: fixed;
  top: clamp(14px, 2vh, 26px);
  left: clamp(14px, 3vw, 40px);
  z-index: 60;
}
@media (max-width: 620px) {
  .es-back { top: auto; bottom: clamp(14px, 3vh, 26px); }
}

/* ============================================================
   21. Startseite: Name, Teaser, Sprungzeile   (v16)
   ============================================================ */

/* Der erste Schirm traegt eine einzige Zeile, mittig und klein:
   der Name, direkt daneben die unscharfe Stelle, daneben der
   Hinweis. Kein Vollbild-Wortzeichen, die Zeile soll wie eine
   Signatur lesen und nicht wie ein Plakat. */
.es-hero-stage { display: grid; place-items: center; width: 100%; }
.es-hero-line {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  gap: 0.3em;
  font-family: var(--es-sans);
  /* Kleiner als zuvor (war clamp(24px, 4.2vw, 60px)): die Zeile
     steht jetzt im Streifen ueber der Karte und soll ihn nicht
     ausfuellen. */
  font-size: clamp(19px, 3vw, 42px);
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 1.12;
  text-align: center;
}
.es-hero-name { font-weight: 700; }

/* Die Unschaerfe sitzt auf einem inneren span, nicht auf dem
   Reveal-Traeger: .es-in setzt filter mit !important auf none und
   wuerde sie beim Einblenden wegwischen. Der Radius steht in em,
   damit er mit der Schriftgroesse mitwaechst und die Stelle auf
   jedem Schirm gleich unleserlich bleibt.
   Darunter stehen gewoehnliche Buchstaben ohne Bedeutung, keine
   Blockzeichen: die verschwimmen zu einem harten Balken, waehrend
   Buchstaben einen weichen Wortschatten ergeben. Ein Firmenname
   darf dort nicht stehen, das Markup ist lesbar. */
.es-hero-at { font-weight: 620; }
.es-hero-at > span {
  display: inline-block;
  filter: blur(0.28em);
  opacity: 0.55;
  user-select: none;
}
.es-hero-hint {
  font-size: 0.33em;
  font-weight: 540;
  letter-spacing: 0.02em;
  color: var(--es-paper-2);
  align-self: center;
}

/* Sprungzeile: jedes Wort kommt von unten herein und schwingt
   nach. Zwei getrennte Kurven mit Absicht, der Federweg gehoert
   auf die Geometrie, nie auf die Deckkraft. Bewegt wird ueber die
   eigenstaendige translate-Property, weil .es-in transform mit
   !important loescht. */
.es-what { max-width: 1200px; margin: 0 auto; width: 100%; }
/* Zwei Gedanken in einer Box: oben die Zeile, die sagt was er
   macht, unten der Projektkopf. Getrennt durch eine Haarlinie,
   nicht durch eine zweite Karte, sonst schwimmt der Satz wieder
   frei auf dem Grund. */
.es-slab-split {
  margin-top: clamp(48px, 8vh, 104px);
  padding-top: clamp(32px, 4.8vh, 58px);
  border-top: 1px solid var(--es-rule);
}
/* Die Zeile bekommt in der Box Luft nach allen Seiten: sie ist der
   erste Satz der Seite und soll nicht am Innenrand kleben. */
.es-slab .es-what { padding: clamp(14px, 2.6vh, 40px) 0 clamp(6px, 1.4vh, 22px); }
.es-slab .es-what-of { font-size: clamp(28px, 4.2vw, 62px); margin-bottom: 0.06em; }
.es-slab .es-leap { font-size: clamp(27px, 4.1vw, 60px); }
.es-slab .es-leap { row-gap: 0.16em; }
.es-slab .es-leap > span { margin-right: 0.46em; }
/* Der Satz soll auf einer Zeile stehen: 34ch warfen "one." um.
   Kein nowrap, sonst laeuft er auf schmalen Fenstern aus der Box;
   die Breite reicht fuer den Satz und bricht erst darunter. */
.es-slab .es-what-sub { margin-top: clamp(26px, 4.4vh, 48px); max-width: 62ch; }
.es-what-line {
  margin: 0;
  font-family: var(--es-sans);
  font-weight: 660;
  letter-spacing: -0.03em;
  line-height: 1.02;
}
.es-what-of { display: block; font-size: clamp(28px, 4.6vw, 68px); }
.es-leap {
  display: flex;
  flex-wrap: wrap;
  font-size: clamp(26px, 4.4vw, 64px);
  margin-top: 0.1em;
}
.es-leap > span {
  display: inline-block;
  margin-right: 0.26em;
  opacity: 0;
  /* Kurzer Weg: bei 0.85em reichte ein Wort waehrend des Flugs bis
     in die Zeile darunter und die beiden verschwammen ineinander.
     0.3em genuegt als Geste und bleibt im eigenen Zeilenraum. */
  translate: 0 0.3em;
  /* Der Kick nach oben steckt in der Kurve, nicht im Weg: die
     Kurve ueberschiesst um 41 Prozent des Wegs (nachgerechnet),
     das Wort geht also rund 6px ueber seine Lage hinaus und faellt
     zurueck. Der Anflug von unten bleibt dabei kurz. */
  transition:
    translate 0.72s cubic-bezier(0.34, 2.4, 0.64, 1),
    opacity 0.34s linear;
}
.es-leap > span.es-in { opacity: 1; translate: 0 0; }
.es-what-sub {
  margin: clamp(20px, 4vh, 42px) 0 0;
  max-width: 34ch;
  font-size: clamp(16px, 1.8vw, 22px);
  font-weight: 500;
  line-height: 1.38;
  color: var(--es-ink-2);
}

@media (prefers-reduced-motion: reduce) {
  .es-leap > span { opacity: 1; translate: none; }
}

/* ============================================================
   23. Sprechblasen fuer die Blog-Uebersicht
   Eine Blase pro Zeile, volle Breite, gleichmaessig gerundet.
   Die kurze Ecke aus dem ersten Anlauf ist raus: bei voller
   Breite las sie sich als Fehler und nicht als Zitat-Geste.
   Beim Zeigen kommt die Blase heraus, sie wandert nicht.
   ============================================================ */
.es-bubbles { display: grid; gap: clamp(10px, 1.2vw, 16px); }

.es-bubble {
  --x: 0;
  /* Randkasten, nicht Inhaltskasten: der Platz fuer den Faecher wird
     ueber die Breite reserviert, und ohne border-box zaehlten die
     Innenabstaende nicht mit. Die aeusserste Blase stand dann 26px
     ueber dem rechten Rand und wurde vom overflow abgeschnitten. */
  box-sizing: border-box;
  position: relative;
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px;
  padding: clamp(22px, 2.4vw, 32px);
  padding-right: clamp(64px, 6vw, 92px);
  border-radius: clamp(20px, 2.2vw, 30px);
  text-decoration: none;
  color: inherit;
  overflow: hidden;
  isolation: isolate;
  /* Die Blase selbst bewegt sich nicht, nur ihr Schatten reagiert. */
  transition: none;
}
@media (min-width: 720px) {
  /* Datum links in eigener Spalte, Text rechts: bei voller Breite
     braucht die Blase eine zweite Kante, sonst franst sie aus. */
  .es-bubble {
    grid-template-columns: clamp(80px, 7.4vw, 104px) minmax(0, 1fr);
    column-gap: clamp(12px, 1.4vw, 22px);
    row-gap: 7px;
    align-items: start;
  }
  .es-bubble .w { grid-column: 1; grid-row: 1 / span 3; padding-top: 6px; }
  .es-bubble .t { grid-column: 2; grid-row: 1; }
  .es-bubble .d { grid-column: 2; grid-row: 2; }
  .es-bubble .m { grid-column: 2; grid-row: 3; }
}
.es-bubble::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image:
    var(--es-grain-img),
    /* Schleier fuer die Verdeckung: kein filter, der wuerde das
       backdrop-filter der eigenen Scheibe aufheben. */
    linear-gradient(rgba(10, 18, 52, min(0.20, calc(0.09 * var(--x)))), rgba(10, 18, 52, min(0.20, calc(0.09 * var(--x))))),
    /* Die Glanzlinie oben lief bis v21 als harte Kante ueber 1.5px.
       Zusammen mit dem hellen Rahmen zitterte der Kartenrand beim
       Scrollen sichtbar. Jetzt weicher und schwaecher: der Rand darf
       ruhig sein, die Scheibe erkennt man am Verlauf. */
    linear-gradient(180deg, rgba(255,255,255,0.62) 0, rgba(255,255,255,0.30) 3px, rgba(255,255,255,0) 22px),
    linear-gradient(155deg, rgba(255,255,255,0.70), rgba(255,255,255,0.20));
  background-blend-mode: soft-light, normal, normal, normal;
  backdrop-filter: blur(16px) saturate(175%);
  border: 1px solid rgba(255,255,255,0.46);
  border-radius: inherit;
  box-shadow: 0 16px 38px -18px rgba(16,28,90,0.26), inset 0 1px 1px rgba(255,255,255,0.60);
  transition: box-shadow var(--es-t) var(--es-ease);
}
/* Beim Zeigen bewegt sich die Blase selbst nicht. Die Einladung
   kommt allein vom Pfeil, der sich dreht; eine Zeile, die unter dem
   Zeiger wegrutscht, ist bei voller Breite mehr Unruhe als Hinweis. */
.es-bubble:hover, .es-bubble:focus-visible {
  outline: none;
}
.es-bubble:hover::before, .es-bubble:focus-visible::before {
  box-shadow: 0 22px 46px -20px rgba(16,28,90,0.34), inset 0 1px 1px rgba(255,255,255,0.96);
}
/* Der Pfeil reagiert auf die Blase, nicht auf sich selbst: die ganze
   Flaeche ist der Link. Schneller als der Sprung, damit die Farbe
   schon steht, waehrend die Blase noch laeuft. */
.es-bubble .es-arrow { pointer-events: none; transition: background-color 0.1s linear 0s, color 0.1s linear 0s; }
.es-bubble .es-arrow svg { transition: rotate 0.14s cubic-bezier(0.2, 1.5, 0.5, 1) 0s; }
.es-bubble:hover .es-arrow,
.es-bubble:focus-visible .es-arrow {
  background: var(--es-paper);
  color: #fff;
}
.es-bubble:hover .es-arrow svg,
.es-bubble:focus-visible .es-arrow svg { rotate: -45deg; }
.es-bubble .w {
  font-size: 9px;
  font-weight: 560;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--es-paper-3);
}
.es-bubble .t {
  font-family: var(--es-serif);
  font-size: clamp(20px, 2vw, 27px);
  line-height: 1.16;
  letter-spacing: -0.015em;
  color: var(--es-paper);
}
.es-bubble .d {
  margin: 0;
  font-size: 13px;
  line-height: 1.6;
  color: var(--es-paper-2);
  max-width: 62ch;
}
.es-bubble .m {
  margin: 0;
  padding-top: 4px;
  font-size: 9px;
  font-weight: 560;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--es-paper-3);
}
.es-bubble .es-arrow {
  width: clamp(36px, 3.2vw, 44px);
  height: clamp(36px, 3.2vw, 44px);
  top: 50%;
  right: clamp(16px, 1.8vw, 26px);
  translate: 0 -50%;
}

/* ============================================================
   25. Haltestrecke: die Box bleibt stehen, bis die Zeile durch ist
   Erst wenn alle Woerter da sind, laeuft die Seite weiter. Die
   Strecke ist ein echtes Element und kein Innenabstand: der laege
   ausserhalb des Kastens, in dem ein Sticky-Element leben darf,
   und die Box bliebe gar nicht stehen (dreimal gemessen bei v10).
   ============================================================ */
.es-hold {
  position: relative;
  z-index: 2;
  margin-top: calc(-1 * var(--es-dwell));
  /* Die Haltestrecke ist genau der Scrollweg, den die Box klebt.
     Sie endet kurz nach dem letzten Wort, damit es sich nicht wie
     eine Pause anfuehlt. */
  --es-hold-run: clamp(340px, 50vh, 540px);
}
/* Der Klebepunkt haengt an der Trennlinie, nicht an der Oberkante:
   die Linie soll waehrend des Intros am unteren Bildrand stehen.
   Der Wert wird gemessen (Fensterhoehe minus Hoehe des oberen
   Kartenteils) und von es-motion.js gesetzt; die Angabe hier ist
   nur der Notwert, bis gemessen wurde. */
.es-hold-pin { position: sticky; top: var(--es-hold-top, clamp(16px, 2.6vh, 34px)); }
.es-hold-run { height: var(--es-hold-run); }

@media (prefers-reduced-motion: reduce) {
  .es-hold { margin-top: 0; }
  .es-hold-pin { position: static; }
  .es-hold-run { display: none; }
}

/* ------------------------------------------------------------
   24. Blasen-Stapel (Variante, aktuell nirgends im Einsatz)
   Aufbewahrt, weil sie einmal gebaut und vermessen ist: wer den
   Faecher will, setzt .es-bubble-stack statt .es-bubbles um den
   Blasen-Block. Die Blog-Uebersicht nutzt bewusst die einfache
   Liste, dort soll nur der Hover zum Klick einladen.

   Dieselbe Mechanik wie beim Karten-Stapel,
   nur weicht die verdeckte Blase nach rechts aus statt nach
   hinten. Alle pinnen auf derselben Hoehe; was von der
   darunterliegenden Blase stehen bleibt, ist ihre rechte Kante.
   Der Platz dafuer wird vorne reserviert (die Blasen sind um
   drei Stufen schmaler), sonst schoebe die Ausweichbewegung
   die Blase aus dem Bild.
   Unter 720px bleibt es eine einfache Liste: der Stapel braucht
   Breite fuer den Faecher, die dort nicht da ist.
   ------------------------------------------------------------ */
.es-bubble-stack {
  --es-stack-shift: clamp(30px, 3.6vw, 54px);
  --es-stack-hold: clamp(90px, 14vh, 170px);
  --es-stack-exit: clamp(220px, 30vh, 380px);
  display: flex;
  flex-direction: column;
  gap: clamp(10px, 1.2vw, 16px);
}
@media (min-width: 720px) {
  .es-bubble-stack {
    /* Mehr Luft zwischen den Blasen: mit dem engen Listenabstand
       kaemen sie fast gleichzeitig an. */
    gap: clamp(40px, 6vh, 80px);
  }
  .es-bubble-stack::after {
    content: "";
    display: block;
    flex: 0 0 auto;
    /* --es-stack-room misst es-motion.js: der leere Rest des Fensters
       unter der gepinnten Blase frisst genau so viel Scrollweg, und
       ohne den Zuschlag endet die Seite vor der Haltestrecke. */
    height: calc(var(--es-stack-hold) + var(--es-stack-exit) + var(--es-stack-room, 0px));
  }
  .es-bubble-stack > .es-bubble {
    position: sticky;
    top: clamp(96px, 15vh, 170px);
    align-self: flex-start;
    width: calc(100% - 3 * var(--es-stack-shift));
    /* Ausweichen nach rechts und die gemeinsame Ausfahrt liegen beide
       in der eigenen translate-Eigenschaft, damit sie sich nicht mit
       dem transform: scale() der Tiefe ueberschreiben. */
    translate: calc(var(--x) * var(--es-stack-shift)) var(--es-exit-y, 0px);
    transform: scale(calc(1 - 0.016 * var(--x)));
    transform-origin: 0 50%;
  }
  /* Im Stapel gilt dasselbe: die Stufe bleibt stehen, wenn gezeigt wird. */
  .es-bubble-stack > .es-bubble:hover,
  .es-bubble-stack > .es-bubble:focus-visible {
    translate: calc(var(--x) * var(--es-stack-shift)) var(--es-exit-y, 0px);
  }
}

/* ============================================================
   22. Career: eine Glaskarte, darin blaue Karten   (v22)
   ============================================================ */

/* Der Werdegang lag bisher in einem Kapitel (Abschnitt 18): der Grund
   nahm die Kartenfarbe an, die blauen Baender hoerten auf, und die
   Stationen standen als Zeilen darauf. Das kostete den ganzen unteren
   Teil der Seite sein Register und brauchte davor eine lange leere
   Strecke, damit der Farbwechsel nicht hart einschneidet.

   Jetzt traegt der Abschnitt eine einzige grosse Glaskarte, und in ihr
   liegen die Stationen als blaue Karten. Damit dreht sich das
   Verhaeltnis um: aussen Glas auf Blau, innen Blau auf Glas. Nach der
   Karte ist der Grund unveraendert da, der Abschluss steht wieder auf
   den Baendern, und es braucht keinen Uebergang mehr. */

.es-career {
  position: relative;
  z-index: 2;
  /* Der Werdegang zog sich frueher unter die Ausfahrt des Stapels
     (margin-top: -31vh), damit zwischen beiden kein leeres Band steht.
     Der Zug stammt aus der Zeit, als die Ausfahrt mit Faktor 2.4 lief
     und der Stapel dem Werdegang davonlief. Seit die Ausfahrt eins zu
     eins laeuft, ist der Zug zu viel: der Werdegang stand schon im
     Bild, waehrend die drei Projektkarten noch gelesen wurden.
     Ganz ohne Zug war der Abstand dann zu gross, also die Mitte
     zwischen beiden: rund die Haelfte des alten Zuges. Gemessen bei
     1280x900 (Zug 117px): der Kopf des Werdegangs erreicht den
     unteren Bildrand bei Scroll 2880, die letzte Projektkarte ist bei
     3170 oben raus. Er schiebt sich also herein, waehrend der Stapel
     ausfaehrt, aber erst lange nach der Haltestrecke. */
  /* Die 76px sind Kilis "zwei Zentimeter hoeher", auf einem Schirm
     mit 96dpi gerechnet. Sie stehen bewusst als fester Zuschlag
     neben dem gemessenen Wert und nicht in ihm: der Wert oben ist
     an der Ausfahrt des Stapels ausgerichtet, der Zuschlag ist eine
     Geschmacksentscheidung und soll getrennt lesbar bleiben. */
  margin-top: calc(-1 * (clamp(70px, 13vh, 155px) + 76px));
}
@media (max-width: 720px) {
  .es-career { margin-top: calc(-1 * (20vh + 76px)); }
}

/* Die schwebende Slab rueckt ihren Inhalt normalerweise weit ein, weil
   dort Flaechentext steht. Hier stehen Karten darin, und die haben
   ihren eigenen Innenabstand: sonst addiert sich beides.

   Der Wert muss fest stehen und darf nicht an --es-inset-x haengen.
   Die Karte zoomt (siehe --zoom), ihre Einrueckung laeuft also von
   6vw auf 0: ein daran gerechneter Innenabstand zoege den Inhalt
   waehrend des Scrollens mit nach aussen, und genau das soll nie
   passieren, nur die Flaeche waechst. Der feste Wert muss zugleich
   groesser sein als die Einrueckung im Startzustand, sonst haengen die
   blauen Karten am Anfang ueber die Glaskante:
   gemessen 115 gegen 77 bei 1280, 34 gegen 13 bei 375. */
.es-career .es-slab-pad { padding-inline: clamp(30px, 9vw, 150px); }

.es-career-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: clamp(16px, 3vw, 40px);
  flex-wrap: wrap;
  padding-bottom: clamp(16px, 2.6vh, 28px);
  border-bottom: 1px solid var(--es-rule);
}

.es-career-list {
  display: flex;
  flex-direction: column;
  gap: clamp(12px, 1.8vh, 22px);
  margin-top: clamp(20px, 3.6vh, 40px);
}

/* Die Werdegang-Karte, drittes Kleid. Erst sattes Blau (flimmerte als
   Stapel), dann kurz Zeilen-Tests, jetzt das Prinzip der Boegen im
   Grund auf die Karte geholt: die Aussenkante steht scharf, das Blau
   laeuft von der Kante nach innen aus und verliert sich. Technisch
   ein mehrstufiger Inset-Glow, ausdruecklich kein Rahmenstrich
   (Kanon: kein 1px-Rahmen um Flaechen). Innen ein Hauch Milchglas,
   damit die Schrift ueber jedem Bogen dahinter ruhig sitzt; die
   Sphaeren schimmern nur noch weich durch. Schrift ist wieder Tinte,
   das weisse Reverse-Register ist mit dem satten Blau gegangen. */
.es-blue {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  border-radius: clamp(14px, 2vw, 24px);
  padding: clamp(22px, 3.2vh, 38px) clamp(22px, 3vw, 46px);
  background: rgba(255, 255, 255, 0.16);
  backdrop-filter: blur(9px) saturate(135%);
  color: var(--es-ink);
  display: grid;
  grid-template-columns: minmax(96px, 14%) 1fr minmax(0, 20%);
  gap: clamp(12px, 2.4vw, 38px);
  align-items: start;
  /* Dieselben Blautoene wie die Boegen (26,44,240 / 48,84,250). Drei
     Stufen: eine knappe helle Kante, ein mittlerer Saum, ein weiter
     Auslauf. Negative Spreads ziehen den Glow von der Kante weg nach
     innen, aussen bleibt der Radius die scharfe Grenze. */
  box-shadow:
    inset 0 0 16px -6px rgba(26, 44, 240, 0.55),
    inset 0 0 46px -10px rgba(48, 84, 250, 0.30),
    inset 0 0 110px -30px rgba(26, 44, 240, 0.16);
}

/* Tinte auf Glas: dieselben Abstufungen wie ueberall auf der Seite,
   kein eigenes Register mehr. */
.es-blue h3 { color: var(--es-ink); }
.es-blue .es-label,
.es-blue .es-meta { color: var(--es-ink-3); }
.es-blue .metric { color: var(--es-ink-2); }
.es-blue .metric::before { background: rgba(18, 20, 23, 0.38); }
.es-blue .es-tag { background: rgba(18, 20, 23, 0.055); color: var(--es-ink-2); }
/* Die Verweiszeile verhaelt sich wie die Aufklappzeile: beim Zeigen
   volle Tinte. In einer Karte, in der sonst nichts anklickbar ist,
   soll man sehen, was einer ist. */
.es-blue a {
  display: inline-flex;
  align-items: center;
  color: var(--es-ink-3);
  transition: color var(--es-t) var(--es-ease);
}
.es-blue a:hover,
.es-blue a:focus-visible { color: var(--es-ink); }
.es-blue details.more { border-top-color: var(--es-rule); }
/* Die Aufklappzeile traegt den Pfeil statt Plus und Minus, und sie
   geht beim Zeigen auf volle Tinte: sie ist der einzige Schalter in
   einer Karte, in der sonst nichts anklickbar ist, das darf man
   sehen. */
.es-blue details.more summary {
  display: inline-flex;
  align-items: center;
  color: var(--es-ink-3);
  transition: color var(--es-t) var(--es-ease);
}
/* Beide Zustaende ausdruecklich: die Seitenregel fuer das Minus im
   offenen Zustand traegt [open] und damit dieselbe Gewichtung wie die
   Grundregel hier. Bei Gleichstand entscheidet die Reihenfolge, und
   das Seiten-Stylesheet steht spaeter. Ohne die zweite Zeile kaeme
   also aufgeklappt das Minus zurueck, neben dem Pfeil. */
.es-blue details.more summary::after,
.es-blue details.more[open] summary::after { content: none; }
.es-blue details.more summary:hover,
.es-blue details.more summary:focus-visible { color: var(--es-ink); }
.es-blue details.more p { color: var(--es-ink-2); }

.es-blue .when { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
/* Die Marken stehen untereinander an der rechten Kante. Als
   umbrechende Zeile ergab sich je nach Anzahl und Wortlaenge ein
   anderes Bild (zwei oben, eine darunter), die Spalte ist bei jeder
   Karte dieselbe Figur. */
.es-blue .marks {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
}

/* Die oberste Karte ist die juengste. Reihenfolge allein sagt noch
   nicht, worauf man zuerst schauen soll, also bekommt sie mehr
   Flaeche und einen groesseren Titel; der Rim bleibt derselbe wie bei
   allen, die Gewichtung kommt aus Flaeche und Schrift. */
.es-blue--now {
  /* Nur mehr Luft nach oben und unten. Seitlich derselbe Wert wie bei
     den anderen Karten: die Marken stehen jetzt als Spalte an der
     Innenkante, ein eigener Wert versetzte diese eine Spalte um ein
     paar Pixel gegen alle darunter. */
  padding: clamp(30px, 4.6vh, 54px) clamp(22px, 3vw, 46px);
}
.es-blue--now h3 { font-size: clamp(24px, 3.2vw, 40px); }
.es-blue--now .metric { font-size: 15px; }

/* Die Marke der laufenden Station: ein Punkt, der schlaegt. Der Punkt
   traegt das reine Blau, das die Flaechen abgegeben haben. */
.es-now-flag {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin: 0 0 10px;
  font-size: 9.5px;
  font-weight: 620;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--es-ink-2);
}
.es-now-flag::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--es-ground-2);
  animation: es-flag-puls 2.4s var(--es-ease) infinite;
}
@keyframes es-flag-puls {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.3; }
}

/* Die versiegelte Karte. Der naechste Schritt steht fest, der Name
   noch nicht draussen. Sie sitzt ueber der laufenden Station und ist
   bewusst kurz: ein Hinweis, kein Eintrag. Ihr Rim ist der kraeftigste
   der Liste, damit sie nicht mit der laufenden Station um Flaeche
   streitet, sondern als hellste Kante ueber ihr steht. */
.es-blue--secret {
  padding-block: clamp(20px, 2.8vh, 32px);
  box-shadow:
    inset 0 0 16px -6px rgba(26, 44, 240, 0.75),
    inset 0 0 52px -10px rgba(48, 84, 250, 0.45),
    inset 0 0 120px -30px rgba(26, 44, 240, 0.24);
}

/* Der Marker ohne Punkt: der schlagende Punkt gehoert der laufenden
   Station, zwei davon untereinander waeren nur Unruhe. */
.es-seal-flag {
  margin: 0 0 10px;
  font-size: 9.5px;
  font-weight: 620;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--es-ink-3);
}

/* Die unscharfe Stelle. Wie im Hero sitzt die Unschaerfe auf einem
   inneren span und nicht auf dem Traeger: die Reveal-Regel loescht
   filter mit !important, und was heute keinen Reveal hat, bekommt
   morgen einen. Der Radius steht in em und waechst mit der Schrift.
   Unter dem Blur stehen ausdruecklich Platzhalterzeichen und kein
   echter Name: das DOM ist lesbar, eine Unschaerfe versteckt nichts. */
.es-secret { margin: 0; }
.es-secret > span {
  display: inline-block;
  filter: blur(0.34em);
  opacity: 0.9;
  user-select: none;
}

/* Nur fuer Vorlesewerkzeuge: die Platzhalter darueber sind fuer sie
   sinnlos, die Aussage der Karte muss trotzdem ankommen. */
.es-sr {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

@media (max-width: 900px) {
  .es-blue { grid-template-columns: 1fr; gap: 12px; }
  .es-blue .when { flex-direction: row; align-items: baseline; gap: 12px; }
  /* Schmal steht kein Rand mehr rechts, an dem sich eine Spalte
     ausrichten koennte: die Marken laufen wieder als Zeile, sonst
     kostet eine Karte drei Zeilen Hoehe fuer drei Woerter. */
  .es-blue .marks { flex-direction: row; flex-wrap: wrap; align-items: flex-start; justify-content: flex-start; }
}
@media (prefers-reduced-motion: reduce) {
  .es-now-flag::before { animation: none; }
}

/* ============================================================
   25. Chat-Box
   Im Ruhezustand nur eine Eingabeleiste, so schmal wie ein
   Knopf. Erst wenn eine Antwort zurueckkommt, waechst der
   Verlauf darueber auf. Die Leiste bleibt dabei an ihrem Platz,
   der Gespraechsraum oeffnet sich nach oben.
   Beide Flaechen sind dieselbe Scheibe wie die Karten: Verlauf,
   Glanzlinie, Korn ueber background-blend-mode, backdrop-filter.
   Kein filter auf einem Vorfahren, der wuerde das Glas toeten.
   ============================================================ */
.cw { width: 100%; max-width: 640px; margin-inline: auto; }
/* Die Zeile steht auf der Pille, nicht als Ueberschrift daneben: sie
   gehoert zur Eingabe und ist deshalb genauso breit und genauso
   ausgerichtet wie sie. */
.cw-title {
  max-width: 640px;
  margin: 0 auto 14px;
  text-align: center;
  font-size: clamp(24px, 2.8vw, 38px);
  line-height: 1.1;
}

.cw-panel {
  max-height: 0;
  opacity: 0;
  overflow: hidden;
  margin-bottom: 0;
  transition:
    max-height 0.55s var(--es-ease),
    opacity 0.3s linear,
    margin-bottom 0.55s var(--es-ease);
}
.cw.is-open .cw-panel {
  max-height: min(46vh, 380px);
  opacity: 1;
  margin-bottom: 10px;
}
.cw-msgs {
  max-height: min(46vh, 380px);
  overflow-y: auto;
  padding: clamp(16px, 2vw, 22px);
  display: flex;
  flex-direction: column;
  gap: 10px;
  border-radius: clamp(18px, 2vw, 26px);
  position: relative;
  isolation: isolate;
}
.cw-msgs::before, .cw-bar::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image:
    var(--es-grain-img),
    linear-gradient(180deg, rgba(255,255,255,0.92) 0, rgba(255,255,255,0.92) 1.5px, rgba(255,255,255,0) 16px),
    linear-gradient(155deg, rgba(255,255,255,0.68), rgba(255,255,255,0.18));
  background-blend-mode: soft-light, normal, normal;
  backdrop-filter: blur(16px) saturate(175%);
  border: 1px solid rgba(255,255,255,0.70);
  border-radius: inherit;
  box-shadow: 0 16px 38px -18px rgba(16,28,90,0.28), inset 0 1px 1px rgba(255,255,255,0.92);
}
.cw-msg {
  max-width: 86%;
  padding: 10px 15px;
  border-radius: 16px;
  font-family: var(--es-sans);
  font-size: 13.5px;
  line-height: 1.62;
  overflow-wrap: anywhere;
}
.cw-msg--bot {
  align-self: flex-start;
  background: rgba(255,255,255,0.66);
  color: var(--es-paper-2);
}
.cw-msg--user {
  align-self: flex-end;
  background: var(--es-paper);
  color: #fff;
}
.cw-msg--error { align-self: flex-start; background: rgba(255,255,255,0.66); color: var(--es-red); }
.cw-msg strong { color: var(--es-paper); font-weight: 600; }
.cw-sources {
  margin-top: 8px;
  font-size: 9px;
  font-weight: 560;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--es-paper-3);
}
.cw-typing { align-self: flex-start; display: flex; gap: 5px; padding: 14px 16px; }
.cw-dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--es-paper-3);
  animation: cwBounce 1.2s infinite;
}
.cw-dot:nth-child(2) { animation-delay: 0.15s; }
.cw-dot:nth-child(3) { animation-delay: 0.3s; }
@keyframes cwBounce {
  0%, 60%, 100% { translate: 0 0; }
  30% { translate: 0 -5px; }
}

.cw-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 5px 5px clamp(16px, 1.6vw, 22px);
  border-radius: 999px;
  position: relative;
  isolation: isolate;
}
.cw-input {
  flex: 1;
  min-width: 0;
  padding: 11px 0;
  background: none;
  border: 0;
  outline: none;
  font-family: var(--es-sans);
  font-size: 13.5px;
  color: var(--es-paper);
}
.cw-input::placeholder { color: var(--es-paper-3); }
.cw-send {
  flex: 0 0 auto;
  padding: 10px 20px;
  border: 0;
  border-radius: 999px;
  background: var(--es-paper);
  color: #fff;
  font-family: var(--es-sans);
  font-size: 10px;
  font-weight: 620;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  cursor: pointer;
  /* Derselbe kurze Sprung nach rechts wie bei den Blasen: der Knopf
     schickt etwas los, also zeigt die Bewegung dorthin. */
  transition: translate 0.16s cubic-bezier(0.2, 1.5, 0.5, 1) 0s, opacity 0.2s linear;
}
.cw-send:hover, .cw-send:focus-visible { translate: 3px 0; outline: none; }
.cw-send:disabled { opacity: 0.4; cursor: default; translate: 0 0; }
.cw-note {
  margin: 12px auto 0;
  max-width: 640px;
  text-align: center;
  font-size: 10.5px;
  line-height: 1.6;
  color: var(--es-paper-3);
}
.cw-note a { color: inherit; }
@media (prefers-reduced-motion: reduce) {
  .cw-panel, .cw-send { transition: none; }
  .cw-dot { animation: none; }
}
