/* stevepatzwaldt.de — Stylesheet
   Ausgelagert aus index.html, damit der Browser es einmal laedt und danach
   aus dem Cache nimmt. Reihenfolge im <head>: fonts.css, dann style.css. */
/* ══════════════════════════════════════════
   TOKENS + RESET
══════════════════════════════════════════ */
:root {
  --bg:         #0d0b0b;
  --fg:         #f0ebe3;
  --fg-2:       rgba(240,235,227,.5);
  --fg-3:       rgba(240,235,227,.14);
  --accent:     #C49498;
  --accent-bg:  rgba(196,148,152,.08);
  --border:     rgba(240,235,227,.07);
  --contact-bg: #C49498;
  --overlay-bg: rgba(13,11,11,.92);   /* Menu-Overlay */
  --island-bg:  #ebebeb;              /* helle Sektions-Inseln (About/Expertise/Clients) */
  --island-fg:  #141414;             /* Text auf hellen Inseln */
  --island-border: rgba(20,20,20,.12);
  --contact-border: rgba(13,11,11,.22);
  --pad:        clamp(24px, 4.5vw, 72px);

  /* Drei Textrollen fuer die ganze Seite. Vorher hatte jede Sektion ihre
     eigenen Werte: About-Fliesstext 13px, Expertise-Fliesstext 14px,
     Intro-Text 21px, Kleinlabels mal 9px/.22em, mal 10px/.14em, mal 11px.
     Ueberschriften und Wortmarken bleiben davon unberuehrt — das ist
     Displaytypografie und folgt der Flaeche, nicht dem Lesetext. */
  --t-lead:  clamp(17px, 1.9vw, 23px);   /* Einstiegssatz einer Sektion */
  --t-body:  clamp(14px, 1.05vw, 16px);  /* Fliesstext ueberall */
  --t-micro: 10px;                       /* Labels, Kategorien, Rechtszeile */

  /* --- Typografie --- */
  --font-display: 'Space Grotesk', 'Syne', sans-serif;   /* Headlines */

  /* --- Motion (vereinheitlicht) --- */
  --ease:     cubic-bezier(.4, 0, .2, 1);   /* Standard */
  --ease-out: cubic-bezier(.2, .7, 0, 1);   /* Entrances / expressiv */
  --t-fast:   .3s;                          /* Hover / Micro */
  --t-base:   .5s;                          /* Standard-Transition */
  --t-slow:   .7s;                          /* Große Reveals */
}

/* ── DAY-MODUS: globaler Token-Flip (das meiste flippt automatisch via var()) ── */
html[data-theme="day"] {
  --bg:         #f4f1ea;             /* warmes Off-White */
  --fg:         #23201c;             /* warmes Fast-Schwarz */
  --fg-2:       rgba(35,32,28,.55);
  --fg-3:       rgba(35,32,28,.16);
  --border:     rgba(35,32,28,.10);
  --overlay-bg: rgba(244,241,234,.94);
  --island-bg:  #e4ded2;             /* Inseln leicht abgesetzt, damit Blöcke lesbar bleiben */
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: auto; }

body {
  background: var(--bg);
  color: var(--fg);
  font-family: 'Syne', sans-serif;
  overflow-x: hidden;
  cursor: none;
  -webkit-font-smoothing: antialiased;
  transition: background-color var(--t-base) var(--ease), color var(--t-base) var(--ease);
}
body.menu-open { overflow: hidden; }
/* Der eigene Cursor gilt ueberall gleich — vorher stand cursor:none nur an
   einzelnen Stellen, dadurch blitzte der System-Pfeil je nach Sektion auf. */
html, a, button, input, textarea, select, [role="button"] { cursor: none; }

/* ══════════════════════════════════════════
   GRAIN OVERLAY
══════════════════════════════════════════ */
#grain {
  position: fixed;
  inset: -50%;
  width: 200%;
  height: 200%;
  pointer-events: none;
  z-index: 350;
  opacity: .045;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  animation: grain-shift 7s steps(6) infinite;
}
@keyframes grain-shift {
  0%   { transform: translate(0,0); }
  20%  { transform: translate(-4%,3%); }
  40%  { transform: translate(3%,-4%); }
  60%  { transform: translate(-3%,2%); }
  80%  { transform: translate(2%,4%); }
  100% { transform: translate(0,0); }
}

/* ══════════════════════════════════════════
   LOADER
══════════════════════════════════════════ */
#loader {
  position: fixed;
  inset: 0;
  background: var(--bg);
  z-index: 600;
  display: flex;
  align-items: center;
  justify-content: center;
}
.loader-name {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .32em;
  text-transform: uppercase;
  color: var(--fg-3);
  overflow: hidden;
}
.loader-name-inner { display: block; transform: translateY(100%); }

/* ══════════════════════════════════════════
   CURSOR (kookie-style: weißer Kreis, difference)
══════════════════════════════════════════ */
.cursor-wrapper {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 9999;
  mix-blend-mode: difference;
}
/* kookie-Modell: EIN gefuellter Punkt, 24px, mix-blend-mode difference.
   Kein Ring, kein zweiter Dot — dadurch sieht er in jeder Sektion gleich aus. */
.cursor {
  position: absolute;
  top: 0; left: 0;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: #ffffff;
  transform: translate(-50%, -50%) scale(1);
  transition: transform .3s cubic-bezier(.165,.84,.44,1), background-color .2s var(--ease);
  will-change: left, top, transform;
}
.cursor.big { transform: translate(-50%, -50%) scale(2.3); }
.cursor.tint { background: var(--accent); }

/* ══════════════════════════════════════════
   CORNER FRAME (kookie-style)
══════════════════════════════════════════ */
.frame {
  position: sticky;
  top: 0; left: 0; right: 0;
  height: 100vh;
  margin-bottom: -100vh;
  z-index: 40;
  pointer-events: none;
  color: var(--fg);
  transition: color var(--t-base) var(--ease);
}
body.frame-dark .frame { color: #0d0b0b; }
/* Hero trägt immer ein dunkles Bild -> Frame dort in beiden Modi hell halten */
body.frame-media .frame { color: #f0ebe3; }
.frame > div { position: absolute; display: flex; align-items: center; }
.frame a, .frame button { pointer-events: auto; cursor: none; }

.frame-tl { top: 30px; left: clamp(20px,3vw,40px); gap: 16px; }
.nav-num { font-size: 14px; font-weight: 600; letter-spacing: .3px; text-transform: uppercase; font-variant-numeric: tabular-nums; }
.nav-line { width: 80px; height: 1px; background: currentColor; opacity: .5; }
.nav-label { font-size: 14px; font-weight: 500; letter-spacing: .3px; text-transform: uppercase; }

.frame-tr { top: 26px; right: clamp(20px,3vw,40px); gap: 22px; }
.frame-logo {
  font-size: 14px; font-weight: 600; letter-spacing: .3px;
  text-transform: uppercase; color: currentColor; text-decoration: none;
}
.menu-btn {
  position: relative;
  width: 26px; height: 26px;
  background: none; border: none;
  color: currentColor;
}
.menu-btn span {
  position: absolute; top: 50%; left: 50%;
  width: 20px; height: 1.5px;
  background: currentColor;
  transform: translate(-50%,-50%);
  transition: transform var(--t-fast) var(--ease);
}
.menu-btn span:nth-child(2) { transform: translate(-50%,-50%) rotate(90deg); }
.menu-btn.open span:nth-child(1) { transform: translate(-50%,-50%) rotate(45deg); }
.menu-btn.open span:nth-child(2) { transform: translate(-50%,-50%) rotate(-45deg); }

/* Day/Night Toggle (Sonne/Mond) — vorerst deaktiviert, Code bleibt für später */
.theme-btn {
  display: none;   /* TODO reaktivieren: auf 'flex' zurück */
  position: relative;
  width: 26px; height: 26px;
  align-items: center; justify-content: center;
  background: none; border: none;
  color: currentColor;
}
.theme-ico { display: block; overflow: visible; }
.theme-ico .ico-sun, .theme-ico .ico-moon {
  transform-origin: 12px 12px;
  transition: opacity var(--t-base) var(--ease), transform var(--t-base) var(--ease);
}
.theme-ico .ico-sun  { opacity: 0; transform: rotate(-90deg) scale(.4); }
.theme-ico .ico-moon { opacity: 1; transform: rotate(0deg) scale(1); }
html[data-theme="day"] .theme-ico .ico-sun  { opacity: 1; transform: rotate(0deg) scale(1); }
html[data-theme="day"] .theme-ico .ico-moon { opacity: 0; transform: rotate(90deg) scale(.4); }

.frame-bl { bottom: 28px; left: clamp(20px,3vw,40px); gap: 18px; }
.frame-bl a {
  font-size: 14px; font-weight: 500; letter-spacing: .2px;
  color: currentColor; text-decoration: none;
  opacity: .75; transition: opacity var(--t-fast) var(--ease);
}
.frame-bl a:hover { opacity: 1; }

.frame-br { bottom: 28px; right: clamp(20px,3vw,40px); gap: 9px; }
.frame-br .rec-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: currentColor;
  animation: rec-pulse 2.2s ease-in-out infinite;
}
.frame-br span:last-child {
  font-size: 14px; font-weight: 600; letter-spacing: .3px; text-transform: uppercase;
}
@keyframes rec-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%       { opacity: .3; transform: scale(.6); }
}

/* ══════════════════════════════════════════
   MENU OVERLAY
══════════════════════════════════════════ */
.menu-overlay {
  position: fixed;
  inset: 0;
  z-index: 400;
  background: var(--overlay-bg);
  backdrop-filter: blur(22px);
  -webkit-backdrop-filter: blur(22px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 0 var(--pad);
  opacity: 0;
  pointer-events: none;
  /* visibility statt nur opacity: mit opacity:0 allein blieben die Menuelinks
     fokussierbar — beim Tabben landete man in einem unsichtbaren Menue.
     visibility:hidden nimmt die Kinder aus der Tab-Reihenfolge, die verzoegerte
     Transition laesst das Ausblenden trotzdem weich auslaufen. */
  visibility: hidden;
  transition: opacity var(--t-base) var(--ease), visibility 0s linear var(--t-base);
}
body.menu-open .menu-overlay { opacity: 1; pointer-events: auto; visibility: visible; transition: opacity var(--t-base) var(--ease), visibility 0s; }
.menu-links { display: flex; flex-direction: column; gap: clamp(4px, 1vh, 14px); }
.menu-links a {
  display: inline-block;
  width: fit-content;
  font-size: clamp(40px, 7vw, 104px);
  font-weight: 800;
  letter-spacing: -.035em;
  line-height: 1.04;
  color: var(--fg);
  text-decoration: none;
  opacity: 0;
  transform: translateY(40px);
  transition: opacity var(--t-base) var(--ease), transform var(--t-slow) var(--ease-out), color var(--t-fast) var(--ease);
}
.menu-links a:hover { color: var(--accent); }
body.menu-open .menu-links a { opacity: 1; transform: translateY(0); }
body.menu-open .menu-links a:nth-child(1) { transition-delay: .12s; }
body.menu-open .menu-links a:nth-child(2) { transition-delay: .18s; }
body.menu-open .menu-links a:nth-child(3) { transition-delay: .24s; }
body.menu-open .menu-links a:nth-child(4) { transition-delay: .30s; }
body.menu-open .menu-links a:nth-child(5) { transition-delay: .36s; }
.menu-foot {
  position: absolute;
  bottom: 64px; left: var(--pad); right: var(--pad);
  display: flex; justify-content: space-between; flex-wrap: wrap; gap: 12px;
  font-size: 11px; font-weight: 600; letter-spacing: .14em; text-transform: uppercase;
  color: var(--fg-2);
}

/* ══════════════════════════════════════════
   HERO
══════════════════════════════════════════ */
#hero {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 0 var(--pad) clamp(96px, 13vh, 130px);
  position: relative;
  overflow: hidden;
}
.hero-bg {
  position: absolute;
  inset: -8% 0 0 0;
  height: 116%;
  z-index: -2;
  background-size: cover;
  background-position: center;
  will-change: transform;
}
.hero-bg::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(120% 90% at 70% 25%, rgba(13,11,11,.25) 0%, rgba(13,11,11,.78) 60%, var(--bg) 100%),
    linear-gradient(to top, var(--bg) 2%, rgba(13,11,11,.2) 40%);
}
/* h1 statt div: die Seite hatte keine Hauptueberschrift. Groesse und Gewicht
   kommen weiterhin von .hero-line, die Browser-Defaults des h1 werden hier
   neutralisiert, damit sich am Bild nichts aendert. */
.hero-name { margin-bottom: 40px; font-size: inherit; font-weight: inherit; }
.hero-line {
  display: block;
  overflow: hidden;
  font-size: clamp(48px, 11.5vw, 170px);
  font-weight: 800;
  letter-spacing: -.045em;
  line-height: .87;
  padding-bottom: .16em; margin-bottom: -.16em;
}
.hero-line-inner { display: block; transform: translateY(128%); }
.hero-line.dim .hero-line-inner { color: var(--fg-3); }
.hero-foot {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  opacity: 0;
}
.hero-tagline {
  font-size: 13px;
  font-weight: 400;
  color: var(--fg-2);
  line-height: 1.75;
  max-width: 300px;
}

/* hero bottom-left label (kookie-style) */
.hero-label {
  position: absolute;
  bottom: 28px; left: clamp(20px,3vw,40px);
  display: flex; align-items: center; gap: 16px;
  opacity: 0;
}
.hl-name { font-size: 14px; font-weight: 600; letter-spacing: .3px; text-transform: uppercase; color: var(--fg); }
.hl-line { width: 64px; height: 1px; background: var(--fg-2); }
.hl-desc { font-size: 14px; font-weight: 500; letter-spacing: .3px; text-transform: uppercase; color: var(--fg-2); }
.hero-rec { position: absolute; bottom: 28px; right: clamp(20px,3vw,40px); display: flex; align-items: center; gap: 9px; opacity: 0; }
.hero-rec .rec-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--fg); animation: rec-pulse 2.2s ease-in-out infinite; }
.hero-rec span:last-child { font-size: 14px; font-weight: 600; letter-spacing: .3px; text-transform: uppercase; color: var(--fg); }

/* scroll cue (pill + arrow) */
.scroll-cue {
  position: absolute;
  bottom: 26px; left: 50%;
  transform: translateX(-50%);
  z-index: 5;
  text-decoration: none;
  opacity: 0;
}
.scroll-cue-pill {
  display: flex; justify-content: center; align-items: flex-start;
  width: 26px; height: 46px;
  border: 1px solid var(--fg-2);
  border-radius: 14px;
  padding-top: 8px;
}
.scroll-cue-arrow {
  font-size: 13px; line-height: 1; color: var(--fg);
  animation: cue 1.8s ease-in-out infinite;
}
@keyframes cue {
  0%, 100% { transform: translateY(0); opacity: .35; }
  50%       { transform: translateY(7px); opacity: 1; }
}

/* ══════════════════════════════════════════
   OUR WORK — COMING SOON
══════════════════════════════════════════ */
#work {
  position: relative;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(100px, 12vh, 160px) var(--pad);
  border-top: 1px solid var(--border);
  overflow: hidden;
}
.work-backdrop {
  position: absolute;
  inset: 0;
  z-index: 0;
  background-size: cover;
  background-position: center;
  opacity: .28;
  filter: blur(4px) saturate(1.1);
  transform: scale(1.08);
  will-change: transform;
}
.work-backdrop::after {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(80% 80% at 50% 50%, rgba(13,11,11,.45) 0%, var(--bg) 92%);
}
.work-inner { position: relative; z-index: 1; text-align: center; }
.work-label {
  display: block;
  font-size: 10px; font-weight: 700; letter-spacing: .24em; text-transform: uppercase;
  color: var(--accent); margin-bottom: 26px; opacity: 0;
}
.work-big {
  font-size: clamp(58px, 15vw, 230px);
  font-weight: 800; letter-spacing: -.05em; line-height: .86;
  color: var(--fg); filter: blur(14px); opacity: .22; user-select: none;
}
.work-soon {
  margin-top: clamp(20px, 3vh, 38px);
  font-size: clamp(13px, 1.6vw, 17px); font-weight: 600; letter-spacing: .42em;
  text-transform: uppercase; color: var(--fg); opacity: 0;
}
.work-soon-sub {
  display: block; margin-top: 14px;
  font-size: 12px; font-weight: 400; letter-spacing: .04em; color: var(--fg-2); opacity: 0;
}

/* ══════════════════════════════════════════
   SECTION SHARED
══════════════════════════════════════════ */
.section {
  padding: clamp(100px, 12vh, 160px) var(--pad);
  border-top: 1px solid var(--border);
  position: relative;
}
.s-label {
  display: block;
  font-size: 10px; font-weight: 700; letter-spacing: .22em; text-transform: uppercase;
  color: var(--accent); margin-bottom: clamp(40px, 6vh, 72px);
  opacity: 0; transform: translateX(-10px);
}
/* FEHLER, der hier stand (01.09., von Steve gesehen, nicht von mir gemessen):
   `h2.exp-big, h3.exp-big { font-size: inherit }` stand in derselben Regel wie
   .s-heading. Bei .s-heading und .contact-heading ist das richtig — dort sitzt
   die Groesse auf den KINDERN (.s-line, .contact-line), die Ueberschrift selbst
   soll nichts beitragen. `.exp-big` traegt seine Groesse dagegen SELBST, und
   `h3.exp-big` (0,1,1) schlaegt `.exp-big` (0,1,0). Ergebnis: BRAND, FILM, KI
   und CODE erbten die 16px des Elternelements.
   Warum die Kontrolle es nicht gefunden hat: sie las
   `document.querySelector('.exp-big')` — und das ist das erste Element mit
   dieser Klasse, naemlich ein Wort des Intro-Cycles. Das ist ein `span`, den
   der Selektor gar nicht trifft. Gemessen wurde also genau das eine Element,
   das nie betroffen war. Lehre: bei einer Regel, die auf mehrere Elemente
   zielt, `querySelectorAll` messen, nie `querySelector`.
   Deshalb jetzt getrennt: erben duerfen nur die, deren Kinder gestalten. */
.s-heading, .contact-heading { margin: 0; font-size: inherit; font-weight: inherit; }
/* Hier nur der Aussenabstand — Groesse und Fettung kommen aus `.exp-big`. */
h2.exp-big, h3.exp-big { margin: 0; }
.s-heading { display: block; }

/* Nur fuer Screenreader: Expertise und Clients tragen ihre Ueberschrift im
   Bild nicht als Text (Expertise zeigt die Disziplinen, Clients die
   Namenswand). Ohne diese Zeilen fehlen beide Sektionen in der Gliederung.
   clip-path statt display:none — Verstecktes wird sonst gar nicht vorgelesen. */
.nur-sr {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; margin: -1px; padding: 0; border: 0;
}
.s-line {
  display: block; overflow: hidden;
  font-size: clamp(44px, 6.8vw, 96px); font-weight: 800; letter-spacing: -.04em; line-height: .9;
  padding-bottom: .16em; margin-bottom: -.16em;
}
.s-line-inner { display: block; transform: translateY(128%); }

/* ══════════════════════════════════════════
   EXPERTISE — HORIZONTAL PIN SCROLL
══════════════════════════════════════════ */
/* ── Scrollraum liegt im CSS, nicht mehr im ScrollTrigger ──────────────────
   Vorher hielt ein GSAP-Pin (`pin: true`) die Sektion fest. Auf iOS arbeitet
   ein Pin mit `position: fixed` plus Neuberechnung per JS — waehrend des
   Momentum-Scrollings laeuft das Scrollen dort aber auf einem eigenen Thread,
   und die Sektion blieb beim Hochscrollen auf der letzten Disziplin stehen.

   GEMESSEN am Vorbild: kookie-kollective.com hat weder auf dem Desktop noch
   mobil einen einzigen GSAP-Pin (ScrollTrigger.getAll().filter(t=>t.pin) ist
   dort leer) — die ganze Seite arbeitet mit `position: sticky`. Sticky wird
   vom Compositor gehalten und ueberlebt Momentum-Scrolling.

   Jetzt genauso: #expertise liefert nur noch die Hoehe, ueber die geklebt
   wird, .exp-viewport klebt. Der ScrollTrigger misst bloss den Fortschritt
   (start 'top top' → end 'bottom bottom') und animiert weiter die Seiten.
   Nebeneffekt: der Scrollweg ist jetzt hier einstellbar statt im JS. */
#expertise {
  position: relative;
  border-top: 1px solid var(--border);
  /* 6,6 statt 5: der Intro-Zyklus hat seit 21.09. einen eigenen Vorlauf
     (VORLAUF in js/site.js), die Timeline ist dadurch 7,9 statt 6,0 lang.
     Mit 6,6 bleibt der Scrollweg je Disziplin genau wie vorher, der
     Zugewinn geht komplett an Erdenken/Umsetzen/Liefern. */
  --exp-seiten: 6.6;
  --exp-weg: 105vh;         /* Scrollweg je Seite */
  height: calc(100vh + var(--exp-seiten) * var(--exp-weg));
}
.exp-viewport { position: sticky; top: 0; height: 100vh; overflow: hidden; }

/* svh = kleiner Viewport, also die Hoehe MIT ausgefahrener Adressleiste.
   Damit springt nichts, wenn die Leiste auf dem Handy ein- und ausfaehrt —
   im Gegensatz zu vh (immer die grosse Hoehe) und dvh (aendert sich staendig).
   Browser ohne svh behalten die vh-Werte darueber. */
@supports (height: 100svh) {
  #expertise { --exp-weg: 105svh; height: calc(100svh + var(--exp-seiten) * var(--exp-weg)); }
  .exp-viewport { height: 100svh; }
}

/* Ohne sticky-Unterstuetzung faellt die Sektion auf dieselbe gestapelte
   Darstellung zurueck, die auch `body.reduced` bekommt. Das JS erkennt den
   Fall ebenfalls und laesst die Querbewegung dann ganz weg. */
@supports not (position: sticky) {
  #expertise { height: auto; }
  .exp-viewport { position: relative; height: auto; }
}
.exp-sidenav {
  position: absolute; left: clamp(20px,3vw,44px); top: 50%; transform: translateY(-50%);
  display: flex; flex-direction: column; gap: clamp(20px,3vh,40px); z-index: 6;
}
.exp-snav-item {
  writing-mode: vertical-rl; transform: rotate(180deg);
  font-size: 13px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
  color: var(--fg-3); transition: color var(--t-fast) var(--ease); white-space: nowrap;
}
.exp-snav-item.is-active { color: var(--accent); }
.exp-track { position: absolute; inset: 0; }
.exp-page {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  /* unten muss der Frame (IG/LinkedIn/REC) frei bleiben, links die vertikale Sidenav */
  padding: clamp(84px,10vh,124px) clamp(72px,7vw,132px) clamp(108px,13vh,150px) clamp(132px,13vw,196px);
  display: flex; flex-direction: column; justify-content: flex-start;
}
/* Abstand Headline -> Textblock, bewusst gesetzt statt per space-between
   erzwungen. Vorher standen beide an den Raendern und damit rund 400px
   auseinander. */
.exp-page:not(.exp-intro) .exp-meta { margin-top: clamp(40px,5.5vh,88px); }
body.reduced #expertise { height: auto; }
body.reduced .exp-viewport { position: relative; height: auto; }
body.reduced .exp-track { position: relative; inset: auto; }
body.reduced .exp-page { position: relative; inset: auto; min-height: 82vh; border-bottom: 1px solid var(--border); }
/* Die Intro-Seite ist eine eigene, zentrierte Komposition: Wortwechsel,
   darunter die + + +, darunter der Text — als Gruppe mittig in der Section.
   Die Disziplin-Seiten bleiben davon unberuehrt (Headline oben, linksbuendig). */
/* Die Disziplin-Seiten haben links mehr Padding (Sidenav) als rechts. Auf der
   Intro-Seite gibt es keine Sidenav-Kollision, deshalb hier symmetrisch —
   sonst steht die zentrierte Gruppe 44px neben der optischen Seitenmitte. */
.exp-intro {
  justify-content: center; align-items: center; text-align: center;
  padding-left: clamp(72px,7vw,132px); padding-right: clamp(72px,7vw,132px);
}
.exp-intro .exp-intro-text { margin-top: clamp(26px,3.6vh,50px); margin-left: auto; margin-right: auto; }
.exp-plus { margin-top: clamp(22px,3vh,44px); font-size: clamp(20px,2.4vw,30px); font-weight: 400; letter-spacing: .3em; color: var(--fg); }
.exp-intro-text { margin-top: clamp(26px,3.6vh,50px); font-size: var(--t-lead); color: var(--fg-2); line-height: 1.5; max-width: 34ch; margin-left: auto; margin-right: auto; }
.exp-intro-text strong { color: var(--fg); font-weight: 700; }
.exp-hint {
  margin-top: 36px; display: flex; align-items: center; gap: 12px;
  font-size: 10px; font-weight: 700; letter-spacing: .2em; text-transform: uppercase; color: var(--fg-3);
}
.exp-hint-line { width: 40px; height: 1px; background: var(--fg-3); }
.exp-big {
  /* 12vw statt 12.5vw: bei 12.5 lag das laengste Wort nur 25px unter dem
     verfuegbaren Satzspiegel — zu knapp, jeder Font-Fallback haette es gerissen. */
  font-size: clamp(52px, 12vw, 200px); font-weight: 800; letter-spacing: -.045em;
  line-height: .9; text-transform: uppercase; color: var(--fg); margin: 0;
}
/* Die Woerter liegen absolut uebereinander. Vorher waren sie zusaetzlich vertikal
   zentriert (top:50% + translateY(-50%)) — dadurch ragten sie oben und unten aus
   .exp-cycle heraus und schoben sich ueber die + + +. Jetzt sitzen sie oben im
   Container, und der Container ist hoch genug fuer Wort UND Blur-Saum (blur(22px)
   blutet rund 22px ueber die Buchstabenkante hinaus). */
/* WICHTIG: ohne eigene font-size rechnet min-height:1.32em gegen die geerbten 16px
   und der Container ist 21px statt 250px hoch. Die Woerter ragen dann heraus und
   schieben sich ueber die + + +. Deshalb dieselbe clamp-Formel wie .exp-intro .exp-big. */
/* min-height gibt dem unscharfen Wort Luft nach oben und unten (blur(22px)
   blutet rund 22px ueber die Buchstabenkante). 1.34em waren zu grosszuegig
   und haben die Gruppe auseinandergezogen; 1.16em lassen bei .9 Zeilenhoehe
   noch rund 26px Saum je Seite. Darunter schieben sich die + + + unters Wort. */
.exp-cycle { position: relative; width: 100%; font-size: clamp(52px, 12vw, 200px); line-height: .9; min-height: 1.16em; }
/* Mittig, aber OHNE transform: translateX(-50%) — GSAP schreibt beim ersten
   yPercent-Tween eine komplette transform-Matrix und haette die Zentrierung
   ueberschrieben, das Wort waere seitlich weggesprungen. left/right:0 plus
   margin-inline:auto zentriert ohne transform und ist damit GSAP-fest. */
.exp-word {
  position: absolute; left: 0; right: 0; top: 0;
  width: max-content; margin-inline: auto; white-space: nowrap;
}
/* dieselbe Groesse wie die Disziplin-Headlines, sonst sitzen die Versalien
   trotz gleicher Oberkante optisch nicht auf einer Linie */
.exp-intro .exp-big { font-size: clamp(52px, 12vw, 200px); }
.exp-meta { max-width: 440px; position: relative; z-index: 2; }
/* Der Balken war der eigentliche Abstandhalter zwischen Headline und Text.
   Zusammen mit justify-content:flex-start auf der Seite ruecken beide nun
   zusammen, statt an die Ober- und Unterkante auseinandergezogen zu werden. */
.exp-bar { display: block; width: 1px; height: clamp(44px,6vh,78px); background: var(--fg-2); margin-bottom: 18px; }
.exp-desc { font-size: var(--t-body); color: var(--fg-2); line-height: 1.75; margin-bottom: 28px; }
.exp-desc strong { color: var(--fg); font-weight: 700; }
.exp-cats { display: flex; gap: clamp(22px,3vw,52px); }
.exp-cats span {
  position: relative; padding-top: 16px;
  font-size: 10px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: var(--fg);
}
.exp-cats span::before { content: ''; position: absolute; top: 0; left: 0; width: 1px; height: 9px; background: var(--accent); }
.exp-objs { position: absolute; inset: 0; pointer-events: none; z-index: 1; overflow: visible; }
canvas.exp-obj.is-seq { height: auto; image-rendering: auto; }
.exp-obj {
  position: absolute; display: block; height: auto;
  will-change: transform, filter, opacity;
  /* Bilder sind auf #ebebeb normalisiert -> sie verschmelzen ohne Blend-Mode */
}
/* vordere Ebene: gross, rechts, leicht angeschnitten (kookie)
   Breite und Rechtsabstand stehen in Variablen, weil die KI-Seite beide
   korrigieren muss (siehe unten) — und zwar in beiden Breakpoints. */
.exp-obj.is-front {
  --w: clamp(340px, 42vw, 690px);
  --rechts: clamp(40px, 5vw, 110px);
  right: var(--rechts); top: 16%; width: var(--w);
}
/* ── KI-Objekt: Groessenausgleich ──────────────────────────────────────────
   Alle acht Objekte liegen auf einer 1100x1100-Leinwand, fuellen sie aber
   unterschiedlich stark aus. Gemessen (Inhalts-Bounding-Box, Schwelle 25
   gegen den #ebebeb-Grund, als Wurzel aus Breite x Hoehe):

     brand 810 px    film 801 px    code 896 px    ->  Mittel 836
     ki    685 px                                  ->  18 % kleiner

   Bei gleicher CSS-Breite steht der KI-Wuerfel deshalb sichtbar kleiner im
   Bild als die anderen drei. Ausserdem sitzt sein Inhalt nicht mittig auf der
   Leinwand (Schwerpunkt 0.409 / 0.565 statt 0.490 / 0.543), er wandert beim
   Vergroessern also nach links unten weg.

   Korrigiert wird die Box, nicht die Datei: Breite x 1.221, und der Anker um
   den gemessenen Schwerpunktversatz zurueckgezogen, damit der Wuerfel dort
   steht, wo die anderen drei stehen. Skalieren per transform ginge nicht —
   GSAP schreibt waehrend des Einflugs seine eigene scale-Matrix. */
.exp-page[data-i="2"] .exp-obj.is-front {
  --w: clamp(415px, 51vw, 842px);
  right: calc(var(--rechts) - .173 * var(--w));
  top: calc(16% - .120 * var(--w));
}
/* hintere Ebene: kleiner, hoeher -> Tiefe. Der Blur kommt pro Seite aus dem SVG-Filter. */
.exp-obj.is-back  { right: clamp(150px,17vw,330px); top: -6%;  width: clamp(220px, 28vw, 470px); }

/* ══════════════════════════════════════════
   LIGHT SECTIONS (About / Expertise / Clients) — kookie
══════════════════════════════════════════ */
.theme-light { background: var(--island-bg); color: var(--island-fg); transition: background-color var(--t-base) var(--ease); }
.theme-light .s-line-inner { color: var(--island-fg); }
.theme-light .about-lead { color: var(--island-fg); }
.theme-light .about-text p { color: rgba(20,20,20,.6); }
/* Kein eigener Grund und kein Rahmen mehr: das Bild traegt seinen Grund selbst
   und ist auf #ebebeb normalisiert. Ein abweichender Kasten wuerde genau die
   Kante zeichnen, die das Bild vermeiden soll. */
.theme-light .about-photo { background: transparent; border-color: transparent; }
.theme-light .about-stats { border-color: rgba(20,20,20,.14); }
.theme-light .stat { border-color: rgba(20,20,20,.14); }
.theme-light .stat-n { color: var(--island-fg); }
.theme-light .stat-l { color: rgba(20,20,20,.55); }
.theme-light .exp-big { color: var(--island-fg); }
.theme-light .exp-plus { color: var(--island-fg); }
.theme-light .exp-intro-text { color: rgba(20,20,20,.62); }
.theme-light .exp-intro-text strong { color: var(--island-fg); }
.theme-light .exp-desc { color: rgba(20,20,20,.62); }
.theme-light .exp-desc strong { color: var(--island-fg); }
.theme-light .exp-bar { background: rgba(20,20,20,.5); }
.theme-light .exp-cats span { color: var(--island-fg); }
.theme-light .exp-snav-item { color: rgba(20,20,20,.32); }
.theme-light .exp-snav-item.is-active { color: var(--accent); }
.theme-light .cl { color: var(--island-fg); }
.theme-light .clients-wall, .theme-light.section { border-color: var(--island-border); }
.theme-light.section { border-top-color: var(--island-border); }
body.reduced .exp-big { filter: none !important; opacity: 1 !important; }
body.reduced .exp-word { position: relative; left: auto; top: auto; transform: none; }
body.reduced .exp-word:not(:first-child) { display: none; }

/* ══════════════════════════════════════════
   ABOUT
══════════════════════════════════════════ */
.about-grid { display: grid; grid-template-columns: 1.1fr .9fr; gap: clamp(56px, 10vw, 140px); align-items: start; margin-top: clamp(52px, 7vh, 88px); }
.about-text p { font-size: var(--t-body); font-weight: 400; color: var(--fg-2); line-height: 1.75; margin-bottom: 16px; max-width: 46ch; opacity: 0; transform: translateY(10px); }
/* WICHTIG die hoehere Spezifitaet: `.about-text p` (0,1,1) hat `.about-lead`
   (0,1,0) ueberstimmt — der Lead wurde deshalb als 13px-Fliesstext gerendert
   statt als Einstiegssatz. Das war der auffaelligste Bruch im Textbild. */
.about-text p.about-lead { font-size: var(--t-lead); font-weight: 700; letter-spacing: -.025em; color: var(--fg); line-height: 1.45; margin-bottom: 28px; max-width: 34ch; opacity: 0; transform: translateX(-16px); }
.about-photo { aspect-ratio: 3/4; background: transparent; border: none; overflow: hidden; opacity: 0; transform: translateY(36px); position: relative; }
/* Ohne `aspect-ratio` (Safari vor 15) haette der Kasten die Hoehe 0 und das
   Portrait waere komplett verschwunden — der teuerste denkbare Ausfall fuer
   ein Bild, das die halbe Sektion traegt. Der alte Prozent-Trick haelt die
   Hoehe auch dort: padding-top 133,33 % ist dasselbe Verhaeltnis wie 3/4. */
@supports not (aspect-ratio: 3 / 4) {
  .about-photo { height: 0; padding-top: 133.333%; }
  .about-photo img { position: absolute; inset: 0; }
}
/* GEMESSENER FEHLER (und wieder rausgenommen): erst stand hier height:116%,
   damit die Parallaxe Material hat. Damit stimmte das Seitenverhaeltnis der Box
   nicht mehr mit dem des Bildes ueberein, `object-fit: cover` hat links und
   rechts je 52px abgeschnitten — und zwar genau die 5,5 %, die im Bild nach
   #ebebeb auslaufen. Ergebnis war eine sichtbare Kante im Bodenbereich.
   Jetzt deckungsgleich: Box 3/4, Bild 1800x2400 = 3/4, also kein Beschnitt.
   Die Parallaxe legt unten eine schmale leere Zeile frei — die faellt nicht
   auf, weil die letzte Bildzeile bereits 235 ist und die Sektion darunter
   dieselbe Farbe hat. */
.about-photo img { width: 100%; height: 100%; object-fit: cover; display: block; will-change: transform; }
/* Das Objektiv als eigene Ebene. Die vier Werte sind GEMESSEN, nicht gesetzt:
   im Originalfoto liegt es bei x 716-955 und y 441-673 von 2480x3312; nach dem
   3:4-Beschnitt und mit 46px Saum fuer die Unschaerfe ergibt das genau diese
   Prozentwerte. Deshalb sitzt es ohne JavaScript exakt an seiner fotografierten
   Stelle — die Bewegung ist Zugabe, nicht Voraussetzung.
   ID-Selektor, damit er die .about-photo-img-Regel darueber schlaegt. */
#about-lens {
  position: absolute;
  left: 27.016%; top: 11.857%; width: 13.387%; height: auto;
  object-fit: contain; z-index: 2; pointer-events: none;
  will-change: transform, filter;
}
.about-stats { display: grid; grid-template-columns: repeat(3, 1fr); border-top: 1px solid var(--border); margin-top: clamp(72px, 9vh, 120px); }
.stat { padding: clamp(36px, 5vh, 56px) 0 clamp(28px, 4vh, 44px); border-right: 1px solid var(--border); }
/* Die Zahlen klebten an der Trennlinie. Die erste Spalte bleibt buendig zum
   Satzspiegel, alle folgenden bekommen Luft nach der Linie. */
.stat + .stat { padding-left: clamp(30px, 3.2vw, 56px); }
.stat:last-child { border-right: none; }
.stat-n { display: block; font-size: clamp(52px, 8.5vw, 118px); font-weight: 800; letter-spacing: -.055em; line-height: .86; color: var(--fg); opacity: 0; transform: translateY(20px); transition: opacity var(--t-slow) var(--ease), transform .8s cubic-bezier(.22,.68,0,1.35); }
.stat-n.pop { opacity: 1; transform: translateY(0); }
.stat-l { display: block; color: var(--fg-2); margin-top: 14px; opacity: 0; transition: opacity var(--t-base) var(--ease) .4s; }
.stat-l.pop { opacity: 1; }

/* ══════════════════════════════════════════
   CLIENTS — BLUR / FOCUS WALL (kookie-style)
══════════════════════════════════════════ */
#clients {
  /* WAR: position:sticky; top:0 — dadurch blieb die Sektion am oberen Rand kleben,
     waehrend Kontakt und Footer daruntergescrollt sind. Genau das sah aus, als
     "kaeme der Footer hinter den Clients raus". kookie macht die Sektion NICHT
     sticky, sondern nur relative mit z-index 2 ueber dem Footer. */
  overflow: hidden;
  /* exakt eine Viewporthoehe: der Kontakt-Block startet genau 100vh vor dem
     Ende dieser Sektion und liegt dahinter (siehe #contact margin-top). */
  height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  position: relative;
  z-index: 3;
  /* deckend, sonst scheint der Footer durch — aber ueber die Theme-Variable,
     nicht ueber --bg: die Sektion traegt .theme-light und war mit --bg
     dunkel auf dunkel, die Namen waren praktisch unsichtbar. */
  background: var(--island-bg);
  /* enger als die uebrigen Sektionen: die Namenswand soll die Hoehe fuellen (kookie: 704px von 900px) */
  padding-top: clamp(56px, 8vh, 84px);
  padding-bottom: clamp(56px, 8vh, 84px);
}
.clients-marquee {
  /* Volle Viewportbreite: bei kookie hat die Namenswand kein Seitenpadding,
     deshalb deckt die Blur-Schicht exakt dieselbe Flaeche ab. Vorher lief die
     Linse ueber ein 100vw-Konstrukt mit eigenem Offset — daher die harte Kante. */
  width: 100vw; margin-left: 50%; transform: translateX(-50%);
  position: relative;
  /* Baender laufen an den Raendern aus, statt mitten im Buchstaben zu brechen */
  /* nur noch waagerecht ausblenden — senkrecht gibt es nichts mehr zu kaschieren */
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 clamp(28px,4.5vw,84px), #000 calc(100% - clamp(28px,4.5vw,84px)), transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0, #000 clamp(28px,4.5vw,84px), #000 calc(100% - clamp(28px,4.5vw,84px)), transparent 100%);
  margin-top: clamp(16px, 2.6vh, 28px);
  /* --x/--y/--r stehen hier, nicht auf einer der Ebenen: Custom Properties
     vererben, also lesen die unscharfe UND die scharfe Ebene denselben Spot. */
  --x: -400px; --y: -400px; --r: 0;
  /* Kein Padding hier: die Luft fuer den Blur-Saum liegt IN den beiden Ebenen,
     damit ihre Boxen deckungsgleich bleiben (siehe .cl-blur / .cl-sharp). */
  /* Das Spaltenlayout liegt auf .cl-blur / .cl-sharp, damit beide Ebenen
     exakt deckungsgleich sind. */
}
.cl-row {
  display: flex;
  width: max-content;
  gap: clamp(30px, 4vw, 80px);
  will-change: transform;
}
.cl-row.dir-l { animation: marq-l var(--dur, 50s) linear infinite; }
.cl-row.dir-r { animation: marq-r var(--dur, 50s) linear infinite; }
/* kookie laesst die Baender auch beim Hovern weiterlaufen — die Lupe braucht die Maus
   auf der Flaeche, ein :hover-Stopp macht die Wand genau dann statisch. */
.cl {
  /* kookie misst 128px bei 1600px Viewport, line-height .9 — die vh-Grenze
     haelt die 5 Reihen auch in flachen Fenstern innerhalb der Sektion. */
  font-size: clamp(30px, min(8vw, 14.2vh), 132px);
  font-weight: 800;
  letter-spacing: -.03em;
  line-height: .9;
  text-transform: uppercase;
  color: var(--fg);
  opacity: .8;
  white-space: nowrap;
  transition: color var(--t-fast) var(--ease);
  cursor: none;
}

/* Lupe (kookie): eine unscharfe Schicht ueber der Marquee, deren Maske am Cursor
   ein Loch hat. Nicht der Spot wird geschaerft — der Blur bekombt ein Loch. */
/* Zwei Ebenen statt backdrop-filter.
   Grund: backdrop-filter greift auf Steves Mac nicht durch die Marquee-Reihen
   durch — die haben `will-change: transform` und eine laufende Animation, liegen
   also auf eigenen Compositing-Ebenen und landen nicht zuverlaessig im Backdrop.
   Ergebnis war ein Schatten-Halo um scharfe Buchstaben statt echter Unschaerfe.

   Jetzt: eine dauerhaft unscharfe Kopie unten (`filter: blur()` — wirkt immer,
   in jedem Browser), darueber das scharfe Original, dessen Maske nur am Cursor
   durchlaesst. Die Maske ist kookies Verlauf, invertiert: innen deckend, aussen
   transparent, mit demselben festen 140px-Saum von (r-80) bis (r+60). */
/* GEMESSENE URSACHE der harten Kante (Zeilenprofil eines Screenshots bei
   1728x830): genau an der Oberkante von .cl-blur sprang die Helligkeit in
   EINEM Pixel um 10.7 von 255, unten spiegelbildlich um 9.4. Eine Maske
   beschneidet per Vorgabe auf die Border-Box (mask-clip: border-box) — der
   weiche Auslauf des Blurs, der ueber die Box hinausragt, wurde also
   weggeschnitten. Weder Padding am Elternelement noch ein senkrechter
   Maskenverlauf konnten daran etwas aendern, weil beide am selben Rand endeten.
   Loesung: die 40px Luft liegen IN den Ebenen. Der Saum bleibt damit innerhalb
   der Box, die Maske hat nichts mehr abzuschneiden — und weil beide Ebenen
   dieselbe Box und dasselbe Padding haben, sitzt der Spot in beiden gleich. */
.cl-blur, .cl-sharp {
  display: flex; flex-direction: column; justify-content: center;
  gap: clamp(9px, 1.8vh, 16px);
  padding: 40px 0;
}
.cl-blur {
  /* inset oben/unten = das Padding von .clients-marquee, damit diese Ebene
     exakt dieselbe Box hat wie .cl-sharp — sonst laufen die beiden Masken
     um die Paddinghoehe auseinander. */
  position: absolute; inset: 0; z-index: 1;
  /* Der Untergrund gehoert MIT auf diese Ebene — das ist der Unterschied
     zwischen filter:blur() und kookies backdrop-filter. backdrop-filter
     verwischt den Hintergrund innerhalb der eigenen Box; eine gleichmaessige
     Flaeche bleibt dabei gleichmaessig, es entsteht keine Kante. filter:blur()
     auf einer Textebene ohne Untergrund verteilt dagegen nur die Buchstaben-
     schwaerze und legt einen grauen Schleier ueber das Band, der genau an der
     Boxkante endet — das war die harte Kante oben und unten.
     Mit Untergrund faellt die Ebene am Rand von #ebebeb nach transparent ab,
     darunter liegt dieselbe Farbe: der Uebergang ist damit unsichtbar. */
  background: var(--island-bg);
  filter: blur(13px);
  pointer-events: none;
  /* kookies Original-Verlauf, also das Gegenstueck zur Maske auf .cl-sharp:
     innen transparent, aussen deckend. Ohne das lag die unscharfe Kopie unter
     den scharfen Buchstaben und blutete ringsum heraus — das war der
     Schlagschatten. Jetzt verschwindet sie dort, wo scharf gezeigt wird. */
  -webkit-mask-image: radial-gradient(circle at var(--x) var(--y),
      transparent 0%,
      transparent      calc(var(--r) * 1px - 80px),
      rgba(0,0,0,.025) calc(var(--r) * 1px - 70px),
      rgba(0,0,0,.05)  calc(var(--r) * 1px - 60px),
      rgba(0,0,0,.10)  calc(var(--r) * 1px - 50px),
      rgba(0,0,0,.15)  calc(var(--r) * 1px - 40px),
      rgba(0,0,0,.225) calc(var(--r) * 1px - 30px),
      rgba(0,0,0,.30)  calc(var(--r) * 1px - 20px),
      rgba(0,0,0,.45)  calc(var(--r) * 1px - 10px),
      rgba(0,0,0,.60)  calc(var(--r) * 1px),
      rgb(0,0,0)       calc(var(--r) * 1px + 60px));
  mask-image: radial-gradient(circle at var(--x) var(--y),
      transparent 0%,
      transparent      calc(var(--r) * 1px - 80px),
      rgba(0,0,0,.025) calc(var(--r) * 1px - 70px),
      rgba(0,0,0,.05)  calc(var(--r) * 1px - 60px),
      rgba(0,0,0,.10)  calc(var(--r) * 1px - 50px),
      rgba(0,0,0,.15)  calc(var(--r) * 1px - 40px),
      rgba(0,0,0,.225) calc(var(--r) * 1px - 30px),
      rgba(0,0,0,.30)  calc(var(--r) * 1px - 20px),
      rgba(0,0,0,.45)  calc(var(--r) * 1px - 10px),
      rgba(0,0,0,.60)  calc(var(--r) * 1px),
      rgb(0,0,0)       calc(var(--r) * 1px + 60px));
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
}
.cl-sharp {
  position: relative; z-index: 2;
  -webkit-mask-image: radial-gradient(circle at var(--x) var(--y),
      rgb(0,0,0) 0%,
      rgb(0,0,0)       calc(var(--r) * 1px - 80px),
      rgba(0,0,0,.975) calc(var(--r) * 1px - 70px),
      rgba(0,0,0,.95)  calc(var(--r) * 1px - 60px),
      rgba(0,0,0,.90)  calc(var(--r) * 1px - 50px),
      rgba(0,0,0,.85)  calc(var(--r) * 1px - 40px),
      rgba(0,0,0,.775) calc(var(--r) * 1px - 30px),
      rgba(0,0,0,.70)  calc(var(--r) * 1px - 20px),
      rgba(0,0,0,.55)  calc(var(--r) * 1px - 10px),
      rgba(0,0,0,.40)  calc(var(--r) * 1px),
      transparent      calc(var(--r) * 1px + 60px));
  mask-image: radial-gradient(circle at var(--x) var(--y),
      rgb(0,0,0) 0%,
      rgb(0,0,0)       calc(var(--r) * 1px - 80px),
      rgba(0,0,0,.975) calc(var(--r) * 1px - 70px),
      rgba(0,0,0,.95)  calc(var(--r) * 1px - 60px),
      rgba(0,0,0,.90)  calc(var(--r) * 1px - 50px),
      rgba(0,0,0,.85)  calc(var(--r) * 1px - 40px),
      rgba(0,0,0,.775) calc(var(--r) * 1px - 30px),
      rgba(0,0,0,.70)  calc(var(--r) * 1px - 20px),
      rgba(0,0,0,.55)  calc(var(--r) * 1px - 10px),
      rgba(0,0,0,.40)  calc(var(--r) * 1px),
      transparent      calc(var(--r) * 1px + 60px));
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
}
.cl.is-lit { color: var(--accent); }
body.reduced .cl-blur { display: none; }
body.reduced .cl-sharp { -webkit-mask-image: none; mask-image: none; }
/* Kennt der Browser ueberhaupt keine Masken, liegt die unscharfe Kopie
   ungefiltert ueber dem scharfen Original — die Namenswand waere dauerhaft
   unlesbar. Das ist der schlimmste der drei Ausfaelle, deshalb hier hart:
   ohne Maskenunterstuetzung faellt die unscharfe Ebene ganz weg und die Wand
   steht einfach scharf da. Der Effekt fehlt, die Namen bleiben lesbar. */
@supports not ((mask-image: radial-gradient(#000, transparent)) or (-webkit-mask-image: radial-gradient(#000, transparent))) {
  .cl-blur { display: none; }
}
@keyframes marq-l { from { transform: translateX(0); } to { transform: translateX(-50%); } }
@keyframes marq-r { from { transform: translateX(-50%); } to { transform: translateX(0); } }

/* ══════════════════════════════════════════
   CONTACT
══════════════════════════════════════════ */
/* kookies Reveal, gemessen: jeder folgende Block beginnt GENAU 100vh vor dem
   Ende des vorherigen (margin-top: -100vh), liegt mit kleinerem z-index dahinter
   und haelt seinen Inhalt per sticky fest. Dadurch schiebt sich der Block davor
   nach oben weg und legt den naechsten frei, statt ihn nur anzustossen.
   Die Sektion selbst ist deshalb 200vh hoch und TRANSPARENT — die Farbe sitzt
   auf .contact-inner, sonst wuerde die untere Haelfte den Footer verdecken.
   overflow muss sichtbar bleiben, sonst greift position:sticky nicht. */
#contact {
  background: none; padding: 0; overflow: visible; border-top: none;
  min-height: 200vh; position: relative; z-index: 2;
  margin-top: -100vh; display: block;
}
.contact-inner {
  position: sticky; top: 0; min-height: 100vh;
  display: flex; flex-direction: column; justify-content: space-between;
  background: var(--contact-bg);
  /* unten kein Padding: die Wortmarke sitzt buendig an der Unterkante */
  padding: clamp(100px, 12vh, 160px) 0 0;
  /* Solange das Panel klebt, ist seine Unterkante die Fensterunterkante.
     overflow:hidden macht sie damit zur Schnittkante fuer die Wortmarke, die
     zum Schluss zu 40% darunter steht. (Betrifft nur Kinder — die Sticky-
     Eigenschaft dieses Elements selbst bleibt davon unberuehrt.) */
  overflow: hidden;
}
.contact-top { padding: 0 var(--pad); }
.contact-s-label { display: block; color: rgba(13,11,11,.38); margin-bottom: clamp(36px, 5vh, 60px); opacity: 0; }
.contact-heading { display: block; margin-bottom: clamp(48px, 6vh, 80px); }
.contact-line { display: block; overflow: hidden; font-size: clamp(44px, 8.5vw, 122px); font-weight: 800; letter-spacing: -.04em; line-height: .88; color: #0d0b0b; padding-bottom: .12em; margin-bottom: -.12em; }
.contact-line-inner { display: block; transform: translateY(124%); }
.contact-cta { display: flex; align-items: center; gap: 20px; padding: 28px 0; border-top: 1px solid var(--contact-border); cursor: none; position: relative; overflow: hidden; opacity: 0; transform: translateY(18px); text-decoration: none; color: inherit; }
.contact-cta::before { content: ''; position: absolute; inset: 0; width: 0; background: rgba(13,11,11,.08); transition: width var(--t-slow) var(--ease); z-index: 0; }
/* Nur mit echter Maus: auf Touch blieb der Hover-Zustand nach dem ersten
   Antippen oder Wischen stehen — die E-Mail-Zeile war dauerhaft dunkel
   hinterlegt (Screenshot iPhone 21.09.). */
@media (hover: hover) { .contact-cta:hover::before { width: 100%; } }
.cta-arrow { font-size: 28px; font-weight: 300; color: rgba(13,11,11,.28); position: relative; z-index: 1; transition: transform var(--t-fast) var(--ease), color var(--t-fast) var(--ease); flex-shrink: 0; }
@media (hover: hover) { .contact-cta:hover .cta-arrow { transform: translateX(8px); color: rgba(13,11,11,.6); } }
.cta-email { font-size: clamp(18px, 3.2vw, 46px); font-weight: 700; letter-spacing: -.02em; color: #0d0b0b; position: relative; z-index: 1; }
.contact-note { font-size: 13px; font-weight: 400; color: rgba(13,11,11,.45); margin-top: 32px; max-width: 42ch; opacity: 0; transform: translateY(12px); }

/* ══════════════════════════════════════════
   WORTMARKE (in der Kontaktsektion)
   Der eigene Footer ist entfallen: dunkle Flaeche, Spaltennavi und
   "Und - was ist deine Idee?" sind weg. Was bleibt, ist die Wortmarke an
   der Unterkante der Kontaktsektion - so wie kookie es macht.
══════════════════════════════════════════ */
/* Hero-Loop: 5,6s, nahtlos geschlossen (die letzten 24 Frames sind ueber die
   ersten 24 geblendet, gemessene Nahtabweichung 1.2 statt 7.2 von 255).
   Blendet erst auf, wenn der erste Frame wirklich da ist — sonst blitzt beim
   Laden kurz Schwarz auf, wo vorher das Standbild lag. */
.hero-vid {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; opacity: 0;
  transition: opacity .9s var(--ease);
  pointer-events: none;
}
.hero-vid.bereit { opacity: 1; }
/* NICHT an body.reduced haengen: die Klasse sitzt auch dann, wenn nur die
   CDNs blockiert sind (reduceMotion = !LIBS_OK || ...). Ein Hintergrundvideo
   braucht aber kein JS — es soll gerade dann laufen, wenn sonst nichts laeuft.
   Unterdrueckt wird es nur, wenn der Nutzer wirklich weniger Bewegung will. */
@media (prefers-reduced-motion: reduce) { .hero-vid { display: none; } }

.foot-mark-wrap {
  position: relative; z-index: 2; width: 100%;
  /* Ausgangslage: deutlich hoeher, mit Luft darunter. Von dort scrollt die
     Marke nach unten und verankert sich an der Fensterkante (siehe JS).
     Abgeschnitten wird an der Unterkante von .contact-inner (overflow:hidden),
     die solange das Panel klebt die Fensterunterkante ist. */
  /* Kein transform-Startwert mehr: GSAP ueberschreibt `transform` komplett,
     der Wert galt also nur, bis das Skript eingriff — und erzeugte genau dort
     einen sichtbaren Versatz. Die Ruhelage setzt jetzt `margin-bottom` (aus
     js/site.js, Funktion `ruhelage`), und ohne JS steht die Marke schlicht
     dort, wo sie hingehoert. */
  /* Kein eigenes overflow und kein negativer Rand mehr: den Anschnitt macht
     jetzt die Unterkante von .contact-inner, und wie weit die Marke darunter
     steht, bestimmt der Scroll (siehe JS). Vorher stand sie fest an der
     Unterkante und hat sich beim Scrollen nicht bewegt. */
  will-change: transform;
}
.foot-mark {
  /* Startwert, wird per JS exakt eingepasst. 19vw statt der frueheren 26vw:
     das ist die gemessene Zielgroesse (275px bei 1440px Fenster = 19,1vw).
     Damit sitzt die Marke auch dann noch im Rahmen, wenn das JavaScript gar
     nicht laeuft — vorher ragte sie in diesem Fall 538px heraus. */
  font-size: 19vw;
  font-weight: 800; letter-spacing: -.06em;
  line-height: .84; text-transform: uppercase; text-align: center;
  /* 25px breiter als der Rahmen auf JEDER Seite, per negativem Rand nach links
     zurueckgezogen. .foot-mark-wrap hat overflow:hidden, das Wort wird also
     links und rechts gleich angeschnitten. Ohne das ragte es nur rechts raus:
     ueberlaufender nowrap-Text ignoriert text-align:center. */
  white-space: nowrap;
  width: calc(100% + 50px); margin-left: -25px;
  color: var(--accent);
  background-image: url('img/foot-mark.webp');
  /* 100% breit, 300% hoch: nur so gibt es ueberhaupt Weg zum Wandern. */
  background-size: 100% 300%;
  background-position: 50% 0%;
  background-repeat: no-repeat;
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
  user-select: none; will-change: background-position;
}
/* Zweite, deckungsgleiche Ebene in Weiss. Sie blendet zum Ende der Sektion
   auf - kookie macht genau das (dort per .kookie-white-overlay). */
.foot-mark-weiss {
  position: absolute; left: -25px; top: 0; right: -25px; width: auto;
  margin-left: 0;
  background-image: none;
  -webkit-text-fill-color: var(--fg); color: var(--fg);
  opacity: 0;
}
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .foot-mark { background-image: none !important; -webkit-text-fill-color: currentColor; color: var(--accent); }
}

/* .menu-sub (Brand/Film/KI/Code) ist entfallen — dieselbe Liste stand zweimal
   im Overlay. Geblieben ist die untere Zeile, um "Code" erweitert. */
.menu-legal { display: flex; gap: 18px; }
.menu-legal a { color: inherit; text-decoration: none; transition: color var(--t-fast) var(--ease); }
.menu-legal a:hover { color: var(--accent); }
.frame-trenner { display: inline-block; width: 1px; height: 10px; background: currentColor; opacity: .35; }

/* ══════════════════════════════════════════
   RESPONSIVE
══════════════════════════════════════════ */
@media (max-width: 860px) {
  /* ── Clients auf Mobil: kookies Geometrie ──────────────────────────────
     Gemessen im Original bei 375x812 (KOOKIE_MECHANIK.md, Nachtrag 4):
     Sektion 496px statt 100vh, Reihen 5x57,6px, Schrift 64px (=17vw),
     gap 16px, Padding 2,5rem. Der Punkt ist nicht die Optik, sondern der
     Scroll: nur wenn die Wand rund 60% des Viewports einnimmt, bleiben ueber
     und unter ihr Flaechen frei, auf denen der Finger noch scrollen kann —
     (Seit 21.09. faengt die Lupe gar nichts mehr ab — siehe Touch-Zweig in
     js/site.js. Die Groessen bleiben, weil sie optisch stimmen.) */
  #clients { height: auto; padding-top: 40px; padding-bottom: 40px; }
  /* 17vw ist kookies Mass (64px auf 375px Breite). Der vh-Deckel greift nur
     auf flachen Fenstern (iPhone SE, Landscape): dort muss die Wand kleiner
     werden, damit die ganze Wand ins Bild passt.
     Rechnung: Sektionshoehe = 204px + 4,5 x Schriftgroesse. */
  .cl { font-size: clamp(38px, min(17vw, 8.6vh), 72px); }
  .cl-blur, .cl-sharp { gap: 16px; padding: 30px 0; }
  /* Die -100vh-Kette faellt auf Mobil weg — kookie macht genau das
     (`.footer { margin-top: 0 }` unter 991px). Sie hat nur einen Sinn,
     solange die Sektion davor exakt 100vh hoch ist; bei Inhaltshoehe wuerde
     der Kontakt 100vh VOR dem Anfang der Clients beginnen und die Sektionen
     darueber ueberlagern. Hoehe und Sticky-Panel bleiben, weil die Wortmarke
     daran gescrubbt wird. */
  #contact { margin-top: 0; min-height: 160vh; }
  /* Mobil: nach dem Andocken nur noch 0,6 Bildschirmhoehen Halten statt einer
     ganzen — dort bewegen sich nur Bildparallaxe und Weissblende, eine volle
     Hoehe davon fuehlte sich wie Stillstand an. svh, damit das Panel nicht
     mit der Adressleiste springt. */
  @supports (height: 100svh) {
    #contact { min-height: 160svh; }
    .contact-inner { min-height: 100svh; }
  }
  /* Headline und E-Mail klebten oben, darunter standen 282px leer (gemessen
     bei 635px Fensterhoehe — 44% der Flaeche). `space-between` auf
     .contact-inner drueckt das erste Kind nach oben und das letzte nach
     unten; auf dem Desktop fuellt der Block den Raum, auf dem Handy nicht.
     `margin-block: auto` verteilt den Rest gleichmaessig ueber und unter dem
     Block, ohne die Wortmarke von der Unterkante zu loesen. */
  .contact-top { margin-block: auto; }

  /* Diese Regel war `.frame-bl { display: none }` und hatte NIE Wirkung:
     `.frame > div { display: flex }` ist spezifischer (Klasse + Typ). Die Zeile
     lief deshalb bis 375px durch und ueberlappte dort das "REC" rechts um 23px
     (gemessen). Ausgeblendet wird sie trotzdem nicht — Impressum und
     Datenschutz muessen nach § 5 DDG unmittelbar erreichbar bleiben.
     Auf Mobil fallen nur die beiden Social-Links und der Trenner weg;
     die stehen im Menue. */
  .frame > .frame-bl { gap: 14px; }
  .frame-bl a[target="_blank"], .frame-bl .frame-trenner { display: none; }
  .hero-foot { flex-direction: column; align-items: flex-start; gap: 20px; }
  .about-grid { grid-template-columns: 1fr; gap: 52px; }
  /* ── Expertise auf Mobil ────────────────────────────────────────────────
     Frueher wurde die Sektion hier gestapelt und per
     `.exp-word:not(:first-child) { display:none }` auf BRAND reduziert — von
     vier Disziplinen war auf dem Handy nur eine zu sehen. Entschieden wurde
     das Gegenteil: der Pin bleibt, die vier Disziplinen takten auch mit dem
     Daumen durch. Die gestapelte Fassung ist deshalb raus; hier stehen nur
     noch die Groessen. Die Sidenav bleibt weg (kein Platz), und je Disziplin
     laeuft nur EIN Objekt (siehe bauExpertise() in js/site.js). */
  .exp-sidenav { display: none; }
  /* Kuerzerer Scrollweg je Seite als auf dem Desktop (0,8 statt 1,05
     Viewporthoehen) — mit dem Daumen ist derselbe Weg zaeh. Stand vorher als
     Parameter im JS; seit dem Umbau auf sticky gehoert er hierher, weil die
     Hoehe der Sektion den Scrollweg bestimmt. */
  #expertise { --exp-weg: 80vh; }
  @supports (height: 100svh) { #expertise { --exp-weg: 80svh; } }
  .exp-page {
    /* links kein Sidenav-Zuschlag mehr, unten Luft fuer die Frame-Zeile */
    padding: clamp(76px,11vh,104px) var(--pad) clamp(96px,13vh,120px);
    justify-content: center;
  }
  .exp-intro { padding-left: var(--pad); padding-right: var(--pad); }
  /* 13vw waren zu zaghaft: auf 390px landete die Expertise-Headline bei 51px und
     damit UNTER den Clients-Namen (66px) — auf dem Desktop liegt sie mit 173px zu
     115px klar darueber. Grund war, dass Hero, Section- und Kontakt-Headline auf
     Mobil an ihren clamp-Boeden haengen (48/44/44), .exp-big aber weiter mit vw
     mitschrumpfte. 17vw hebt sie auf Clients-Niveau. Mehr geht nicht: das laengste
     Wort ist "Umsetzen" aus dem Intro-Zyklus, und bei 18vw bleiben je Seite nur
     noch 3-7px Luft — zu wenig fuer den Blur-Saum und jeden Font-Rueckfall.
     Gemessen mit mess/expfit.mjs ueber 360/375/390/414/430/768. */
  .exp-big, .exp-cycle, .exp-intro .exp-big { font-size: clamp(40px, 17vw, 96px); }
  .exp-meta { max-width: none; }
  .exp-desc { margin-bottom: 22px; }
  .exp-bar { height: clamp(28px,4vh,44px); margin-bottom: 14px; }
  /* Das Objekt sitzt unten rechts hinter dem Text und wird am Rand
     angeschnitten — dieselbe Idee wie auf dem Desktop, nur auf Daumenformat. */
  .exp-obj.is-front { --w: min(84vw, 420px); right: -14vw; top: auto; bottom: 4%; }
  /* derselbe gemessene Ausgleich wie oben, nur unten statt oben verankert */
  .exp-page[data-i="2"] .exp-obj.is-front {
    --w: min(103vw, 513px);
    right: calc(-14vw - .173 * var(--w));
    top: auto; bottom: calc(4% - .061 * var(--w));
  }
  .exp-obj.is-back  { display: none; }
  /* Auf Mobil rechnet passeWortmarke() die Groesse ebenfalls aus der
     gemessenen Breite — feste vw-Werte wuerden das ueberschreiben. */
  .foot-mark, .foot-mark-weiss { width: calc(100% + 30px); }
  .foot-mark { margin-left: -15px; }
  /* `margin-left: 0` ist hier PFLICHT: das Element traegt beide Klassen
     (`class="foot-mark foot-mark-weiss"`), die Regel `.foot-mark` darueber
     gibt ihm also ebenfalls -15px. Zusammen mit `left: -15px` sass die weisse
     Ebene dadurch 30px links statt 15 — gemessen 15px Versatz gegen die
     Bildebene, sichtbar als doppelter, unscharfer Schriftzug beim Aufblenden.
     Auf dem Desktop stand die Korrektur laengst, im Mobilblock fehlte sie. */
  .foot-mark-weiss { left: -15px; right: -15px; width: auto; margin-left: 0; }
}

/* ══════════════════════════════════════════
   SCHMALE SCREENS — Frame entzerren
══════════════════════════════════════════ */
@media (max-width: 620px) {
  .frame-logo { display: none; }              /* Wortmarke kollidiert mit Nav-Label; Hero zeigt Namen ohnehin */
  .frame-tl { gap: 12px; }
  .nav-line { width: 48px; }
  /* GEMESSENE URSACHE des horizontalen Ueberlaufs auf dem iPhone SE:
     drei Spalten passen bei 375px nicht mehr. Verfuegbar sind 375 - 2x24px
     Rand = 327px, also 109px je Spalte; abzueglich der 30px Spaltenluft bleiben
     79px. Die Zahl "500+" ist bei der Mindestgroesse 52px aber rund 120px
     breit — 11px zu viel (Chromium 11, WebKit und Firefox 12, alle drei
     Engines identisch). Das war der einzige Ueberlauf auf der ganzen Seite.
     Untereinander gestellt behalten die Zahlen ihre Groesse, und die Linien
     laufen waagerecht statt senkrecht. */
  .about-stats { grid-template-columns: 1fr; }
  .stat {
    border-right: none; border-bottom: 1px solid var(--border);
    padding: clamp(26px, 4vh, 38px) 0 clamp(20px, 3vh, 30px);
  }
  .stat + .stat { padding-left: 0; }
  .stat:last-child { border-bottom: none; }
}

/* ══════════════════════════════════════════
   TOUCH — kein Custom-Cursor
══════════════════════════════════════════ */
@media (hover: none), (pointer: coarse) {
  body { cursor: auto; }
  .cursor-wrapper { display: none; }
}

/* ══════════════════════════════════════════
   REDUCED MOTION
══════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; } }
body.reduced #loader { display: none !important; }
body.reduced #grain { display: none; }
body.reduced .hero-line-inner,
body.reduced .loader-name-inner,
body.reduced .s-line-inner,
body.reduced .contact-line-inner { transform: none !important; }
body.reduced .hero-foot,
body.reduced .hero-label,
body.reduced .scroll-cue,
body.reduced .hero-rec,
body.reduced .frame,
body.reduced .work-label,
body.reduced .work-soon,
body.reduced .work-soon-sub,
body.reduced .s-label,
body.reduced .about-lead,
body.reduced .about-text p,
body.reduced .about-photo,
body.reduced .stat-n,
body.reduced .stat-l,
body.reduced .contact-s-label,
body.reduced .contact-cta,
body.reduced .contact-note { opacity: 1 !important; transform: none !important; }

/* ══════════════════════════════════════════
   EYEBROW-LABELS — einheitliches Muster
══════════════════════════════════════════ */
.s-label, .work-label, .contact-s-label, .stat-l, .exp-hint,
.exp-cats span, .menu-foot span, .foot-bar {
  font-size: var(--t-micro); font-weight: 700; letter-spacing: .2em; text-transform: uppercase;
}

/* ══════════════════════════════════════════
   DISPLAY-TYPO — einheitlich Space Grotesk (max. 700)
══════════════════════════════════════════ */
.hero-line, .s-line, .exp-big, .contact-line,
.work-big, .menu-links a, .stat-n, .cl, .loader-name {
  font-family: var(--font-display);
  font-weight: 700;
}
.hero-line { letter-spacing: -.03em; }


/* ══════════════════════════════════════════
   SPARMODUS  (html.sparen — per ?sparen=1 oder Panel)
   Drosselt die Effekte, die auf schwacher Grafik teuer werden koennen.
   Bewusst konservativ: die Bewegung bleibt, nur die Unschaerfen werden
   kleiner. Gedacht zum A/B-Vergleich im echten Browser.
══════════════════════════════════════════ */
html.sparen .cl-blur { filter: blur(8px); }
html.sparen .exp-big { filter: none !important; }
html.sparen .foot-mark { will-change: auto; }

/* ══════════════════════════════════════════
   GROSSE DISPLAYS  (ab 1800px Breite)

   Gemessen ueber acht Aufloesungen: saemtliche clamp()-Deckel sind
   zwischen 1450px und 1730px Viewportbreite erreicht. Ab da friert die
   komplette Typografie ein, waehrend die Flaeche weiterwaechst — auf
   WQHD und 4K schrumpft das Layout dadurch optisch zusammen (Headline
   200px in 1728px Breite = 11,6% der Flaeche, dieselben 200px in 3840px
   = 5,2%).

   --gross ist ein gemeinsamer Zuschlag, der erst ab 1800px greift und bei
   20px endet. Er ist zusaetzlich an die Hoehe gekoppelt (2.6vh - 26px),
   damit flache Fenster wie 3440x1440 nicht denselben Zuschlag bekommen
   wie ein 4K-Schirm — sonst waeren die fuenf Clients-Reihen dort nicht
   mehr in die Sektion gegangen.

   Multiplikatoren: Displaytypo waechst am staerksten, Lesetext nur
   leicht (16px -> 20px bleibt lesbar, 16px -> 30px waere es nicht).
   Unter 1800px aendert dieser Block nichts. */
@media (min-width: 1800px) {
  :root {
    --gross: clamp(0px, min(calc(1.8vw - 32.4px), calc(2.6vh - 26px)), 20px);

    --t-lead:  calc(23px + var(--gross) * .35);
    --t-body:  calc(16px + var(--gross) * .20);
    --t-micro: calc(10px + var(--gross) * .125);
    --pad:     calc(72px + var(--gross) * 1.6);
  }

  /* --- Displaytypografie --- */
  .hero-line     { font-size: calc(170px + var(--gross) * 3.80); }
  .work-big      { font-size: calc(230px + var(--gross) * 5.15); }
  .exp-big,
  .exp-cycle,
  .exp-intro .exp-big { font-size: calc(200px + var(--gross) * 4.50); }
  .s-line        { font-size: calc(96px  + var(--gross) * 2.15); }
  .contact-line  { font-size: calc(122px + var(--gross) * 2.75); }
  .stat-n        { font-size: calc(118px + var(--gross) * 2.65); }
  .cl            { font-size: calc(132px + var(--gross) * 2.20); }
  .menu-links a  { font-size: calc(104px + var(--gross) * 2.35); }
  .cta-email     { font-size: calc(46px  + var(--gross) * 1.05); }
  .exp-plus      { font-size: calc(30px  + var(--gross) * .65); }
  .work-soon     { font-size: calc(17px  + var(--gross) * .15); }

  /* --- Navigation, Labels, Kleintext --- */
  .nav-num, .nav-label, .hl-name, .hl-desc,
  .hero-rec span:last-child { font-size: calc(14px + var(--gross) * .15); }
  .cta-arrow     { font-size: calc(28px + var(--gross) * .40); }
  .contact-note  { font-size: calc(13px + var(--gross) * .15); }
}

/* ══════════════════════════════════════════
   TASTATURFOKUS

   Die Seite hatte keine einzige :focus-Regel bei 28 fokussierbaren
   Elementen — zusammen mit cursor:none war sie per Tastatur blind
   bedienbar. Zwei Ringe statt einem, weil der Fokus auf drei Untergruenden
   sitzen kann: dunkel (#0d0b0b), hell (#ebebeb) und rosa (#C49498). Ein
   einfarbiger Ring verschwindet auf mindestens einem davon.

   :focus-visible statt :focus — Mausklicks loesen ihn nicht aus, nur
   Tastatur. Das transparente outline ist fuer den Windows-Kontrastmodus,
   der box-shadow ignoriert. */
:focus-visible {
  outline: 2px solid transparent;
  outline-offset: 2px;
  box-shadow: 0 0 0 2px #0d0b0b, 0 0 0 5px #f0ebe3;
  border-radius: 3px;
}
.theme-light :focus-visible,
.contact-inner :focus-visible {
  box-shadow: 0 0 0 2px #f0ebe3, 0 0 0 5px #0d0b0b;
}
/* Grosse Wortmarken brauchen den Ring enger am Text, sonst laeuft er aus dem Bild */
.menu-links a:focus-visible { border-radius: 6px; box-shadow: 0 0 0 2px #0d0b0b, 0 0 0 5px #f0ebe3; }

/* RUECKFALL fuer Browser ohne :focus-visible (Safari vor 15.4). Dort war die
   Seite per Tastatur wieder blind, weil oben ausschliesslich :focus-visible
   steht. Der Trick ist das Paar aus beiden Regeln:
     - `:focus` gibt jedem Browser einen Ring
     - `:focus:not(:focus-visible)` nimmt ihn dort wieder weg, wo der Fokus
       nur von der Maus kommt. Browser, die :focus-visible NICHT kennen,
       verstehen auch diesen Selektor nicht und verwerfen die ganze Regel —
       sie behalten also den Ring. Genau das soll passieren. */
:focus {
  outline: 2px solid transparent;
  outline-offset: 2px;
  box-shadow: 0 0 0 2px #0d0b0b, 0 0 0 5px #f0ebe3;
  border-radius: 3px;
}
.theme-light :focus, .contact-inner :focus { box-shadow: 0 0 0 2px #f0ebe3, 0 0 0 5px #0d0b0b; }
:focus:not(:focus-visible) { outline: none; box-shadow: none; }

/* Clients-Wand auf Touch: vertikales Scrollen gehoert immer dem Browser.
   Die Lupe hoert nur passiv mit (js/site.js, Touch-Zweig). Vorher schluckte
   ein touchmove mit preventDefault jeden Wisch auf der Wand. */
@media (hover: none), (pointer: coarse) {
  .clients-marquee { touch-action: pan-y; }
}

/* ── Hero im Hochformat ──────────────────────────────────────────────────
   GEMESSEN (21.09.): Video und Ersatzbild sind 16:9. Hochkant zeigt
   object-fit:cover nur die mittleren 34% der Breite — und genau dort ist das
   Material fast schwarz (Helligkeit 13 von 255; links 88, rechts 62). Die
   Glasobjekte liegen an den Raendern und wurden weggeschnitten. Auf dem
   iPhone sah der Hero deshalb aus, als liefe weder Video noch Ersatzbild.
   Rechter Ausschnitt: die Objekte stehen oben rechts, der Name unten links
   bleibt auf dunklem Grund. Das Overlay ist leichter, weil der radiale
   Verlauf im Hochformat die Ecken komplett zugedeckt hat. */
@media (max-aspect-ratio: 4/5) {
  .hero-vid { object-position: 100% 50%; }
  .hero-bg  { background-position: 100% 50%; }
  .hero-bg::after {
    background: linear-gradient(to top, var(--bg) 3%, rgba(13,11,11,.62) 34%, rgba(13,11,11,.12) 72%);
  }
}
