@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url(assets/fonts/inter-latin-var.woff2) format('woff2');
}

:root {
  --ink: #080d14;
  --paper: #fff;
  --glow: #dceaf8;

  /* Paper world: the inside of a panel is always ink on white and never flips
     with body.is-open. Mirror image of the --type family further down. */
  --ink-soft: #2b3546;
  --ink-2: rgba(8, 13, 20, .68);
  --ink-line: rgba(8, 13, 20, .12);
  --ink-edge: rgba(8, 13, 20, .25);

  /* Focus ring: light on the dark corridor, dark on every island of paper. */
  --ring: #fff;

  --display: 'Times New Roman MT', 'Times New Roman', Times, 'Inter', Georgia, serif;
  --body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;

  --gutter: clamp(1.25rem, 5.2vw, 5.5rem);
  /* Ein Besitzer für die Wortmarken-Geometrie: der Intro-Versatz von
     .curtain__mark rechnet mit genau diesen beiden Werten. */
  --mark-gap: clamp(.4rem, .8vw, .8rem);
  --chev-lg: clamp(.42rem, .62vw, .62rem);

  /* Wo das schwarze Tor auf dem Schirm liegt. Das Motiv fuellt die Buehne per
     object-fit: cover, und die Buehne ist 106 % gross (inset: -3 %). Wo das Tor
     darin landet, haengt damit allein am Fensterformat: fuellt das Bild die
     Breite, rechnet alles in vw, sonst zentriert es sich in der Breite und
     rechnet in vh. Die beiden Anteile sind am Motiv gemessen, das Tor liegt
     zwischen 44.76 % und 54.57 % der Bildbreite.
     Wortmarke und Intro-Marke haengen an diesen Werten, damit die Wort-/Bildidee
     in jedem Format sitzt: vorher war die Marke an vw gekoppelt, das Tor an den
     cover-Faktor, und die beiden liefen mit jedem Fenster auseinander. */
  --img-w: 106vw;
  --door-l: calc(var(--img-w) * .4476 - 3vw);
  --door-r: calc(var(--img-w) * .5457 - 3vw);
  --door-w: calc(var(--door-r) - var(--door-l));
  /* Wie viel der Torbreite die Gruppe [njuː] ausfuellt. Der Rest ist Luft links
     davon, damit die Klammer nicht auf der Torkante klebt. */
  --nju-fill: .93;
  /* Gemessene Breite von "[njuː]" in Times New Roman fett bei letter-spacing
     -.028em, in em. Wird die Display-Schrift getauscht, neu messen, sonst sitzt
     der Farbwechsel nicht mehr auf der Torkante. */
  --nju-em: 2.221;

  /* viewport-fit=cover legt den Inhalt unter Notch und Home-Indicator.
     Die Ränder rechnen deshalb gegen die Safe-Area. */
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  --safe-r: env(safe-area-inset-right, 0px);
  --edge-l: max(var(--gutter), var(--safe-l));
  --edge-r: max(var(--gutter), var(--safe-r));

  --ease: cubic-bezier(.16, 1, .3, 1);
  --ease-soft: cubic-bezier(.4, 0, .2, 1);
  /* Fuer Wege ueber eine grosse Strecke: die beiden oben schiessen sofort los,
     was richtig ist fuer etwas, das erscheint, aber falsch fuer etwas, das aus
     dem Stand losfaehrt und an einem genauen Punkt wieder anhaelt. */
  --ease-travel: cubic-bezier(.77, 0, .175, 1);

  /* Typo liegt als Weiß im dunkleren Korridor – das Geheimnisvolle,
     nicht Tinte auf aufgehelltem Foto. --type kippt beim Öffnen nicht mehr. */
  --type: #fff;
  --type-2: rgba(255, 255, 255, .7);
  --type-line: rgba(255, 255, 255, .28);
  --type-glow: 0 1px 14px rgba(0, 0, 0, .75), 0 0 24px rgba(0, 0, 0, .45);
  --type-hover: #e8f2ff;

  /* pointer parallax, written by main.js, unitless -1..1 */
  --mx: 0;
  --my: 0;
}

/* Hochformatigere Fenster als das Motiv: dann fuellt das Bild die Hoehe und
   sitzt in der Breite mittig, das Tor wandert also mit der Fensterhoehe. */
@media (max-aspect-ratio: 2361/1329) {
  :root {
    --img-w: calc(1.06 * 1.77653 * 100vh);
    --door-l: calc(50vw - var(--img-w) / 2 + var(--img-w) * .4476);
    --door-r: calc(50vw - var(--img-w) / 2 + var(--img-w) * .5457);
  }
}

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

html, body {
  height: 100%;
  margin: 0;
  background: #04080f;
}

body {
  font-family: var(--body);
  color: #fff;
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

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

.skip {
  position: absolute;
  top: .6rem; left: .6rem;
  z-index: 40;
  padding: .55rem .9rem;
  background: var(--paper);
  color: var(--ink);
  font: 500 .82rem/1 var(--body);
  text-decoration: none;
  transform: translateY(-160%);
  transition: transform .2s var(--ease-soft);
}
.skip:focus-visible { transform: none; }

:where(button, a, summary):focus-visible {
  outline: 1px solid var(--ring);
  outline-offset: .45rem;
}

/* Islands of paper: the ring stays dark here even while the scene dims. */
.panel, .skip { --ring: var(--ink); }

/* ============================================================
   SZENE
   ============================================================ */
.scene {
  position: fixed;
  inset: 0;
  overflow: hidden;
  z-index: 0;
}

.scene__bg {
  position: absolute;
  inset: -3%;
  width: 106%;
  height: 106%;
  object-fit: cover;
  filter: saturate(1.14) contrast(1.22) brightness(.84);
  /* Parallaxe und Zustands-Zoom liegen auf getrennten Properties, nicht in einer
     gemeinsamen transform. Sonst erbt die Parallaxe die Transition des Zooms und
     wird 60-mal pro Sekunde neu angestossen: main.js glaettet bereits selbst,
     die zweite Glaettung darueber ruckelt nur. */
  translate: calc(var(--mx) * 12px) calc(var(--my) * 9px);
  scale: var(--bgz, 1);
  /* filter wird bewusst nicht animiert. Eine Filter-Transition zwingt den Browser,
     das Vollbild in jedem einzelnen Frame neu zu rastern, und war gemessen der
     komplette Ruckler beim Oeffnen: 39 ms schlechtester Frame und 15 verpasste
     Frames, ohne sie 9 ms und keiner. Der Umschlag springt stattdessen im selben
     Frame wie der Klassenwechsel, der den Neuaufbau ohnehin traegt: eine
     Verzoegerung darauf legt den einen teuren Frame nur mitten in die sonst
     glatte Bewegung. Weich bleibt der Uebergang durch Schleier und Zoom. */
  transition: scale 1.1s var(--ease-soft), filter 0s;
}

/* Reserve the compositor layer for the full bleed image only once main.js has
   actually wired up the pointer parallax. Without a fine hover pointer, or
   under reduced motion, nothing here ever moves. */
.has-parallax .scene__bg { will-change: translate; }

/* Der Video-Loop erbt Lage, Filter und Parallaxe von .scene__bg und liegt
   unsichtbar über dem Standbild, bis main.js ihn geladen und gestartet hat. */
.scene__video {
  opacity: 0;
  /* scale muss hier mit aufgezaehlt werden. Diese Kurzschreibweise ersetzt die
     von .scene__bg vollstaendig, und sobald der Loop laeuft, ist das Video die
     Ebene, die man sieht: ohne die Zeile springt sein Zoom beim Oeffnen hart,
     waehrend das verdeckte Standbild darunter brav weich zoomt. */
  transition: opacity 1.2s var(--ease-soft), scale 1.1s var(--ease-soft);
}
.scene__video.is-live { opacity: 1; }

/* Grade hält den Korridor geheimnisvoll: Vignette, das Tor bleibt der Fixpunkt.
   Kein Aufhellen mehr – die Headlines stehen in Weiß auf dem Bild. */
.scene__grade {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(120% 108% at 50% 48%, transparent 44%, rgba(6, 12, 22, .5) 100%),
    linear-gradient(180deg, rgba(6, 12, 22, .32) 0%, transparent 16%),
    linear-gradient(0deg, rgba(6, 12, 22, .28) 0%, transparent 22%);
}

/* The photo carries its own frame of dark glass in the outer ~13 % of its width.
   object-fit: cover moves that frame through the viewport: cropped away at 4:3,
   sitting right under the brandbar from 16:9 on. These two corner blooms are
   anchored to the frame instead of to the crop. Lightening light pixels is a
   no-op, so the bright centre and the black gate stay untouched.
   Own layer on purpose: the media queries replace the whole background shorthand
   above, which is how the landscape block lost its top veil.
   .scene__scrim is a later sibling, so the open state dims this along with it. */
.scene__grade::before {
  content: none;
}

.scene__scrim {
  position: absolute;
  inset: 0;
  /* .82 statt .74: der Schleier ist nicht nur Stimmung, er ist der Kontrastträger
     für die zurückgetretene Navigation. Gemessen sinkt der hellste Bildpunkt
     hinter den Labels damit von L .064 auf L .036, was dem Fixwert unten Luft gibt. */
  background: rgba(5, 11, 20, .82);
  opacity: 0;
  transition: opacity .65s var(--ease-soft);
}

/* Zwei Haarlinien, die das Raster der Vorlage aufnehmen */
.scene__rules::before,
.scene__rules::after {
  content: '';
  position: absolute;
  left: var(--gutter);
  right: var(--gutter);
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--type-line) 12%, var(--type-line) 88%, transparent);
  transition: background .6s var(--ease-soft);
}
.scene__rules::before { top: clamp(3.6rem, 8vh, 5.6rem); }
.scene__rules::after  { bottom: clamp(3.6rem, 8vh, 5.6rem); }

/* ============================================================
   MARKE
   ============================================================ */
.brandbar {
  position: fixed;
  top: clamp(1.1rem, 3vh, 2rem);
  left: var(--edge-l);
  right: var(--edge-r);
  z-index: 8;
  display: flex;
  align-items: baseline;
  margin: 0;
}

.brandbar__mark {
  /* Tap target grows to 27px (WCAG 2.2 AA 2.5.8). The negative margin returns
     the added height to the layout, so the header keeps its position. */
  padding: .35rem 0;
  margin: -.35rem 0;
  font: 700 clamp(1rem, 1.35vw, 1.35rem)/1 var(--display);
  letter-spacing: -.01em;
  color: var(--type);
  text-decoration: none;
  transition: color .6s var(--ease-soft);
}
.brandbar__mark b { font-weight: 700; }

/* ============================================================
   NAVIGATION IM BILDRAUM
   ============================================================ */
.constellation {
  position: fixed;
  inset: 0;
  z-index: 3;
}

.node,
.wordmark {
  position: absolute;
  appearance: none;
  border: 0;
  background: none;
  padding: 0;
  margin: 0;
  color: var(--type);
  cursor: pointer;
  text-align: left;
  -webkit-tap-highlight-color: transparent;
  /* Keine Transition auf dieser transform: sie traegt nur die Parallaxe, und die
     glaettet main.js schon per rAF. Eine Transition daneben wuerde auf acht
     Elementen 60-mal pro Sekunde neu starten. */
  transform: translate3d(calc(var(--mx) * var(--k, 20) * 1px), calc(var(--my) * var(--k, 20) * .62px), 0);
  transition: opacity .55s var(--ease-soft), color .6s var(--ease-soft);
}

.node__in,
.wordmark__in {
  display: inline-flex;
  align-items: flex-end;
  gap: var(--mark-gap);
  transition: transform .5s var(--ease), color .3s linear;
}

.node__label {
  font: 700 clamp(1.15rem, 1.95vw, 2.1rem)/1.06 var(--display);
  letter-spacing: -.005em;
}


.chev {
  flex: none;
  width: clamp(.28rem, .42vw, .42rem);
  height: auto;
  overflow: visible;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.05;
  stroke-linecap: butt;
  stroke-linejoin: miter;
  stroke-miterlimit: 8;
  transition: transform .45s var(--ease);
  transform-origin: 50% 50%;
  margin-bottom: .38em;
}
.chev--lg { width: var(--chev-lg); }
.chev--back { transform: rotate(180deg); }

@media (hover: hover) {
  .node:enabled:hover .node__in { transform: translateX(.42rem); }
  .node:enabled:hover { color: var(--type-hover); }
}

/* offener Zustand: aktiver Punkt bleibt, die anderen treten zurück */
.node[aria-expanded='true'] .chev,
.wordmark[aria-expanded='true'] .chev { transform: rotate(90deg); }

/* Die zurückgetretenen Punkte bleiben bedienbar: direkt von einem Thema zum
   nächsten springen ist die Kern-Interaktion dieser Seite, nicht ein Extra.
   Ein bedienbares Element muss seinen Kontrast selbst tragen, also darf die
   Zurücknahme NICHT über opacity laufen: opacity blendet gegen den Hintergrund,
   damit hängt der Kontrast am Foto und ist nicht vorhersagbar. Ein fester Wert
   ist es. Gemessen gegen den hellsten Punkt hinter den Labels (L .036 beim
   Schleier .82): #aebccd ergibt 6.3:1 und deckt damit auch die kleinen Labels
   patente-Unterzeile und kontakt mit ihren geforderten 4.5:1 ab.
   Der aktive Punkt bleibt reines Weiß, das sind 15:1, die Hierarchie trägt also
   der Unterschied zwischen beiden und nicht das Verschwinden der anderen. */
body.is-open .node,
body.is-open .wordmark { color: #c5d0dc; }
body.is-open .node[aria-expanded='true'],
body.is-open .wordmark[aria-expanded='true'] { color: #fff; }
body.is-open .hint { opacity: 0; }

.node--meta           { left: 14%;          top: 23%; }
.node--innovation     { left: 66%;          top: 18%; }
.node--transformation { left: var(--gutter); top: 43%; }
.node--food           { left: 16%;          top: 64%; }
.node--patente        { left: 47%;          top: 74%; }
.node--referenzen     { left: auto;         right: var(--gutter); bottom: 12%; }
.node--kontakt        { left: var(--gutter); bottom: 8.4%; }

.node--kontakt .node__label {
  font-family: var(--body);
  font-weight: 400;
  font-size: clamp(.78rem, 1vw, 1rem);
  letter-spacing: .04em;
}
.node--kontakt .chev { margin-bottom: .2em; }

/* Wortmarke sitzt auf dem schwarzen Tor: die Türe in die neue Welt.
   [njuː] steht im Tor und leuchtet weiß, world beginnt genau an der rechten
   Torkante und ist Tinte auf hellem Boden. Verankert wird an dieser Kante,
   nicht an der Fenstermitte: das Tor wandert mit dem Format, die Mitte nicht. */
.wordmark {
  left: var(--door-r);
  top: 50%;
  translate: calc(-1 * var(--nju-fill) * var(--door-w)) -50%;
  /* Die Tiefe steht als data-k im Markup, main.js schreibt sie als Inline-Wert;
     hier steht sie nur als Rueckfall ohne JS. 12 ist die Tiefe des Motivs: eine
     flachere laesst die Marke unter dem Zeiger gegen das Tor wandern, und genau
     auf dem Tor soll sie stehen. */
  --k: 12;
}
.wordmark__in {
  transform: scale(var(--wm-s, 1)) translateY(var(--wm-y, 0));
  align-items: center;
}
.wordmark__text {
  position: relative;
  display: inline-block;
  /* Die Groesse haengt am Tor, nicht am Fenster: [njuː] soll das Tor ausfuellen,
     damit die Klammergruppe ganz im Schwarz steht und world genau daneben
     anfaengt. */
  font: 700 clamp(1.6rem, calc(var(--door-w) * var(--nju-fill) / var(--nju-em)), 12rem)/1 var(--display);
  letter-spacing: -.028em;
  white-space: nowrap;
}
.wordmark__text b {
  font-weight: 700;
  color: #080d14;
}
.wordmark__nju { color: #fff; }
.wordmark .chev {
  margin-bottom: 0;
  margin-top: .1em;
  /* Der Pfeil steht immer rechts vom Tor, also immer auf hellem Boden. Weiß
     verliert sich dort, wie es "world" vor der Torkanten-Verankerung tat.
     In der Anordnung des Klienten ist er ebenfalls Tinte. */
  color: #080d14;
}

body:not(.is-open) .wordmark .chev {
  animation: nudge 3.4s var(--ease-soft) 3s infinite;
}
@keyframes nudge {
  0%, 88%, 100% { opacity: 1; transform: none; }
  92%           { opacity: .45; transform: translateX(.22rem); }
}

@media (hover: hover) {
  .wordmark:enabled:hover .wordmark__in {
    transform: scale(calc(var(--wm-s, 1) * 1.018)) translateY(var(--wm-y, 0));
  }
}

/* ============================================================
   OFFENER ZUSTAND DER SZENE
   ============================================================ */
body.is-open {
  --bgz: 1;
  --wm-s: 1;
  --wm-y: 0;
}
body.is-open .scene__bg { filter: saturate(1.14) contrast(1.22) brightness(.84); }
body.is-open .scene__scrim { opacity: 0; }

/* ============================================================
   PANELS
   ============================================================ */
/* Die Bühne bleibt durchlässig, damit die Navigationspunkte neben dem
   offenen Panel weiter anklickbar sind. Nur Panel und Pager fangen Klicks. */
.stage {
  position: fixed;
  inset: 0;
  z-index: 5;
  pointer-events: none;
}

.panel {
  position: absolute;
  z-index: 2;
  width: min(28rem, 38vw);
  max-height: min(56dvh, 30rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 1.25rem 1.35rem 1.35rem;
  background: var(--paper);
  color: var(--ink);
  transform: none;
  /* Richtung des Aufreissens als Variable, nicht als eigene clip-path-Regel:
     die Panels im unteren Bildraum reissen von unten nach oben auf und setzen
     dafuer weiter unten --clip-zu. Stuende dort clip-path, wuerde die
     ID-Spezifitaet die Oeffnen-Regel schlagen, und genau das ist passiert:
     Functional Food, IT-Patente, Referenzen und Impressum blieben auf jedem
     Bildschirm zugeschnitten, unter 861px sogar alle acht. */
  --clip-zu: inset(0 0 100% 0);
  clip-path: var(--clip-auf, var(--clip-zu));
  opacity: 0;
  visibility: hidden;
  /* visibility hängt an der Opacity-Dauer, nicht an der clip-path-Dauer: bei
     0s .8s blieb das längst unsichtbare Panel noch rund 500 ms fokussierbar,
     gemessen nahm die CTA darin 450 ms nach dem Schließen noch den Fokus an. */
  transition: clip-path .8s var(--ease), opacity .3s linear, visibility 0s .3s;
}
.panel.is-on {
  --clip-auf: inset(0 0 0 0);
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: clip-path .8s var(--ease), opacity .3s linear, visibility 0s;
}

/* Jedes Panel klappt an seiner Headline auf, nicht alle am selben Fleck. */
#panel-marke {
  left: 50%;
  top: 58%;
  width: min(30rem, 42vw);
  translate: -50% 0;
}
#panel-meta {
  left: 14%;
  top: 32%;
}
#panel-innovation {
  left: auto;
  right: var(--gutter);
  top: 26%;
}
#panel-transformation {
  left: var(--gutter);
  top: 52%;
}
#panel-food,
#panel-patente,
#panel-referenzen,
#panel-kontakt {
  --clip-zu: inset(100% 0 0 0);
}
/* Die drei unteren Panels stehen seitlich bzw. ueber ihrem Auslöser, nicht auf
   ihm: sonst verdeckt das offene Panel genau den Punkt, den man gerade
   angeklickt hat, und man sieht nicht mehr, welches Thema offen ist. */
#panel-food {
  left: 30%;
  top: auto;
  bottom: 14%;
}
#panel-patente {
  left: 60%;
  top: auto;
  bottom: 16%;
}
#panel-referenzen {
  left: auto;
  right: var(--gutter);
  top: auto;
  bottom: 17%;
  width: min(46rem, 54vw);
  max-height: min(68dvh, 38rem);
}
#panel-kontakt {
  left: var(--gutter);
  top: auto;
  bottom: 12%;
  width: min(30rem, 40vw);
}

.panel--wide {
  width: min(46rem, 54vw);
  max-height: min(68dvh, 38rem);
}

/* Inhalt setzt sich nach dem Aufreißen des Panels */
.panel > * {
  opacity: 0;
  transform: translateY(1rem);
  transition: opacity .5s var(--ease-soft), transform .6s var(--ease);
}
.panel.is-on > * { opacity: 1; transform: none; }
.panel.is-on > :nth-child(1) { transition-delay: .16s; }
.panel.is-on > :nth-child(2) { transition-delay: .24s; }
.panel.is-on > :nth-child(3) { transition-delay: .32s; }
.panel.is-on > :nth-child(4) { transition-delay: .4s; }

/* Der Fokus landet beim Öffnen auf dem Panel. Tastatur-Nutzer sollen das sehen. */
.panel:focus-visible { outline: 1px solid var(--ring); outline-offset: .45rem; }

.quote {
  margin: 0 0 .85rem;
  padding: 0;
  border: 0;
}
.quote p {
  margin: 0;
  font: 400 clamp(1.05rem, 1.4vw, 1.35rem)/1.28 var(--display);
  letter-spacing: -.008em;
  text-wrap: pretty;
}
.quote cite {
  display: block;
  margin-top: .45rem;
  font: italic 400 .78rem/1.35 var(--display);
  letter-spacing: 0;
  color: var(--ink-2);
  text-align: right;
}

.prose p {
  margin: 0 0 .55em;
  font-size: clamp(.9rem, .98vw, 1.02rem);
  line-height: 1.42;
  color: var(--ink-soft);
  text-wrap: pretty;
}
.prose p:last-child { margin-bottom: 0; }
.prose b { color: var(--ink); font-weight: 600; }

/* ---------- Referenzen ---------- */
.refs {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: clamp(1.1rem, 1.9vw, 1.7rem) clamp(1.5rem, 2.6vw, 2.6rem);
}
.ref { padding-top: clamp(1rem, 1.7vw, 1.5rem); border-top: 1px solid var(--ink-line); }
.ref:first-child { padding-top: 0; border-top: 0; }

/* Fünf Referenzen: 2x2 plus die letzte über die volle Breite */
@media (min-width: 1100px) {
  .refs { grid-template-columns: 1fr 1fr; }
  .ref:nth-child(2) { padding-top: 0; border-top: 0; }
  .ref:nth-child(5) { grid-column: 1 / -1; }
}

.ref__title {
  margin: 0 0 .45em;
  font: 600 clamp(1.02rem, 1.32vw, 1.32rem)/1.28 var(--display);
  letter-spacing: -.01em;
  text-wrap: balance;
}
.ref__body {
  margin: 0;
  font-size: clamp(.86rem, .95vw, .98rem);
  line-height: 1.58;
  color: var(--ink-soft);
  text-wrap: pretty;
}
.ref__tag {
  display: inline-flex;
  align-items: center;
  gap: .5em;
  margin: .7em 0 0;
  font: 600 clamp(.8rem, .88vw, .9rem)/1 var(--body);
  letter-spacing: .01em;
  color: var(--ink);
}
.ref__tag::before {
  content: '';
  width: 1.1em;
  height: 1px;
  background: var(--ink);
}
.ref__shot {
  display: block;
  width: 100%;
  /* 20.5rem statt der frueheren 22: die BeautySweeties-Referenz laeuft als
     fuenfte ueber beide Spalten, ihr Bild waere sonst breiter als das von
     addUltra in seiner einzelnen Spalte. So sind beide gleich gross. */
  max-width: 20.5rem;
  height: auto;
  margin-top: .9rem;
  mix-blend-mode: multiply;
}
/* ---------- Kontakt ---------- */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: clamp(1.1rem, 2vw, 2rem);
}
.card__title {
  margin: 0 0 .35em;
  font: 600 clamp(1.02rem, 1.3vw, 1.28rem)/1.2 var(--display);
}
.card__role,
.card__row {
  /* Kein eigener Abstand pro Zeile: der Rhythmus kommt allein aus der
     Zeilenhoehe, sonst sitzt jede Zeile anders als ihre Nachbarn. */
  margin: 0;
  font-size: .95rem;
  line-height: 1.55;
  color: var(--ink-soft);
}
.card__row a {
  /* Zielgroesse 24 CSS-Pixel nach WCAG 2.2 AA 2.5.8, getragen von der
     Zeilenhoehe selbst (.95rem * 1.55). Vorher lag hier ein Polster fuer 44 px,
     das machte die beiden Link-Zeilen fast doppelt so hoch wie ihre Nachbarn
     und der Block sah ungleich gesetzt aus.
     Die Linie unter dem Text ist eine text-decoration, kein border-bottom: ein
     Rahmen saenke an den Boden der Box, die Unterlaenge bleibt an den Zeichen. */
  display: inline-block;
  padding: 0;
  color: var(--ink);
  text-decoration-line: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--ink-edge);
  text-underline-offset: .16em;
  transition: text-decoration-color .2s linear;
}
.card__row a:hover { text-decoration-color: var(--ink); }

.cta {
  display: inline-flex;
  align-items: center;
  gap: .7rem;
  margin-top: clamp(1.2rem, 2vw, 1.8rem);
  padding: .85rem 1.3rem;
  background: var(--ink);
  color: #fff;
  font: 500 .9rem/1 var(--body);
  letter-spacing: .02em;
  text-decoration: none;
  transition: background .25s linear;
}
.cta:hover { background: #16202e; }
.cta .chev { width: .32rem; margin: 0; stroke-width: 1.05; }

.legal {
  margin-top: clamp(1.1rem, 1.9vw, 1.6rem);
  padding-top: .4rem;   /* the missing .6rem now sits in the summary padding */
  border-top: 1px solid var(--ink-line);
}
.legal summary {
  cursor: pointer;
  /* 12px label in a 32px tap target, full panel width (WCAG 2.2 AA 2.5.8).
     .legal padding-top and .legal p margin give the added height back. */
  padding: .6rem 0;
  font: 500 .78rem/1 var(--body);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.legal summary:hover { color: var(--ink); }
.legal p {
  margin: .3rem 0 0;   /* summary padding carries the rest of the old .9rem */
  font-size: .82rem;
  line-height: 1.6;
  color: var(--ink-soft);
}

/* ============================================================
   PAGER
   ============================================================ */
.pager {
  /* The pager does not change world with the state but with the layout: on
     desktop it sits on the dimmed scene, on the phone on the white bar.
     These three names are what get repointed. */
  --pager-fg: var(--type-2);
  --pager-fg-strong: var(--type);
  --pager-line: var(--type-line);
  position: absolute;
  z-index: 3;
  right: var(--gutter);
  bottom: clamp(1.1rem, 3.2vh, 2.2rem);
  display: flex;
  align-items: center;
  gap: clamp(.8rem, 1.6vw, 1.6rem);
  opacity: 0;
  /* Ohne visibility blieben die drei Pager-Knöpfe im geschlossenen Zustand
     fokussierbar: Tab lief ins Unsichtbare, der Ring war nicht zu sehen und
     Enter tat nichts. Die Verzögerung entspricht Delay plus Dauer der Opacity. */
  visibility: hidden;
  transform: translateY(.6rem);
  transition: opacity .4s var(--ease-soft) .3s, transform .5s var(--ease) .3s, visibility 0s .7s;
  pointer-events: none;
}
body.is-open .pager {
  opacity: 1;
  visibility: visible;
  transform: none;
  pointer-events: auto;
  transition: opacity .4s var(--ease-soft) .3s, transform .5s var(--ease) .3s, visibility 0s;
}

.pager__btn,
.pager__close {
  appearance: none;
  border: 0;
  background: none;
  padding: .4rem;
  margin: 0;
  -webkit-tap-highlight-color: transparent;
  color: var(--pager-fg);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: .6rem;
  transition: color .2s linear;
}
.pager__btn:hover,
.pager__close:hover { color: var(--pager-fg-strong); }

.pager__txt { display: grid; text-align: left; }
.pager__btn--next .pager__txt { text-align: right; }
/* No opacity on top of --pager-fg: the alpha is already in the colour, a second
   knock-back is what breaks 1.4.3. Size and tracking carry the hierarchy. */
.pager__txt i {
  font: 400 .62rem/1 var(--body);
  font-style: normal;
  letter-spacing: .18em;
  text-transform: uppercase;
}
.pager__txt b {
  font: 500 .92rem/1.3 var(--display);
  letter-spacing: -.005em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 11rem;
}

.pager__close {
  gap: .45rem;
  padding: .5rem .7rem;
  border: 1px solid var(--pager-line);
}
.pager__close svg {
  width: .58rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
}
.nur-klein { display: none; }

.pager__esc {
  font: 400 .62rem/1 var(--body);
  letter-spacing: .12em;
  text-transform: uppercase;
}

/* ============================================================
   HINWEIS
   ============================================================ */
.hint {
  position: fixed;
  z-index: 2;
  left: 50%;
  bottom: clamp(1rem, 2.6vh, 1.8rem);
  transform: translateX(-50%);
  margin: 0;
  display: flex;
  align-items: center;
  gap: .6rem;
  font: 400 .64rem/1 var(--body);
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--type-2);
  white-space: nowrap;
  transition: opacity .5s var(--ease-soft), color .6s var(--ease-soft);
}
.hint span { width: 1.3rem; height: 1px; background: var(--type-line); }
.hint.is-gone { opacity: 0; }

/* ============================================================
   INTRO-VORHANG
   ============================================================ */
/* Die Papierwelt liegt beim ersten Aufruf als Blatt über der Bildwelt: die
   Klammern stehen leer, klappen auf, [njuː] schreibt sich in den Spalt, dann
   hebt sich das Blatt mit derselben clip-path-Mechanik, mit der die Panels
   öffnen. Am Desktop steht die Marke exakt auf der Position der echten
   Wortmarke, der Wechsel Papier zu Bild passiert also hinter stehender Tinte.
   Der Vorhang fängt keine Zeiger, die Seite ist ab dem ersten Frame bedienbar.
   intro.js entscheidet nur, ob übersprungen wird, und räumt den Rest auf. */
.curtain {
  position: fixed;
  inset: 0;
  z-index: 30;
  background: var(--paper);
  clip-path: inset(0 0 0 0);
  pointer-events: none;
  /* Die Marke steht bewusst einen Moment fertig da, bevor das Blatt geht:
     unter 1.5s liest man den Namen nicht zu Ende, bevor er schon weg ist. */
  animation: curtain-lift .75s var(--ease) 1.6s forwards;
}

.curtain__mark {
  position: absolute;
  /* Dieselbe Verankerung wie die echte Wortmarke: rechte Torkante, gleicher
     Versatz, gleiche Groesse. Nur so bleibt die Marke beim Anheben des Blatts
     stehen, und der Wechsel Papier zu Bild passiert hinter stehender Tinte. */
  left: var(--door-r);
  top: 50%;
  translate: calc(-1 * var(--nju-fill) * var(--door-w)) -50%;
  /* Dieselbe Parallaxe wie die echte Marke. Ohne sie steht die Papier-Marke
     still, waehrend die Tinte darunter dem Zeiger folgt, und beim Anheben des
     Blatts springt die Marke um bis zu 12px zur Seite. */
  transform: translate3d(calc(var(--mx) * 12px), calc(var(--my) * 7.44px), 0);
  margin: 0;
  font: 700 clamp(1.6rem, calc(var(--door-w) * var(--nju-fill) / var(--nju-em)), 12rem)/1 var(--display);
  letter-spacing: -.028em;
  white-space: nowrap;
  color: var(--ink);
}
.curtain__mark b { font-weight: 700; }

.curtain__half {
  display: inline-block;
  animation: bracket-part .55s var(--ease) .32s both;
}
/* .8em ist die halbe Breite von njuː: geschlossen bleibt ein schmaler Spalt
   stehen, die Klammern lesen sich als leeres [ ] und überlappen nie. */
.curtain__half--l { --dx: .8em; }
.curtain__half--r { --dx: -.8em; }

/* Von der Mitte nach außen freigestellt, synchron zum Spalt, den die Klammern
   öffnen. Die negativen Kanten lassen Ober- und Unterlängen in Ruhe. */
.curtain__nju {
  display: inline-block;
  animation: nju-in .5s var(--ease-soft) .38s both;
}

.curtain__world { animation: world-in .45s linear .72s both; }

@keyframes bracket-part { from { transform: translateX(var(--dx)); } }
@keyframes nju-in {
  from { clip-path: inset(-.3em 50%); }
  to   { clip-path: inset(-.3em -.3em); }
}
@keyframes world-in { from { opacity: 0; } }
@keyframes curtain-lift {
  to { clip-path: inset(0 0 100% 0); visibility: hidden; }
}

/* Der Korridor setzt hinter dem sich hebenden Blatt sanft zurück, als träte
   man durch das Tor. Auf .scene statt .scene__bg, damit weder Parallaxe noch
   der --bgz-Zoom des offenen Zustands überschrieben werden. */
.scene { animation: scene-settle 2s var(--ease-soft); }
@keyframes scene-settle {
  from { transform: scale(1.045); }
  to   { transform: scale(1); }
}

/* intro.js setzt no-intro vor dem ersten Paint: schon gesehen in dieser
   Session oder per Hash direkt zu einem Thema. Dann keinerlei Ouvertüre. */
html.no-intro .curtain { display: none; }
html.no-intro .scene { animation: none; }

@media (max-width: 860px) {
  /* Querformat unter 861px: die echte Wortmarke steht mittig, nicht im Tor.
     Die Intro-Marke steht deshalb erst in der Mitte und faehrt an deren Platz,
     bevor das Blatt geht. Hochkant ueberschreibt das weiter unten: dort sitzt
     die Marke wie am Desktop schon auf dem Tor. Das Delta misst intro.js,
     weil die Zielposition an Viewporthoehe und Schriftgroesse haengt. */
  .curtain__mark {
    /* Kein Tor-Anker hier: unter 861px steht die Marke nicht im Tor, sondern
       mittig und faehrt von dort an ihren Platz in der Liste. */
    left: 50%;
    top: 50%;
    translate: -50% -50%;
    font-size: clamp(1.9rem, 11.5vw, 3.2rem);
    animation: mark-travel .62s var(--ease-travel) 1.5s both;
  }

  /* Das Blatt geht erst, wenn die Marke steht: 1.5s Wartezeit plus .62s Fahrt.
     Andernfalls ueberholt die Schnittkante sie unterwegs und schneidet sie
     mitten in der Fahrt ab, denn curtain-lift laeuft auf --ease und hat nach
     einem Fuenftel seiner Zeit schon vier Fuenftel der Strecke hinter sich.
     Der Wert muss deshalb an mark-travel haengen, nicht daneben liegen. */
  .curtain { animation-delay: 2.12s; }
}

@keyframes mark-travel {
  to { translate: calc(-50% + var(--travel-x, 0px)) calc(-50% + var(--travel-y, 0px)); }
}
/* visibility gehoert zwingend dazu, nicht nur opacity: der Vorhang faengt
   bewusst keine Zeiger, die Seite ist ab dem ersten Frame bedienbar. Ohne die
   Zeile liegen die Themen die ganzen 2.4s als unsichtbare, aber tabbare und
   antippbare Schaltflaechen da, mit unsichtbarem Fokusring (WCAG 2.4.7/2.4.11).
   Genau der Fehler, gegen den die Seite an drei anderen Stellen schon
   ausdruecklich visibility einsetzt. Diskrete Werte springen zur Haelfte des
   Keyframes um, da ist die Deckkraft laengst wieder oben. */
@keyframes node-in {
  from { opacity: 0; visibility: hidden; transform: translateY(12px); }
}

/* ============================================================
   RESPONSIVE
   ============================================================ */
@media (max-width: 1180px) {
  .node--meta { left: 10%; top: 21%; }
  .node--innovation { left: 58%; top: 16%; }
  .node--food { left: 12%; top: 62%; }
  /* Unter dem Tor ist hier kein Platz mehr fuer Punkt UND Panel nebeneinander:
     das Panel ist ab hier 34rem breit. Der Punkt geht deshalb an den rechten
     Rand und das Panel nach links, statt sich gegenseitig zu verdecken. Die
     Anordnung des Klienten gilt fuer breite Fenster, dort sitzt der Punkt
     mittig unter dem Tor. */
  .node--patente { left: auto; right: var(--gutter); top: 66%; }
  #panel-patente { left: var(--gutter); right: auto; bottom: 16%; }
  .panel { width: min(34rem, 54vw); }
  .panel--wide { width: min(46rem, 70vw); }
}

@media (max-width: 860px) {
  .scene__rules { display: none; }

  /* Kein Schleier mehr: das Hochkant-Motiv ist unten bereits hell verlaengert,
     ein Overlay hat genau den matschigen Boden erzeugt, den der Klient sieht. */
  .scene__grade { background: none; }
  .scene__bg,
  body.is-open .scene__bg {
    filter: saturate(1.06) contrast(1.08) brightness(1.04);
    translate: none;
    /* Fester Mobil-Zuschnitt, nicht die Desktop-Mitte. */
    object-position: 52% center;
  }

  /* Kleines Logo oben links: auf dem Telefon braucht es niemand, die
     Wortmarke sitzt im Tor. Der Platz laesst die Liste hoeher rutschen. */
  .brandbar { display: none; }

  /* Wortmarke bleibt auf dem Tor, die Themen liegen als Liste auf dem Bild.
     left/top/right/bottom muessen mit, sonst bleiben die Desktop-Koordinaten
     als Versatz stehen und die Liste faehrt auseinander. */
  .node {
    position: static;
    left: auto;
    right: auto;
    top: auto;
    bottom: auto;
    transform: none;
    translate: none;
  }

  .constellation {
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    padding: clamp(5.5rem, 15vh, 8rem) var(--edge-r) calc(clamp(1.6rem, 5vh, 2.6rem) + var(--safe-b)) var(--edge-l);
  }

  .wordmark {
    position: absolute;
    left: 50%;
    top: 26%;
    translate: -50% -50%;
    transform: none;
    /* Ueber 560px Breite laedt weiterhin das Querformat: dort bleibt die Marke
       mittig und ganz weiss. Die Tor-Verankerung steht im Block darunter und
       gilt nur zusammen mit dem Hochkant-Motiv. */
    margin: 0;
    padding: 0;
  }
  .wordmark__in { transform: none; }
  .wordmark__text { font-size: clamp(1.9rem, 11.5vw, 3.2rem); }
  .wordmark__text b { color: #fff; }
  /* Unter 861px steht die Marke im Listenfluss ueber dem Tor, nicht darin:
     dort ist die ganze Marke weiß und der Pfeil liegt auf dem Bild. */
  .wordmark .chev { color: inherit; }

  .nur-gross { display: none; }
  .nur-klein { display: inline; }
  @media (hover: hover) {
    .wordmark:hover .wordmark__in { transform: none; }
  }

  .node {
    width: 100%;
    padding: clamp(.34rem, 1.1vh, .55rem) 0;
  }
  /* Der Haken steht direkt hinter der Zeile wie am Rechner, nicht am rechten
     Rand: er gehoert zum Wort, nicht zur Spalte. Trennlinien tragen die Zeilen
     hier nicht mehr, der engere Satz reicht als Ordnung. */
  .node__in { width: auto; justify-content: flex-start; align-items: center; }
  .node__label {
    font-size: 1.35rem;
    font-weight: 700;
    letter-spacing: -.02em;
    line-height: 1.14;
  }
  .node--kontakt .node__label { font-size: 1rem; }
  .chev { margin-bottom: 0; width: .34rem; }
  .chev--lg { width: .48rem; }

  /* Reihenfolge der Liste = DOM-Reihenfolge = Reihenfolge des Pagers. Kein
     order hier: das würde die Fokus-Reihenfolge von der sichtbaren trennen. */

  /* Die Liste zieht hinter dem weichenden Blatt nach: die Marke ist dann schon
     oben angekommen, das Blatt gibt von unten nach oben frei, und genau in die
     freie Flaeche staffeln sich die Themen ein. Nur beim echten Intro, deshalb
     haengt es an html:not(.no-intro): sonst laege die Liste bei jedem
     Folgeaufruf und bei jedem Deep-Link erst einmal unsichtbar da.
     Die Wortmarke bleibt bewusst aussen vor, sie ist der Fixpunkt und war aus
     Sicht des Betrachters die ganze Zeit da.
     Gezaehlt wird "of .node", nicht nach Kindposition: vor der Liste stehen
     eine sr-only-Ueberschrift und die Wortmarke, und ein weiteres Element
     davor wuerde eine Positionszaehlung stillschweigend verschieben. Genau das
     ist beim Bauen passiert, sichtbar nur daran, dass der letzte Punkt zu
     frueh dastand. */
  html:not(.no-intro) .node {
    animation: node-in .42s var(--ease) both;
  }
  html:not(.no-intro) .node:nth-child(1 of .node) { animation-delay: 2.4s; }
  html:not(.no-intro) .node:nth-child(2 of .node) { animation-delay: 2.455s; }
  html:not(.no-intro) .node:nth-child(3 of .node) { animation-delay: 2.51s; }
  html:not(.no-intro) .node:nth-child(4 of .node) { animation-delay: 2.565s; }
  html:not(.no-intro) .node:nth-child(5 of .node) { animation-delay: 2.62s; }
  html:not(.no-intro) .node:nth-child(6 of .node) { animation-delay: 2.675s; }
  html:not(.no-intro) .node:nth-child(7 of .node) { animation-delay: 2.73s; }

  body.is-open { --bgz: 1; }

  /* Hochkant deckt das Sheet den Schirm, hinter ihm gibt es nichts zu wählen.
     visibility statt opacity, damit die Liste dann auch nicht mehr im
     Tab-Fokus liegt: ein Geist bei 18 % wäre ein unsichtbares Fokusziel. */
  .constellation { transition: opacity .4s var(--ease-soft), visibility 0s; }
  /* Bei offenem Thema geht die Liste weg, sonst laege sie unter dem Panel. Die
     Marke bleibt stehen: sie ist das Logo im Tor, kein Listenpunkt.
     Ausgeblendet wird deshalb Punkt fuer Punkt und nicht der Container: eine
     opacity 0 auf dem Container nimmt den ganzen Teilbaum mit, auch ein Kind,
     das ausdruecklich opacity 1 und visibility visible traegt. Anfassbar bleibt
     so ein Kind uebrigens trotzdem, der Fehler faellt beim Klicken also nicht
     auf, nur beim Hinsehen. */
  body.is-open .node {
    opacity: 0;
    visibility: hidden;
    transition: opacity .4s var(--ease-soft), visibility 0s .4s;
  }

  .panel, .panel--wide,
  #panel-marke, #panel-meta, #panel-innovation, #panel-transformation,
  #panel-food, #panel-patente, #panel-referenzen, #panel-kontakt {
    left: max(1rem, var(--safe-l));
    right: max(1rem, var(--safe-r));
    top: auto;
    bottom: calc(4.4rem + var(--safe-b));
    width: auto;
    max-height: 64dvh;
    translate: none;
    transform: none;
    padding:
      clamp(1.1rem, 4vw, 1.5rem)
      max(clamp(1rem, 4vw, 1.4rem), var(--safe-r))
      clamp(1.1rem, 4vw, 1.5rem)
      max(clamp(1rem, 4vw, 1.4rem), var(--safe-l));
    --clip-zu: inset(100% 0 0 0);
  }

  .pager {
    /* Island of paper: here the bar sits on white, not on the scene. */
    --pager-fg: var(--ink-2);
    --pager-fg-strong: var(--ink);
    --pager-line: var(--ink-edge);
    --ring: var(--ink);
    left: 0; right: 0;
    bottom: 0;
    padding:
      .8rem
      max(clamp(1.1rem, 4.6vw, 1.8rem), var(--safe-r))
      calc(.8rem + var(--safe-b))
      max(clamp(1.1rem, 4.6vw, 1.8rem), var(--safe-l));
    justify-content: space-between;
    background: var(--paper);
    border-top: 1px solid var(--ink-line);
  }

  /* Verlauf über der Leiste zeigt, dass der Text darunter weitergeht */
  .pager::before {
    content: '';
    position: absolute;
    left: 0; right: 0; bottom: 100%;
    height: 2.4rem;
    background: linear-gradient(to top, var(--paper), transparent);
    pointer-events: none;
  }
  .pager__txt b { max-width: 7.5rem; }
  /* Direction leaves the screen but stays in the accessible name: display: none
     would strip "Vorher"/"Weiter" from the button label (WCAG 2.4.6). */
  .pager__txt i {
    position: absolute;
    width: 1px; height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .hint { display: none; }
}

/* Kurze Hochkant-Viewports: kein Extra-Schleier. Das Motiv bleibt hell. */

/* Touch hat kein Hover, braucht aber eine Rückmeldung auf den Druck */
@media (hover: none) {
  .node:active { color: var(--type-hover); }
  .node:active .node__in { transform: translateX(.3rem); }
  .wordmark:active { opacity: .62; }
  .cta:active { background: #16202e; }
  .pager__btn:active, .pager__close:active { color: var(--pager-fg-strong); }
}

/* Kurze Landscape-Viewports: Liste darf scrollen statt zu kollidieren */
@media (max-width: 860px) and (max-height: 560px) {
  .constellation { overflow-y: auto; justify-content: flex-start; padding-top: 4.5rem; }
  .wordmark { padding-bottom: 1rem; }

  /* 88dvh schiebt die Panel-Oberkante hier mitten in die Kopfzeile, die dann
     weiß auf weiß verschwindet. Die Höhe lässt die Zeile frei. */
  .panel, .panel--wide { max-height: calc(100dvh - 4.6rem); }
}

/* Quer gehaltenes Handy: sieben Themen in zwei Spalten, dann passt alles ohne
   Scrollen und ohne angeschnittene Zeile. Der Schleier trägt nur dieses
   Listenband am Boden, das Tor am Fluchtpunkt bleibt darüber frei. */
@media (max-width: 860px) and (max-height: 560px) and (min-width: 620px) {
  .constellation {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: clamp(1.5rem, 4vw, 3rem);
    align-content: end;
    overflow: visible;
    padding-top: 3.4rem;
    /* Die Liste rutscht so tief, dass ihre erste Zeile unter dem Tor beginnt. */
    padding-bottom: calc(1rem + var(--safe-b));
  }

  .scene__grade { background: none; }

  /* Der dunkle linke Bildrand liegt in diesem Zuschnitt genau unter der
     Kopfzeile. Der Schein aus den Ecken trägt sie, statt den Korridor zu fluten. */
  .scene__grade::before { content: none; }

  /* Wortmarke auf dem Tor, weiße Times. */
  .wordmark {
    position: absolute;
    top: 42%;
    left: 50%;
    translate: -50% -50%;
    margin: 0;
    padding: 0;
  }
  .wordmark__text { font-size: clamp(1.7rem, 5.4vw, 2.4rem); }

  .node { padding: .3rem 0; }
  .node__label { font-size: 1.02rem; }
  .node--kontakt .node__label { font-size: .88rem; }
}

/* ============================================================
   BEWEGUNG REDUZIERT
   ============================================================ */
/* Not a blanket off switch: the state change stays perceivable, it only loses
   its travel. Everything that carries the inversion crossfades on one shared
   beat, while parallax, zoom, blur, the clip-path reveal, the staggered
   content and the nudge loop drop out. */
@media (prefers-reduced-motion: reduce) {
  :root { --fade: 120ms; }

  /* Die Themen stehen sofort da, ohne Fahrt und ohne Staffelung. Praktisch
     setzt intro.js hier ohnehin no-intro, aber die Zusage darf nicht daran
     haengen, dass ein Skript laeuft: faellt intro.js aus (Ad-Blocker, 404),
     bliebe die Liste sonst 2.4s leer vor jemandem, der Bewegung abbestellt hat. */
  .curtain__mark, .node { animation: none; }

  /* Scene holds still: no pointer offset, no zoom, no blur. The scrim alone
     carries the change of the scene. */
  /* translate und scale einzeln abraeumen: die Szene traegt beides seit dem
     Entkoppeln nicht mehr in einer gemeinsamen transform. */
  .scene__bg { translate: none; scale: none; transition: filter var(--fade) linear; }
  body.is-open .scene__bg { filter: saturate(1.14) contrast(1.22) brightness(.84); }

  /* Nodes stay put, the wordmark takes its smaller size without flying there,
     and the idle nudge is gone for good. */
  .node, .wordmark, .curtain__mark { transform: none; }
  .node__in, .wordmark__in { transition: color var(--fade) linear; }
  body:not(.is-open) .wordmark .chev { animation: none; }

  /* One beat for the whole inversion: the ink flips to white while scrim and
     panel crossfade. */
  .scene__scrim,
  .scene__rules::before, .scene__rules::after,
  .brandbar__mark,
  .node, .wordmark, .node__label, .wordmark__text,
  .chev, .hint { transition-duration: var(--fade); }

  /* The panel fades as one block instead of wiping open. visibility now hangs
     on that fade: with the old .8s delay the invisible panel would stay
     focusable for another 680ms after closing. */
  .panel {
    clip-path: none;
    transition: opacity var(--fade) linear, visibility 0s var(--fade);
  }
  .panel.is-on { transition: opacity var(--fade) linear, visibility 0s; }
  .panel > * { opacity: 1; transform: none; }

  .pager { transform: none; transition: opacity var(--fade) linear; }

  /* Das Intro entfällt vollständig, nicht nur seine Bewegung: ein Blatt, das
     ohne Animation liegen bliebe, würde die Seite dauerhaft verdecken. */
  .curtain { display: none; }
  .scene { animation: none; }
}

/* Hochkant: Motiv 1024×819 per cover. Zuschnitt fest 52 % (nicht aendern).
   Themen und Tor rechnen gegen das gerenderte Bild, nicht gegen vw. */
@media (max-width: 860px) and (max-aspect-ratio: 1/1) {
  .scene__bg {
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: 52% center;
    translate: none;
  }

  .constellation {
    container-type: size;
    /* Hoehe fuellt immer: das Motiv ist 4:3, das Telefon ist schmaler. */
    --disp-h: 100cqh;
    --disp-w: calc(100cqh * 1024 / 819);
    --img-pos: .52;
    --img-left: calc(var(--img-pos) * (100cqw - var(--disp-w)));
    --img-top: 0px;
    --menu-x: .335;
    --door-l: calc(var(--img-left) + var(--disp-w) * .4668);
    --door-r: calc(var(--img-left) + var(--disp-w) * .5508);
    --door-w: calc(var(--disp-w) * .084);
    --door-y: calc(var(--img-top) + var(--disp-h) * 247 / 819);
    padding: 0;
  }

  .node {
    position: absolute;
    left: calc(var(--img-left) + var(--disp-w) * var(--menu-x));
    right: auto;
    width: max-content;
    padding: 0;
    display: flex;
    align-items: center;
  }
  .node:nth-child(1 of .node) { top: calc(var(--img-top) + var(--disp-h) * 392 / 819); height: calc(var(--disp-h) * 60 / 819); }
  .node:nth-child(2 of .node) { top: calc(var(--img-top) + var(--disp-h) * 452 / 819); height: calc(var(--disp-h) * 67 / 819); }
  .node:nth-child(3 of .node) { top: calc(var(--img-top) + var(--disp-h) * 519 / 819); height: calc(var(--disp-h) * 64 / 819); }
  .node:nth-child(4 of .node) { top: calc(var(--img-top) + var(--disp-h) * 583 / 819); height: calc(var(--disp-h) * 65 / 819); }
  .node:nth-child(5 of .node) { top: calc(var(--img-top) + var(--disp-h) * 648 / 819); height: calc(var(--disp-h) * 63 / 819); }
  .node:nth-child(6 of .node) { top: calc(var(--img-top) + var(--disp-h) * 711 / 819); height: calc(var(--disp-h) * 62 / 819); }
  .node:nth-child(7 of .node) { top: calc(var(--img-top) + var(--disp-h) * 773 / 819); height: calc(var(--disp-h) * 46 / 819); }

  .wordmark {
    left: var(--door-r);
    top: var(--door-y);
    translate: calc(-1 * var(--nju-fill) * var(--door-w)) -50%;
  }
  .wordmark__text {
    font-size: calc(var(--door-w) * var(--nju-fill) / var(--nju-em));
  }
  .wordmark__text b { color: #080d14; }
  .wordmark .chev { color: #080d14; }

  /* Papier-Marke sitzt von Frame 1 auf dem Tor, wie am Desktop: keine Fahrt
     mehr zur alten Listenmitte. .curtain ist nicht im Constellation-Container,
     deshalb dieselbe Bildraum-Rechnung hier noch einmal, per eigener cqh.
     Beide Boxen sind fixed inset 0, die Werte bleiben deckungsgleich. */
  .curtain {
    container-type: size;
    animation-delay: 1.6s;
    --disp-h: 100cqh;
    --disp-w: calc(100cqh * 1024 / 819);
    --img-pos: .52;
    --img-left: calc(var(--img-pos) * (100cqw - var(--disp-w)));
    --img-top: 0px;
    --door-l: calc(var(--img-left) + var(--disp-w) * .4668);
    --door-r: calc(var(--img-left) + var(--disp-w) * .5508);
    --door-w: calc(var(--disp-w) * .084);
    --door-y: calc(var(--img-top) + var(--disp-h) * 247 / 819);
  }
  .curtain__mark {
    left: var(--door-r);
    top: var(--door-y);
    translate: calc(-1 * var(--nju-fill) * var(--door-w)) -50%;
    font-size: calc(var(--door-w) * var(--nju-fill) / var(--nju-em));
    animation: none;
    transform: none;
  }

  /* Blatt geht wieder bei 1.6s, die Zeilen folgen der Schnittkante von unten. */
  html:not(.no-intro) .node:nth-child(1 of .node) { animation-delay: 1.68s; }
  html:not(.no-intro) .node:nth-child(2 of .node) { animation-delay: 1.735s; }
  html:not(.no-intro) .node:nth-child(3 of .node) { animation-delay: 1.79s; }
  html:not(.no-intro) .node:nth-child(4 of .node) { animation-delay: 1.845s; }
  html:not(.no-intro) .node:nth-child(5 of .node) { animation-delay: 1.9s; }
  html:not(.no-intro) .node:nth-child(6 of .node) { animation-delay: 1.955s; }
  html:not(.no-intro) .node:nth-child(7 of .node) { animation-delay: 2.01s; }
}
