/* ── SELF-HOSTED FONT — Geist Mono Variable (100–900) ──
   Ersetzt Google Fonts (DSGVO). Ein File, alle Gewichte echt. */
@font-face {
  font-family: 'Geist Mono';
  src: url('../fonts/GeistMono-Variable.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

*, *::before, *::after { margin:0; padding:0; box-sizing:border-box; }
:root {
  --black:#090909; --cream:#F0EDE8; --gold:#B8963E;
  --mono:'Geist Mono',monospace;
  --pad:clamp(1.2rem,5vw,3.2rem);
  /* GEMEINSAME TYPO-SKALA — Welcome (Out of the Box) und die Lesezone
     (Human-in-the-Loop) greifen auf DIESELBEN Werte zu, damit Überschrift
     und Fließtext über die Sektionen hinweg konsistent bleiben. */
  --fs-heading: clamp(1.5rem, 4vw, 2.7rem);
  --lh-heading: 1.12;
  --fs-body: clamp(.9rem, 1.7vw, 1.08rem);
  --lh-body: 1.72;
  /* Wie weit die Willkommens-Sektion unter die auslaufende Bühne greift.
     90vh statt 55vh: Der Auftritt der Sektion hängt daran, dass die
     Kopfzeilen-Leiste gelandet ist. Bei 55vh geschah das, während die
     Sektionsoberkante noch 500 px unterhalb der Bildoberkante stand — der
     Inhalt war also schon sichtbar und wanderte danach nur noch hoch. Genau
     diese 500 px las man als leeren Raum zwischen dering.lab-Reveal und
     Willkommens-Sektion. Mit 90vh bleiben rund 200 px Restweg: genug, dass
     die Sektion sichtbar nachrückt, ohne zu hängen. */
  --welcome-overlap: 90vh;
}
html { overflow-x:hidden; }
body { background:var(--black); }

/* BG-FX — WebGL "lebendiges Schwarz"; Gold/Cream der Kino-Sequenz
   (#stage, z:1) verdecken es beim Handoff. */
#bg-fx { position:fixed; inset:0; width:100%; height:100%; z-index:0; pointer-events:none; display:block; }


/* ═══════════════════════════════════
   PIN: one long scroll runway, THREE stages + two transitions:
   STAGE A — DENNIS / RINGELBERG. (centered block, grows past viewport, blur-fades)
   STAGE B — Mediadesign Digital & Print. (approaches from far, grows to reading size)
   HANDOFF — background crossfades black → cream, nav flips light, text flips dark
   PORTAL  — the gold dot after "Print." swallows the screen and STAYS:
             you arrive inside the dot; the claim lives on the gold.
═══════════════════════════════════════ */
#pin {
  position: relative;
  z-index: 1;
  height: 320vh; /* overridden by JS for mobile */
}

/* VERWORFEN: Hier lag ein Band, das die Wandfarbe über die Oberkante von
   #welcome hinaus nach oben fortsetzte. Es hat nicht funktioniert, und der
   Grund ist wichtig genug, um ihn festzuhalten:
   Die Wandfarbe ist rgb(44,39,28), nach dem Filter rgb(35,31,22) — der
   Grund dahinter ist #090909. Das sind praktisch dieselben Töne. Die
   waagerechte Kante, die man an dieser Stelle sieht, ist GAR KEINE
   Farbkante. Es sind die Liquid-Linien, die verschwinden, sobald etwas
   Deckendes darüberliegt. Eine deckende Fläche nach oben zu verlängern
   verschiebt diese Linie also nur — sichtbar bleibt sie.
   Zusätzlich entstand ein wandernder Spalt: .welcome-portrait fährt beim
   Auftritt aus translateY(18px) heraus, das feste Band nicht. */

#stage {
  position: sticky;
  top: 0;
  height: 100svh;
  overflow: hidden;
  /* transparent: body is black, fixed WebGL background (#bg-fx, z:0) shows through.
     JS animates this to rgba(cream, α) for the black→cream handoff crossfade. */
  background: transparent;
}

/* ── PORTRÄT IM ERÖFFNUNGSBILD (Versuch) ──
   Ebene hinter der Typo (z:2, Zoom-Layer liegt auf z:3). Das Bild sitzt
   linksbündig und läuft über die volle Bühnenhöhe.
   WICHTIG gegen den „abgeschnitten"-Eindruck rechts: es gibt dort KEINE
   Bildkante, sondern eine weiche Ausblendung, die schon deutlich vor dem
   Bildrand einsetzt — das Porträt geht ins Dunkel über, statt zu enden.
   Zusätzlich unten ausgeblendet, damit die Meta-Zeile frei steht. */
#intro-portrait {
  position: absolute;
  inset: 0;
  z-index: 2;
  margin: 0;
  overflow: hidden;
  pointer-events: none;
  opacity: 0;                 /* Deckkraft führt die Intro-Choreografie */
  /* Filter sitzt auf der FIGUR, nicht auf dem Bild: so bekommen Foto und
     fortgesetzte Wandfarbe exakt dieselbe Behandlung und es entsteht kein
     Farbversatz an der Nahtstelle. */
  filter: grayscale(.2) contrast(1.05) brightness(.72);
  /* Die Ausblendung setzt ERST BEI 70 % ein. Das Gesicht endet je nach
     Fensterbreite bei 50 % (breit) bis 62 % (schmal) — es liegt damit auf
     JEDER Größe vollständig im deckenden Bereich. Begänne die Kante
     früher, liefe sie durchs Gesicht, und genau das würde als
     „abgeschnitten" gelesen. */
  -webkit-mask-image:
    linear-gradient(to right, #000 0%, #000 70%, rgba(0,0,0,.45) 84%, transparent 96%),
    linear-gradient(to bottom, #000 0%, #000 64%, transparent 98%);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(to right, #000 0%, #000 70%, rgba(0,0,0,.45) 84%, transparent 96%),
    linear-gradient(to bottom, #000 0%, #000 64%, transparent 98%);
  mask-composite: intersect;
}
/* SCHMALE SCHIRME: Dort füllt das Gesicht fast die ganze Breite, die Typo
   kann ihm also nicht ausweichen wie am Desktop (wo sie rechts daneben
   steht) — sie landet zwangsläufig auf Mund/Kinn. Deshalb wird die
   Ausblendung nach unten VORGEZOGEN: die ausdrucksstarke Bildhälfte
   (Stirn bis Augen, bis ca. 250 px) bleibt voll deckend stehen, die untere
   Gesichtshälfte löst sich hinter dem Namen ins Dunkel auf. Der Name steht
   dadurch auf ruhigem Grund, statt mit dem Gesicht zu konkurrieren. */
@media (max-width: 700px) {
  #intro-portrait {
    -webkit-mask-image:
      linear-gradient(to right, #000 0%, #000 70%, rgba(0,0,0,.45) 84%, transparent 96%),
      linear-gradient(to bottom, #000 0%, #000 32%, rgba(0,0,0,.25) 50%, transparent 72%);
    mask-image:
      linear-gradient(to right, #000 0%, #000 70%, rgba(0,0,0,.45) 84%, transparent 96%),
      linear-gradient(to bottom, #000 0%, #000 32%, rgba(0,0,0,.25) 50%, transparent 72%);
  }
}

/* Gemeinsame Ebene für Foto UND fortgesetzte Wandfarbe. Die Scroll-
   Choreografie fährt NUR diese Deckkraft — dadurch verschwinden beide
   zusammen. (Die Deckkraft der FIGUR gehört der Einblendung beim Laden;
   lägen beide auf demselben Element, überschriebe eines das andere.)
   WANDFARBE direkt aus der Bilddatei ausgelesen (Rohwerte — der Filter
   der Figur wirkt darauf genauso wie auf das Foto). Weil das Bild über
   die HÖHE gerechnet wird, reicht es auf breiten Fenstern nicht bis zum
   rechten Rand; dieser Verlauf setzt die Wand dort nahtlos fort. */
.ip-layer {
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom,
    rgb(44,39,28) 0%, rgb(49,43,29) 20%, rgb(40,33,26) 45%,
    rgb(32,26,21) 70%, rgb(23,15,12) 92%);
}

#intro-portrait img {
  /* ÜBER DIE HÖHE gerechnet, NICHT über die Breite. Das ist der Kern:
     mit object-fit:cover skaliert ein breites Fenster das Bild über die
     Breite hoch (bei 1871x983 auf 2265 px Höhe) — alles unterhalb der Nase
     fällt dann aus dem Bild. Höhengebunden passt der Kopf dagegen auf
     JEDEM Seitenverhältnis vollständig ins Bild. */
  position: absolute;
  top: 0;
  height: 100%;
  width: auto;                /* Seitenverhältnis folgt der Höhe */
  max-width: none;
  display: block;
  /* AUGEN-ANKER: translateX(-42.4%) verschiebt um exakt den Abstand, den
     das Auge vom linken Bildrand hat — dadurch liegt es unabhängig von
     Bildgröße und Fensterformat immer auf `left`. */
  left: 20%;
  transform: translateX(-42.4%);
  /* Rechte Bildkante löst sich in die fortgesetzte Wandfarbe auf. Das
     Gesicht endet bei 62 % der Bildbreite und bleibt unberührt. */
  -webkit-mask-image: linear-gradient(to right, #000 0%, #000 76%, transparent 100%);
  mask-image: linear-gradient(to right, #000 0%, #000 76%, transparent 100%);
}

/* Eyebrow + meta — small overlay text, top/bottom corners */
.eyebrow {
  position: absolute;
  top: clamp(1.4rem,4vw,2.5rem);
  left: var(--pad);
  font-family: var(--mono);
  font-size: clamp(8px,2vw,11px);
  color: var(--gold);
  letter-spacing: .14em;
  text-transform: uppercase;
  z-index: 4;
  opacity: 0;
}
.eyebrow::before { content:'// '; opacity:.4; }

.meta {
  position: absolute;
  /* top ist inline via JS gesetzt (positionMeta() in main.js) — sitzt
     unmittelbar unter dem NAME BLOCK (DENNIS RINGELBERG.), nicht mehr
     am Viewport-Rand, damit der Scroll-Hinweis sofort ins Auge fällt. */
  left: 50%;
  transform: translateX(-50%);
  display:flex; justify-content:center; align-items:center;
  gap: 2rem;
  z-index: 4;
  opacity: 0;
  font-family:var(--mono); font-size:clamp(10px,1.6vw,12px); color:#7a7a72;
  letter-spacing:.14em; text-transform:uppercase;
  white-space: nowrap;
}
.meta::before { content:''; width:26px; height:1px; background:var(--gold); display:block; flex-shrink:0; }
@media(max-width:600px){ .meta{flex-wrap:wrap;font-size:9px;gap:1rem;} .meta::before{width:20px;} }

/* ── SOCIAL im Eröffnungsbild — unten links, blendet mit Eyebrow/Meta aus
   (Opazität steuert die Intro-Choreografie in main.js). Outline-Icons in
   currentColor; Hover/Focus färbt auf Gold. ── */
.hero-social {
  position: absolute;
  left: var(--pad);
  bottom: clamp(1.6rem, 4vh, 2.8rem);
  z-index: 5;
  display: flex;
  gap: 1.1rem;
  opacity: 0;                 /* JS fährt die Deckkraft mit dem Intro */
}
.hero-social a {
  display: block;
  color: rgba(240, 237, 232, 0.55);
  transition: color .35s ease, transform .35s cubic-bezier(.2,.8,.25,1);
}
.hero-social a:hover,
.hero-social a:focus-visible {
  color: var(--gold);
  transform: translateY(-3px);
}
.hero-social svg {
  width: 21px; height: 21px;
  display: block;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* die kleinen Punkte (LinkedIn-i, Instagram-Blende) sind gefüllt */
.hero-social svg .sd { fill: currentColor; stroke: none; }
@media (max-width: 600px) {
  .hero-social { gap: .9rem; }
  .hero-social svg { width: 19px; height: 19px; }
}

/* SCROLL HINT — nur "scroll to explore" wird prominenter: heller, atmet
   leicht und trägt einen tickenden Pfeil nach unten als klaren Scroll-Cue.
   "dering.lab — 2026" bleibt dezent (nicht markiert). */
.meta span:first-child {
  color: var(--cream);
  font-weight: 500;
  animation: metaPulse 2.6s ease-in-out infinite;
}
.meta span:first-child::after {
  content: '';
  display: inline-block;
  width: 6px; height: 6px;
  margin-left: .8em;
  border-right: 1.5px solid var(--gold);
  border-bottom: 1.5px solid var(--gold);
  vertical-align: middle;
  animation: metaBob 1.5s ease-in-out infinite;
}
@keyframes metaPulse { 0%,100%{opacity:.78} 50%{opacity:1} }
@keyframes metaBob {
  0%,100%{ transform: rotate(45deg) translate(-1px,-1px); }
  50%    { transform: rotate(45deg) translate(2px,2px); }
}
@media (prefers-reduced-motion: reduce) {
  .meta span:first-child, .meta span:first-child::after { animation: none; }
}

/* ── CENTERED ZOOM LAYER ──
   Both "RINGELBERG." and "Mediengestalter Digital & Print." live here,
   absolutely centered, one fades/grows then hands off to the next.
*/
#zoom-layer {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 3;
  pointer-events: none;
}

.zoom-word {
  font-family: var(--mono);
  font-weight: 700;
  letter-spacing: -.04em;
  line-height: .94;
  text-align: center;
  will-change: font-size, opacity, color, filter;
  position: absolute;
}

/* NAME BLOCK — DENNIS + RINGELBERG. stacked tightly, one unit */
#name-block {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: .92;
  white-space: nowrap;
  /* ── LEICHT ANGEHOBEN, um Tiefe gegen das Porträt dahinter zu erzeugen ──
     Der Versatz nach oben plus der weiche Schlagschatten darunter lösen die
     Schrift von der Wand: Sie liegt sichtbar VOR dem Bild statt darauf.
     translate (Einzel-Eigenschaft), NICHT transform: Die Intro-Timeline
     schreibt transform auf dieses Element (.from(nameBlock, {y:24}) in
     main.js) — eine transform-Regel hier würde sie überschreiben.
     GSAP liest translate beim Start ein und faltet es in seine eigene
     Matrix; die Einblendung läuft dadurch von y:24 auf y:−10 statt auf 0,
     der Versatz bleibt also erhalten (nachgemessen: matrix(…,0,-10)).
     WICHTIG für später: Ein gsap.set(nameBlock, {y:0}) würde ihn wieder
     wegnehmen — der Versatz gehört ab hier GSAP, nicht mehr dem CSS.
     Schattenmaße in px, nicht em: Der Name wächst in Phase A auf ein
     Vielfaches; em-Werte bliesen den Schatten auf Hunderte Pixel auf. */
  translate: 0 -10px;
  text-shadow:
    0 2px 3px rgba(0,0,0,.35),
    0 10px 26px rgba(0,0,0,.45);
}
#name-block .nb-line {
  display: block;
}
#nb-dennis { color: var(--cream); }
#nb-ringel { color: var(--cream); }
#nb-ringel .dot { color: var(--gold); }

#zw-medien {
  color: var(--cream);
  opacity: 0;
  white-space: normal;
  max-width: 92vw;
  text-align: center;
  word-spacing: normal;
}
#zw-medien .amp {
  display: inline-block;
  margin: 0 0.12em;
}
#zw-medien #zw-since {
  /* sits at the exact vertical center of the block = the seam between
     both lines. The lines slide apart (JS) and the tag reveals in the gap.
     Enlarged, uppercase HUMAN-IN-THE-LOOP — one clean line. */
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  font-family: var(--mono);
  font-weight: 500;
  font-size: 0.24em; /* was .16em — larger presence in the seam */
  letter-spacing: .15em;
  text-transform: uppercase;
  color: var(--gold);
  white-space: nowrap;
  opacity: 0;
  will-change: opacity, letter-spacing;
}
/* Phones: the zoom-word is smaller in px, so the em-relative tag came out
   tiny. Boost the factor (and tighten tracking) so it reads clearly. The
   seam widens to match via seamSplit in JS. */
@media (max-width: 700px) {
  #zw-medien #zw-since {
    font-size: 0.50em;
    letter-spacing: .10em;
  }
}
#zw-medien .dot { color: var(--gold); }
/* Line 2 wrapper: inline-block so it centers as a unit and anchors
   the absolutely-positioned since-tag hanging off its right edge. */
#zw-medien .dp-line { display: inline-block; position: relative; white-space: nowrap; will-change: transform; }
/* Line 1 sits flush-left with line 2's left edge.
   Exact offset is measured at runtime (JS, in em → scales with the zoom);
   -1.7ch is the no-JS fallback from the same font math. */
#zw-medien .md-line { display: inline-block; transform: translateX(-1.7ch); }

/* The gold period after "Print." simply fades out with the rest of the
   sentence (JS drives its opacity in sync) — der Satzpunkt nach "loop"
   ist jetzt das Portal (siehe #hitl-slide / #portal-dot). */
#zw-medien .dp-line .dot {
  display: inline-block;
  will-change: opacity;
}

/* ── HITL SLIDE — Velocity-Text-Szene, EIN durchgehender Shot ──
   Übernimmt den Seam-Tag mit pixelgleicher Geometrie (unsichtbarer
   harter Schnitt — kein Fade, kein Szenenwechsel), wächst dann an Ort
   und Stelle zur riesigen Zeile, verankert an ihrer linken Kante; JS
   fährt font-size/weight/tracking während des Zooms, danach translateX
   (Scroll-Fortschritt) + skewX (gedämpfte Scroll-Geschwindigkeit) —
   der klassische Velocity-Scroll-Text-Effekt. */
#hitl-slide {
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  z-index: 3;
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
  white-space: nowrap;
  font-family: var(--mono);
  font-weight: 700;
  letter-spacing: .02em;
  line-height: .85;
  color: var(--gold);
  will-change: opacity;
}
#hitl-text {
  display: inline-block;
  transform-origin: left bottom; /* Skew lehnt aus der Grundlinie, wie in der Referenz */
  will-change: transform;
}

/* ── PORTAL DOT ──
   Massives Gold-Quadrat. JS legt es während des Plunge JEDEN FRAME auf
   die live gemessene Tinten-Box des Satzpunktes (left/top/width/height
   inline) und skaliert es, bis es über jede Viewport-Kante hinausragt —
   eine ECHT wachsende Fläche, keine Farbüberblendung. transform:scale
   auf einer einfarbigen Box bleibt perfekt scharf (keine Glyphen-
   Rasterung), und es gibt kein font-size-Limit im Weg. Liegt über dem
   Slide-Text (z:3, später im DOM) und unter dem Claim (z:4). */
#portal-dot {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 10px;  /* echte Geometrie setzt JS aus der gemessenen Tinte */
  height: 10px;
  transform: translate(-50%, -50%);
  transform-origin: center;
  background: var(--gold);
  visibility: hidden;
  z-index: 3;
  pointer-events: none;
  will-change: transform;
}

/* ── CLAIM — final state, lives ON the gold (inside the dot) ── */
/* PLACEHOLDER TEXT — Leitmotiv als Arbeitsstand, Claim-Findung folgt separat.
   Cream on gold is soft (~2.7:1) — fine for huge display type; the accent
   word flips to ink as the contrast anchor, inverting the brand palette. */
#claim {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  z-index: 4;
  padding: 0 var(--pad);
  pointer-events: none;
  font-family: var(--mono);
  font-weight: 700;
  font-size: clamp(1.4rem, 4.6vw, 3.2rem);
  letter-spacing: -.02em;
  line-height: 1.18;
  color: var(--cream);
  opacity: 0;
  will-change: opacity, transform, filter;
}
#claim .hi { color: #111; }

/* ── SCROLL-HINWEIS AUF DEM CLAIM ──
   Erscheint erst, wenn der Claim steht und 15 Sekunden nicht gescrollt
   wurde (siehe main.js). Tinte statt Creme, weil er auf dem Gold liegt. */
.claim-hint {
  position: absolute;
  left: 50%;
  bottom: clamp(2rem, 7vh, 4.5rem);
  transform: translateX(-50%);
  z-index: 5;
  display: flex;
  align-items: center;
  gap: .9em;
  opacity: 0;
  pointer-events: none;
  font-family: var(--mono);
  font-size: clamp(10px, 1.6vw, 12px);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: rgba(17, 17, 17, 0.72);
  white-space: nowrap;
  transition: opacity .9s ease;
}
.claim-hint::before {
  content: '';
  width: 26px; height: 1px;
  background: rgba(17, 17, 17, 0.45);
  flex-shrink: 0;
}
/* tickender Pfeil nach unten — derselbe Duktus wie im Eröffnungsbild */
.claim-hint::after {
  content: '';
  width: 6px; height: 6px;
  border-right: 1.5px solid rgba(17, 17, 17, 0.72);
  border-bottom: 1.5px solid rgba(17, 17, 17, 0.72);
  animation: metaBob 1.5s ease-in-out infinite;
  flex-shrink: 0;
}
.claim-hint.show { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .claim-hint::after { animation: none; transform: rotate(45deg); }
}

/* ── TREPPCHEN-HOVER ──
   Nur der Text ist hoverbar (nicht der ganze Screen). Beim Hovern heben
   sich die einzelnen Buchstaben leicht an — gestaffelt (--i), sodass die
   Anhebung wie eine Treppe durch den Satz läuft. pointer-events schaltet
   das JS nur frei, solange der Claim sichtbar auf dem Gold steht. */
.claim-text { pointer-events: none; }
.claim-line { display: block; }
/* Wort-Klammer: verhindert Zeilenumbruch MITTEN im Wort (z. B. "can’t").
   Ohne sie ist jeder Buchstabe eine eigene inline-block-Box, und der
   Browser darf zwischen ihnen umbrechen, sobald die Zeile eng wird. */
.cw { display: inline-block; white-space: nowrap; }
#claim .cl {
  display: inline-block;
  transition: transform .34s cubic-bezier(.2, .8, .25, 1);
  transition-delay: calc(var(--i, 0) * 12ms);
}
.claim-text:hover .cl { transform: translateY(-.18em); }
@media (prefers-reduced-motion: reduce) {
  #claim .cl { transition: none; }
  .claim-text:hover .cl { transform: none; }
}

/* ── BRAND — final lockup; FIXED so it persists as header after the pin.
   During the pin, fixed and absolute-in-stage coincide (stage = viewport),
   so the reveal choreography needs no changes. ── */
#brand {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: center;
  visibility: hidden;
  pointer-events: none;
  font-family: var(--mono);
  font-weight: 800;
  font-size: clamp(2.6rem, 9vw, 6.8rem);
  letter-spacing: -.03em;
  color: var(--cream);
  transition: color .5s;
}
.brand-row { display: flex; align-items: baseline; will-change: transform; }
/* Der Wrap macht nur noch PLATZ (Breite 0 → 1.3em) — er clippt das Logo
   NICHT mehr. Ein overflow:hidden hier hätte das runde Siegel durch eine
   harte vertikale Kante freigewischt ("kommt aus einem Schlitz"); das
   Logo bekommt stattdessen eine eigene Bewegung (siehe #brand-logo).
   Sized in em → scales with the header travel. */
#brand-logo-wrap {
  transition: filter .5s;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  align-self: center;
  width: 0;
  will-change: width;
}
/* STEMPEL: Das ®-Siegel kommt leicht größer und gegengedreht herein und
   setzt sich scharf auf seinen Platz — wie ein Prägestempel, statt aus
   einer Kante geschoben zu werden. JS fährt scale/rotate/opacity/blur. */
#brand-logo {
  width: .92em;
  height: .92em;
  flex: 0 0 auto;
  margin-right: .38em;
  opacity: 0;
  transform-origin: 50% 50%;
  filter: blur(6px);
  will-change: opacity, filter, transform;
}
#brand-l, #brand-r { opacity: 0; will-change: opacity, transform; }
/* Solid square: its bottom margin edge IS its flex baseline →
   it sits on the text baseline exactly like a typographic period. */
#brand-dot {
  width: .18em;
  height: .18em;
  background: var(--gold);
  margin: 0 .09em;
  transform-origin: 50% 50%;
  will-change: transform;
}

/* ── MENU — beneath the landed header lockup ── */
#menu-rule {
  width: min(640px, 78vw);
  height: 1px;
  margin: 0 auto;
  background: rgba(200,184,154,0.22);
  transition: background .5s;
  transform: scaleX(0);
  transform-origin: center;
  will-change: transform;
}
#menu {
  width: min(1200px, 96vw);
  margin: 14px auto 0;
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: .7em 2em;
  visibility: hidden;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .18em;
  text-transform: uppercase;
  white-space: nowrap;
}
#menu a {
  color: #C8B89A;
  text-decoration: none;
  opacity: 0;
  filter: blur(6px);
  pointer-events: auto;
  will-change: opacity, filter, letter-spacing;
  transition: color .3s;
}
#menu a:hover { color: var(--cream); }
/* ── EINZEILIG BLEIBEN AUF SCHMALEN SCHIRMEN ──
   Bei 375 px messen die fünf Links zusammen 279 px, die vier Abstände
   (2em = 21 px) weitere 84 px — zusammen 363 px auf 360 px Platz. Es fehlen
   drei Pixel, und das Menü bricht auf zwei Zeilen um. Das ist nicht nur
   unschön: Die Kopfzeile wird dadurch höher als die 118 px, mit denen alle
   Abstände der Sektionen rechnen.
   Zuerst nur der ABSTAND enger — die Sperrung der Versalien ist Teil der
   Anmutung und soll so lange wie möglich bleiben. Erst darunter wird auch
   sie reduziert. */
@media (max-width: 430px) {
  #menu { gap: .6em 1.4em; }
}
@media (max-width: 365px) {
  #menu { gap: .6em 1em; letter-spacing: .12em; }
}

/* ── LUPE — Buchstaben wachsen unter dem Cursor ──
   Jeder Buchstabe ist ein eigenes inline-block-Span (JS splittet den Text
   beim Init). Die Skalierung fährt JS pro Pointer-Bewegung nach dem
   horizontalen Abstand zum Cursor — mittiger transform-origin, damit die
   Buchstaben symmetrisch aufgehen wie unter einem Vergrößerungsglas.
   transform verändert den Textfluss NICHT: die Nachbarn bleiben stehen,
   nichts springt oder reflowt. Die kurze Transition gibt der Linse etwas
   Trägheit, sodass sie der Maus weich folgt statt zu schnappen. */
#menu a .ml {
  display: inline-block;
  transform-origin: 50% 50%;
  transition: transform .16s cubic-bezier(.2, .8, .25, 1);
  will-change: transform;
}
@media (prefers-reduced-motion: reduce) {
  #menu a .ml { transition: none; }
}

/* ── HEADER BAR — full-width; height follows content (rule + menu),
   so nothing can overflow into the next section. Solidifies on landing
   and carries as the constant element across the section seam. ── */
#header-bar {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 80;
  padding: 70px 0 18px;
  pointer-events: none;
  background: rgba(9,9,9,0);
  border-bottom: 1px solid rgba(200,184,154,0);
  backdrop-filter: blur(0px);
  transition: background .5s, border-color .5s, backdrop-filter .5s;
}
#header-bar.on {
  background: rgba(9,9,9,0.9);
  backdrop-filter: blur(20px);
  border-bottom-color: rgba(200,184,154,0.1);
}

/* ── WELCOME — first normal section; the sticky stage scrolls out and
   the page becomes a regular website beneath the persistent header ── */
#welcome {
  /* transparent: black body + the living particle field show through —
     the former dead runway becomes the welcome stage.
     Negative top margin tucks the section under the releasing stage
     (also transparent), so content appears through it EARLY instead of
     waiting for the full release scroll. */
  position: relative;
  z-index: 1;
  background: transparent;
  margin-top: calc(-1 * var(--welcome-overlap));
  /* KEIN Innenabstand mehr auf der Sektion selbst — das Porträt läuft
     randabschneidend bis an die Viewport-Kante. Die Content-Seite trägt
     ihren eigenen Innenabstand (siehe .welcome-content). */
  padding: 0;
}
/* Sektion spannt den vollen Viewport auf — das Porträt füllt sie als
   Ebene komplett aus, der Inhalt liegt darüber. */
#welcome {
  min-height: 100svh;
  display: flex;
  align-items: center;
  overflow: hidden;
}
/* EINE Anordnung für JEDEN Viewport — bewusst keine strukturellen
   Breakpoint-Varianten: Logo oben rechts, Text unten links, Porträt
   als Ebene dahinter. Dadurch bleibt die Bildwirkung vom Telefon bis zum
   großen Bildschirm dieselbe, statt am Breakpoint umzuspringen. */
.welcome-content {
  position: relative;
  z-index: 2;                     /* über der Porträt-Ebene */
  align-self: stretch;            /* füllt die Sektionshöhe */
  width: 100%;
  max-width: 1240px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  /* Oben mindestens Kopfzeilenhöhe freihalten — sonst verschwindet die
     Oberkante des Logos hinter der fixierten Kopfzeile (118 px). */
  padding: max(150px, 17vh) var(--pad) clamp(2.5rem, 7vh, 5rem);
  min-width: 0;
}
/* space-between spannt die Komposition auf: Logo ganz oben, Text ganz
   unten — genau der Aufbau, der die Blickachse trägt. */
.welcome-col {
  width: 100%;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: clamp(1.2rem, 3vh, 2rem);
  min-width: 0;
}
/* Doppelt geführte Selektoren, weil `.welcome-col > *` nur die Spezifität
   EINER Klasse hätte (der Universalselektor zählt nicht) und sonst von der
   späteren `.welcome-figure`-Regel überschrieben würde.
   Logo RECHTS oben (der Kopf sitzt links, der Blick geht schräg nach
   rechts oben), Text LINKS unten in der abgedunkelten Zone. */
.welcome-col .welcome-figure { align-self: flex-end; width: clamp(160px, 22vw, 300px); }
.welcome-col .welcome-text { align-self: flex-start; width: min(100%, 620px); }

/* ── BLICKACHSE AUF BREITEN SCHIRMEN ──
   Das Gesicht endet auf jeder Desktop-Größe bei rund 30–32 % der
   Sektionsbreite (Folge des Augen-Ankers bei 20 % und des höhengebundenen
   Bildes). Am rechten Rand läge das Logo fast auf Augenhöhe — der Blick
   ginge daran vorbei (gemessen 5°). Deshalb wird es hier frei UNMITTELBAR
   RECHTS NEBEN das Gesicht gesetzt, wo die Blickachse tatsächlich
   hinführt. Schmale Schirme brauchen das nicht: dort steht das Logo
   ohnehin dicht neben dem Kopf. */
@media (min-width: 841px) {
  #welcome {
    --logo-left: 34vw;                        /* knapp rechts der Gesichtskante */
    --logo-w: clamp(150px, 12vw, 220px);
    --logo-top: max(140px, 15svh);            /* frei von der Kopfzeile (118 px) */
    --logo-gap: clamp(1.5rem, 3vw, 3.2rem);
  }
  .welcome-col .welcome-figure {
    position: absolute;
    align-self: auto;
    top: var(--logo-top);
    left: var(--logo-left);
    width: var(--logo-w);
  }
  /* ÜBERSCHRIFT + FLIESSTEXT stehen UNTER dem Logo, linksbündig mit ihm.
     Vorher standen sie RECHTS DANEBEN und oben bündig — der Block klebte
     dadurch in der oberen rechten Ecke, und die gesamte untere Hälfte der
     Sektion blieb leer. Unter dem Logo bekommt er die volle Restbreite
     (statt rund 470 px) und rückt in die leere Zone.
     Der Logo-Kasten ist 600:480, seine Höhe also 0.8 x Breite. */
  .welcome-col .welcome-text {
    position: absolute;
    align-self: auto;
    top: calc(var(--logo-top) + var(--logo-w) * 0.8 + var(--logo-gap));
    left: var(--logo-left);
    /* var(--pad), nicht 0: absolute Kinder messen ab dem PADDING-Kasten,
       der Innenabstand der Sektion zählt für sie also nicht mit — mit 0
       stieße der Text auf schmaleren Desktops an die Viewport-Kante. */
    right: var(--pad);
    width: auto;
  }
}
@media (min-width: 1241px) {
  /* Ab hier ist .welcome-content auf 1240 px begrenzt und zentriert.
     `left` bezieht sich auf dessen Kasten, nicht auf die Sektion — der
     Versatz muss gegengerechnet werden, damit das Logo weiterhin bei
     34 % der SEKTIONSBREITE steht: 0.34·B − (B−1240)/2 = 620px − 0.16·B. */
  #welcome { --logo-left: calc(620px - 16vw); }
}
/* kleines Label über Abschnitten (z. B. "about me") */
.w-label {
  font-family: var(--mono);
  font-size: 9px;
  color: var(--gold);
  letter-spacing: .2em;
  text-transform: uppercase;
  margin-bottom: 1.4rem;
  opacity: .75;
}
.w-label::before { content: '/* '; }
.w-label::after  { content: ' */'; }
/* ── „WELCOME TO THE LAB." ──
   Velocity-Variante, aber LINKSBÜNDIG: Sie steht in einer Textspalte, nicht
   als mittiger Sektionstitel. Höhere Spezifität als .vel-name, deshalb
   müssen Größe und Ausrichtung hier gesetzt werden — sonst gewönne diese
   Regel und die gemeinsamen Werte kämen nie an. */
#welcome h1.vel-name {
  /* 1.5rem als Untergrenze: „WELCOME TO THE LAB." stand bei 320 px exakt
     auf der Kastenkante (282 von 282 px). Jetzt bleiben 18 px Reserve. */
  font-size: clamp(1.5rem, 3.4vw, 3rem);
  letter-spacing: -.02em;
  color: var(--cream);
  text-align: left;
  min-height: 1.1em;
  margin: 0 0 1.2rem;
}
#welcome .vn-dot { color: var(--gold); }
#welcome p,
#welcome .w-quote {
  /* Fließtext-Größe identisch mit .flow-body der folgenden Sektion */
  font-family: var(--mono);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: rgba(240,237,232,0.68);
  max-width: 54ch;
  margin: 0;
}
/* ── ADAPTIVE HEADER THEME ──
   Sections declare data-theme="light|dark". The header always inverts
   against the section beneath it: cream-on-dark over dark sections,
   ink-on-cream over light ones — and so on, section after section.
   The logo (light artwork) flips via filter on its WRAP, because the
   img itself carries an inline blur filter from the reveal. */
body.header-light #header-bar.on {
  background: rgba(240,237,232,0.92);
  border-bottom-color: rgba(0,0,0,0.08);
}
body.header-light #brand { color: #111; }
body.header-light #brand-logo-wrap { filter: invert(1) brightness(.2); }
body.header-light #menu a { color: #6f6a5e; }
body.header-light #menu a:hover { color: #111; }
body.header-light #menu-rule { background: rgba(0,0,0,0.15); }
/* gold accents (dot, index numbers) work on both — they stay */

/* KEIN overflow:hidden hier — der Partikel-Canvas ist absichtlich größer
   als die Figur (siehe #otb-canvas: padL/padR/padT/padB), damit die
   Partikel beim Berühren nach außen ausweichen können. Eine Grenze an
   dieser Stelle würde sie an einer unsichtbaren Kante abschneiden.
   Das Einfangen des Überstands übernimmt jetzt #welcome selbst
   (overflow:hidden dort) — dadurch bläht der Canvas auch auf schmalen
   Bildschirmen den Viewport nicht mehr auf. */
.welcome-figure { width: 100%; margin: 0; position: relative; }
.welcome-figure img { width: 100%; height: auto; display: block; }
/* ── PORTRÄT ALS VOLLBILD-EBENE ──
   Füllt die ganze Sektion (inset:0). Es liegt ÜBER dem fixierten
   Liquid-Canvas und blendet nach unten weich aus: dort schimmert das
   Liquid wieder durch und der Text steht auf ruhigem Grund.
   VERWORFEN: eine klare senkrechte Kante rechts statt der Wandfarbe. Der
   Schnitt wirkte unsauber — die weiche Auflösung in die Wand trägt das
   Bild besser. */
.welcome-portrait {
  position: absolute;
  inset: 0;
  z-index: 1;
  margin: 0;
  overflow: hidden;
  pointer-events: none;
  /* ── ANLAUF IN FESTEN PIXELN, NICHT IN PROZENT ──
     Früher lief er bis 13 % der SEKTIONSHÖHE. Der Scheitel liegt aber bei
     4,8 % der BILDhöhe unter dem Bildanfang — beide wandern unterschiedlich
     schnell, und ab etwa 1050 px Fensterhöhe überholte der Anlauf den
     Scheitel: Der Kopf wurde oben durchsichtig.
     130 px sind fest und liegen auf JEDER Fensterhöhe über dem Scheitel
     (der frühestens bei 172 px beginnt, siehe .welcome-portrait img). */
  -webkit-mask-image:
    linear-gradient(to bottom, transparent 0px, #000 130px, #000 32%, rgba(0,0,0,.6) 62%, rgba(0,0,0,.2) 86%, transparent 100%);
  mask-image:
    linear-gradient(to bottom, transparent 0px, #000 130px, #000 32%, rgba(0,0,0,.6) 62%, rgba(0,0,0,.2) 86%, transparent 100%);
}
/* Gemeinsame Ebene für Foto UND fortgesetzte Wandfarbe. Der Farbfilter
   sitzt hier (nicht auf der Figur), damit beide identisch behandelt werden
   und der Einblende-Weichzeichner der Figur ihn nicht überschreibt. */
.wp-layer {
  position: absolute;
  inset: 0;
  filter: grayscale(.15) contrast(1.06) brightness(.92);
}
/* ── DIE WAND ──
   Setzt die Wand des Fotos nach rechts fort: Weil das Bild über die HÖHE
   gerechnet wird, reicht es auf breiten Fenstern nicht bis zum Rand.
   Der Anlauf oben (0 → 165 px) blendet sie über der Sektionskante ein. */
.wp-wall {
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom,
    rgb(44,39,28) 0%, rgb(49,43,29) 20%, rgb(40,33,26) 45%,
    rgb(32,26,21) 70%, rgb(23,15,12) 92%);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0px, rgba(0,0,0,.4) 70px, #000 165px);
  mask-image: linear-gradient(to bottom, transparent 0px, rgba(0,0,0,.4) 70px, #000 165px);
}
.welcome-portrait img {
  /* ÜBER DIE HÖHE gerechnet, NICHT über die Breite — derselbe Kern wie im
     Eröffnungsbild: mit object-fit:cover skaliert ein breites Fenster das
     Bild über die Breite hoch, und alles unterhalb der Stirn fällt heraus.
     Höhengebunden passt der Kopf auf JEDEM Seitenverhältnis vollständig
     ins Bild — Desktop sieht dadurch aus wie Mobil. */
  position: absolute;
  /* SCHUTZZONE: Das Foto beginnt erst 150 px unter der Sektionskante — die
     Kopfzeile ist 118 px hoch, darüber bleiben also gut 30 px Luft, und der
     Scheitel (4,8 % der Bildhöhe tiefer) liegt frühestens bei 172 px.
     Vorher waren es 88 px: Das Foto lief unter der Kopfzeile an, der Kopf
     stieß fast an die Sektionskante — genau der beengte Eindruck. */
  top: 150px;
  height: calc(100% - 150px);
  /* DECKEL BEI EINEM VIEWPORT: Die Sektion darf mit dem Text mitwachsen
     (min-height ist eine Untergrenze). Ohne diesen Deckel wüchse das
     höhengebundene Bild einfach mit — bei einem zusätzlichen Absatz auf
     einem 667-px-Telefon von 570 auf 678 px Breite, das Gesicht also um
     ein Fünftel größer. Der Text darf Platz brauchen, das Porträt nicht. */
  max-height: calc(100svh - 150px);
  width: auto;                /* Seitenverhältnis folgt der Höhe */
  max-width: none;
  display: block;
  /* AUGEN-ANKER: translateX(-42.4%) verschiebt um exakt den Abstand des
     Auges vom linken Bildrand — es liegt dadurch unabhängig von
     Fensterformat und Bildgröße immer auf `left`. */
  left: 20%;
  transform: translateX(-42.4%);
  /* ZWEI Masken, verschnitten:
     RECHTS  die Bildkante löst sich in die fortgesetzte Wandfarbe auf; das
             Gesicht endet bei 62 % der Bildbreite und bleibt unberührt.
     OBEN    das Foto läuft in sich selbst von null an, statt an seiner
             Oberkante schlagartig einzusetzen.
             2.6 %, NICHT 5 %: Der Scheitel liegt bei 4,8 % der Bildhöhe.
             Ein Anlauf über 5 % war damit LÄNGER als der Weg bis zum
             Scheitel und machte die Haarspitzen auf jedem Fenster
             durchsichtig. Bei 2.6 % ist er auf halbem Weg fertig — der
             Abstand bleibt in Prozent, weil auch der Scheitel mit der
             Bildhöhe wandert. */
  -webkit-mask-image:
    linear-gradient(to right, #000 0%, #000 76%, transparent 100%),
    linear-gradient(to bottom, transparent 0%, #000 2.6%);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(to right, #000 0%, #000 76%, transparent 100%),
    linear-gradient(to bottom, transparent 0%, #000 2.6%);
  mask-composite: intersect;
}
#welcome p + p { margin-top: 1rem; }

/* KEINE Sonderregel für Mobilgeräte mehr: Der Überlapp lag hier früher
   höher (80vh gegen 55vh), weil die Kino-Pin-Strecke auf Mobil kürzer ist
   und danach eine lange leere Scrollstrecke blieb. Seit der Grundwert bei
   90vh liegt, deckt er beide Fälle ab — nachgemessen bleiben 210 px
   Restweg auf dem Desktop und 164 px auf dem Telefon. Mit einer eigenen
   Mobilregel (100vh) waren es dort nur noch 83 px: Die Sektion erschien
   praktisch fertig, statt sichtbar nachzurücken. */

/* ── WELCOME ENTRANCE — staggered text + clip unveil (Variante B) ──
   Text children resolve one after another (blur → sharp, the site's
   established language). The figure unveils via a clip edge wiping
   left→right — like a sheet being pulled aside — with a slight inner
   counter-drift for depth. Class-driven, reverses on scroll-back. */
.welcome-text > * {
  opacity: 0;
  transform: translateY(18px);
  filter: blur(6px);
  transition: opacity .7s ease, transform .7s ease, filter .7s ease;
}
.welcome-text > *:nth-child(2) { transition-delay: .10s; }
.welcome-text > *:nth-child(3) { transition-delay: .20s; }
.welcome-text > *:nth-child(4) { transition-delay: .30s; }
/* ── VERKETTUNG: ERST DIE ÜBERSCHRIFT, DANN DER TEXT ──
   Die ÜBERSCHRIFT erscheint mit dem Auftritt der Sektion (.in) — sie muss
   da sein, damit sie überhaupt einfahren kann.
   ALLES ANDERE wartet zusätzlich auf .head-in: Die Klasse setzt
   velocityHeading(), sobald die Zeile ihre Fahrt zu 92 % hinter sich hat.
   Vorher liefen Fahrt und Textauftritt nebeneinander her — beides passierte
   gleichzeitig und wirkte dadurch beliebig statt als ein Ablauf. */
#welcome.in .welcome-text > .vel-name { opacity: 1; transform: none; filter: blur(0); }
#welcome.in.head-in .welcome-text > * { opacity: 1; transform: none; filter: blur(0); }
/* Porträt: derselbe Blur/Fade wie der Text, aber zuerst dran (es steht
   ja an erster Stelle im DOM) — Logo-Reveal übernimmt weiterhin das
   Partikelsystem selbst. */
.welcome-portrait {
  opacity: 0;
  transform: translateY(18px);
  filter: blur(6px);
  transition: opacity .7s ease, transform .7s ease, filter .7s ease;
}
#welcome.in .welcome-portrait { opacity: 1; transform: none; filter: blur(0); }


/* ── NEXT — helle Folge-Sektion (PLACEHOLDER) ── */
/* ── NEXT — light follow-up act (placeholder) ── */
/* ── DEPTH — heller Parallax-Raum ──
   Sektion ist TRANSPARENT: sie zeigt das fixierte bg-fx-Canvas. Am
   Sektionsanfang hellt der Shader seinen Grund als bewusster Beat von
   Schwarz auf Cream auf ("Licht an", u_light) — die Linien laufen dabei
   ununterbrochen weiter. Tiefe über drei Geschwindigkeiten:
   Liquid fix (0×) → Geisterwort langsam → Hauptwort schnell (Velocity-
   Stil von rechts, Skew aus Scroll-Geschwindigkeit) → Copy im Fluss. */
/* DEPTH — jetzt eine RUHIGE, normal scrollende Creme-Lesezone (kein Sticky,
   kein 3D). Statement + Bio laufen als EIN zusammenhängender Fluss; jede
   Zeile wechselt beim Scrollen Wort für Wort die Farbe (juanmora-Stil, JS).
   Oben ein weicher Übergang aus der dunklen Welcome-Zone → nahtloser Fluss. */
#depth {
  position: relative;
  /* Beschneidet die Stempel-Überschrift auf die eigene Sektion. Ohne das
     quillt die im Anflug riesige Schrift nach oben heraus und legt sich über
     den Text der Sektion davor. So wirkt sie stattdessen, als käme sie hinter
     deren Kante hervor — genau wie in der Galerie (#sc-stage). */
  overflow: hidden;
  z-index: 1;
  /* Kein Übergangsverlauf — die Lesezone setzt direkt mit ruhigem Creme an
     (klare Kante zur dunklen Out-of-the-Box-Zone darüber).
     VOLL DECKEND: Der Grund dahinter wechselt von Schwarz auf Creme; nur
     eine deckende Fläche verbirgt diesen Wechsel zuverlässig. */
  background: linear-gradient(to bottom, var(--cream) 0%, #efe9df 100%);
  padding: clamp(3.5rem, 8vh, 6.5rem) var(--pad) clamp(5rem, 13vh, 9rem);
}
.flow-inner { max-width: 1080px; margin: 0 auto; }

/* ── VELOCITY-ÜBERSCHRIFTEN — eine Sprache für ALLE Sektionstitel ──
   Nimmt die Sprache des Velocity-Textes aus dem Eröffnungsbild auf: Der
   Scroll-FORTSCHRITT treibt die Fahrt vom Bildschirmrand zur Mitte, die
   Scroll-GESCHWINDIGKEIT eine gedämpfte Neigung. Der äußere Kasten hält
   nur den Platz frei — die Zeile fährt darin, kann also weit außerhalb
   starten, ohne das Layout zu verschieben.
   Hier stehen nur die gemeinsamen Maße; Farbe, Größe und Abstand setzt
   jede Sektion bei sich selbst (.hitl-name / .exp-name / .blog-name). */
.vel-name {
  position: relative;
  display: block;
  /* KEIN overflow:hidden hier. Die Kästen sind in der Breite begrenzt und
     zentriert — die Zeile erschiene an IHRER Kante, mitten im Satzspiegel,
     statt vom Rand des Bildschirms zu kommen. Beschnitten wird von der
     jeweiligen Sektion, und die reicht über die volle Breite. */
  font-family: var(--mono);
  font-weight: 800;
  /* EINE Größe für alle drei Überschriften — sie stehen hier und nicht bei
     den Varianten, damit sie gar nicht erst auseinanderlaufen können.
     Bemessen ist sie an der LÄNGSTEN Zeile („HUMAN-IN-THE-LOOP."): Sie muss
     auch auf 375 px einzeilig in den Satzspiegel passen (nowrap). Die
     kürzeren Zeilen bekommen dieselben Werte, auch wenn dort mehr ginge —
     gleiche Größe ist hier die Gestaltungsabsicht. */
  /* Untergrenze 1.65rem, nicht 1.75rem: Bei 320 px Gerätebreite maß die
     längste Zeile („HUMAN-IN-THE-LOOP.") 287 px in einem 282-px-Kasten. Sie
     lief über und wurde vom overflow:hidden der Sektion abgeschnitten — ohne
     dass es als Seitenüberlauf auffiel. Mit 1.65rem bleiben 11 px Reserve. */
  font-size: clamp(1.65rem, 6.6vw, 4.6rem);
  line-height: 1.1;
  letter-spacing: -.03em;
  min-height: 1.1em;
  margin: 0;
  text-align: center;
  white-space: nowrap;
}
.vn-inner {
  display: inline-block;
  white-space: nowrap;
  will-change: transform;
}
/* ── AUSGANGSLAGE — NUR WENN DIE CHOREOGRAFIE AUCH LÄUFT ──
   Sie gehört ins CSS, sonst blitzt die Zeile bei langsamem Skript kurz
   fertig an ihrem Platz auf, bevor die Fahrt übernimmt.
   Sie darf aber NICHT unbedingt gelten: Die Unterseiten laden sub.js statt
   main.js, dort gibt es kein velocityHeading(). Eine fest verdrahtete
   Startlage ließe die Überschrift dort für immer unsichtbar außerhalb des
   Bildes stehen. main.js setzt die Klasse als erstes — noch vor dem ersten
   Bild, weil es blockierend am Ende des <body> steht. Ohne main.js steht
   die Überschrift einfach da. */
.js-vel .vn-inner {
  transform: translateX(-60vw);
  opacity: 0;
}
.js-vel .vn-dot { opacity: 0; }
/* Der PUNKT als eigenes Statement: eigene Farbe, eigene Choreografie.
   inline-block + eigener Ursprung, damit ihn das Skalieren nicht aus der
   Grundlinie hebt und kein Umbruch entsteht. */
.vn-dot {
  display: inline-block;
  transform-origin: 50% 78%;     /* etwa auf Höhe der Punkt-Tinte */
  will-change: transform, opacity;
}
/* MARKENZEICHEN in der Zeile (siehe .blog-name im Markup). Es hängt an der
   Schriftgröße, wächst und schrumpft also mit der Überschrift mit.
   1.24em statt 1em: Der Kreis soll Ober- und Unterlänge überragen, sonst
   wirkt er neben der fetten Versalschrift wie ein Schreibfehler.
   Der Versatz nach unten setzt seine Mitte auf die Mitte der Versalhöhe. */
.vn-mark {
  display: inline-block;
  height: 1.24em;
  width: auto;
  margin-left: .22em;
  vertical-align: baseline;
  position: relative;
  top: .2em;
  /* Wächst aus der eigenen Mitte auf, nicht aus einer Ecke. */
  transform-origin: 50% 50%;
  will-change: transform, opacity;
}
/* Ausgangslage nur, wenn die Choreografie läuft — ohne main.js (Unterseiten)
   steht die Marke einfach da. Gleiche Absicherung wie bei .vn-inner. */
.js-vel .vn-mark { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  .vn-inner, .vn-dot { transform: none; opacity: 1; }
}

/* HUMAN-IN-THE-LOOP — Schrift im Ton des Punktes (Gold), der PUNKT
   dagegen Tinte: Er ist das Statement und setzt sich so gegen die Zeile ab. */
.hitl-name {
  color: var(--gold);
  margin-bottom: clamp(2rem, 5vh, 3.4rem);
}
.hitl-name .vn-dot { color: #111; }

/* Zwei Spalten: LINKS der Text, RECHTS das schwebende Platzhalterbild. */
.hitl-grid {
  display: grid;
  grid-template-columns: 1.05fr .95fr;
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
}
.hitl-text { max-width: 58ch; min-width: 0; }
.hitl-figure { margin: 0; min-width: 0; }
/* Der SCHWEBER: Nur dieses innere Element wird bewegt, nie das Rasterfeld
   — dadurch kann die Parallaxe das Layout nicht verschieben. */
.hf-float { will-change: transform; }
.hf-media {
  position: relative;
  /* 3:4 = Originalformat von potrait_dennis.png (1760 x 2352). Dadurch
     wird nichts beschnitten. */
  aspect-ratio: 3 / 4;
  overflow: hidden;
  background: #e9e3d8;
}
.hf-media img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transform: scaleX(-1);   /* gespiegelt — Blick zeigt zur Textspalte */
}
.hf-media.hf-fallback {
  background:
    repeating-linear-gradient(135deg, rgba(184,150,62,0.14) 0 14px, transparent 14px 28px),
    #ece6db;
}
.hf-cap {
  font-family: var(--mono);
  font-size: .68rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: rgba(17,17,17,.42);
  margin-top: 1.1rem;
}
@media (max-width: 720px) {
  .hitl-grid { grid-template-columns: 1fr; }
  .hitl-text { max-width: none; }
  .hitl-figure { max-width: 420px; margin: 0 auto; }
}

/* Zwei Spalten: LINKS der gesamte Text, RECHTS das Bild. Überschrift und
   Fließtext stehen dadurch direkt beieinander (kein Abstand mehr durch ein
   hohes Bild dazwischen). align-items:start = Bild oben bündig. */
.flow-grid {
  display: grid;
  grid-template-columns: 1.05fr .95fr;
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
}
/* Text-Spalte im ruhigen Lesemaß */
.flow-text { max-width: 58ch; }
/* Rechte Spalte: klammert Porträt UND Steckbrief zu EINEM Rasterelement.
   Ohne sie wären es drei Elemente im zweispaltigen Raster — der Steckbrief
   liefe dann in die nächste Zeile nach links, statt unter dem Bild zu sitzen. */
.flow-side { min-width: 0; }
.flow-eyebrow {
  font-family: var(--mono);
  font-weight: 700;
  font-size: clamp(.72rem, 1.1vw, .82rem);
  letter-spacing: .2em;
  text-transform: uppercase;
  margin: 0 0 1.1rem;
}
.flow-lead {
  font-family: var(--mono);
  font-weight: 500;
  font-size: var(--fs-body);      /* = #welcome Fließtext */
  line-height: var(--lh-body);
  margin: 0;
}
.flow-head {
  font-family: var(--mono);
  font-weight: 800;
  font-size: clamp(1.15rem, 2.4vw, 1.6rem);
  line-height: 1.3;
  letter-spacing: -.01em;
  margin: clamp(1.8rem, 4.5vh, 2.8rem) 0 .8rem;
}
.flow-body {
  font-family: var(--mono);
  font-weight: 500;
  font-size: var(--fs-body);      /* = #welcome Fließtext */
  line-height: var(--lh-body);
  margin: 0;
}

/* Porträt: steht fest im Textfluss (KEIN Sticky) — es wandert also nicht
   mit. Der frei werdende Raum darunter trägt den Steckbrief. */
.flow-figure {
  position: static;
  margin: 0;
  /* Kleiner als die Spalte: so bleibt das Format erhalten (kein Anschnitt),
     das Bild wirkt ruhiger, und Porträt + Steckbrief enden zusammen etwa
     auf Höhe der Textspalte. */
  max-width: 330px;
}

/* ── STECKBRIEF unter dem Porträt ──
   Nimmt bewusst dieselbe Sprache wie die Projekt-Fakten (.proj-facts) auf:
   goldenes Kürzel, Wert gegenüberliegend. Hier auf hellem Grund, daher
   Tinte statt Creme. Die Zeilen steigen gestaffelt auf ([data-reveal]).
   Trennlinien sind KEINE festen Border mehr, sondern animierte Pixelzeilen
   (::before/::after) — siehe .ff-pixel-rule weiter unten. */
.ff-facts {
  margin: clamp(1.8rem, 4.5vh, 2.8rem) 0 0;
  padding-top: 0;
  font-family: var(--mono);
}
/* Trennlinie ÜBER der ersten Zeile — ein normaler Block-Pseudo auf dem <dl>
   selbst (kein Flex-Kontext), macht keine Umwege nötig. */
.ff-facts::before {
  content: '';
  display: block;
  animation-delay: 0s;
}
/* Jede Zeile: Label linksbündig, Wert rechtsbündig, EINE Reihe. Die
   Trennlinie NACH der Zeile ist ein ::after mit flex-basis:100% — in einem
   flex-wrap-Container erzwingt das einen Umbruch auf eine eigene, volle
   Breite, ohne dt/dd zu stören (dritter „Flex-Zeilen"-Trick). */
.ff-facts > div {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: .35rem 1.2rem;
  padding: .9rem 0 0;
}
.ff-facts > div:not(:last-child) { padding-bottom: .9rem; }
.ff-facts > div:not(:last-child)::after {
  content: '';
  flex: 1 0 100%;
}
/* ── Pixelzeile: kleine orangene Quadrate statt einer durchgezogenen Linie,
   die Helligkeit atmet leicht und asynchron pro Zeile — wirkt wie ein
   leises Funkeln statt einer starren Trennlinie. */
.ff-facts::before,
.ff-facts > div:not(:last-child)::after {
  height: 3px;
  margin: 0 0 .9rem;
  background-image: repeating-linear-gradient(to right,
    var(--gold) 0 3px, transparent 3px 7px);
  opacity: .5;
  animation: ffPixelGlow 3.4s ease-in-out infinite;
}
.ff-facts::before { margin: 0 0 .9rem; }
.ff-facts > div:not(:last-child)::after { margin: .9rem 0 0; }
@keyframes ffPixelGlow {
  0%, 100% { opacity: .38; filter: brightness(.9); }
  50%      { opacity: .8;  filter: brightness(1.45); }
}
/* Delays NACH der animation-Shorthand gesetzt — die Shorthand oben würde
   sie sonst wieder auf 0s zurücksetzen (sie resettet alle Teilwerte, die
   sie nicht selbst nennt). So blinkt jede Zeile leicht versetzt. */
.ff-facts > div:nth-child(1)::after { animation-delay: .6s; }
.ff-facts > div:nth-child(2)::after { animation-delay: 1.3s; }
.ff-facts > div:nth-child(3)::after { animation-delay: 2s; }
.ff-facts > div:nth-child(4)::after { animation-delay: 2.7s; }
@media (prefers-reduced-motion: reduce) {
  .ff-facts::before,
  .ff-facts > div:not(:last-child)::after { animation: none; opacity: .55; }
}
.ff-facts dt {
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--gold);
  flex: 0 0 auto;
}
.ff-facts dd {
  margin: 0;
  font-size: clamp(.86rem, 1.5vw, .98rem);
  line-height: 1.5;
  text-align: right;
  flex: 0 0 auto;
  color: rgba(17, 17, 17, 0.82);
}
.ff-media {
  position: relative;
  z-index: 1;
  /* Originalformat des Porträts (1009 x 1256 = 4:5) — dadurch wird NICHTS
     angeschnitten. Die Höhe wird stattdessen über die Breite gesteuert
     (max-width auf .flow-figure). */
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: #e9e3d8;
  /* Übergabe an die Galerie: wächst aus der Mitte heraus. will-change wird
     bewusst NUR während der Übergabe gesetzt — dauerhaft erzeugt es eine
     eigene Compositing-Ebene, die beim Sticky-Scrollen leicht zittert. */
  transform-origin: 50% 50%;
  /* Das Bild wird beim Scrollen dauerhaft im 3D-Raum bewegt (wie die
     Karussellkarten), daher hier fest angemeldet. */
  will-change: transform, opacity, filter;
  backface-visibility: hidden;
}
.ff-media img {
  width: 100%; height: 100%;
  /* contain statt cover: das Bild wird vollständig gezeigt, nie beschnitten */
  object-fit: contain;
  display: block;
  filter: grayscale(.28) contrast(1.03);
}
.ff-media.ff-fallback {
  background:
    repeating-linear-gradient(135deg, rgba(184,150,62,0.14) 0 14px, transparent 14px 28px),
    #ece6db;
}
.ff-cap {
  font-family: var(--mono);
  font-weight: 700;
  font-size: .78rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: rgba(17,17,17,.72);
  margin-top: 1.8rem;
}
@media (max-width: 720px) {
  .flow-grid { grid-template-columns: 1fr; }
  .flow-text { max-width: none; }
  /* Einspaltig: Porträt und Steckbrief laufen unter dem Text mit */
  .flow-side { max-width: 340px; margin-top: 1.4rem; }
  /* Kleiner als die Desktop-Spaltenbreite — auf schmalen Screens füllte
     das Porträt sonst fast den ganzen Bildschirm. */
  .flow-figure { max-width: min(230px, 56vw); margin: 0 auto; }
}

#welcome .w-quote { margin: 0; }
.w-sign {
  display: inline-block;
  margin-top: 1.1rem;
  font-family: var(--mono);
  font-size: clamp(.8rem, 1.4vw, .95rem);
  letter-spacing: .06em;
  color: var(--gold);
  text-decoration: none;
  transition: color .3s;
}
.w-sign:hover { color: var(--cream); }


/* ── LOGO-PARTIKELFELD ──
   Der Maßhalter spannt nur den Designraum auf (600 x 480), an dem sich das
   Partikelsystem ausrichtet; sichtbar ist er nicht. Gezeichnet wird alles
   auf dem Canvas darüber. */
.otb-stage {
  display: block;
  width: 100%;
  aspect-ratio: 600 / 480;
}

/* Partikel-Overlay: Größe/Position setzt das JS explizit (Overscan), damit
   der Partikelkreis beim Hovern nicht an der Canvas-Kante abgeschnitten wird.
   Wichtig: <canvas> ist ein replaced element — CSS width:auto würde die
   Bitmap-Größe übernehmen, deshalb NICHT per Inset stretchen. */
#otb-canvas {
  position: absolute;
  left: 0;
  top: 0;
  pointer-events: none;
}

/* Unsichtbarer Sprungmarken-Anker, der die fixe Kopfhöhe berücksichtigt */
.anchor {
  position: absolute;
  top: -110px;
  left: 0;
  width: 1px; height: 1px;
  pointer-events: none;
}

/* ═══════════════════════════════════════════════
   SHOWCASE — horizontales Bildercarousell (frei ziehbar)
   Die Sektion scrollt NORMAL durch; die Galerie wird direkt bedient
   (ziehen mit der Maus, wischen auf Touch) und läuft endlos.
   ═══════════════════════════════════════════════ */
#showcase {
  position: relative;
  z-index: 1;
  /* transparent: der Liquid-Hintergrund der Seite läuft hier durch
     (Konsistenz) — der abdunkelnde Schleier steht weiter unten */
  background: transparent;
  /* KEINE Laufstrecke mehr (vorher 250vh mit sticky Bühne): Die Sektion
     hielt den Seiten-Scroll fest, um das Karussell zu treiben — die Seite
     stand dabei still, was den Lesefluss unterbrach.
     Unten knapper als oben: Der Abstand zur Blogsektion addierte sich
     sonst mit deren Polster zu über 200 px reinem Leerraum. */
  padding: clamp(4rem, 10vh, 7rem) 0 clamp(2.5rem, 5vh, 3.5rem);
}
#sc-stage {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: center;
  box-sizing: border-box;
  /* 3D-Bühne: die Karten leben in echtem Perspektivraum (Coverflow) */
  perspective: 1600px;
  perspective-origin: 50% 46%;
}
/* Überschrift läuft MIT im Layout (nicht mehr absolut in der Ecke): dadurch
   entsteht ein echter, verlässlicher Abstand zum Karussell darunter. Die
   Bühne zentriert Überschrift + Track gemeinsam. */
.sc-head {
  position: relative;
  z-index: 2;
  text-align: center;
  padding: 0 var(--pad);
  margin-bottom: clamp(1.5rem, 5.5vh, 4.5rem); /* Abstand zum Karussell */
}
/* EXPLORE — auf dunklem Grund: Zeile in Creme, Punkt in Gold.
   Größe kommt aus .vel-name, damit sie mit den anderen beiden identisch ist. */
.exp-name {
  color: var(--cream);
}
/* EXPLORE kommt von RECHTS (fromRight in main.js). Die Ausgangslage muss
   mitdrehen — mit dem Standardwert (−60vw) blitzte die Zeile beim Laden
   kurz auf der falschen Seite auf, bevor das Skript übernimmt. */
.js-vel .exp-name .vn-inner { transform: translateX(60vw); }
.exp-name .vn-dot { color: var(--gold); }
/* Kurze Einordnung direkt unter der Überschrift: sagt in einem Satz, worum
   es in der Sektion geht, bevor die Galerie übernimmt. */
.exp-lead {
  max-width: 48ch;
  margin: clamp(1rem, 2.5vh, 1.7rem) auto 0;
  font-family: var(--mono);
  font-size: clamp(.82rem, 1.4vw, .95rem);
  line-height: 1.65;
  color: rgba(240,237,232,0.6);
}
/* Kurzbeschreibung unter dem Karussell — füllt den Leerraum unter den
   Karten und schafft ein visuelles Gegengewicht zur Überschrift oben.
   Als drittes Flex-Kind in #sc-stage zählt sie bei justify-content:center
   mit, statt nur unten dranzuhängen. */
.sc-note {
  position: relative;
  z-index: 2;
  text-align: center;
  /* Breiter gesetzt (62ch statt 44ch): Der Text läuft dadurch über zwei
     statt vier Zeilen und gibt der Bühne rund 45 px zurück — Platz, den
     der Kopfzeilen-Abstand oben braucht. */
  max-width: 62ch;
  margin: clamp(1.75rem, 6vh, 4.5rem) auto 0;
  padding: 0 var(--pad);
  font-family: var(--mono);
  font-size: clamp(.8rem, 1.4vw, .92rem);
  line-height: 1.6;
  color: rgba(240,237,232,0.55);
}
#sc-track {
  display: flex;
  align-items: center;
  gap: clamp(1.5rem, 4vw, 3.5rem);
  /* KEIN Rand-Padding: die Zentrierung übernimmt der JS-Offset. Padding
     würde den nahtlosen Endlos-Umschlag (Modulo) zerstören. */
  width: max-content;
  will-change: transform;
  transform-style: preserve-3d;
  /* pan-y: Wischen NACH OBEN/UNTEN bleibt beim Browser (die Seite scrollt
     also über der Galerie normal weiter), waagerechtes Ziehen übernimmt
     das Skript. Ohne diese Angabe verschluckt der Browser die Geste. */
  touch-action: pan-y;
  cursor: grab;
  user-select: none;
  -webkit-user-select: none;
}
#sc-track.is-dragging { cursor: grabbing; }
#sc-track img { -webkit-user-drag: none; user-drag: none; }
.sc-card {
  flex: 0 0 auto;
  /* ~⅓ der Viewportbreite — ABER zusätzlich an die Viewport-HÖHE gekoppelt
     (36svh). Ohne diese zweite Schranke wuchs die Spalte aus Überschrift +
     Karussell + Notiz über die 100svh der Bühne hinaus; #sc-stage schneidet
     mit overflow:hidden ab, und weil der Inhalt zentriert ist, fehlte oben
     und unten je die Hälfte des Überschusses — die Überschrift war dadurch
     dauerhaft angeschnitten. */
  /* Die Höhen-Schranke rechnet die Kopfzeile HERAUS, statt einen festen
     svh-Anteil zu nehmen: Auf niedrigen Fenstern frisst die Kopfzeile
     anteilig viel mehr Platz, ein fester Anteil ließ die Spalte dort
     wieder unter die Kopfzeile rutschen. So schrumpfen die Karten nur
     dort, wo es nötig ist, und bleiben auf hohen Schirmen groß. */
  width: clamp(150px, min(34vw, calc((100svh - 130px) * 0.30)), 420px);
  transform-style: preserve-3d;
  backface-visibility: hidden;
  will-change: transform;
}
.sc-media {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: #141414;
  border: 1px solid rgba(200,184,154,0.12);
}
.sc-media img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  filter: grayscale(.35) contrast(1.03);
  transition: transform .8s cubic-bezier(.2,.8,.25,1), filter .5s;
}
.sc-card:hover .sc-media img { transform: scale(1.05); filter: grayscale(0); }
/* Fallback, falls ein Unsplash-Bild nicht lädt: goldgetönte Kachel */
.sc-media.sc-fallback {
  background:
    repeating-linear-gradient(135deg, rgba(184,150,62,0.16) 0 14px, transparent 14px 28px),
    #17150f;
}
.sc-meta {
  /* RASTER statt Flexzeile: Nummer, Titel und Kategorie passten bei
     schmalen Karten (150 px) nie nebeneinander — der Titel lief über die
     Kategorie und beide standen übereinander. Im Raster bekommt die
     Kategorie eine eigene Zeile unter dem Titel; überlaufen kann dort
     nichts mehr, und lange Titel dürfen einfach umbrechen. */
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: .8em;
  row-gap: .3rem;
  align-items: baseline;
  margin-top: .9rem;
  font-family: var(--mono);
  font-size: clamp(.72rem, 1.2vw, .85rem);
  letter-spacing: .04em;
  color: rgba(240,237,232,0.62);
}
.sc-idx { grid-column: 1; grid-row: 1; color: var(--gold); }
/* .sc-title, NICHT .sc-name: Die Sektions-Überschrift hieß früher ebenso —
   ihre Schriftgröße (bis 3.2rem) schlug dadurch auf die Kartentitel durch
   und ließ sie übereinanderlaufen. Getrennte Namen, getrennte Maße. */
.sc-title {
  grid-column: 2; grid-row: 1;
  color: var(--cream);
  min-width: 0;
  overflow-wrap: anywhere;      /* lieber umbrechen als überlaufen */
}
.sc-tag {
  grid-column: 2; grid-row: 2;  /* eigene Zeile unter dem Titel */
  text-transform: uppercase;
  letter-spacing: .14em;
  font-size: .82em;
}

/* ── DIE VIER ACHSEN — Strategy · Design · Content · Systems ──
   Der inhaltliche Kern der Sektion. Sie stehen UNTER der Galerie: Die
   Bilder sind der Einstieg, die Achsen die Lesart dazu. Bewusst ohne
   Kasten und Rahmen — die Pixelzeile darüber trennt, mehr braucht es
   nicht (gleiche Geste wie im Steckbrief, gleiche Keyframes). */
.exp-axes {
  list-style: none;
  margin: clamp(2.5rem, 7vh, 5rem) auto 0;
  /* Eigener Innenabstand: #showcase hat seitlich bewusst keinen (die
     Galerie läuft randabschneidend durch). */
  padding: 0 var(--pad);
  max-width: 1240px;
  width: 100%;
  box-sizing: border-box;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(1.6rem, 3vw, 3rem);
}
.exp-axis { position: relative; min-width: 0; }
.ea-rule {
  display: block;
  height: 3px;
  margin-bottom: 1.1rem;
  background-image: repeating-linear-gradient(to right,
    var(--gold) 0 3px, transparent 3px 7px);
  opacity: .5;
  animation: ffPixelGlow 3.4s ease-in-out infinite;
}
/* Delays NACH der animation-Shorthand — sie würde sie sonst auf 0s
   zurücksetzen. So atmet jede Spalte leicht versetzt. */
.exp-axis:nth-child(2) .ea-rule { animation-delay: .8s; }
.exp-axis:nth-child(3) .ea-rule { animation-delay: 1.6s; }
.exp-axis:nth-child(4) .ea-rule { animation-delay: 2.4s; }
@media (prefers-reduced-motion: reduce) {
  .ea-rule { animation: none; opacity: .55; }
}
.ea-idx {
  display: block;
  font-family: var(--mono);
  font-size: .72rem;
  letter-spacing: .2em;
  color: var(--gold);
  margin-bottom: .5rem;
}
.ea-title {
  font-family: var(--mono);
  font-weight: 800;
  font-size: clamp(1rem, 1.7vw, 1.25rem);
  letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--cream);
  margin: 0 0 .6rem;
}
.ea-copy {
  font-family: var(--mono);
  font-size: clamp(.76rem, 1.25vw, .86rem);
  line-height: 1.62;
  color: rgba(240,237,232,0.58);
  margin: 0;
}
/* 4 → 2 → 1 Spalten. Bei vier Spalten unter ~980px würde jede Spalte
   schmaler als das Lesemaß der Copy. */
@media (max-width: 980px) { .exp-axes { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .exp-axes { grid-template-columns: 1fr; } }

/* ═══════════════════════════════════════════════
   DE.BLOG — Vorschaukarten, Link auf /blog (heller Grund)
   ═══════════════════════════════════════════════ */
#de-blog {
  position: relative;
  z-index: 1;
  /* DURCHSICHTIG — der Liquid-Hintergrund der Seite läuft hier durch,
     genau wie in der Galerie darüber. Die Karten tragen die Fläche, nicht
     ein Farbgrund; dadurch bleibt die Topografie zwischen ihnen sichtbar.
     (Vorher Gold. Der Goldgrund verlangte, ALLE goldenen Akzente in dieser
     Sektion auf Tinte zu drehen — mit dem dunklen Grund gehen sie jetzt
     wieder auf Creme und Gold zurück, siehe die Regeln hier drunter.) */
  background: transparent;
  /* beschneidet die einfahrende Überschrift auf die eigene Sektion (siehe #depth) */
  overflow: hidden;
  /* TRENNLINIE wie zum Footer. Beide Sektionen sind jetzt dunkel und
     durchsichtig — ohne Linie war zwischen Galerie und Blog gar keine
     Grenze zu sehen, und die 210 px Abstand lasen sich als Leerraum statt
     als Zäsur. Zum Vergleich: Zur Fußzeile sind es 208 px, aber dort
     trennt eine Haarlinie, und genau deshalb wirkt sie richtig. */
  border-top: 1px solid rgba(200,184,154,0.1);
  /* Oben knapper als unten: Die Linie trägt die Zäsur jetzt, das Polster
     muss sie nicht mehr ersetzen. */
  padding: clamp(2.75rem, 6vw, 4.5rem) var(--pad) clamp(4rem, 11vw, 8rem);
}
.blog-inner { max-width: 1100px; margin: 0 auto; }
/* Titel steht MITTIG; der „alle beiträge"-Link sitzt rechts am Rand, ohne
   die Zentrierung zu verschieben (deshalb absolut statt im Flex-Fluss). */
.blog-head {
  position: relative;
  text-align: center;
  margin-bottom: clamp(2.4rem, 6vw, 4rem);
}
.blog-head .blog-all {
  position: absolute;
  right: 0;
  bottom: 0;
}
@media (max-width: 720px) {
  /* auf schmalen Fenstern untereinander — sonst liefe der Link in den Titel */
  .blog-head .blog-all { position: static; display: inline-block; margin-top: 1.2rem; }
}
/* Auf dunklem Grund: Zeile in Creme, Punkt in Gold — identisch zu EXPLORE,
   damit die drei Sektionstitel auch farblich EINE Familie sind. */
.blog-name {
  color: var(--cream);
}
.blog-name .vn-dot { color: var(--gold); }
/* Kurze Einordnung unter der Überschrift, wie in der EXPLORE-Sektion. */
.blog-lead {
  max-width: 52ch;
  margin: clamp(1rem, 2.5vh, 1.7rem) auto 0;
  font-family: var(--mono);
  font-size: clamp(.82rem, 1.4vw, .95rem);
  line-height: 1.65;
  color: rgba(240,237,232,0.6);
}
.blog-lead .acc { color: var(--gold); }
.blog-all {
  font-family: var(--mono);
  font-size: clamp(.78rem, 1.3vw, .9rem);
  letter-spacing: .08em;
  color: rgba(240,237,232,0.62);
  text-decoration: none;
  white-space: nowrap;
  transition: color .3s;
}
.blog-all:hover { color: var(--gold); }
.arr { display: inline-block; margin-left: .5em; transition: transform .3s; }
.blog-all:hover .arr, .bc-more:hover .arr { transform: translateX(4px); }
.blog-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(1.2rem, 3vw, 2.2rem);
}
.blog-card {
  display: flex;
  flex-direction: column;
  padding: clamp(1.3rem, 2.4vw, 1.9rem);
  /* Aufgehellte Fläche auf dunklem Grund — die Karte hebt sich als eigene
     Ebene ab, lässt die Topografie dahinter aber noch durchscheinen.
     Identisch zur Kartenvariante der Blog-Unterseite (.sub-list). */
  background: rgba(240,237,232,0.05);
  border: 1px solid rgba(200,184,154,0.14);
  text-decoration: none;
  box-shadow: 0 10px 24px -18px rgba(0,0,0,0.7);
  transition: box-shadow .45s cubic-bezier(.2,.8,.25,1),
              border-color .4s, background .4s;
}
/* ── AUFBAU BEIM SCROLLEN + DYNAMISCHES ANHEBEN ──
   DREI Bewegungen auf einer Karte, aufgeteilt auf ZWEI voneinander
   unabhängige Eigenschaften:

   translate  (Scroll, ohne Übergang — muss dem Finger sofort folgen)
     --enter  der Aufbau von unten: Die Karte startet 72 px tief und fährt
              gestaffelt hoch, während das Feld ins Bild scrollt.
     --rise   die anschließende Parallaxe über die restliche Durchfahrt.
   transform  (Zeiger, mit Übergang — darf weich sein)
     --lift   die Anhebung beim Überfahren.

   WARUM getrennt: Beides in einer transform-Regel bräuchte EINEN Übergang.
   Der Zeiger will 0,45 s Weichheit, der scrollgetriebene Aufbau darf
   keine haben — eine Federung auf einem ohnehin schon geglätteten
   Lenis-Scroll liest sich als Nachziehen. `translate` und `transform` sind
   eigenständige Eigenschaften, werden nacheinander angewandt und können
   deshalb getrennte Übergänge tragen. */
.blog-grid .blog-card {
  --enter: 72px;                 /* Ausgangslage: die Karte steht tief */
  opacity: 0;
  translate: 0 calc(var(--rise, 0px) + var(--enter, 0px));
  transform: translateY(var(--lift, 0px));
  transition: transform .45s cubic-bezier(.2,.8,.25,1),
              box-shadow .45s cubic-bezier(.2,.8,.25,1),
              border-color .4s, background .4s;
}
.blog-grid .blog-card:hover,
.blog-grid .blog-card:focus-visible {
  --lift: -14px;
  border-color: rgba(184,150,62,0.55);
  background: rgba(240,237,232,0.09);
  box-shadow: 0 26px 48px -22px rgba(0,0,0,0.85);
}
/* Ohne Bewegung stehen die Karten einfach da. Wichtig: Das Skript steigt
   bei reduzierter Bewegung früh aus und setzt die Deckkraft dann nie —
   ohne diese Regel bliebe die Sektion leer. */
@media (prefers-reduced-motion: reduce) {
  .blog-grid .blog-card {
    opacity: 1;
    translate: none;
    transform: none;
    filter: none;
    transition: none;
  }
}
/* ── KARTENTEXT ──
   Grundfarben für DUNKLE Karten. Sie galten vorher für Tinte auf Creme,
   weil die Startseite einen Goldgrund hatte — auf der Blog-Unterseite lagen
   dieselben Bausteine aber schon immer auf Schwarz, und Datumszeile wie
   „weiterlesen" standen dort in Tinte auf fast schwarzem Grund, also
   praktisch unsichtbar. Mit dem dunklen Grund auf beiden Seiten stimmen die
   Grundwerte jetzt überall, und die Sonderregeln für .sub-list entfallen. */
.bc-date {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .18em;
  text-transform: uppercase;
  /* 0.62 statt 0.5: Bei 10 px gesperrter Versalschrift reichen die 4,7:1
     aus der ersten Fassung nicht — so sind es 6,4:1. */
  color: rgba(240,237,232,0.62);
  margin-bottom: 1rem;
}
.bc-title {
  font-family: var(--mono);
  font-weight: 700;
  font-size: clamp(1rem, 1.7vw, 1.2rem);
  line-height: 1.35;
  letter-spacing: -.01em;
  color: var(--cream);
  margin: 0 0 .8rem;
}
.bc-excerpt {
  font-family: var(--mono);
  font-size: clamp(.82rem, 1.4vw, .92rem);
  line-height: 1.7;
  color: rgba(240,237,232,0.62);
  margin: 0 0 1.4rem;
  flex: 1;
}
.bc-more {
  font-family: var(--mono);
  font-size: .82rem;
  letter-spacing: .06em;
  color: var(--gold);
}

/* ═══════════════════════════════════════════════
   CONTACT — Formular (dunkel), vorbereitet für WP-Plugin
   ═══════════════════════════════════════════════ */
#contact {
  position: relative;
  z-index: 1;
  /* transparent: der Liquid-Hintergrund scheint durch — der abdunkelnde
     Schleier und der Blog-Übergang stehen weiter unten */
  background: transparent;
  padding: clamp(4rem, 11vw, 8rem) var(--pad);
}
.contact-inner {
  max-width: 1100px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1fr 1.1fr;
  gap: clamp(2rem, 6vw, 5rem);
  align-items: start;
}
.contact-title {
  font-family: var(--mono);
  font-weight: 700;
  font-size: clamp(1.8rem, 4.6vw, 3.2rem);
  letter-spacing: -.02em;
  color: var(--cream);
  margin: .3rem 0 1.2rem;
}
.contact-copy {
  font-family: var(--mono);
  font-size: clamp(.85rem, 1.6vw, 1rem);
  line-height: 1.75;
  color: rgba(240,237,232,0.66);
  max-width: 42ch;
  margin: 0 0 2rem;
}
.contact-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .9rem; }
.contact-list li {
  display: flex;
  gap: 1.2em;
  font-family: var(--mono);
  font-size: clamp(.82rem, 1.4vw, .92rem);
  color: rgba(240,237,232,0.8);
}
.cl-k {
  color: var(--gold);
  min-width: 3.2em;
  letter-spacing: .1em;
  text-transform: uppercase;
  font-size: .82em;
  padding-top: .15em;
}
.contact-list a { color: rgba(240,237,232,0.8); text-decoration: none; transition: color .3s; }
.contact-list a:hover { color: var(--gold); }
.contact-form { display: grid; gap: 1.2rem; }
.cf-field { display: grid; gap: .5rem; }
.cf-label {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: rgba(240,237,232,0.5);
}
.cf-field input,
.cf-field textarea {
  font-family: var(--mono);
  font-size: clamp(.88rem, 1.5vw, 1rem);
  color: var(--cream);
  background: rgba(240,237,232,0.04);
  border: 1px solid rgba(200,184,154,0.18);
  padding: .85em 1em;
  width: 100%;
  resize: vertical;
  transition: border-color .3s, background .3s;
}
.cf-field input::placeholder,
.cf-field textarea::placeholder { color: rgba(240,237,232,0.3); }
.cf-field input:focus,
.cf-field textarea:focus {
  outline: none;
  border-color: var(--gold);
  background: rgba(240,237,232,0.07);
}
.cf-submit {
  justify-self: start;
  margin-top: .4rem;
  font-family: var(--mono);
  font-size: clamp(.82rem, 1.4vw, .92rem);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--black);
  background: var(--gold);
  border: none;
  padding: .95em 1.8em;
  cursor: pointer;
  transition: background .3s, transform .3s;
}
.cf-submit:hover { background: #cfa74a; transform: translateY(-2px); }
.cf-submit .arr { margin-left: .7em; }

/* ═══════════════════════════════════════════════
   FOOTER
   ═══════════════════════════════════════════════ */
#site-footer {
  position: relative;
  z-index: 1;
  border-top: 1px solid rgba(200,184,154,0.1);
  /* beschneidet die einfahrende Überschrift auf den Footer (siehe #depth) */
  overflow: hidden;
  padding: clamp(3rem, 7vw, 5rem) var(--pad) 1.5rem;
  font-family: var(--mono);
}
/* ── SCHLUSSMARKE ──
   Der Markenname als Velocity-Überschrift wie in allen Sektionen davor.
   Die Seite öffnet mit „DENNIS RINGELBERG." und schließt mit „DERING.LAB." —
   dieselbe Geste, einmal am Anfang, einmal am Ende. */
.footer-top {
  max-width: 1100px;
  margin: 0 auto clamp(2.5rem, 6vw, 4rem);
  text-align: center;
}
.foot-name { color: var(--cream); }
.foot-name .vn-dot { color: var(--gold); }
.foot-tag {
  max-width: 46ch;
  margin: clamp(1rem, 2.5vh, 1.6rem) auto 0;
  font-size: clamp(.8rem, 1.3vw, .9rem);
  line-height: 1.7;
  color: rgba(240,237,232,0.5);
}
.footer-inner {
  max-width: 1100px;
  margin: 0 auto;
  display: grid;
  /* Drei GLEICHE Spalten statt 1.4fr/1fr/1fr: Seit die Marke oben steht,
     braucht keine Spalte mehr Sonderbreite. */
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(2rem, 5vw, 4rem);
  align-items: start;
}
/* EINE Spaltenklasse statt .footer-nav/.footer-social — die alten Namen
   beschrieben den INHALT, standen aber für dieselbe Form, und in der
   „social"-Spalte lagen zuletzt auch Impressum und Datenschutz. */
.footer-col { display: grid; gap: .7rem; align-content: start; }
/* Spaltenüberschrift im Kommentar-Duktus der Seite (siehe .w-label) */
.footer-label {
  font-family: var(--mono);
  font-weight: 700;
  font-size: 9px;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--gold);
  opacity: .75;
  margin: 0 0 .3rem;
}
.footer-label::before { content: '/* '; }
.footer-label::after  { content: ' */'; }
.footer-col a {
  font-size: .85rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: rgba(240,237,232,0.62);
  text-decoration: none;
  width: max-content;
  transition: color .3s;
}
.footer-col a:hover { color: var(--gold); }
/* Rechtliches in der Basiszeile: kleiner als die Spaltenlinks, damit es
   die Zeile nicht dominiert, aber deutlich klickbar. */
.footer-legal { display: flex; gap: 1.4rem; flex-wrap: wrap; }
.footer-legal a {
  color: rgba(240,237,232,0.4);
  text-decoration: none;
  transition: color .3s;
}
.footer-legal a:hover { color: var(--gold); }
.footer-base {
  max-width: 1100px;
  margin: clamp(2.5rem, 5vw, 3.5rem) auto 0;
  padding-top: 1.4rem;
  border-top: 1px solid rgba(200,184,154,0.08);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  font-size: .72rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: rgba(240,237,232,0.4);
}

/* ── Responsive für die neuen Sektionen ── */
@media (max-width: 840px) {
  .blog-grid { grid-template-columns: 1fr; }
  .contact-inner { grid-template-columns: 1fr; }
  .footer-inner { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 520px) {
  .blog-head { flex-direction: column; align-items: flex-start; }
  /* ── FOOTER AUF MOBIL: ZWEI SPALTEN, NICHT EINE ──
     Einspaltig standen die drei beschrifteten Gruppen als eine einzige lange
     Liste untereinander — das las sich nicht als Gliederung, sondern als
     angehängter Text. Und es kollidierte mit der zentrierten Überschrift
     darüber: mittig, dann plötzlich linksbündig.
     Jetzt trägt die NAVIGATION als längste Liste die ganze linke Spalte,
     Kontakt und Social stapeln sich rechts daneben. Damit stehen fünf Zeilen
     links gegen zwei Gruppen rechts — ein ruhiges Gleichgewicht statt einer
     Kolonne. */
  .footer-inner {
    grid-template-columns: 1fr 1fr;
    column-gap: clamp(1rem, 4vw, 1.6rem);
    row-gap: clamp(1.4rem, 5vw, 2rem);
  }
  .footer-col:nth-child(1) { grid-column: 1; grid-row: 1 / span 2; }
  .footer-col:nth-child(2) { grid-column: 2; grid-row: 1; }
  .footer-col:nth-child(3) { grid-column: 2; grid-row: 2; }
  /* Mitwachsende Größe und engere Sperrung: „HALLO@DERING.LAB" ist mit
     16 Zeichen der längste Eintrag und muss in eine halbe Spalte passen —
     bei 320 px Gerätebreite sind das nur 133 px. */
  .footer-col a {
    font-size: clamp(.72rem, 3.4vw, .85rem);
    letter-spacing: .06em;
  }
  /* Die Basiszeile hat drei Teile — nebeneinander wird das unter 520 px
     zum Flattersatz. Untereinander und mittig bleibt sie ruhig. */
  .footer-base { flex-direction: column; justify-content: center; text-align: center; }
}

/* ═══════════════════════════════════════════════
   DEPTH & POLISH — Tiefe durch einfache Effekte
   ═══════════════════════════════════════════════ */

/* ── FILMKORN — global über allem, gibt flachen Flächen Textur/Tiefe.
   Statisches SVG-Rauschen als data-URI, sehr niedrige Deckkraft, per
   mix-blend über den Grund gelegt. pointer-events:none. ── */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 120;
  pointer-events: none;
  opacity: .05;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='170' height='170'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ── FARBRAND — weicher, warmer Goldschimmer rundum den Viewport (fix,
   über allen Sektionen, pointer-events:none). Sehr dezent, gibt der Seite
   einen gerahmten, „belichteten" Rand (juanmora-Anleihe, in deiner Palette). */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 115;
  pointer-events: none;
  box-shadow:
    inset 0 0 140px rgba(184, 150, 62, 0.13),
    inset 0 0 46px rgba(184, 150, 62, 0.10);
}

/* ═══════════════════════════════════════════════
   STORY — Bio-Text mit Scroll-Farb-Reveal (juanmora-Stil)
   Ruhige, helle Lesezone nach der Depth-Section. Das JS zerlegt den Text
   in Wörter (.sf-w); beim Scrollen wechseln sie Wort für Wort die Farbe
   (gedämpft → Tinte, Akzentwörter → Gold). Grundfarbe setzt das JS.
   ═══════════════════════════════════════════════ */
#story {
  position: relative;
  z-index: 1;
  background:
    radial-gradient(120% 90% at 50% 0%, #f6f3ef 0%, var(--cream) 46%, #e9e3d8 100%);
  padding: clamp(2.5rem, 7vh, 5rem) var(--pad) clamp(4rem, 11vh, 8rem);
}
.story-inner { max-width: 960px; margin: 0 auto; }
.story-lead {
  font-family: var(--mono);
  font-weight: 600;
  font-size: clamp(1.3rem, 3vw, 2.15rem);
  line-height: 1.5;
  letter-spacing: -.01em;
  margin: 0;
}
.story-head {
  font-family: var(--mono);
  font-weight: 800;
  font-size: clamp(1.45rem, 3.2vw, 2.35rem);
  line-height: 1.25;
  letter-spacing: -.02em;
  margin: clamp(2.6rem, 7vh, 5rem) 0 1.1rem;
}
.story-body {
  font-family: var(--mono);
  font-weight: 500;
  font-size: clamp(1.12rem, 2.4vw, 1.65rem);
  line-height: 1.58;
  margin: 0;
}
/* Wort-Spans: Farbe kommt pro Frame aus dem JS (kein CSS-Übergang, damit der
   Scrub exakt sitzt). Akzentwörter (.acc → data-acc) laufen auf Gold statt Tinte. */
.sf-w { will-change: color; }
@media (prefers-reduced-motion: reduce) {
  .sf-w { color: #111 !important; }
}

/* ── HINTERGRUND-TIEFE — Verläufe statt flacher Füllungen ── */
/* Showcase: KEIN deckender Grund — stattdessen ein halbtransparenter
   Schleier, durch den das Liquid der Seite sichtbar bleibt. Er hält die
   Karten lesbar und dunkelt zu den Rändern hin ab. */
/* Galerie-Schleier — halbtransparent (das Liquid scheint durch) und nach
   UNTEN ausblendend, damit er ohne Kante in das Blog-Übergangsband läuft,
   in dem der Shader von Schwarz auf Creme aufhellt. */
#showcase {
  background: linear-gradient(to bottom,
    rgba(20,19,18,0.66) 0%,
    rgba(11,11,10,0.84) 45%,
    rgba(9,9,9,0.86)    calc(100% - 300px),
    rgba(9,9,9,0.42)    calc(100% - 120px),
    rgba(9,9,9,0)       100%);
}
/* ÜBERGANG BLOG → CONTACT (hell → dunkel) — jetzt exakt nach dem Muster des
   Galerie-Übergangs, der gut funktioniert: KEINE Creme-Ausblendung (die
   wirkte matschig), sondern eine klare Kante gegen den halbtransparenten
   dunklen Schleier, durch den die Liquid-Topografie durchscheint. */
#contact {
  background:
    radial-gradient(100% 80% at 82% 8%, rgba(184,150,62,0.10) 0%, transparent 45%),
    radial-gradient(120% 85% at 50% 12%,
      rgba(22,21,20,0.62) 0%, rgba(12,11,10,0.80) 55%, rgba(6,6,6,0.90) 100%);
}
/* ÜBERGANG GALERIE → BLOG — bewusst eine KLARE KANTE, genau wie beim
   Übergang in die Galerie (der gut funktioniert). Kein weicher Verlauf und
   kein Aufhellen des Liquids: beides führt über Schwarz zwangsläufig durch
   Mittelgrau und erzeugt eine flaue graue Fläche. Die Tiefe entsteht
   stattdessen auf der DUNKLEN Seite — der Galerie-Schleier blendet nach
   unten aus, sodass die Topografie direkt vor der Kante klar zu sehen ist. */
/* ACHTUNG: Diese Regel stand hier mit einem eigenen Grund für #de-blog und
   überschrieb den weiter oben — eine Änderung nur oben blieb wirkungslos.
   Sie ist jetzt bewusst LEER geräumt statt gelöscht, damit die Falle beim
   nächsten Mal auffällt: Der Grund von #de-blog wird oben gesetzt. */
/* Der Grund des Footers steht NUR hier — die Regel oben trug früher
   zusätzlich ein flaches #070707, das an dieser Stelle ohnehin überschrieben
   wurde. Dieselbe Doppelung wie bei #de-blog; sie ist jetzt aufgelöst. */
#site-footer {
  background:
    radial-gradient(90% 120% at 50% 0%, #0d0c0a 0%, #070707 60%);
}

/* ── REVEAL-ON-SCROLL — Überschriften/Karten/Text steigen geblurt auf.
   IntersectionObserver setzt .rvl-in (siehe main.js). Staffelung via
   --d (inline oder nth-child). ── */
[data-reveal] {
  opacity: 0;
  transform: translateY(32px);
  filter: blur(9px);
  transition:
    opacity .95s cubic-bezier(.2,.8,.2,1),
    transform 1.05s cubic-bezier(.2,.8,.2,1),
    filter .95s ease;
  transition-delay: var(--d, 0s);
}
[data-reveal].rvl-in { opacity: 1; transform: none; filter: blur(0); }
@media (prefers-reduced-motion: reduce) {
  [data-reveal] { opacity: 1; transform: none; filter: none; transition: none; }
}

/* ── CAROUSEL-TIEFE — Bild größer als Rahmen (für Innen-Parallax), große
   Geister-Nummer hinter jeder Karte, weicher Schatten ── */
.sc-media { box-shadow: 0 30px 60px -30px rgba(0,0,0,0.7); }
.sc-media img {
  width: 118%;
  height: 118%;
  margin: -9% 0 0 -9%;   /* zentriert, lässt Raum zum Verschieben */
  will-change: transform, filter;
}
.sc-card { position: relative; }
.sc-card::before {
  content: attr(data-n);
  position: absolute;
  top: -.55em;
  left: -.15em;
  /* echtes Zurückstaffeln im 3D-Raum statt z-index — die Nummer schwebt
     hinter der Karte und foreshortened mit der Perspektive */
  transform: translateZ(-90px);
  font-family: var(--mono);
  font-weight: 800;
  font-size: clamp(4rem, 11vw, 8.5rem);
  line-height: 1;
  color: rgba(184,150,62,0.12);
  pointer-events: none;
}


/* ═══════════════════════════════════════════════
   UNTERSEITEN — Blog, Beitrag, Projekt, Seiten, 404
   ═══════════════════════════════════════════════
   Unterseiten teilen Schrift, Farben, Kopfzeile, Footer und den Liquid-
   Hintergrund mit der Startseite, haben aber KEINE Kino-Choreografie.
   Kopfzeile und Menü sind hier deshalb von Anfang an sichtbar (auf der
   Startseite blendet sie das JS erst ein). Verhalten: assets/js/sub.js
   ═══════════════════════════════════════════════ */
body.subpage { background: var(--black); }

/* ── Kopfzeile: dauerhaft sichtbar, kompakter als auf der Startseite ── */
body.subpage #header-bar {
  padding: 16px 0 14px;
  background: rgba(9, 9, 9, 0.9);
  backdrop-filter: blur(20px);
  border-bottom-color: rgba(200, 184, 154, 0.1);
}
body.subpage #menu-rule { transform: scaleX(1); }
body.subpage #menu { visibility: visible; margin-top: 12px; }
body.subpage #menu a { opacity: 1; filter: none; }

/* Kompakte, klickbare Marke — führt zurück zur Startseite */
.sub-brand {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 0;
  margin: 0 auto 12px;
  font-family: var(--mono);
  font-weight: 800;
  font-size: clamp(1rem, 2.2vw, 1.25rem);
  letter-spacing: -.02em;
  color: var(--cream);
  text-decoration: none;
  pointer-events: auto;
  width: max-content;
  transition: opacity .3s;
}
.sub-brand:hover { opacity: .78; }
.sub-brand img {
  width: .95em; height: .95em;
  margin-right: .38em;
  align-self: center;
}
.sub-brand .sb-dot {
  width: .17em; height: .17em;
  background: var(--gold);
  margin: 0 .09em;
}

/* ── Seitenrahmen ── */
.sub-main {
  position: relative;
  z-index: 1;
  /* oben Platz für die fixe Kopfzeile */
  padding: clamp(9rem, 16vh, 12rem) var(--pad) clamp(5rem, 12vh, 8rem);
}
.sub-inner { max-width: 1080px; margin: 0 auto; }
.sub-narrow { max-width: 720px; margin: 0 auto; }

/* ── Seitenkopf (Eyebrow + Titel + Beitragsdaten) ── */
.sub-hero { margin-bottom: clamp(2.5rem, 7vh, 4.5rem); }
.sub-title {
  font-family: var(--mono);
  font-weight: 800;
  font-size: var(--fs-heading);
  line-height: var(--lh-heading);
  letter-spacing: -.02em;
  color: var(--cream);
  margin: .4rem 0 0;
}
.sub-lead {
  font-family: var(--mono);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: rgba(240, 237, 232, 0.68);
  max-width: 62ch;
  margin: 1.4rem 0 0;
}
.sub-meta {
  display: flex;
  flex-wrap: wrap;
  gap: .6em 1.6em;
  margin-top: 1.6rem;
  font-family: var(--mono);
  font-size: .72rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: rgba(240, 237, 232, 0.42);
}
.sub-meta .sm-k { color: var(--gold); margin-right: .5em; }

/* ── Fließtext (Beiträge, Rechtstexte) ── */
.prose {
  font-family: var(--mono);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: rgba(240, 237, 232, 0.74);
}
.prose > * + * { margin-top: 1.3em; }
.prose h2 {
  font-weight: 800;
  font-size: clamp(1.15rem, 2.4vw, 1.55rem);
  line-height: 1.3;
  letter-spacing: -.01em;
  color: var(--cream);
  margin-top: 2.6em;
}
.prose h3 {
  font-weight: 700;
  font-size: clamp(1rem, 2vw, 1.2rem);
  color: var(--cream);
  margin-top: 2em;
}
.prose a { color: var(--gold); text-decoration: underline; text-underline-offset: .25em; }
.prose a:hover { color: var(--cream); }
.prose ul, .prose ol { padding-left: 1.4em; }
.prose li + li { margin-top: .5em; }
.prose strong { color: var(--cream); font-weight: 700; }
.prose blockquote {
  border-left: 2px solid var(--gold);
  padding-left: 1.2em;
  color: rgba(240, 237, 232, 0.9);
}
.prose figure { margin-top: 2em; }
.prose img { width: 100%; height: auto; display: block; }
.prose figcaption {
  margin-top: .8rem;
  font-size: .7rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: rgba(240, 237, 232, 0.4);
}
.prose hr { border: 0; border-top: 1px solid rgba(200, 184, 154, 0.14); margin: 2.6em 0; }

/* ── Blog-Liste auf der Unterseite (nutzt die Karten der Startseite) ── */
.sub-list { display: grid; gap: clamp(1rem, 2.5vw, 1.6rem); }
@media (min-width: 780px) { .sub-list { grid-template-columns: repeat(3, 1fr); } }
/* KEINE Sonderfarben mehr nötig: Seit die Startseiten-Sektion ebenfalls auf
   dunklem Grund steht, sind die Grundwerte von .blog-card und .bc-* bereits
   die dunkle Variante (siehe „KARTENTEXT" weiter oben). Die frühere Umkehr
   deckte ohnehin nur Titel und Anrisstext ab — Datumszeile und
   „weiterlesen" blieben dabei in Tinte auf Schwarz stehen und waren auf
   dieser Seite praktisch unlesbar. */

/* Blätter-Navigation */
.sub-pager {
  display: flex;
  justify-content: center;
  gap: 1.4rem;
  margin-top: clamp(2.5rem, 7vh, 4rem);
  font-family: var(--mono);
  font-size: .75rem;
  letter-spacing: .16em;
  text-transform: uppercase;
}
.sub-pager a, .sub-pager .current {
  color: rgba(240, 237, 232, 0.55);
  text-decoration: none;
  transition: color .3s;
}
.sub-pager a:hover { color: var(--gold); }
.sub-pager .current { color: var(--gold); }

/* Zurück-Link */
.back-link {
  display: inline-block;
  margin-top: clamp(3rem, 8vh, 5rem);
  font-family: var(--mono);
  font-size: .75rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: rgba(240, 237, 232, 0.55);
  text-decoration: none;
  transition: color .3s;
}
.back-link:hover { color: var(--gold); }
.back-link .arr { transform: rotate(180deg); margin: 0 .5em 0 0; }

/* ── Projekt-Detailseite ── */
.proj-cover {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: #141414;
  border: 1px solid rgba(200, 184, 154, 0.12);
  margin-bottom: clamp(2rem, 6vh, 3.5rem);
}
.proj-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.proj-facts {
  display: grid;
  gap: 1.6rem 2.4rem;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  padding: clamp(1.4rem, 4vh, 2.2rem) 0;
  border-top: 1px solid rgba(200, 184, 154, 0.14);
  border-bottom: 1px solid rgba(200, 184, 154, 0.14);
  margin-bottom: clamp(2rem, 6vh, 3.5rem);
  font-family: var(--mono);
}
.pf-k {
  display: block;
  font-size: .68rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: .5rem;
}
.pf-v { color: var(--cream); font-size: .95rem; }
.proj-gallery { display: grid; gap: clamp(1rem, 3vw, 2rem); margin-top: clamp(2.5rem, 7vh, 4rem); }
@media (min-width: 780px) { .proj-gallery.two { grid-template-columns: 1fr 1fr; } }
.proj-gallery img {
  width: 100%; height: auto; display: block;
  border: 1px solid rgba(200, 184, 154, 0.1);
}

/* ── 404 ── */
.err-wrap {
  min-height: 62vh;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
}
.err-code {
  font-family: var(--mono);
  font-weight: 800;
  font-size: clamp(4.5rem, 18vw, 12rem);
  line-height: .9;
  letter-spacing: -.05em;
  color: var(--cream);
}
.err-code .dot { color: var(--gold); }
