/* ============================================================
   DRKNSD — Styles v5
   Schwarz / Weiss. Eckig, harte Kanten, keine Rundungen.
   Reduziert auf Hero + Projektnamen. Keine Trennlinien,
   keine kleinen Beitexte. Eine Schriftstaerke fuer alles.

   v5: EINE Bewegungskurve und drei Zeitstufen fuer die ganze
   Seite, alle Abstaende auf dem 4/8er Raster. Typografie,
   Farben und Kantenhaerte bleiben unveraendert.
   ============================================================ */

:root {
  color-scheme: dark;
  --bg: #000000;
  --fg: #ffffff;
  --dim: #6f6f6f;
  --soft: #d6d6d6;
  --void: #050505;
  --scrim: 0, 0, 0;
  --scrim-top: 0.4;
  --scrim-bottom: 0.5;
  --logo-invert: 0;
  /* Satzbreite der Seite. Bis 15.09.2026 ein fester Riegel von 1600px:
     auf einem breiten Schirm standen Hero und Werkliste ueber die volle
     Breite, alles dazwischen (Latest Projects, Selected Works, die
     Projektseiten, die Fussmarke) blieb in diesem Riegel stehen und sass
     als schmales Band in der Mitte (Leon 01.10.2026: "die Hero-Seite
     sehr breit und die Portfolio Section sehr breit und dann die Sachen
     dazwischen haben quasi eine fixe Breite ... waere geil wenn das
     auch sich dann groesser skalieren wuerde, also wenn die Videos dann
     groesser werden wuerden"; zur Fussmarke: "die ist auch so
     eingerueckt, sollte aber alles einheitlich sein").
     Jetzt waechst das Mass mit dem Fenster. Die untere Schranke von
     1600px haelt alle Fenster bis 1684px exakt auf dem alten Stand —
     dort war nie der Riegel wirksam, sondern der Seitenrand, und ein
     kleineres Mass wuerde die Seite dort schmaler machen statt
     breiter. Die obere Schranke steht, damit Fliesstext auch auf einer
     Leinwand lesbar bleibt; die Textspalten haben darueber hinaus ihre
     eigenen ch-Grenzen. */
  --maxw: clamp(1600px, 95vw, 2400px);
  /* Eine Kurve fuer alles, damit die Seite als ein Koerper reagiert */
  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  --t-fast: 200ms;
  --t-base: 300ms;
  --t-slow: 400ms;
  --pad: 24px;
  /* Hoehe der Marke oben rechts auf den Unterseiten (30px Bild + 2x4px
     Polster). Der Film darunter rechnet seinen Abstand daraus, damit er
     auf keiner Unterseite unter Back und Marke laeuft (Leon 02.10.2026:
     "so ruecken, dass das Logo und das Back nicht ins Video reinragt bei
     keiner der Unterseiten"). */
  --head-h: 38px;
  /* ---- Abstandsleiter ----
     Leon 13.08.: "Check mal alle Spaces zwischen den Seiten ist das alles
     Apple Design konform von den Abstaenden etc? Teilweise etwas
     unbalanciert."
     Er hat recht, und der Grund war messbar: die Sektionen standen auf
     drei verschiedenen Systemen gleichzeitig. Latest rechnete in vh
     (7vh/8vh), die Unterseiten in vw (8vw/10vw), Works in festen Prozent
     der Hoehe, dazu einzelne feste Werte. vh und vw wachsen aber in
     verschiedene Richtungen — auf einem breiten, flachen Laptopschirm
     wurde der eine Abstand gross und der andere klein, obwohl beide
     dasselbe trennen sollten. Genau das liest sich als unbalanciert.
     Jetzt eine Leiter, eine Achse (Breite), alle Endwerte Vielfache von
     8. Vier Stufen reichen fuer die ganze Seite:
       --gap-s   Bloecke innerhalb einer Sektion
       --gap-m   zusammengehoerende Sektionen
       --gap-l   Regelabstand zwischen zwei Sektionen
       --gap-xl  die grosse Zaesur */
  --gap-s: clamp(24px, 3vw, 48px);
  --gap-m: clamp(40px, 5vw, 72px);
  --gap-l: clamp(64px, 8vw, 120px);
  --gap-xl: clamp(96px, 12vw, 176px);
  /* Kantenlaenge des Wuerfel-Hosts. Steht hier, weil sich die Zeile
     unter dem Wuerfel daran haengt: schriebe man das Mass zweimal,
     liefe der Abstand beim naechsten Anfassen auseinander. */
  /* Deckel von 820 auf 1040 (01.10.2026, Leon: "wenn wir es relativ
     breit anschauen ... dass sich das alles so sauber skaliert"). Der
     Wuerfel muss in die Fensterhoehe passen, deshalb bleibt vmin das
     Mass — auf einem breiten, flachen Schirm bindet ohnehin die Hoehe
     und es aendert sich nichts. Nur auf hohen grossen Schirmen stand
     vorher der Deckel im Weg und der Wuerfel blieb klein, waehrend
     ringsum alles mitwuchs. */
  --cube-size: clamp(320px, 68vmin, 1040px);
  /* Typo aus dem Logo (Leon 12.08.: "die haupt schriftart soll die gleiche
     sein wie das drknsd logo"). Die Wortmarke im Logo wurde gegen die
     schweren kursiven Grotesken gemessen: Umriss von DRKNSD auf gleiche
     Hoehe und Breite normiert und die Deckung gerechnet. Figtree Black
     Italic deckt 0,82, Inter 0,80, Montserrat nur 0,72 — Montserrat war
     also wirklich die falsche Familie und ist raus.
     Alles auf der Seite laeuft in dieser Familie, nur in zwei Staerken. */
  --font: "Figtree", "Helvetica Neue", Helvetica, Arial, sans-serif;
  /* Nur fuer die technische Kleinstbeschriftung an der Hero. Monospace,
     damit Ziffern in einer festen Spur laufen und der hochzaehlende
     Counter nicht zappelt. */
  /* Martian Mono statt Roboto Mono (Leon 07.08.: "zu rund und zu duenn,
     wir wollen eher was Eckiges, Kantiges"). Gerade Abstriche, flache
     Bogen, quadratische Punzen — liest sich als Geraetebeschriftung.
     Im schmalen Schnitt (wdth 75), sonst ist die neunstellige Zahl
     breiter als der Wuerfel. */
  --mono: "Martian Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* Bright Side: gleiche Seite, gedrehte Werte. Alles laeuft ueber diese
   Variablen, deshalb kippt der Schalter die komplette Seite auf einmal. */
html.bright {
  /* Muss mitwandern. Stand vorher nur einmal fest auf `dark` in :root,
     also hat der Bright Mode dem System weiter "diese Seite ist dunkel"
     gemeldet — iOS hat die Statusleiste dann in Weiss beschriftet, auf
     einer weissen Seite (Leon 25.08.2026). */
  color-scheme: light;
  --bg: #ffffff;
  --fg: #000000;
  --dim: #9b9b9b;
  --soft: #2e2e2e;
  --void: #f2f2f2;
  --scrim: 255, 255, 255;
  /* Oben kraeftiger, damit Logo, Navigation und Schalter in Schwarz
     auch ueber dem dunklen Hero-Bild lesbar bleiben. */
  --scrim-top: 0.82;
  --scrim-bottom: 0.35;
  --logo-invert: 1;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

/* Die Grundfarbe steht auf html UND body. iOS malt den Bereich hinter
   der Statusleiste und den Ueberzug beim Verscrollen aus der Wurzel, und
   wenn dort nichts Deckendes steht, nimmt Safari Weiss.
   overscroll-behavior nimmt den Gummizug am oberen Rand ganz heraus.
   Genau der hat Leon den weissen Streifen gezeigt (25.08.: "wenn ich auf
   der Webseite hochscrolle, dann sieht man auch einen weissen
   Hintergrund"). Ohne Gummizug gibt es keine Flaeche mehr, die ueber dem
   Dokument aufgehen und etwas anderes zeigen koennte. */
html {
  -webkit-text-size-adjust: 100%;
  background: var(--bg);
  overscroll-behavior-y: none;
}

body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font);
  font-weight: 700;
  line-height: 1.35;
  overflow-x: hidden;
  overscroll-behavior-y: none;
  /* Bis zur Unterkante der Notch-Region durchziehen. Ohne viewport-fit
     und dieses Polster sass die Kopfzeile darunter und darueber blieb
     eine fremde Flaeche stehen. */
  min-height: 100svh;
  -webkit-font-smoothing: antialiased;
  transition: background-color var(--t-slow) var(--ease), color var(--t-slow) var(--ease);
}

a { color: inherit; text-decoration: none; }
img, video { display: block; max-width: 100%; }

/* Kleintext-Basis: gleiche Familie, gleiche Schraege, nur kleiner.
   Ersetzt die duenne, weit gesperrte Variante aus v3. */
.ui {
  font-weight: 800;
  font-style: italic;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

/* Magnetischer Cursor blendet den System-Cursor aus (nur Desktop, per JS-Klasse)
   Die Liste war abzaehlbar: body, a, .magnetic. Alles andere behielt
   seinen eigenen Zeiger — Knoepfe und der Modus-Schalter bekommen vom
   Browser `cursor: pointer`, Textbloecke `text`, der Wuerfel-Host
   `grab`. Beim Fahren ueber diese Stellen blitzte der echte Zeiger auf,
   obwohl nur das Viereck zu sehen sein soll (Leon 01.10.2026: "irgendwie
   sieht man beim Bewegen manchmal so den richtigen Cursor, da soll man
   ja eigentlich nur das Viereck sehen").
   Jetzt deckt die Regel jedes Element ab. `!important` ist noetig, weil
   einzelne Bausteine ihren Zeiger selbst setzen (z.B. `.mode-switch`
   mit `cursor: pointer`, `.hero-cube` mit `grab`) und dabei die
   hoehere Spezifitaet haben. Der Griff-Zeiger am Wuerfel faellt damit
   weg — das Viereck ist der Zeiger der Seite, das ist Absicht. */
body.cursor-on, body.cursor-on * { cursor: none !important; }

/* Waehrend des Intros wird nicht gescrollt */
body.intro-lock { overflow: hidden; }

/* ---------- Streifen hinter der Statusleiste ----------
   Die Seite laeuft mit `viewport-fit=cover` bis unter die Notch. Welche
   Farbe iOS dort zeigt, haengt sonst allein an `theme-color`, und darauf
   ist kein Verlass: Safari zieht den Wert beim Umschalten nicht immer
   nach, dann steht ueber einer schwarzen Seite ein weisser Balken
   (Leon 25.08.2026: "dieser weisse Bereich oben sieht Kacke aus, muss
   immer je nach Schalter im Ton der Seite").
   Deshalb malt die Seite den Streifen selbst, in `var(--bg)`. Damit
   folgt er dem Schalter zwangslaeufig und blendet mit derselben Kurve
   um wie der Rest. z-index 300 liegt ueber allem, auch ueber der
   Intro-Ebene (200) — sonst waere der Streifen genau waehrend der
   Startanimation wieder fremd. Auf Geraeten ohne Notch ist
   `env(safe-area-inset-top)` gleich 0, dann ist der Streifen nicht da. */
body::before {
  content: "";
  position: fixed;
  top: 0; left: 0; right: 0;
  height: env(safe-area-inset-top, 0px);
  background: var(--bg);
  z-index: 300;
  pointer-events: none;
  transition: background-color var(--t-slow) var(--ease);
}

/* ============================================================
   Intro — Wuerfel
   Kleines schwarzes Viereck wackelt, dreht sich in die Tiefe,
   wird als Wuerfel lesbar, rast auf den Betrachter zu und
   entlaesst ihn in der Hero.
   ============================================================ */
.intro {
  position: fixed;
  top: 0; right: 0; bottom: 0; left: 0; /* kein inset-Shorthand: iOS < 14.5 kennt es nicht */
  z-index: 200;
  background: #000000; /* schwarz — Safe-Area-Streifen oben/unten bleiben so dunkel */
  transition: opacity var(--t-slow) var(--ease);
}
.intro.done { opacity: 0; pointer-events: none; }
.intro.off { display: none; }

/* Waehrend der Drehung faehrt die Kopfzeile schon mit ein, also muss
   sie in dieser Zeit ueber dem Intro liegen statt darunter (Leon
   13.08.). Danach nimmt intro.js Klasse und Inline-Werte wieder weg
   und die Leiste sitzt wie immer auf ihrer eigenen Ebene. */
.topbar.intro-riding { z-index: 205; }

/* Cube-Animation laeuft auf einem Canvas-Element (per JS erzeugt).
   Kein CSS preserve-3d — iOS Safari unterstuetzt es nicht zuverlaessig. */

/* ---------- Koernung ueber der ganzen Seite ----------
   Liegt ueber allem Inhalt (hoechster Inhalts-z-index ist 60), aber
   unter dem Box-Cursor (100) und dem Intro (200) — der Zeiger soll
   scharf bleiben. Die Kachel baut grain.js einmal und haengt sie als
   Hintergrundbild an, hier steht nur Lage und Staerke.
   Der Mischmodus ist NICHT beliebig. overlay stand hier zuerst und war
   auf dieser Seite wirkungslos: overlay rechnet unterhalb der Mitte
   2 mal Grundwert mal Auflage, und der Grundwert ist hier reines
   Schwarz. Null mal irgendwas bleibt null — gemessen kam auf den
   dunklen Flaechen exakt 0,00 Streuung heraus, also gar kein Korn.
   Dasselbe gilt fuer soft-light und multiply auf Schwarz.
   Deshalb: screen auf der dunklen Seite, das kann Schwarz aufhellen.
   Auf der hellen Seite kann screen umgekehrt nichts mehr ausrichten
   (Weiss bleibt Weiss), dort traegt multiply das Korn. */
.page-grain {
  position: fixed;
  top: 0; right: 0; bottom: 0; left: 0;
  /* Ueber der Intro-Ebene (200), nicht darunter. Lag das Korn nur auf
     der Seite, fehlte es waehrend der Startanimation und kam in dem
     Moment dazu, in dem die Intro-Ebene weggenommen wird — gemessen ein
     Sprung von 4 bis 9 Stufen ueber die ganze Flaeche. Klein, aber auf
     einem glatten Verlauf sichtbar, und es war der letzte Rest des
     Uebergangs (Leon 01.09.). */
  z-index: 210;
  pointer-events: none;
  mix-blend-mode: screen;
  opacity: 0.055;
  background-repeat: repeat;
}
html.bright .page-grain { mix-blend-mode: multiply; opacity: 0.045; }

/* ---------- Magnetischer Box-Cursor ---------- */
.box-cursor {
  position: fixed;
  top: 0; left: 0;
  width: 16px; height: 16px;
  /* Immer weiss: der difference-Blend dreht ihn auf der Bright Side
     automatisch nach schwarz. Mit var(--fg) waere er dort unsichtbar. */
  border: 1.5px solid #ffffff;
  z-index: 100;
  pointer-events: none;
  transform: translate(-50%, -50%);
  /* Breite und Hoehe stehen hier BEWUSST nicht mehr im transition.
     cursor.js setzt sie jeden Frame neu und faehrt sie selbst weich
     nach; eine CSS-Blende darueber laeuft der eigenen Nachfuehrung
     hinterher und addiert ihre 200ms obendrauf. Genau das war das
     zaehe Gefuehl beim Zurueckschnappen (Leon 11.08.: "dauert zu
     lange, besonders zurueck auf Cursorgroesse"). */
  transition: border-color var(--t-fast) var(--ease), opacity var(--t-fast) var(--ease);
  mix-blend-mode: difference;
}
.box-cursor.hidden { opacity: 0; }

/* ---------- Top Bar ---------- */
.topbar {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 50;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--pad);
  padding-top: max(var(--pad), env(safe-area-inset-top));
  /* Die Leiste liegt ueber die volle Breite, ist aber bis auf Logo und
     Navigation durchsichtig. Ohne das fing sie nach dem Sprung auf WORKS
     die Maus ueber der ersten Namenszeile ab (14.09.2026: GG Poker,
     Deutschland, Adieu liessen sich nicht hovern). */
  pointer-events: none;
  /* Faehrt beim Runterscrollen aus dem Bild und beim Hochscrollen wieder
     ein (topbar.js setzt is-away). Stand bis 01.10.2026 nur am Handy,
     jetzt auf jeder Breite (Leon: "kannst du diese Header-Sektion beim
     Runterscrollen verschwinden lassen und beim Hochscrollen wieder
     auftauchen lassen"). Nur transform und opacity, damit im Layout
     nichts springt. */
  transition: transform 360ms var(--ease), opacity 260ms var(--ease);
  will-change: transform;
}
.topbar.is-away {
  transform: translateY(-110%);
  opacity: 0;
  pointer-events: none;
}
.topbar a, .topbar button, .topbar .magnetic { pointer-events: auto; }

/* Marke und Navigation stehen negativ zu dem, was hinter ihnen liegt
   (Leon 01.10.2026: "es waere auch nicht schlecht wenn Works und Contact
   und das Logo auch so negativ sind, wenn es auf schwarz ist dass es dann
   hell wird, weil sonst wird es manchmal einfach schlecht zu sehen,
   besonders wenn man die Videos jetzt darueber hat").

   difference statt einer Modusabfrage: die Leiste liegt ueber Filmen und
   ueber der Seite, und nur der Blend rechnet gegen das, was wirklich
   dahinter liegt. Eine Umschaltung nach Modus traf den Fall Video nicht —
   im Bright Mode stand die schwarze Marke auf dunklem Bild.
   Darum sind Schrift und Logo hier fest weiss: difference dreht Weiss auf
   hellem Grund selbst nach Schwarz. Waere das Logo schon schwarz
   (mode.js tauscht die Datei), kaeme auf weissem Grund wieder Weiss
   heraus. Die Umschaltung in mode.js ueberspringt die Kopfzeile deshalb.
   Bekannte Grenze: auf mittlerem Grau wird der Unterschied klein. */
.topbar { mix-blend-mode: difference; }
.topbar nav a { color: #ffffff; }
.topbar .logo-box img { filter: none; }
/* Ohne Kasten (Leon 11.08.: "kasten um das drknsd braucht man nicht
   oben im header"). Das Zeichen ist selbst schon ein Rahmen, der
   zweite drumherum war ein Rahmen um einen Rahmen. Das Polster bleibt,
   sonst klebt die Marke an der Bildkante. */
.logo-box {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 8px 12px;
  line-height: 0;
  gap: 5px;
}
/* Das Original-Lockup traegt den Schriftzug selbst, deshalb steht
   darunter kein getippter Text mehr (Leon 11.08.: "nutz ueberall das
   original logo was ich dir gegeben habe"). Es ist hochformatig, bei
   den alten 24px waere es keine 21px breit und der Schriftzug nicht
   mehr lesbar — daher ueber die Hoehe groesser gestellt. */
.logo-box img { height: 46px; width: auto; display: block; }
.topbar nav { display: flex; gap: 8px; }
.topbar nav a {
  display: inline-block;
  padding: 8px 12px;
  font-size: 13px;
  font-weight: 800;
  font-style: italic;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

/* ---------- Hero ---------- */
.hero {
  position: relative;
  height: 100vh;
  height: 100svh;
  width: 100%;
  overflow: hidden;
}
.hero .hero-room, .hero .hero-fallback {
  position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%;
}
/* Das Fallback liegt im DOM nach dem Raum und wuerde ihn sonst
   verdecken. Es ist nur die Grundfarbe darunter. */
.hero .hero-room { z-index: 1; display: block; }
/* Der Lichtschein liegt ueber dem Wuerfel (z-index 3), aber vor den
   Beschriftungen (4) — die stehen im DOM danach und bleiben damit
   obenauf, sonst wuerde der Schein ueber die FIG-Zeilen laufen.
   screen statt normalem Auftragen: der Schein soll ADDIEREN, sonst
   legt er nur einen milchigen Schleier ueber den Wuerfel. */
.hero .hero-glow {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  width: 100%; height: 100%;
  z-index: 4;
  display: block;
  pointer-events: none;
  mix-blend-mode: screen;
}
.hero .hero-fallback { background: var(--void); z-index: 0; }
.hero::after {
  content: "";
  position: absolute; top: 0; right: 0; bottom: 0; left: 0;
  z-index: 2; /* ueber dem Video, unter dem Logo */
  background: linear-gradient(180deg, rgba(var(--scrim),var(--scrim-top)) 0%, rgba(var(--scrim),0) 35%, rgba(var(--scrim),var(--scrim-bottom)) 100%);
  pointer-events: none;
}
/* Uebergang Hero -> Works: der Nullraum ist unten noch aufgehellt, die
   Works-Sektion beginnt in var(--bg). Ohne Verlauf steht dort ein
   klarer Strich (Leon 07.08.). Diese Blende faehrt die untere Haelfte
   der Hero genau auf --bg herunter, also exakt auf die Farbe des
   naechsten Abschnitts. Zwei Stopps in der Mitte biegen die Kurve,
   sonst liest sich der lineare Abfall selbst wieder als Kante. */
.hero::before {
  content: "";
  position: absolute; right: 0; bottom: 0; left: 0;
  height: 55%;
  z-index: 2;
  /* Die Blende darf erst spaet zupacken: der Schatten des Wuerfels
     liegt bei rund 73% dieser Bahn und wurde von der alten Kurve zu
     drei Vierteln weggeloescht (Leon 07.08.: "man sieht den irgendwie
     nicht mehr"). Der Endpunkt bleibt exakt --bg, die Kante zur
     Works-Sektion ist also weiterhin zu. */
  background: linear-gradient(180deg,
    rgba(var(--scrim), 0) 0%,
    rgba(var(--scrim), 0.12) 50%,
    rgba(var(--scrim), 0.45) 76%,
    var(--bg) 100%);
  pointer-events: none;
}

/* Wuerfel zentriert in der Hero, an der Stelle wo vorher das Logo
   stand. Quadratisch, damit die Perspektive nicht kippt. */
.hero-cube {
  position: absolute;
  top: 50%; left: 50%;
  /* --bob setzt room.js, --scroll-s und --scroll-y ebenfalls.
     Scale laeuft beim Scrollen von 1.0 auf ~0.68, damit der Wuerfel
     kleiner wird wenn der User nach unten scrollt (Leon 11.08.2026). */
  transform: translate(-50%, -50%) translateY(var(--bob, 0px)) translateY(var(--scroll-y, 0px)) scale(var(--scroll-s, 1));
  z-index: 3;
  width: var(--cube-size);
  height: var(--cube-size);
  /* Der Wuerfel nimmt die Geste selbst an, ringsum bleibt gescrollt */
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
}
/* Keine Greifhand. Auf dieser Seite gibt es genau einen Zeiger, den
   Kasten — ein zweiter Systemcursor ueber dem Wuerfel bricht das
   (Leon 11.08.: "da soll nur der Kasten Cursor sein"). Dass der Wuerfel
   greifbar ist, zeigt er selbst: er nimmt beim Hovern Fahrt auf und
   skaliert an. */
/* Die Skalierung liegt auf dem Canvas, nicht auf dem Host: der Host
   traegt bereits das Zentrier-translate und wuerde davon springen. */
.hero-cube .cube-canvas {
  display: block; width: 100%; height: 100%;
  /* Gaussische Unschaerfe: nur ein Hauch, der Wuerfel soll plastisch
     wirken und nicht unscharf (Leon 07.08.: 1.2px war zu stark). */
  filter: blur(0.4px);
  transition: transform var(--t-base) var(--ease), filter var(--t-base) var(--ease);
}
/* Hover: der Wuerfel nimmt die Naehe des Zeigers an, bevor man greift.
   Klein gehalten, es ist ein Hinweis auf Greifbarkeit, kein Effekt. */
@media (hover: hover) and (pointer: fine) {
  .hero-cube.hot .cube-canvas { transform: scale(1.03); }
}
/* Die Videoquelle haengt im DOM, damit iOS sie dekodiert, ist aber
   nicht zu sehen — sichtbar wird sie nur als Textur im WebGL. */
.hero-cube .cube-video-src {
  position: absolute; top: 0; left: 0;
  width: 1px; height: 1px;
  opacity: 0; pointer-events: none;
}
/* Ohne WebGL bleibt die Wortmarke stehen */
.hero-cube.cube-fallback {
  background: url("../media/drknsd_logo_white.png") center / contain no-repeat;
  filter: invert(var(--logo-invert));
}

/* Kein Invert-Filter mehr: mode.js laedt die passende Originaldatei
   (weiss oder schwarz). Invertieren stimmte nur solange das Logo auf
   der Seitenfarbe liegt — auf der Projektseite sitzt es ueber dem Film
   und wurde im Bright Mode schwarz auf schwarz (Leon 11.08.).
   Ueber Medien traegt es einen weichen Schatten, damit es auf hellen
   wie dunklen Bildern steht. */
.logo-box.on-media img {
  filter:
    drop-shadow(0 1px 2px rgba(0,0,0,0.45))
    drop-shadow(0 4px 10px rgba(0,0,0,0.35));
}
/* Leon 02.10.2026: "das Back und Dark and Side Logo nimmt noch zu viel
   Platz ein". Die Marke auf den Unterseiten stand auf derselben Hoehe wie
   im Kopf der Startseite und schob sich am Handy ueber das Video. Auf den
   Unterseiten steht sie frei im Bild und darf kleiner sein, auf der
   Startseite bleibt sie wie sie ist. --head-h rechnet die Hoehe fuer den
   Abstand zum Film weiter unten. */
.logo-box.on-media { padding: 4px 6px; mix-blend-mode: difference; }
/* Gleiche Begruendung wie beim Back-Knopf und wie in der Kopfzeile der
   Startseite: die Marke steht fest ueber der Seite und laeuft beim
   Scrollen von schwarzem Filmbereich auf die helle Platte. Mit den
   Schlagschatten blieb dort ein grauer Kasten stehen. mode.js laesst
   on-media bewusst weiss, difference dreht sie auf hellem Grund selbst
   nach schwarz. */
.logo-box.on-media img { height: 30px; filter: none; }

/* ---------- Works / Textwand ---------- */
/* Die Werkliste ist so hoch wie ihr Inhalt. Bis 14.09.2026 war sie fest
   eine Bildschirmhoehe und die Namen standen mittig darin: bei Leons
   Fenster (1467x1032) blieben ueber und unter dem Block je 182 Punkte
   Schwarz, zusammen mit dem Abstand der Latest-Sektion 299 Punkte bis
   zum ersten Namen (Leon: "den Abstand zwischen dieser Latest Projects
   Region und diesem Portfolio ... finde ich zu gross"). */
.works {
  position: relative;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* Alle Projektnamen als ein fliessender Block, vertikal zentriert */
.work-cloud {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  padding: var(--gap-s) var(--pad);
  overflow: hidden;
}
.work-cloud-inner {
  width: 100%;
  font-weight: 900;
  font-style: italic;
  text-transform: uppercase;
  font-size: clamp(40px, 6.9vw, 104px);
  line-height: 0.98;
  letter-spacing: -0.03em;
  color: var(--dim);
  white-space: normal;
  word-spacing: 0.03em;
}

/* Kleine Zeile ueber der Werkliste. Nicht alles darin ist eine
   DRKNSD-Produktion, bei einigen hat Specter Berlin nur Regie gefuehrt
   (Leon 14.09.2026: "ueber dem Portfolio muss einmal stehen DRKNSD
   Productions and Works by Specter Berlin ... in klein"). Gleiche
   Schrift wie die Kopfzeile der Mailzeile. */
.works-kicker {
  margin: 0;
  padding: var(--gap-s) var(--pad) 0;
  font-family: var(--mono);
  font-stretch: 75%;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--dim);
}

.work-word {
  color: var(--dim);
  transition: color var(--t-base) var(--ease), opacity var(--t-base) var(--ease);
  white-space: nowrap;
  /* Stapelreihenfolge gegen das Vorschaufenster (z-index 40): die
     Namen liegen darunter, der gerade gehoverte darueber. Damit deckt
     das Video die Wand ab, laesst aber genau das Wort frei, zu dem es
     gehoert (Leon 11.08.: "wenn das Video das Wort ueberdeckt, soll es
     hinter dem Wort stehen, ueber den anderen aber drueber"). */
  position: relative;
  z-index: 1;
}
/* Trenner ist das Hashtag aus der Wortmarke, kursiv und in der
   Groesse der Projektnamen. */
.work-sep {
  color: var(--dim);
  font-style: italic;
}

/* Der angewaehlte Name leuchtet, der Rest dimmt weiter runter. Das galt
   bisher nur fuer Zeigergeraete — auf dem Handy waehlt aber der Scroll
   aus, und dort stand der Name zu dem die Vorschau lief in genau
   demselben Grau wie alle anderen (Leon 13.08.: "am Handy wenn man durch
   die Projekte scrollt muss das was angewaehlt ist und von dem eine
   Vorschau gezeigt wird sich hervorheben durch staerkere Farbe. Klares
   weis oder schwarz"). var(--fg) ist genau das: reines Weiss im dunklen
   Modus, reines Schwarz im hellen. */
.work-cloud.has-hover .work-word { opacity: 0.32; }
.work-cloud.has-hover .work-word.is-active {
  opacity: 1;
  color: var(--fg);
  z-index: 45;
}

/* ---------- Hero-Beschriftung links und rechts vom Wuerfel ----------
   Klein, ruhig, auf Hoehe der Wuerfelmitte. Links die Views-Zahl aus
   der YouTube API, rechts die Ortsmarke. */
/* Leon 07.08.2026: "mach diese Texte eher wie so ein Micrographic Ding,
   nicht so wie Schrift."
   Micrographics = technische Kleinstbeschriftung als Gestaltungsmittel:
   Messgeraete, Schaltplaene, Typenschilder. Deshalb Monospace statt der
   kursiven Hausschrift, Index-Kennung vor jedem Block, Koordinaten als
   Textur und alles auf einem engen Stapel. Es liest sich als Markierung
   am Objekt, nicht als Satz.
   Alles bleibt voll deckend weiss (Leon 07.08.), die Staffelung laeuft
   ueber Groesse und Laufweite, nicht ueber Graustufen. */
.hero-meta {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 4;
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-family: var(--mono);
  font-stretch: 75%;
  color: var(--fg);
  text-transform: uppercase;
  white-space: nowrap;
  pointer-events: none;
}
.hero-meta-left  { left:  clamp(20px, 5vw, 88px); }
.hero-meta-right { right: clamp(20px, 5vw, 88px); text-align: right; align-items: flex-end; }

/* Anrisskante ueber dem Block — kurz wie ein Skalenstrich, nicht als
   Trennlinie durchs Bild. */
.hero-meta::before {
  content: "";
  width: 16px; height: 1px;
  background: var(--fg);
  margin-bottom: 4px;
}

/* Laufweiten deutlich enger als bei Roboto Mono: Martian Mono bringt
   den Weissraum schon in der Glyphe mit, die alten Werte rissen die
   Zeilen auseinander. Gewichte eine Stufe hoeher, damit nichts duenn
   wirkt. */
/* Alles slim (Leon 02.09.: "ich glaub ich wuerde die alle duenn machen.
   Alles slim und schlank und duenn"): die Micrographics laufen eine
   Gewichtsstufe leichter, Werte in 500 statt 700. Martian Mono liegt
   in 500/600/700 vor, leichter geht ohne neue Schriftdatei nicht. */
.meta-tag {
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 0.13em;
}
/* Die Zahl laeuft sekundenweise hoch. Ohne tabellarische Ziffern
   wackelt bei jedem Takt die ganze Zeile, weil die 1 schmaler ist als
   die 8. font-variant-numeric reicht dafuer nicht ueberall, deshalb
   zusaetzlich das OpenType-Feature direkt. */
.meta-val {
  font-size: clamp(13px, 1.3vw, 17px);
  font-weight: 500;
  letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}
.meta-sub {
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 0.1em;
}
/* Die mittlere Stufe (.meta-sub--mid, "AND WORKS") ist am 02.10.2026
   raus: die Zeile rechts am Wuerfel steht jetzt in EINER Groesse und
   klappt zwischen ihren beiden Angaben um, siehe index.html und
   micro.js claimFlap. */
/* Trenner zwischen zwei Angaben: ein Punkt auf halber Hoehe, wie auf
   einem Typenschild. Als Element statt als Zeichen, damit die Laufweite
   ihn nicht mitzieht. */
.meta-sep {
  display: inline-block;
  width: 3px; height: 3px;
  margin: 0 8px;
  background: var(--fg);
  vertical-align: 0.12em;
}

/* Waehrend der Wuerfel zurueckfaehrt liegen die Beschriftungsbloecke
   ueber der Intro-Ebene, damit sie sich schon aufbauen koennen.
   NUR der z-index wird angehoben, die Position bleibt `absolute`.
   Hier stand vorher `position: fixed` mit der Annahme, `fixed` sitze
   in dieser Phase exakt wo `absolute` sass. Das stimmt nur, solange
   Hero und Viewport gleich hoch sind. Auf dem iPhone ist die Hero
   100svh, also OHNE die eingeblendete URL-Leiste, waehrend `fixed`
   gegen den vollen Viewport rechnet. Die Bloecke sassen dadurch um
   die Hoehe der URL-Leiste zu tief und sprangen am Intro-Ende nach
   oben — gemessen 90 Bildpunkte (Leon 25.08.2026: "ein Sprung, als
   wuerde sich nach der Animation eine Ebene aktivieren").
   Der z-index allein reicht: .hero ist zwar position:relative, hat
   aber z-index auto und keinen Transform, bildet also keinen
   Stacking Context. Die Kinder konkurrieren direkt mit dem Intro
   (z-index 200) auf der Wurzelebene. */
.hero-meta.over-intro, .hero-stats.over-intro, .hero-tag.over-intro { z-index: 205; }

/* ============================================================
   Aufloesung der Wortmarke ueber dem Wuerfel (Leon 03.09.2026, seit 15.09. oben)
   Sie haengt am selben Mass wie der Wuerfel (--cube-size), damit der
   Abstand zu seiner Unterkante auf jedem Format derselbe bleibt. Das
   Schweben und die Verkleinerung beim Scrollen macht sie NICHT mit:
   diese Variablen setzt room.js am Wuerfel-Host, ein Geschwister
   kommt nicht an sie heran, und alle anderen Beschriftungen in der
   Hero stehen aus demselben Grund still.
   pointer-events: none ist Pflicht, die Zeile liegt im Quadrat des
   Wuerfel-Hosts und wuerde dort sonst den Griff abfangen.
   ============================================================ */
.hero-tag {
  position: absolute;
  left: 50%;
  /* Seit 15.09.2026 ueber dem Wuerfel (Leon), spiegelbildlich zur
     alten Lage unten: die Unterkante der Zeile steht 0.42 der
     Kantenlaenge ueber der Mitte, die hoechste Ecke des Wuerfels bei
     0.414. Herleitung der alten Lage: die tiefste Ecke des Wuerfels
     steht bei 0.414 dieser Laenge unter der Mitte (Lage: Koerperdiagonale nach unten vorn,
     bei 1440x900 gemessene 701px gegen 448px Mitte). Weiter oben
     liefe die Zeile bei jeder Drehung ins Bild, weiter unten steht
     sie im unteren Panel-Streifen. Der Streifen faengt bei rund
     Fensterhoehe minus 152px an, das ist auf kurzen Fenstern die
     engere Grenze, siehe Medienabfrage unten. */
  bottom: calc(50% + var(--cube-size) * 0.52);
  transform: translateX(-50%);
  z-index: 4;
  pointer-events: none;
  text-align: center;
  opacity: 0;
  transition: opacity var(--t-slow) var(--ease);
}
.hero-tag.revealed { opacity: 1; }
.hero-tag .meta-tag {
  /* Eine Spur groesser und heller als die Beschriftungen an den
     Seiten: sie benennt die Marke selbst, ist also keine Fussnote.
     Der Abstand zwischen den Zeichen ist der Ton der Seite. */
  font-family: var(--mono);
  font-stretch: 75%;
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 0.13em;
  text-indent: 0.13em;
  color: var(--fg);
  white-space: nowrap;
}
/* it's DARKINSIDE <-> DRKNSD als Abflugtafel (Leon 15.09.2026): die
   wegfallenden Buchstaben klappen durch und schrumpfen auf null, der
   Rest steht still und rueckt mittig zusammen. */
.hero-tag .flap-col {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  transition: max-width 0.32s var(--ease), opacity 0.22s var(--ease);
}
.hero-tag .flap-col.gone { max-width: 0 !important; opacity: 0; }
/* Kurze Fenster: der Panel-Streifen unten sitzt in fester Hoehe ueber
   der Kante, der Wuerfel schrumpft mit dem Fenster. Ab hier reicht der
   Platz zwischen beiden nicht mehr, also rueckt die Zeile naeher an
   den Wuerfel. Lieber dicht am Wuerfel als im Streifen. */
/* Nur im Querformat: dort misst sich der Wuerfel an der Hoehe und
   frisst den Platz nach unten auf. Im Hochformat haengt seine Groesse
   an der Breite, unter ihm ist reichlich Luft, und die Zeile darf auf
   ihrem Regelabstand bleiben. */
@media (max-height: 860px) and (min-aspect-ratio: 1/1) {
  .hero-tag { bottom: calc(50% + var(--cube-size) * 0.48); }
}
@media (max-height: 700px) and (min-aspect-ratio: 1/1) {
  .hero-tag { bottom: calc(50% + var(--cube-size) * 0.44); }
}

/* ============================================================
   Ausklappbare Zusatzinfos (Leon 11.08.: "infos koennen sich auch ab
   und zu auf und zuklappen, also nur die zahl und dann kommen auf
   einmal die anderen infos dazu und gehen dann wieder weg … automatisch
   und auf hover")

   Geklappt wird ueber `grid-template-rows: 0fr → 1fr`, NICHT ueber
   max-height. Bei max-height muesste man einen Wert raten: zu klein
   schneidet ab, zu gross macht die Bewegung am Ende zaeh, weil sie
   Zeit fuer Hoehe verbraucht die es nicht gibt. Mit dem Grid-Trick
   faehrt der Kasten exakt auf seine echte Hoehe.
   ============================================================ */
/* Der `grid-template-rows: 0fr → 1fr` Trick funktioniert hier NICHT:
   die Bloecke sind Flex-Container, dort loest `1fr` gegen den freien
   Raum auf — und den gibt es in einer nach Inhalt wachsenden Spalte
   nicht. Gemessen blieb der offene Kasten auf 6 Bildpunkten stehen,
   also nur das Polster. Deshalb ueber max-height, aber ohne Ratewert:
   micro.js misst die echte Hoehe und setzt sie. */
/* Die Klappkaesten haengen AUSSERHALB des Flusses, `position: absolute`
   direkt unter ihrem Wirt.

   Vorher standen sie als letztes Kind im Fluss und wuchsen ueber
   `max-height` von 0 auf ihre Hoehe. Die Beschriftungsbloecke sind aber
   unten verankert (`bottom: 9vh` auf dem Handy), also hat jedes Aufklappen
   den ganzen Block nach OBEN geschoben und beim Zuklappen wieder zurueck.
   Gemessen: 90 Layout-Spruenge in 24 Sekunden, der Block wanderte staendig
   um 10 Bildpunkte hin und her, und zwar endlos, weil die Automatik im
   Takt weiterlaeuft. Das ist das "es springt rum", das Leon mehrfach
   gemeldet hat (25.08.2026) — es lag nie am Intro, sondern hier.

   Ausserhalb des Flusses kann das Aufklappen die Hoehe des Wirts nicht
   mehr veraendern, also bewegt sich nichts mehr. Die Klappbewegung selbst
   bleibt wie sie war. */
.meta-more {
  display: block;
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  overflow: hidden;
  max-height: 0;
  opacity: 0;
  transition: max-height 420ms var(--ease), opacity 260ms var(--ease);
}
/* Bezugsrahmen fuer die Klappkaesten. Ohne das wuerden sie an der naechsten
   positionierten Ebene weiter oben haengen und quer ueber die Seite springen. */
.meta-face, .hstat { position: relative; }
.meta-more-in {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding-top: 6px;
}
.meta-more.is-open { opacity: 1; }
/* Die Bloecke am unteren Hero-Rand klappen nach OBEN auf. Nach unten
   liefen die Zusatzzeilen aus der Hero heraus und wurden an der Kante
   beschnitten (Leon 02.09.: "dann rutschen manchmal die Infos aus dem
   Bild raus ... auf der Hero-Seite soll alles sichtbar sein"). */
.hero-stats .meta-more {
  top: auto;
  bottom: 100%;
}
.hero-stats .meta-more .meta-more-in {
  padding-top: 0;
  padding-bottom: 6px;
}
.meta-more-in b {
  font-weight: 700;
  font-size: 9px;
  letter-spacing: 0.1em;
  color: var(--fg);
}
.meta-more-in span {
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 0.1em;
  color: var(--dim);
}
/* Der rechte Block ist rechtsbuendig gesetzt, die Zusatzzeilen auch */
.hero-meta-right .meta-more-in { align-items: flex-end; }

@media (prefers-reduced-motion: reduce) {
  .meta-more { transition: none; }
}

/* Zahlen reissen kurz auf (micro.js setzt die Klasse). Zwei Kopien der
   Zahl als Pseudoelemente, jede zeigt nur ihre Haelfte und faehrt in
   die Gegenrichtung — dieselbe Mechanik wie bei den Auftraggebern.
   Der echte Text bleibt dabei unangetastet, deshalb kann der Zaehler
   ungestoert weiterlaufen. */
.meta-val.num-tear { position: relative; }
.meta-val.num-tear::before,
.meta-val.num-tear::after {
  content: attr(data-ghost);
  position: absolute; left: 0; top: 0;
  width: 100%; height: 100%;
  background: inherit;
  pointer-events: none;
}
.meta-val.num-tear::before {
  clip-path: inset(0 0 54% 0);
  animation: numTearA 300ms steps(3, end);
}
.meta-val.num-tear::after {
  clip-path: inset(46% 0 0 0);
  animation: numTearB 300ms steps(3, end);
}
/* Ausschlaege halbiert (Leon 02.09.: "wackelt so hart links, rechts
   ... sie sollen so ein bisschen in ihrem eigenen Bereich bleiben").
   Der Riss bleibt lesbar, verlaesst die eigene Spalte aber nicht mehr. */
@keyframes numTearA {
  0%   { transform: translateX(0); opacity: 1; }
  35%  { transform: translateX(-3px); opacity: 0.9; }
  65%  { transform: translateX(2px); opacity: 1; }
  100% { transform: translateX(0); opacity: 0; }
}
@keyframes numTearB {
  0%   { transform: translateX(0); opacity: 1; }
  35%  { transform: translateX(2px); opacity: 0.95; }
  65%  { transform: translateX(-2px); opacity: 1; }
  100% { transform: translateX(0); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .meta-val.num-tear::before, .meta-val.num-tear::after { display: none; }
}

/* Leuchte vor dem Wort LIVE (micro.js setzt sie). Sie sitzt auf der
   Grundlinie wie der Trenner, ist aber rund und blinkt — der Trenner
   markiert eine Trennung, die Leuchte einen Zustand. Bewusst nur der
   Punkt: ein blinkendes Wort liest sich als Fehlermeldung. */
@keyframes liveBeat {
  0%, 46%  { opacity: 1; }
  55%, 96% { opacity: 0.18; }
  100%     { opacity: 1; }
}
.live-dot {
  display: inline-block;
  width: 4px; height: 4px;
  border-radius: 50%;
  margin-right: 5px;
  background: var(--fg);
  vertical-align: 0.06em;
  animation: liveBeat 1.6s var(--ease) infinite;
}
@media (prefers-reduced-motion: reduce) { .live-dot { animation: none; } }

/* Die Bloecke treten erst nach dem Intro auf und schreiben sich dann
   Zeile fuer Zeile auf (micro.js). Vorher stuende dort der Platzhalter
   aus dem HTML und spraenge beim Nachladen um.
   .loaded bleibt als Notnagel drin: faellt micro.js aus, zeigt
   youtube.js den Block trotzdem, nur eben ohne Auftritt. */
.hero-meta {
  opacity: 0;
  transition: opacity var(--t-slow) var(--ease);
}
.hero-meta.revealed, .hero-meta-left.loaded { opacity: 1; }

/* Schreibkopf: blinkender Block am Ende der Zeile, solange sie laeuft.
   Nur waehrend der Bewegung, danach steht die Zeile ruhig da. */
@keyframes microCaret { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }
.decoding::after {
  content: "\25AE";
  margin-left: 0.12em;
  animation: microCaret 480ms steps(1, end) infinite;
}

/* ============================================================
   Creed — der Text der Firma zwischen Wuerfel und Werkliste
   (Leon 03.09.2026)
   Zwei ungleiche Spalten: links nur die Kopfzeile als Beschriftung,
   rechts der Satz. Kein gleichmaessiges Raster und kein Kasten, der
   Abschnitt hat genau ein starkes Zeichen, und das ist die
   Schlusszeile in der Schrift der Wortmarke.
   Seit 01.10.2026 ist der Abschnitt die letzte Sektion: er steht hinter
   der Werkliste und vor der Mailzeile.
   Abstaende: am Rechner oben und unten --gap-l (Leon 01.10.2026: "du
   kannst diesem About-Text oben und unten ein bisschen mehr Luft geben
   auf der Web-Version", und zusaetzlich "ein bisschen mehr Platz auch
   zwischen dem Text und Hello Tag"). Vorher oben --gap-s und unten
   --gap-m, das war als Zwischensektion gedacht; als Schlussabschnitt
   stand der Text zu eng zwischen Namenswand und Mailadresse.
   Am Handy genau andersherum, siehe die Regel in der 860er Abfrage.
   ============================================================ */
.creed {
  padding: var(--gap-l) var(--pad) var(--gap-l);
}
.creed-inner {
  max-width: var(--maxw);
  margin: 0 auto;
  display: grid;
  /* Die schmale Spalte traegt nur die Kopfzeile. Sie ist die Satzkante
     der Seite, nicht eine zweite Textspalte, deshalb so eng. */
  grid-template-columns: minmax(0, 0.62fr) minmax(0, 2fr);
  gap: clamp(20px, 3vw, 56px);
  align-items: start;
}
/* Die Micrographic-Kopfzeile ist raus (Leon 03.09.2026: "diese
   Micrographic-Ueberschrift ueber den Text braucht es nicht"). Die
   schmale Spalte bleibt als Einzug stehen, sie ist die Satzkante der
   Seite. Der Text steht dadurch nicht an der Bildkante, sondern hat
   links Luft, und die Schlusszeile darunter faengt genau dort an. */
.creed-col {
  /* Nur die rechte Spalte, die linke bleibt der Einzug. */
  grid-column: 2;
  display: flex;
  flex-direction: column;
  /* Absatzabstand, nicht Sektionsabstand: die drei Absaetze sollen als
     ein Text lesbar bleiben und nicht in drei Bloecke zerfallen. */
  gap: clamp(14px, 1.5vw, 24px);
}
.creed-body {
  margin: 0;
  /* Zeilenlaenge ueber die Schriftgroesse begrenzt, wie beim
     Einleitungstext davor. Ueber die volle Spaltenbreite gezogen
     laesst sich der Absatz nicht mehr lesen. */
  max-width: 30em;
  font-size: clamp(16px, 1.28vw, 22px);
  line-height: 1.5;
  font-weight: 700;
  color: color-mix(in srgb, var(--fg) 76%, transparent);
}
/* Ein Schlagwort je Absatz in voller Helligkeit und kursiv, damit die
   laute Stimme der Schlusszeile schon im Text vorkommt und nicht erst
   ganz unten (Leon 03.09.2026: "dass es sich durch den ganzen Text
   durchzieht ... dass man wirklich hier und da mal haengen bleibt").
   Bewusst nur die Helligkeit und die Kursive, keine zweite Schrift und
   keine Farbe: der Absatz bleibt ein Absatz, es fallen nur drei Stellen
   auf. Der Wortlaut aendert sich dabei nicht, es kommen nur <b> dazu. */
.creed-body b {
  font-weight: 900;
  font-style: italic;
  letter-spacing: -0.01em;
  /* 02.10.2026, Leon: die Hervorhebungen duerfen fett bleiben, aber nicht
     weiss, das stach aus dem grauen Absatz heraus. Keine eigene Farbe mehr,
     sie erben die 76 Prozent von .creed-body. */
  color: inherit;
}
/* Das eine starke Zeichen des Abschnitts. Gleiche Machart wie die
   Wortmarke von Latest Projects: zweizeilig, kursiv, die zweite Zeile
   eingerueckt, damit der Block eine Treppe hat statt zweier
   linksbuendiger Zeilen. */
.creed-kick {
  /* Ueber beide Spalten, also ab der Satzkante der Seite (Leon
     03.09.2026: "vom Placement irgendwie noch nicht so geil"). Vorher
     hing sie in der rechten Spalte und lief nach rechts aus dem
     Textblock heraus, waehrend links unter der Kopfzeile nichts stand.
     Jetzt liegt sie als Grundlinie unter dem eingerueckten Text, faengt
     dort an wo die Sektion anfaengt und hat Platz bis zum Rand. */
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  /* Reihenfolge beachten: die Kurzform `margin` wuerde ein davor
     stehendes margin-top wieder auf 0 setzen. */
  margin: clamp(30px, 3.6vw, 64px) 0 0;
  font-weight: 900;
  font-style: italic;
  text-transform: uppercase;
  font-size: clamp(26px, 3.9vw, 62px);
  line-height: 0.88;
  letter-spacing: -0.04em;
  color: color-mix(in srgb, var(--fg) 42%, transparent);
}
/* Nur der Schlusssatz steht in voller Helligkeit. Beide Zeilen gleich
   hell waeren zwei Ansagen, gemeint ist eine mit Anlauf. */
.creed-kick b:last-child {
  margin-left: clamp(16px, 2.4vw, 46px);
  color: var(--fg);
}

@media (max-width: 860px) {
  /* Am Handy soll der Block hoeher sitzen (Leon 01.10.2026, mit Pfeil
     auf dem Bildschirmfoto: "am Handy den Text hier hoch"). Gemessen bei
     390 Punkten: zwischen dem letzten Namen der Werkliste und dem ersten
     Satz lagen 141 Punkte, davon 40 von hier und 101 aus dem unteren
     Rand der Werkliste. Beide muessen runter, mit den 40 allein blieben
     117 stehen.
     Der untere Rand der Werkliste wird nur gekuerzt, wenn About direkt
     folgt: endet die Seite mit der Namenswand, braucht sie ihre Luft.
     Unten bleibt --gap-m: die groessere Luft zur Mailadresse wollte Leon
     ausdruecklich nur am Rechner. */
  .creed { padding-top: 0; padding-bottom: var(--gap-m); }
  .works:has(+ .creed) { padding-bottom: var(--gap-m); }
  /* Auf dem Handy untereinander: zwei Spalten liessen fuer den Text
     keine lesbare Zeile mehr uebrig. */
  .creed-inner { grid-template-columns: 1fr; gap: 0; }
  /* Die Kennung SECTION 01 / ABOUT (.creed-inner::before) ist raus
     (14.09.2026): mit Latest Projects vor dem Abschnitt stimmte die
     Nummer nicht mehr, und die Seite fuehrt seit Leons Reduktion
     ("Section 02 AI-driven wegmachen") gar keine Sektionsnummern mehr.
     Bitte nicht zurueckbauen. */
  /* Auf dem Handy gibt es keinen Einzug mehr, also faengt der Text an
     derselben Kante an wie die Schlusszeile. */
  .creed-col { grid-column: 1; gap: 12px; }
  .creed-body { max-width: none; line-height: 1.45; }
  /* Die Schlusszeile ist das eine starke Zeichen des Blocks und stand
     mit 26 Punkten kaum ueber dem Text, dazu brach "FUTURE." allein um.
     Jetzt doppelt so gross wie der Text, und die erste Zeile bricht
     ausgeglichen (THAT'S THE / UNSTOPPABLE FUTURE.). Die Obergrenze 34
     ist gemessen: bis dahin passt "UNSTOPPABLE FUTURE." bei 375 und 390
     Punkten Breite in eine Zeile, darueber wuerde es drei Zeilen und
     das Einzelwort kaeme zurueck. Die Marke bleibt in einer Zeile. */
  .creed-kick {
    margin: 26px 0 0;
    font-size: clamp(28px, 8.2vw, 34px);
    line-height: 0.94;
    letter-spacing: -0.035em;
  }
  .creed-kick b { text-wrap: balance; }
  .creed-kick b:last-child { margin-left: 0.55em; white-space: nowrap; }
}

/* ============================================================
   Latest — Featured-Arbeiten zwischen Hero und Portfolio
   ============================================================ */
/* Deutlich kompakter (Leon 11.08.: "latest projects nimmt viel zu viel
   platz ein"). Vorher 12vh oben und 16vh unten plus 22vh Versatz an der
   kleinen Kachel — die Sektion war ueber zwei Bildschirme hoch, obwohl
   nur zwei Arbeiten darin stehen. */
.latest {
  /* Steht seit 14.09.2026 direkt unter der Hero. Oben --gap-m wie vorher
     der Creed an dieser Stelle: die Hero faehrt unten schon per Blende auf
     --bg, ein voller Regelabstand liesse die Marke losgeloest stehen, und
     die Kopfzeile SECTION 02 darueber gibt es nicht mehr. Unten nur
     --gap-s: die Seitenspalte der Buehne haelt ohnehin die Hoehe des
     laengsten Werks frei, mit --gap-m standen gemessen 132 Punkte bis
     zum Creed (Ziel 90 bis 120). */
  padding: var(--gap-m) var(--pad) var(--gap-s);
}
/* ============================================================
   Ueberschrift als Marke (Leon 13.08.: "die ueberschrift von latest
   projects sieht lame aus ... muss eher wie ein logo in unserem micro
   graphic style kommen"). Zweizeilig, die zweite Zeile eingerueckt,
   damit der Block eine Kante hat statt zweier linksbuendiger Zeilen.

   Seit 14.09.2026 steht nur noch die Wortmarke (Leon: "diesen
   Rennstreifen, diese Zielgerade da wegmachen, den Barcode wegmachen,
   dieses Nummer 1 bis 05 wegmachen, dieses GIF mit dem Kopf, der sich
   dreht, wegmachen, Section 02 AI-driven wegmachen"). Mit dem Markup
   sind die Regeln fuer .latest-logo-row, .ll-head, das Schachbrett an
   der Marke, .ll-foot mit Barcode und Kapsel sowie .latest-cards/.lcard
   (Kopf-Loop) raus. Bitte nicht zurueckbauen.
   ============================================================ */
/* Die Marke steht links auf der Satzkante der Seite. */
.latest-logo {
  max-width: var(--maxw);
  margin: 0 auto clamp(26px, 3.4vw, 46px);
}
/* .ll-mark bleibt fuer die (abgeschaltete) Bitcoin-Zusage in brand.js. */
.ll-mark {
  display: flex;
  align-items: center;
  gap: clamp(8px, 1vw, 18px);
}
.ll-word {
  display: flex;
  flex-direction: column;
  margin: 0;
  font-weight: 900;
  font-style: italic;
  text-transform: uppercase;
  font-size: clamp(34px, 5.6vw, 86px);
  line-height: 0.82;
  letter-spacing: -0.045em;
  color: var(--fg);
}
/* Die zweite Zeile rueckt ein und traegt damit die Treppe, die im
   Moodmaterial jede Marke hat. */
.ll-word b:last-child { margin-left: clamp(14px, 2.2vw, 40px); }

/* ============================================================
   Einleitung (derzeit ohne Text, siehe index.html)
   ============================================================ */
.latest-lead {
  max-width: var(--maxw);
  margin: 0 auto clamp(26px, 3.4vw, 46px);
}

/* Zwei gleich hohe Panels nebeneinander (Leon 12.08.: "der ganze teil
   nimmt zu viel platz ein durch das hochkant gasag. mach ein layout was
   weniger platz schluckt und mehr in den micrographic style passt").
   Das Hochformat war der Hoehentreiber: eine 4/5-Kachel neben einer
   16/9-Kachel zieht die ganze Zeile auf ihre Hoehe, dazu kam der
   Versatz. Beide laufen jetzt im selben Breitbild und die Sektion ist
   so hoch wie ihre Kacheln. */
.latest-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(20px, 3vw, 44px);
  align-items: start;
  max-width: var(--maxw);
  margin: 0 auto;
}

/* ------------------------------------------------------------
   Latest als Buehne (Leon 13.08.2026)
   Ueberschrift, kurzer Text, dann EINE grosse Flaeche mit vier
   durchlaufenden Loops und den Angaben daneben.
   ------------------------------------------------------------ */
/* Der Text sitzt an der linken Kante unter der Ueberschrift, nicht in
   der Mitte der Spalte: die Sektion hat eine Satzkante, und die ist die
   der Ueberschrift. Deshalb die volle Breite als Rahmen und die
   Zeilenlaenge ueber die Schriftgroesse begrenzen. */
.latest-intro {
  width: 100%;
  margin: 0;
  font-size: clamp(15px, 1.15vw, 19px);
  line-height: 1.5;
  font-weight: 700;
  color: color-mix(in srgb, var(--fg) 74%, transparent);
}
/* Buehne ueber die ganze Breite (Leon 01.10.2026: "wenn das Video, was
   dort zu sehen ist, einmal ueber die komplette Breite geht, sozusagen von
   der Seite, dass es wirklich gross ist"). Vorher stand der Film in der
   breiteren von zwei Spalten (1.62fr zu 1fr) und die Angaben daneben: in
   Leons Fenster waren das 855 von 1419 Punkten Breite. Jetzt liegt alles
   untereinander, der Film nimmt die ganze Fensterbreite, Titel und Werte
   stehen darunter. Die Spaltenaufteilung und mit ihr .feature-side,
   .feature-main, .feature-title und .feature-kicker sind raus. */
.feature {
  display: block;
  max-width: var(--maxw);
  margin: 0 auto;
}
/* Kein Rahmen. Der 1px Strich stand als heller Faden links und rechts
   neben dem Loop und las sich als Fehler, nicht als Fassung
   (Leon 01.09.: "dieser leichte weisse Rahmen bei dem Latest Project um
   das Vorschau-Video kann weg", "links und rechts noch so kleine weisse
   Striche"). Gemessen war er es auch: auf 1440 lag die Helligkeit an
   x=24 und x=861 bei 64 bzw. 57 gegen 10 im Bild daneben. Das Bild
   steht jetzt allein, ohne Rahmen und seit 02.10.2026 auch ohne Schatten
   (Leon: "unter dem Latest Project Video ist so ein kleiner Schatten,
   den braucht man nicht, generell die Kacheln brauchen keine
   Schatten"). Geprueft wird das von qa/check_schatten.py. */
.feature-stage {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border: 0;
  /* Schwarz, auch solange noch nichts dekodiert ist. Weiss um ein Bild
     liest Leon als Fehler, nicht als helles Design. */
  background: #000;
}
/* Von Kante zu Kante: der Film tritt aus dem Satzspiegel heraus, die
   Titel darunter bleiben darin. 50% bezieht sich auf die Spalte, 50vw auf
   das Fenster, die Differenz ist genau der Seitenrand. body hat
   overflow-x: hidden, eine waagerechte Leiste kann daraus nicht entstehen.

   Die Hoehe ist 16/9 der ganzen Breite, aber gedeckelt: ungedeckelt waere
   die Flaeche in Leons Fenster (1467x1032) 825 Punkte hoch und die Titel
   darunter stuenden ausserhalb des Bildes — dann versteht niemand, dass
   die Filme zu den Titeln gehoeren. Mit dem Deckel stehen Film und Liste
   zusammen in einem Fenster, der Loop laeuft als breites Band (gemessen
   2.09:1) statt 16/9. object-fit: cover an .fclip schneidet mittig, die
   Loops liegen ohnehin in verschiedenen Formaten vor. */
.feature--wide .feature-stage {
  width: 100vw;
  margin-left: calc(50% - 50vw);
  height: min(calc(100vw * 9 / 16), 68svh);
  aspect-ratio: auto;
}
.feature-link { position: absolute; inset: 0; display: block; }
/* Alle vier Loops liegen uebereinander und laufen mit. Nur der aktive
   ist sichtbar — ein Wechsel per src-Tausch beginnt sonst mit einem
   schwarzen Frame, weil der neue Film erst laden muss. */
.fclip {
  position: absolute; inset: 0;
  opacity: 0;
  transform: scale(1.06);
  transition: opacity 900ms var(--ease), transform 1400ms var(--ease);
  pointer-events: none;
  will-change: opacity, transform;
}
.fclip.is-on { opacity: 1; transform: scale(1); z-index: 2; }
/* Das weichende Bild geht nach innen weg, das kommende von aussen
   herein. Beide gleichzeitig aufblenden wuerde nur ueberlagern. */
.fclip.is-out { opacity: 0; transform: scale(0.965); z-index: 1; }
.fclip video, .fclip img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}

/* Die Werte aus data.js, Monospace, klein. Dieselbe Klasse benutzt die
   Werkliste weiter unten (latest-list.js), deshalb bleibt sie hier. Die
   Beschriftungen RELEASED, FORMAT, ARTIST, RUNTIME, VIEWS sind seit
   14.09.2026 raus (Leon: "die Daten an sich noch lassen, aber quasi die
   Ueberschriften dafuer wegnehmen"). */
.fstat-val {
  font-family: var(--mono);
  font-stretch: 75%;
  text-transform: uppercase;
  font-size: clamp(12px, 1.05vw, 15px);
  font-weight: 500;
  letter-spacing: 0.01em;
  color: var(--fg);
}
/* ------------------------------------------------------------
   Die Titel unter der Buehne (Leon 01.10.2026)
   ------------------------------------------------------------
   Vorher eine Reiterzeile in 20px, alle Titel nebeneinander — Leon:
   "diese kleinen Schriften, die da drunter stehen, entweder man macht die
   viel viel fetter und groesser, dass sie auch so komplett ueber den
   Screen gehen". Jetzt eine Zeile je Werk, der Titel in Anzeigegroesse
   links, die Werte in Monospace rechts an der Satzkante: damit spannt
   jede Zeile ueber die ganze Breite. Das laufende Werk steht hell, die
   anderen stark gedimmt — dieselbe Lesart wie in der Werkliste weiter
   unten, wo die Namen durch die feste Vorschau laufen.
   Keine Trennlinien zwischen den Zeilen: die Zeile selbst ist die
   Struktur (Leon 14.09.2026). */
.feature-list {
  display: flex;
  flex-direction: column;
  margin-top: clamp(16px, 1.8vw, 30px);
}
.ftab {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: clamp(16px, 2vw, 44px);
  width: 100%;
  padding: clamp(4px, 0.5vw, 10px) 0;
  background: none;
  border: 0;
  text-align: left;
  /* Seit 01.10.2026 ein <a> und kein <button>: die Zeile fuehrt auf die
     Projektseite. Der Strich darunter muss deshalb weg. */
  text-decoration: none;
  cursor: pointer;
  color: color-mix(in srgb, var(--fg) 24%, transparent);
  transition: color 320ms var(--ease);
}
/* Gewechselt wird im Vorbeifahren (Leon 01.10.2026: "dass man nicht auf
   die Sachen draufklicken muss, sondern sobald man drueber hovert, dass
   es dann schon switcht"). Der Zeiger macht die Zeile deshalb nicht noch
   zusaetzlich heller als das laufende Werk — sie IST nach dem Hover das
   laufende Werk. Kein Anheben mehr, bei dieser Schriftgroesse waere das
   ein Ruck im ganzen Block. */
.ftab.is-on { color: var(--fg); }
.ftab-name {
  font-size: clamp(27px, 4.35vw, 70px);
  font-weight: 900;
  font-style: italic;
  letter-spacing: -0.038em;
  line-height: 0.98;
  text-transform: uppercase;
}
/* Die Werte erben die Farbe der Zeile, damit die gedimmten Zeilen
   wirklich eine Einheit sind und nicht ihre Angaben vorlaut dazwischen
   stehen. */
.ftab-meta {
  flex: none;
  display: flex;
  gap: clamp(10px, 1.3vw, 26px);
  font-family: var(--mono);
  font-stretch: 75%;
  text-transform: uppercase;
  font-size: clamp(10px, 0.9vw, 14px);
  font-weight: 500;
  letter-spacing: 0.02em;
  white-space: nowrap;
  color: currentColor;
}
/* Der Beschreibungssatz zum laufenden Werk, unter der Liste.
   Stapel statt Fluss: der sichtbare Satz und die unsichtbaren Zwillinge
   aller anderen Werke liegen im selben Rasterfeld. Die Hoehe richtet
   sich damit immer nach dem laengsten Text, und beim Wechsel springt
   nichts mehr (Leon 02.09.: "Latest Projects: Hoehe springt"). Ohne das
   waren es gemessen 44 bis 63 Bildpunkte Sprung, je nach Breite. */
.feature-blurb {
  margin: clamp(14px, 1.6vw, 26px) 0 0;
  max-width: 62ch;
  font-size: clamp(14px, 1.02vw, 17px);
  line-height: 1.5;
  font-weight: 700;
  color: color-mix(in srgb, var(--fg) 70%, transparent);
  display: grid;
}
.feature-blurb > .fb-text,
.feature-blurb > .fb-ghost { grid-area: 1 / 1; }
/* visibility statt display oder opacity: der Zwilling wird gesetzt und
   traegt seine Hoehe, ist aber weder sichtbar noch anklickbar und wird
   nicht vorgelesen. */
.feature-blurb > .fb-ghost { visibility: hidden; pointer-events: none; }
.feature-blurb.is-empty { display: none; }
/* Die Segmentreihe unter den Reitern ist raus (Leon 02.09.: die Balken
   suggerierten ein Karussell und taten auf dem Handy nichts). */
@media (prefers-reduced-motion: reduce) {
  .fclip { transition: opacity 200ms linear; transform: none; }
  .fclip.is-on, .fclip.is-out { transform: none; }
}

/* ------------------------------------------------------------
   Latest als Liste — nur die Vergleichsfassung index-liste.html
   (Leon 14.09.2026: "nicht durchrotieren laesst, sondern so alle
   Projekte untereinander auflistet ... dass man quasi fuenf Videos da
   so listet"). Gefuellt von latest-list.js.
   Je Werk EIN Bildschirm mit einem dominanten Element, dem Loop. Er ist
   so gross, dass Film und die Zeile darunter zusammen in Leons Fenster
   passen, darunter Titel links, Werte und Text rechts. Knapper Abstand
   zum naechsten Werk aus der Leiter.
   ------------------------------------------------------------ */
.llist {
  max-width: var(--maxw);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--gap-m);
}
.lrow {
  display: block;
  /* Breite aus der Fensterhoehe: Film plus Zeile darunter (rund 250
     Punkte) passen in einen Bildschirm. Auf breiten, flachen Fenstern
     wird der Film dadurch schmaler statt hoeher als der Schirm. */
  width: min(100%, calc((100svh - 250px) * 16 / 9));
  color: inherit;
  text-decoration: none;
}
/* Schwarz in jedem Modus, auch waehrend der Loop laedt (Leon 03.09.).
   Das Standbild liegt als Hintergrund und deckt sofort. */
.lrow-media {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: #000 center / cover no-repeat;
  /* Kein Schatten (Leon 02.10.2026, Kacheln tragen keinen). */
}
/* Das Video blendet erst mit dem ersten laufenden Bild auf und dann ueber
   eine Kurve, nicht als Schalter. Bis dahin ist es durchsichtig. */
.lrow-media video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  opacity: 0;
  transition: opacity 600ms var(--ease);
  /* Gegen den Bruchpixel-Spalt am Rand, wie bei .preview-float. */
  transform: scale(1.004);
}
.lrow-media.is-live video { opacity: 1; }
.lrow-cap {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(20px, 2.6vw, 40px);
  align-items: start;
  margin-top: clamp(16px, 1.8vw, 26px);
}
.lrow-title {
  margin: 0;
  font-size: clamp(38px, 4.6vw, 76px);
  font-weight: 900;
  font-style: italic;
  letter-spacing: -0.035em;
  line-height: 0.94;
  text-transform: uppercase;
  color: var(--fg);
}
.lrow-side {
  display: flex;
  flex-direction: column;
  gap: 12px;
  /* Die Werte stehen auf der Hoehe der Oberlaenge des Titels, nicht an
     der Oberkante seiner Zeilenbox. */
  padding-top: 0.35em;
}
.lrow-vals {
  display: flex;
  flex-wrap: wrap;
  gap: 6px clamp(18px, 2vw, 32px);
}
.lrow-blurb {
  margin: 0;
  max-width: 44ch;
  font-size: clamp(14px, 1.02vw, 17px);
  line-height: 1.5;
  font-weight: 700;
  color: color-mix(in srgb, var(--fg) 70%, transparent);
}
@media (hover: hover) and (pointer: fine) {
  .lrow-title { transition: opacity var(--t-base) var(--ease); }
  .lrow:hover .lrow-title { opacity: 0.7; }
}
@media (max-width: 760px) {
  .llist { gap: var(--gap-l); }
  .lrow { width: 100%; }
  .lrow-cap { grid-template-columns: 1fr; gap: 12px; }
  .lrow-side { padding-top: 0; }
  .lrow-blurb { max-width: none; }
  /* Die Angaben an den Werken (Datum, Gattung, Kuenstler, Views) standen
     am Handy fast so gross wie der Beschreibungssatz und lasen sich damit
     als zweite Textzeile statt als Beschriftung (Leon 02.10.2026: "diese
     micrographic texte, dieses Datum, Musikvideo und so, das ist noch
     viel zu gross am Handy, das muss viel kleiner"). Jetzt auf der Stufe
     der Beschriftungen an der Hero (.meta-sub, 9px) mit etwas mehr
     Laufweite, damit sie in Versalien lesbar bleiben. */
  .llist .lrow-vals .fstat-val {
    font-size: 9.5px;
    letter-spacing: 0.06em;
  }
  .llist .lrow-vals { gap: 4px 14px; }
}
@media (prefers-reduced-motion: reduce) {
  .lrow-media video { transition: none; }
}
/* ------------------------------------------------------------
   Ausgewaehlte Werke unter About, Sektion #selected (Leon 14.09.2026:
   "nochmal fuenf Videos einzeln listen, quasi in dem Design von Latest
   Projects, aber jetzt nicht, dass sie durchrotieren, sondern wirklich
   eins unter dem anderen"). Dieselbe Aufteilung und dieselben Masse wie
   die Latest-Buehne (.feature): Film links 1.62fr, Titel, Werte und Text
   rechts. Nur traegt die Flaeche das Format des Loops statt 16/9: die
   Loops sind auf klare Kanten geschnitten, ein Beschnitt auf 16/9 naehme
   bei Summertime (2,7:1) je Seite ein Sechstel weg.
   ------------------------------------------------------------ */
.selected { padding-bottom: var(--gap-m); }

/* Selected Works bleibt in beiden Modi schwarz (Leon 01.10.2026: "kannst
   du Selected Works einfach immer schwarz machen, weil die Animation auf
   schwarz einfach geiler aussieht, du kannst ja so einen leichten
   Uebergang machen zwischen weiss und schwarz, und dann wird es unten
   wieder je nachdem welchen Modus man anhat").

   Die Sektion dreht ihre eigenen Farben um, nicht die der Seite: --bg und
   --fg werden hier neu gesetzt, damit alles darin (Titel, Werte, Texte)
   ohne eigene Regel mitzieht. Die Seite aussen bleibt unberuehrt.

   Die Kante ist hart und bleibt es. Erst lief oben und unten ein weiches
   Band von der Seitenfarbe nach Schwarz, das hat Leon sofort verworfen
   ("Uebergang sieht Kacke aus, mach einfach klare Kante"). Also eine
   Flaeche, kein Verlauf — bitte nicht wieder weichzeichnen. */
.selected--flow {
  --bg: #000000;
  --fg: #ffffff;
  --soft: #d6d6d6;
  --dim: #6f6f6f;
  --void: #050505;
  color: var(--fg);
  background: #000000;
}
.llist--side .lrow {
  width: 100%;
  display: grid;
  grid-template-columns: minmax(0, 1.62fr) minmax(0, 1fr);
  gap: clamp(20px, 2.6vw, 40px);
  align-items: start;
}
.llist--side .lrow-media { aspect-ratio: var(--ratio, 16 / 9); }
.llist--side .lrow-cap {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin-top: 0;
}
.llist--side .lrow-side { padding-top: 0; gap: 14px; }
@media (max-width: 860px) {
  .llist--side .lrow { grid-template-columns: 1fr; gap: 22px; }
}
/* Panel wie auf den Unterseiten: Kopfzeile, Flaeche, Fusszeile. Der
   Rahmen ist eine Haarlinie in der Beschriftungsfarbe, keine Kante im
   Bild. */
.post {
  display: block;
  position: relative;
  padding: 10px 10px 12px;
  border: 1px solid color-mix(in srgb, var(--fg) 22%, transparent);
  transition: border-color var(--t-base) var(--ease);
}
.post:hover { border-color: color-mix(in srgb, var(--fg) 55%, transparent); }

/* Die Kachel lebt jetzt: das Bild zieht unter dem Zeiger leicht an,
   der Titel rueckt nach und die Kennung wird hell. Vorher passierte
   beim Hovern gar nichts, deshalb las sich der Block als Bild mit
   Bildunterschrift statt als anklickbare Arbeit (Leon 11.08.: "sieht
   immernoch langweilig aus"). */
.post .post-media video, .post .post-media img {
  transform: scale(1.001);
  transition: transform 620ms var(--ease);
}
.post:hover .post-media video, .post:hover .post-media img { transform: scale(1.045); }
.post .post-title { transition: transform 320ms var(--ease); }
.post:hover .post-title { transform: translateX(8px); }
.post .post-kicker { transition: color 320ms var(--ease); }
.post:hover .post-kicker { color: var(--fg); }

/* Die Kennung (Jahr und Monat, z.B. 2606) steht gross und halb
   durchsichtig in der Kachelecke, wie die Nummern in den Moods. Sie
   liegt IM Bild statt darueber hinauszuragen — sonst braeuchte die
   Sektion wieder Luft nach oben, die sie gerade verloren hat. */
.post::before {
  content: attr(data-no);
  position: absolute;
  top: 44px; right: 18px;
  z-index: 2;
  font-size: clamp(34px, 4.4vw, 74px);
  font-weight: 900; font-style: italic;
  letter-spacing: -0.05em;
  line-height: 1;
  color: #fff;
  mix-blend-mode: difference;
  opacity: 0.4;
  pointer-events: none;
  transition: opacity 320ms var(--ease), transform 320ms var(--ease);
}
.post:hover::before { opacity: 0.72; transform: translateY(-3px); }

/* Kennung ueber der Kachel — dieselbe Mono-Zeile wie an der Hero.
   Sie bindet die Sektion an die Beschriftung des Wuerfels, vorher
   stand hier gar kein Zeichen aus dem System (Leon 11.08.: "micro
   graphics fehlen"). */
.post-kicker {
  display: flex;
  align-items: center;
  margin-bottom: 12px;
  font-family: var(--mono);
  font-stretch: 75%;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--dim);
}
.post-kicker .meta-sep { background: var(--dim); }
/* Kopfzeile sitzt zwischen Rahmen und Bild, wie die Zeile ueber der
   Platte auf den Unterseiten: links die Angaben, rechts der Anschlag. */
.post-kicker {
  justify-content: flex-start;
  gap: 0;
  padding-bottom: 8px;
  margin-bottom: 10px;
  border-bottom: 1px solid color-mix(in srgb, var(--fg) 16%, transparent);
}
.post-kicker::after {
  content: "";
  flex: 1;
  height: 1px;
  margin: 0 10px;
  background: repeating-linear-gradient(90deg,
    currentColor 0 2px, transparent 2px 7px);
  opacity: 0.5;
  order: 90;
}
/* Zeichen und Punktreihe stehen links und rechts der Angaben. Die
   Punktreihe bekommt die hoechste Ordnung, damit sie hinter der
   gestrichelten Linie sitzt und nicht mitten in der Zeile. */
.post-kicker .mg { width: 18px; height: 18px; margin-right: 10px; opacity: 0.8; }
.post-kicker .dots { order: 99; }
/* Der Anriss laeuft aufrecht, die Schraege traegt Titel und Kennung */
.post-excerpt.is-placeholder { color: var(--dim); }
/* Die Flaeche traegt den Block, nicht ein Rahmen */
.post-media {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--void);
}
.post-media video, .post-media img {
  width: 100%; height: 100%; object-fit: cover;
  transition: transform var(--t-slow) var(--ease);
}
/* Datum in derselben Familie und Schraege wie alle Beschriftungen */
.post-date {
  margin-top: 16px;
  font-size: 14px;
  font-weight: 800;
  font-style: italic;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--dim);
}
/* Fusszeile: Titel links, Pfeil rechts. Beides auf einer Linie, damit
   die Kachel unten genauso abschliesst wie oben. */
.post-foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin-top: 10px;
}
.post-title {
  font-size: clamp(18px, 2.1vw, 30px);
  font-weight: 900;
  font-style: italic;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  line-height: 1.06;
  color: var(--fg);
}
.post-go {
  font-family: var(--mono);
  font-stretch: 75%;
  font-size: 13px;
  font-weight: 700;
  color: var(--dim);
  transition: transform var(--t-base) var(--ease), color var(--t-base) var(--ease);
}
.post:hover .post-go { color: var(--fg); transform: translateX(5px); }
/* Fliesstext: aufrecht, damit der Beitrag lesbar bleibt. Die Schraege
   traegt die Marke in Titeln und Beschriftungen, nicht im Absatz.
   Auf eine Zeile geklemmt — der Anriss ist hier ein Hinweis, kein
   Absatz, und jede weitere Zeile geht auf die Hoehe der Sektion. */
.post-excerpt {
  margin-top: 6px;
  font-size: 14px;
  font-weight: 700;
  line-height: 1.5;
  color: var(--soft);
  max-width: 52ch;
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
@media (hover: hover) and (pointer: fine) {
  .post:hover .post-media video,
  .post:hover .post-media img { transform: scale(1.03); }
}

/* Floating Preview (Desktop) — folgt der Maus, versetzt, verdeckt die Namen nicht */
.preview-float {
  position: fixed;
  top: 0; left: 0;
  /* Groesse setzt main.js pro Projekt (setFormat): das Format springt
     von Projekt zu Projekt, deshalb steht hier kein festes 16/9 mehr.
     Die Werte hier greifen nur bis der erste Hover kommt. */
  /* KEIN max-width hier. Die Groesse rechnet main.js und schreibt sie als
     Inline-Breite, ein `max-width` im Stylesheet gewinnt aber gegen eine
     Inline-Breite und deckelt sie stillschweigend. Genau das ist passiert:
     hier stand `max-width: 300px`, auf dem Handy `260px`, und damit war
     jede Vergroesserung im JS wirkungslos — das Fenster blieb auf dem
     Deckel stehen. Leon hat mehrfach gemeldet, die Vorschau sei zu klein,
     und die Aenderungen davor haben nie gegriffen, weil sie an der
     falschen Stelle sassen (25.08.2026). Die Werte unten greifen nur, bis
     der erste Hover kommt. */
  width: 22vw;
  aspect-ratio: 16 / 9;
  z-index: 40;
  pointer-events: none;
  opacity: 0;
  /* JS setzt transform direkt per inline-style, kein transition darauf */
  transition: opacity var(--t-fast) var(--ease);
  overflow: hidden;
  /* Grund ist das Standbild des Werks (main.js setzt background-image).
     Der Loop deckt es ab sobald er ein Bild hat, solange er laedt steht
     dort das Bild und kein schwarzes Fenster. */
  background: var(--void) center / cover no-repeat;
  will-change: transform;
}
.preview-float.visible { opacity: 1; }
/* Aufmachen ohne Aufblenden. Leon 01.09.2026: "Ich will einfach, dass das
   Video direkt da ist. Wofuer dieses kleine Video, diese komische
   Vorschau sieht scheisse aus." Die 200ms Blende beim Erscheinen liest
   er als Vorstufe, nicht als Uebergang. main.js setzt die Klasse nur fuer
   ein Bild, das Zumachen blendet weiterhin weich aus — dort ist es kein
   Warten, sondern ein Nachhall. */
.preview-float.instant { transition: none; }
.preview-float video, .preview-float img {
  width: 100%; height: 100%; object-fit: cover;
}
/* Ohne bekannte Masse zeichnet object-fit klein und mittig statt
   fuellend, siehe makeVideo() in main.js. Solange also lieber gar nichts
   vom Video zeigen — darunter liegt das Standbild als Grund und deckt
   den Kasten voll aus. Kein Uebergang, sonst blendet das Video ueber
   dem Standbild sichtbar auf.
   display statt opacity (02.09.): mit opacity existiert die Videoebene
   schon und Safari malte sie beim ersten Frame teils im falschen
   Massstab — genau das halbgrosse Bild, das Leon wieder gesehen hat
   ("zeigt er das Vorschaubild halb so gross wie das richtige Bild").
   Mit display:none entsteht die Ebene erst, wenn das Video seine Masse
   kennt, es gibt keinen Zustand mehr aus dem sie falsch kommen kann. */
.preview-float video.cold { display: none; }
/* Alle Loops wohnen dauerhaft im Kasten (main.js, makeVideo). Nur der
   eingeschaltete wird gezeichnet. display:none statt 1x1 Bildpunkt:
   beim Einschalten entsteht die Videoebene frisch in der Endgroesse,
   statt aus einem alten Zustand nachzuziehen (Safari, doppelte
   Pixeldichte: Bild halb so gross, Leon 01.09.2026). */
.preview-float video:not(.is-on) { display: none; }

/* Vorrat der vorgeladenen Loops. Bewusst NICHT display:none und nicht
   visibility:hidden — beides nimmt dem Browser den Grund, das Video
   ueberhaupt aufzumachen, und genau darum geht es hier. Ein Bildpunkt in
   der Ecke, durchsichtig und ohne Griff. */
.loop-preload {
  position: fixed;
  left: 0; bottom: 0;
  width: 1px; height: 1px;
  overflow: hidden;
  opacity: 0.001;
  pointer-events: none;
  z-index: -1;
}
.loop-preload video { width: 1px; height: 1px; object-fit: cover; }

/* ============================================================
   Hero-Stats — Horizontaler Panel-Streifen FIG.03–FIG.06
   Vier Micrographic-Panels am unteren Rand der Hero: Katalog,
   Gesamtlaufzeit, Spitzenwert, Live-Rate. Gleiches Typo-System
   wie FIG.01/02, desselbe mono-Font, dieselbe Staffelung.
   ============================================================ */
.hero-stats {
  position: absolute;
  bottom: 0; left: 0; right: 0;
  z-index: 4;
  /* Drei feste Zonen statt space-between (Leon 02.09.): mit flex hing
     die Mitte des mittleren Blocks an den Breiten seiner Nachbarn —
     DRKNSD CHARTS stand deshalb nie auf der echten Seitenmitte und
     wanderte mit, sobald links oder rechts eine Zahl breiter wurde
     ("es bewegt sich die ganze Zeit"). Im Raster hat jeder Block seine
     Zone, die Mitte ist die Mitte, und was in einer Zone passiert,
     schiebt die anderen nicht mehr an. */
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: start;
  padding: 0 clamp(20px, 5vw, 88px) 32px;
  font-family: var(--mono);
  font-stretch: 75%;
  color: var(--fg);
  text-transform: uppercase;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--t-slow) var(--ease);
}
.hero-stats .hstat:nth-child(1) { justify-self: start; }
.hero-stats .hstat:nth-child(2) { justify-self: center; }
.hero-stats .hstat:nth-child(3) { justify-self: end; }
/* Feste Mindestbreite fuer die Charts: die Titelzeile wechselt alle
   viereinhalb Sekunden ihre Laenge, und ein zentrierter Block wuerde
   sich bei jedem Wechsel neu um die Mitte legen — genau das Wackeln,
   das Leon meint. Breit genug fuer den laengsten Kurztitel. */
.hero-stats .hstat:nth-child(2) { min-width: 216px; }
.hero-stats.loaded { opacity: 1; }
.hstat {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.hstat::before {
  content: "";
  width: 16px; height: 1px;
  background: var(--fg);
  margin-bottom: 4px;
}

/* Auftraggeber-Kachel: drei Wortmarken untereinander, die versetzt
   durchwechseln. Feste Zeilenhoehe, sonst huepft der ganze Streifen
   sobald ein laengerer Name einlaeuft. */
.hstat-clients { min-width: 190px; }
.client-strip { display: flex; flex-direction: column; gap: 2px; }
/* Eine Spur kleiner und leichter (Leon 02.09.: "dann koennte man
   ueberlegen, ob man die Logos sogar fast noch ein bisschen kleiner
   macht"). */
.client-mark {
  position: relative;
  height: 14px; line-height: 14px;
  font-size: 11px; font-weight: 500;
  letter-spacing: 0.02em;
  white-space: nowrap; overflow: hidden;
}
/* Der Wechsel reisst die Marke in zwei versetzte Haelften auf. Zwei
   Kopien per Pseudoelement, jede zeigt nur ihre Haelfte (clip-path)
   und faehrt in die Gegenrichtung — genau das liest man als Glitch.
   Ohne die Kopien koennte man nur die ganze Zeile schieben, und das
   sieht nach Wackeln aus statt nach Stoerung. */
.client-mark.glitching::before,
.client-mark.glitching::after {
  content: attr(data-ghost);
  position: absolute; left: 0; top: 0;
  width: 100%; height: 100%;
  background: inherit;
}
.client-mark.glitching::before {
  clip-path: inset(0 0 52% 0);
  animation: clientGlitchA 260ms steps(3, end);
}
.client-mark.glitching::after {
  clip-path: inset(48% 0 0 0);
  animation: clientGlitchB 260ms steps(3, end);
}
/* Auch hier: halber Ausschlag, die Marken bleiben in ihrer Zeile. */
@keyframes clientGlitchA {
  0%   { transform: translateX(0); opacity: 1; }
  40%  { transform: translateX(-2px); opacity: 0.85; }
  70%  { transform: translateX(1px); opacity: 1; }
  100% { transform: translateX(0); opacity: 0; }
}
@keyframes clientGlitchB {
  0%   { transform: translateX(0); opacity: 1; }
  40%  { transform: translateX(2px); opacity: 0.9; }
  70%  { transform: translateX(-1px); opacity: 1; }
  100% { transform: translateX(0); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .client-mark.glitching::before, .client-mark.glitching::after { display: none; }
}
/* Unter dem Strip stehen die Gattungen statt einer Zaehlung. "19
   PARTNERS" las sich wie eine Obergrenze (Leon 12.08.). */
#client-count { display: flex; align-items: center; }

/* ---------- Zwei Angaben auf einem Platz ----------
   Leon 12.08.: "catalouge und total watchtime kann sich einen platz
   teilen, wechseln sich ab". Beide Flaechen liegen uebereinander; die
   abgeschaltete bleibt im Fluss, damit ihre Tafelspalten stehen
   bleiben, ist aber weder sichtbar noch anfassbar. */
/* Beide Flaechen liegen uebereinander in EINER Rasterzelle, statt die
   abgeschaltete per `position: absolute` aus dem Fluss zu nehmen.

   Der Unterschied ist die Breite: absolut gesetzte Kinder zaehlen fuer die
   Breite des Wirts nicht mit, also war der Wechselplatz immer genau so
   breit wie die gerade sichtbare Flaeche — und sprang bei jedem Wechsel auf
   ein anderes Mass (gemessen 95 auf 70 Bildpunkte). Weil der rechte Block
   am rechten Rand haengt und rechtsbuendig setzt, wandert dabei der ganze
   Text seitlich. Im Raster zaehlen beide Flaechen fuer die Breite, der
   Platz steht also auf dem Mass der breiteren und bleibt stehen. */
.meta-swap { position: relative; display: grid; }
.meta-swap > .meta-face { grid-area: 1 / 1; }
.meta-face {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: inherit;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--t-base) var(--ease);
}
/* Kein `position: absolute` mehr fuer die abgeschaltete Flaeche, sie liegt
   jetzt ueber `grid-area` in derselben Zelle wie die aktive. Sichtbar ist
   weiterhin nur eine, das regeln Deckkraft und `visibility`. */
.meta-face:not(.is-on) { position: relative; }
.meta-face.is-on { opacity: 1; visibility: visible; pointer-events: auto; }
.hero-meta-left .meta-face { align-items: flex-start; }

/* ---------- Mailzeile unter der Titelwand ----------
   Leon 12.08.: "unter portfolio section einmal die mail adresse
   mittig". */
.works-mail {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  /* Unten nur der kleine Abstand: die Fussmarke darunter bringt ihren
     eigenen Vorlauf mit, vorher addierten sich beide zu 234 Punkten
     Schwarz (Leon 14.09.2026: "da ist auch zu viel Schwarz oben und
     unten"). */
  padding: var(--gap-s) var(--pad) var(--gap-s);
  text-align: center;
}
.works-mail-kicker {
  display: flex;
  align-items: center;
  font-family: var(--mono);
  font-stretch: 75%;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--dim);
}
.works-mail-kicker .meta-sep { background: var(--dim); }
.works-mail-adr {
  font-size: clamp(24px, 3.6vw, 54px);
  font-weight: 900;
  font-style: italic;
  /* Weiter als die Titel (-0.03em): bei der engen Laufweite liefen in
     HELLO@DRKNSD das O, das Zeichen und das D ineinander (Leon 02.09.:
     "das mit dem O zusammen und dem D, das ist nicht schoen"). Eine
     Mailadresse ist zum Abtippen da, sie darf nicht verschmelzen. */
  letter-spacing: -0.005em;
  text-transform: uppercase;
  color: var(--fg);
  transition: opacity var(--t-base) var(--ease);
}
.works-mail-adr:hover { opacity: 0.62; }

/* ============================================================
   Die 24-Stunden-Zusage — ein Bitcoin, und wir stehen da
   Leon 13.08.: "Bau irgendwo bitcoin wallet ein so donate here. Haben
   die Idee wenn jemand 1 bitcoin bezahlt sind wir in 24 h vor Ort und
   nehmen uns dem Projekt an."
   Kein Spendenknopf, kein fremdes Zeichen, keine Farbe: es ist ein
   Tarif, also steht es als Tarif da. Marke links, Bedingung und Tafeln
   rechts, Adresse unten. Genau die Bauform der Latest-Marke und der
   Hero-Tafeln, damit es zur Seite gehoert.
   ============================================================ */
/* Unten bewusst NICHT die grosse Zaesur: darunter folgt die Mailzeile
   mit eigenem Vorlauf, und zwei grosse Abstaende hintereinander addieren
   sich zu einem Loch. Genau diese Stellen meinte Leon mit unbalanciert. */
.btc-deal { padding: var(--gap-l) var(--pad) var(--gap-m); }
.btc-inner {
  max-width: var(--maxw);
  margin: 0 auto;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--gap-m);
  align-items: start;
}
.btc-logo { width: max-content; max-width: 100%; }
/* Dieselbe Figur wie an der Latest-Marke */
.btc-logo .chk {
  flex: 0 0 auto;
  width: clamp(26px, 2.6vw, 44px);
  height: clamp(52px, 6.4vw, 108px);
  background-image: conic-gradient(
    var(--fg) 0 25%, transparent 0 50%,
    var(--fg) 0 75%, transparent 0
  );
  background-size: 50% 25%;
  transform: skewX(-11deg);
}
.btc-body { max-width: 760px; }
/* Satzbreite bewusst kurz: der Satz ist die Bedingung des Angebots und
   soll in einem Blick lesbar sein, nicht als Absatz. */
.btc-claim {
  margin: 0 0 var(--gap-s);
  max-width: 30ch;
  font-size: clamp(19px, 2vw, 28px);
  font-weight: 800;
  font-style: italic;
  line-height: 1.32;
  letter-spacing: -0.015em;
  color: var(--soft);
}
.btc-stats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap-s);
  margin-bottom: var(--gap-s);
}
.btc-stats .hstat { min-width: 150px; }
.btc-wallet { display: flex; flex-direction: column; gap: 10px; }
.btc-wallet .meta-tag {
  font-family: var(--mono);
  font-stretch: 75%;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--dim);
}
/* Kein Rahmen: die Flaeche traegt sich ueber einen ganz leichten Grund
   aus der eigenen Textfarbe. Der faellt in beiden Modi richtig aus,
   ohne dass ein zweiter Farbwert gepflegt werden muss, und die Seite
   bekommt keine weitere Linie. */
.btc-adr {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap-s);
  width: 100%;
  max-width: 660px;
  padding: 16px 18px;
  border: 0;
  background: none;
  color: var(--fg);
  cursor: none;
  text-align: left;
  overflow: hidden;
}
.btc-adr::before {
  content: "";
  position: absolute;
  inset: 0;
  background: currentColor;
  opacity: 0.07;
  transition: opacity var(--t-base) var(--ease);
}
.btc-adr:hover::before { opacity: 0.13; }
.btc-adr.copied::before { opacity: 0.2; }
.btc-adr-val,
.btc-adr-act {
  position: relative;
  font-family: var(--mono);
  font-stretch: 75%;
  font-weight: 600;
}
.btc-adr-val {
  font-size: clamp(11px, 1.1vw, 14px);
  letter-spacing: 0.02em;
  word-break: break-all;
}
.btc-adr-act {
  flex: 0 0 auto;
  font-size: 10px;
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  color: var(--dim);
}
.btc-adr:hover .btc-adr-act { color: var(--fg); }

/* ---------- Micrographic-Zeichen an der Hero ----------
   Leon 12.08.: "auf der hero seite noch mehr logos aus dem micrographic
   mood uebernehmen". Sie tragen keine Angabe, deshalb stehen sie
   gedimmt und klein — sie sollen die Zahlen nicht ueberholen. */
.mg-row { display: flex; align-items: flex-end; gap: 10px; }
.hero-meta-right .mg-row { justify-content: flex-end; }
/* Die Zeichen der unteren Reihe (.hstat .mg) sind raus (Leon 14.09.2026,
   "an Micro-Graphics wieder so ein bisschen zuruecknehmen"), dort steht
   wieder der kurze Skalenstrich aus .hstat::before. */
/* .hero-marks ist raus (Leon 02.09.): die drei halbtransparenten
   Zeichen unter dem Logo trugen keine Angabe. brand.js baut den Block
   nicht mehr. */

/* ============================================================
   Kontaktseite (contact.html)

   Sie traegt die Klasse .hero und bekommt damit denselben Nullraum wie
   die Startseite — Wuerfel, Schatten, Schein, Verlauf. Vorher war sie
   eine Liste auf schwarzem Grund und damit die einzige Seite ohne den
   Hauptdarsteller (Leon 11.08.: "viel zu langweilig").

   Seit 01.10.2026 stehen Schrift und Wuerfel als EIN zentriertes Paar
   im Bild und nicht mehr an den beiden Fensterkanten (Leon: "nicht so
   schoen der wuerfel der klebt so an der rechten seite bei der contact
   page, der soll sich eigentlich so mittig setzen zwischen die schrift
   und die schrift ein bisschen so mittig sein, das sieht irgendwie
   nicht geil formatiert aus"). Vorher sass der Wuerfel auf -3vmin und
   stand damit rechts aus dem Bild heraus (gemessen bei 1467: Kasten
   982 bis 1498, also 31 Punkte abgeschnitten), die Mailzeile klebte
   gleichzeitig am linken Rand und endete genau dort, wo der Wuerfel
   begann — kein Zwischenraum, keine Luft aussen.

   Statt beide Elemente einzeln an die Kanten zu haengen, spannt die
   Seite jetzt ein zentriertes Band auf: Schrift an dessen linker Kante,
   Wuerfel an dessen rechter, der Rest teilt sich als Luft auf beide
   Seiten. Das Band waechst mit dem Fenster (80vw) und ist bei 1300
   gedeckelt, damit das Paar auf einem grossen Schirm nicht wieder
   auseinanderlaeuft: Schrift und Wuerfel hoeren bei 70 Punkten bzw. 440
   Punkten auf zu wachsen, ein weiteres Band reisst dann nur ein Loch
   zwischen beide (bei 1700 und 2560 Fensterbreite gemessen: 489 Punkte
   Leere in der Mitte). Die Schriftgroesse haengt an derselben Bewegung,
   sonst wird die Zeile breiter als ihr Platz und laeuft in den Wuerfel.
   Wer hier etwas aendert: nachmessen, dass die Mailzeile rechts VOR dem
   Wuerfel endet, das ist die enge Grenze.

   room.js holt sich die Lage des Wuerfels selbst, deshalb wandern
   Raumlicht und Schatten ohne weiteres Zutun mit.
   ============================================================ */
.contact-room {
  --room-inset: clamp(24px, 5vw, 88px);
  --room-band: min(clamp(1100px, 80vw, 1300px), calc(100% - 2 * var(--room-inset)));
  --room-gut: calc((100% - var(--room-band)) / 2);
  --room-cube: clamp(260px, 38vmin, 440px);
  --room-gap: clamp(28px, 3.4vw, 72px);
}
.contact-room .hero-cube {
  left: auto;
  right: var(--room-gut);
  width: var(--room-cube);
  height: var(--room-cube);
  transform: translateY(-50%) translateY(var(--bob, 0px));
}
.contact-stack {
  position: absolute;
  top: 50%;
  left: var(--room-gut);
  width: calc(var(--room-band) - var(--room-cube) - var(--room-gap));
  transform: translateY(-54%);
  z-index: 5;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1.2vh;
}
/* Das Instagram-Zeichen unten links stand auf dem alten Rand und damit
   nach dem Umbau nicht mehr unter der Mailzeile. Es haengt jetzt an
   derselben Bandkante, sonst stehen zwei linke Kanten im Bild. */
.contact-room .hero-meta-left { left: var(--room-gut); }
/* Die drei Zeilen stehen versetzt statt in einer Spur. Eine saubere
   Kante links waere wieder eine Linie durchs Bild, und die Seite fuehrt
   keine (siehe .works). */
.contact-line {
  /* Von 5.6vw/88 heruntergesetzt am 01.10.2026: die Zeile war bei 1467
     Punkten 905 breit und damit breiter als ihre Haelfte des Bandes, sie
     lief direkt in den Wuerfel. 4.4vw ergibt dort 711 bei 879 Platz. */
  font-size: clamp(28px, 4.4vw, 70px);
  font-weight: 900; font-style: italic;
  /* Wie bei der Mailzeile unter der Werkliste: weiter gestellt, damit
     O, @ und D nicht ineinanderlaufen (Leon 02.09., gilt auch hier). */
  letter-spacing: -0.005em;
  line-height: 1.02;
  text-transform: uppercase;
  /* Auf der Titelwand stehen zwanzig Namen und nur der gehoverte
     leuchtet, deshalb sind sie dort alle gedimmt. Hier stehen drei
     Zeilen, und wenn die alle dunkel bleiben liest sich die Seite als
     nicht fertig geladen. Also andersherum: sie stehen hell und der
     Zeiger hebt eine davon ganz heraus. */
  color: var(--soft);
  transition: color var(--t-base) var(--ease);
  white-space: nowrap;
}
.contact-line:nth-child(2) { margin-left: 5vw; }
.contact-line:nth-child(3) { margin-left: 2vw; }
a.contact-line:hover, a.contact-line.copied { color: var(--fg); }

/* Kennung vor dem Handle. Ohne sie liest sich "@drknsd" unter einer
   Mailadresse wie ein zweiter Mailweg (Leon 11.08.: "vor @drknsd muss
   man checken, dass es instagram ist"). Gezeichnet statt als Bild:
   gerundetes Quadrat, Kreis, Punkt — dieselbe Strichstaerke wie die
   technischen Zeichen, kein Markenblau. */
/* Echtes Instagram-Glyph als SVG statt aus Rahmen und Pseudoelementen
   zusammengesetzt (Leon 11.08.: "instagram logo auf contact seite ist
   verkackt"). Der Nachbau hatte zu dicke Striche, eine zu eckige
   Rundung und der Punkt sass daneben — bei einem Zeichen, das jeder
   kennt, faellt jede Abweichung sofort auf. Die Strichstaerke ist
   relativ zur viewBox, dadurch bleibt sie in jeder Groesse gleich. */
.contact-line-insta { display: inline-flex; align-items: center; gap: 0.34em; }
.contact-chan {
  flex: 0 0 auto;
  width: 0.66em; height: 0.66em;
  overflow: visible;
  /* Aufrecht. Die Schraege der Schrift mitzunehmen war der Fehler: bei
     einem Zeichen, das jeder auswendig kennt, liest sich jede
     Verzerrung als Fehler und nicht als Anpassung — das Quadrat wird
     zur Raute und der Kreis zur Ellipse (Leon 01.09.: "dieses Instagram
     Icon bei der Contact Seite ... sieht irgendwie komisch aus, so
     schraeg"). Den Bezug zur kursiven Zeile stellt jetzt der Abstand
     her, nicht eine Verformung. */
  transform: none;
}
.contact-chan rect, .contact-chan circle {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  /* runde Enden und Ecken, sonst wirkt der Rahmen bei kleiner
     Darstellung kantiger als das Original */
  stroke-linejoin: round;
}
.contact-chan .dot { fill: currentColor; stroke: none; }

/* Der Statusblock sitzt unten links und laeuft im selben Typo-System wie
   die Beschriftung an der Hero. */
.contact-room .hero-meta {
  top: auto;
  bottom: clamp(28px, 7vh, 72px);
  transform: none;
  z-index: 5;
}

@media (max-width: 860px) {
  /* Auf dem Handy sitzt der Wuerfel oben und die Zeilen darunter,
     nebeneinander waere beides zu klein. */
  .contact-room .hero-cube {
    right: auto; left: 50%;
    /* Groesser und in die Mitte des freien Bandes zwischen Marke oben
       (Unterkante 94) und Mailzeile unten (Leon 14.09.2026: "Wuerfel am
       Handy auf der Kontakt Seite kann groesser"). Vorher 62vmin bei
       34 Prozent, das liess ueber der Mailzeile ein Drittel Bild leer.
       92vw fuellt die Breite bis auf den Seitenrand, 60svh haelt ihn
       auf kurzen Schirmen im Band. Gemessen bei 375x667: Kasten
       115 bis 459, Marke endet bei 94, Mailzeile beginnt bei 485. */
    top: 43%;
    width: min(92vw, 60svh);
    height: min(92vw, 60svh);
    transform: translate(-50%, -50%) translateY(var(--bob, 0px));
  }
  .contact-stack {
    top: auto;
    /* Nicht mehr 16vh. Die Mailzeile und der Status-Block hingen beide
       fuer sich vom unteren Rand, und seit die Markenreihe mit dem
       Instagram-Zeichen am Status-Block sitzt (14.09.), ist der Block
       98 Punkte hoch; der Stempel rechts endet bei 135 Punkten. 16vh
       sind auf einem iPhone 107 bis 135 Punkte, also lief die Zeile in
       die Ringe und in den Barcode (Leon 14.09.2026: "kontakt seite am
       iphone ist auch komisch texte liegen uebereinander"). Jetzt in
       Punkten: Stand des Blocks (5vh) plus seine Hoehe plus Luft.
       Gemessen: 390x844 Abstand 42 -> 24 Punkte frei, 375x667 -6 -> 22.
       Die 98 standen hier bis zuletzt, obwohl der Block unten links seit
       01.10.2026 nur noch das Instagram-Zeichen traegt: die engere
       Grenze war damals nicht der Block links, sondern der
       Barcode-Stempel RECHTS, dessen Oberkante 135 Punkte ueber dem
       unteren Rand stand. Der Stempel ist am 01.10.2026 ganz
       weggefallen (Leon: "dieser barcode und so das kann immer noch
       weg"), damit ist das Zeichen links wieder die Grenze und die
       Zeile darf heruntersteigen. Wer hier etwas aendert, misst gegen
       die Oberkante von #contact-meta. */
    bottom: calc(5vh + 52px + 22px);
    left: var(--pad); right: var(--pad);
    /* Am Rechner traegt die Zeile eine gerechnete Breite aus dem
       zentrierten Band. Am Handy spannt sie zwischen beiden Raendern,
       eine Breite daneben wuerde das right ausser Kraft setzen. */
    width: auto;
    transform: none;
  }
  /* Am Handy steht nichts in einem Band, das Zeichen sitzt am Rand. */
  .contact-room .hero-meta-left { left: var(--pad); }
  .contact-line { font-size: clamp(24px, 7.6vw, 44px); white-space: normal; }
  .contact-line:nth-child(2), .contact-line:nth-child(3) { margin-left: 0; }
  .contact-room .hero-meta { bottom: 5vh; }
  /* Das Instagram-Zeichen ist 26 Punkte gross, als Tippflaeche zu klein.
     Unsichtbarer Rand ringsum auf 46 Punkte, das Bild bleibt gleich. */
  .contact-room .mg-insta { position: relative; }
  .contact-room .mg-insta::after { content: ""; position: absolute; inset: -10px; }
}

/* Kontakt am Handy quer (14.09.2026, derselbe Anlass). 390 Punkte
   Hoehe reichen nicht fuer Wuerfel, Mailzeile und Status-Block
   untereinander. Darum drei Spalten: Zeile links in der Mitte, der
   Wuerfel rechts der Mitte und oben, Zeichen unten links wie gehabt.
   Die 60 Prozent und die 38 Prozent stammen aus der Zeit mit dem
   Barcode-Stempel unten rechts, der am 01.10.2026 weggefallen ist. Die
   Masse bleiben gemessen stehen, es gibt rechts jetzt nur mehr Luft. */
@media (max-width: 860px) and (orientation: landscape) and (max-height: 540px) {
  .contact-room .hero-cube {
    /* Quer ebenfalls groesser (14.09.2026, zweite Runde): 74vmin statt
       58. Der Kasten sass bei 60 Prozent und 38 Prozent, weil er rechts
       vor dem Barcode-Stempel enden musste (gemessen 844x390: Kasten bis
       650, Stempel ab 662). Dadurch blieben zur Mailzeile nur 7 Punkte
       Luft und oben stiess er an den Bildrand. Der Stempel ist am
       01.10.2026 weggefallen, also rueckt der Kasten in den frei
       gewordenen Platz: 66 Prozent quer, 50 Prozent hoch. Gemessen
       844x390: Zeile bis 355, Kasten 412 bis 701, rechts 143 Punkte
       frei, senkrecht mittig. */
    left: 66%;
    top: 50%;
    width: clamp(220px, 74vmin, 300px);
    height: clamp(220px, 74vmin, 300px);
  }
  .contact-stack {
    top: 50%; bottom: auto;
    right: auto;
    transform: translateY(-54%);
  }
  .contact-line { font-size: clamp(22px, 3.6vw, 30px); white-space: nowrap; }

  /* Startseite quer: 844 Punkte Breite fallen nicht unter die
     Handy-Regeln (760), also stand die Hero wie am Rechner, nur in 390
     Punkten Hoehe. Der Panel-Streifen unten, die Beschriftungsbloecke
     in der Mitte und ihre aufgeklappten Zusatzzeilen lagen uebereinander
     (LIVE COUNT ueber FEATURED PROJECTS, SOURCE ueber MERCEDES, die
     Markenzeile ueber DRKNSD CHARTS). Gleiche Antwort wie im Hochformat:
     Streifen weg, Bloecke nach unten, Zusatzzeilen klappen nach oben. */
  .hero-stats { display: none; }
  .hero-meta { top: auto; bottom: 9vh; transform: none; gap: 4px; }
  .hero-meta .meta-more { top: auto; bottom: 100%; }
  .hero-meta .meta-more .meta-more-in { padding-top: 0; padding-bottom: 6px; }
}

/* ============================================================
   Detailseite (project.html)
   ============================================================ */
.detail-hero { position: relative; height: 84vh; width: 100%; overflow: hidden; }
.detail-hero video, .detail-hero .detail-fallback { position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%; object-fit: cover; }
/* Loops laufen in Farbe (Leon 11.08.2026). Kontrast bleibt leicht
   angehoben, damit die Schrift darueber noch traegt.
   Ausdrueckliche Stapelung: die Fallback-Flaeche traegt seit 02.09. das
   Poster als Grund und steht im DOM NACH dem Video — ohne z-index
   wuerde sie den laufenden Loop zudecken. */
.detail-hero video { filter: contrast(1.05); z-index: 1; }
.detail-hero .detail-fallback { background: var(--void); z-index: 0; }
.detail-hero::after { content: ""; position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 2; background: linear-gradient(180deg, rgba(var(--scrim),0.4), rgba(var(--scrim),0) 40%, rgba(var(--scrim),0.6)); }
.detail-title {
  position: absolute; left: var(--pad); bottom: 40px; z-index: 2;
  font-size: clamp(46px, 11vw, 170px); font-weight: 900; font-style: italic;
  letter-spacing: -0.03em; line-height: 0.86; text-transform: uppercase;
}
.detail-body { display: grid; grid-template-columns: 1fr 1fr; gap: 0; }
.detail-text-col { padding: var(--gap-l) var(--pad); }
.detail-side-col { padding: var(--gap-l) var(--pad); }
.detail-tags { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 4vh; }
.detail-tags span {
  color: var(--dim); font-size: 13px; font-weight: 800; font-style: italic;
  letter-spacing: 0.02em; text-transform: uppercase;
}
.detail-text p { font-size: clamp(16px, 1.9vw, 21px); font-weight: 700; line-height: 1.6; color: var(--soft); margin-bottom: 1.3em; }
.detail-credits .label { color: var(--dim); font-size: 13px; font-weight: 800; font-style: italic; letter-spacing: 0.02em; text-transform: uppercase; margin-bottom: 16px; }
.detail-credits ul { list-style: none; }
.detail-credits li { font-size: 15px; font-weight: 700; padding: 8px 0; color: var(--soft); }

/* Der ganze Film. Keine Ueberschrift darueber, die Seite fuehrt keine
   Rubrikzeilen. Das Seitenverhaeltnis haelt der Rahmen, damit beim Laden
   nichts springt. */
/* Die Buehne des Films ist immer schwarz, in beiden Modi (Leon 03.09.:
   "der Hintergrund ist immer schwarz von dem Video, auch wenn es laedt,
   dann soll es nicht weiss zu sehen sein"). Im Bright Mode stand der
   Rahmen vorher in einer weissen Flaeche — bei breiten Formaten wie
   Adieu (2.4:1) las sich das als weisse Balken ueber und unter dem
   Bild. Die Platte darunter ist ohnehin schwarz, also faengt die Seite
   jetzt durchgehend dunkel an. Die Marke oben rechts traegt `on-media`
   und bleibt deshalb in beiden Modi weiss. */
.detail-film { padding: 0 var(--pad); background: #000; }
/* box-shadow statt border: der schwarze Ring liegt AUSSEN am Kasten und
   deckt die Subpixel-Kante ab, die `aspect-ratio` erzeugt (die Hoehe
   kommt hier auf 595.98px heraus). Ohne ihn blitzt an der Oberkante
   der weisse Seitenhintergrund durch — Leon 02.09.: "man sieht oben
   so einen kleinen weissen Strich ueber dem Vimeo-Player". overflow
   haelt zusaetzlich alles im Kasten. Ein border wuerde das Layout
   verschieben, box-shadow nicht. */
/* Der Rahmen traegt das ECHTE Bildformat des Films, nicht pauschal 16:9.
   Vorher stand hier fest 16/9, und die drei Breitwandfilme (adieu 1920x804,
   Deutschland und Hell Yes 1920x800) bekamen dadurch vom Vimeo-Player
   eigene Balken oben und unten. In Safari malt der Player diese Flaeche
   weiss, weil das Player-Dokument selbst keinen Grund setzt und Safari
   fuer den fremden Rahmen dann die helle Systemflaeche nimmt — Chromium
   nimmt dort schwarz, deshalb war der Fehler hier nie zu sehen, bei Leon
   dagegen immer (03.09.2026, fuenfmal gemeldet). Mit dem echten Format
   sitzt das Bild randlos im Rahmen, es gibt gar keine Balken mehr, in
   keinem Browser. --film-ar setzt project.html aus filmRatio in data.js.
   --film-arn ist derselbe Wert als Zahl, weil calc() mit einem
   Seitenverhaeltnis nicht rechnen kann. */
.film-frame { position: relative; width: 100%; margin: 0 auto;
              aspect-ratio: var(--film-ar, 16 / 9); background: #000;
              box-shadow: 0 0 0 1px #000; overflow: hidden; }
/* Der Iframe bleibt unsichtbar, bis der Player wirklich steht. Bevor
   dessen eigenes Stylesheet greift, malt der Browser die Fremdseite
   weiss — genau das war der weisse Ladescreen vor dem Film (Leon
   02.09.: "bevor das Bild dann das Video reinlaedt, ist das Ding
   weiss ... das muss schwarz sein"). Solange traegt der schwarze
   Rahmen die Flaeche, project.html setzt .on nach dem Laden. */
.film-frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; opacity: 0; transition: opacity 400ms var(--ease); }
.film-frame iframe.on { opacity: 1; }

.detail-next { padding: var(--gap-l) var(--pad); text-align: center; }
.detail-next .label { color: var(--dim); font-size: 13px; font-weight: 800; font-style: italic; letter-spacing: 0.02em; text-transform: uppercase; margin-bottom: 16px; }
.detail-next a { font-size: clamp(32px, 6vw, 74px); font-weight: 900; font-style: italic; letter-spacing: -0.03em; text-transform: uppercase; }

/* Leon 02.10.2026: "braucht das nicht diese weisse Flaeche darunter, das
   kann einfach ein kleiner Rahmen sein". Der Knopf stand als gefuellte
   Platte in --bg und war damit das schwerste Element der Seite. Jetzt
   traegt er nur noch eine Haarlinie und den Grund, der dahinter liegt.
   Ohne Fuellung bleibt er trotzdem in beiden Modi lesbar: --fg und der
   Grund der Platte darunter drehen sich gemeinsam mit dem Modus. */
.detail-back {
  position: fixed; top: var(--pad); left: var(--pad); z-index: 60;
  font-size: 11px; font-weight: 800; font-style: italic;
  letter-spacing: 0.02em; text-transform: uppercase;
  border: 1px solid currentColor; padding: 5px 9px; background: transparent;
  /* Ohne Fuellung muss sich der Knopf gegen den Grund wehren, der gerade
     hinter ihm liegt: schwarzer Filmbereich oben, helle Platte beim
     Scrollen, und das in beiden Modi. Fest --fg reichte nicht, die Schrift
     blieb weiss und waere auf der hellen Platte verschwunden. difference
     mit reinem Weiss dreht Schrift und Rahmen am Grund selbst um, genau
     wie die Kopfzeile der Startseite (Leon 01.10.2026). */
  color: #ffffff; mix-blend-mode: difference;
}

/* Pianetti Layout — neue Sektionen auf der Projektseite.
   Leon 11.08.2026: "Unterseiten nach Pianetti-Aufbau
   text → video → text → BTS → Kontakt → alle Projekte". */

/* Intro-Text direkt unter dem Hero.
   Keine Trennlinie: die Seite fuehrt keine Striche (Leon 05.08.2026),
   die Sektionen trennen sich ueber Abstand und Satzbreite. */
.detail-intro {
  padding: var(--gap-l) var(--pad);
}
.detail-intro-inner {
  max-width: var(--maxw);
  margin: 0 auto;
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: clamp(30px, 6vw, 90px);
  align-items: start;
}

/* Der Film traegt den ersten Bildschirm und steht darin frei — vorher
   klebte er randlos an der Oberkante und lag damit unter der fixen
   Kopfzeile, die Seite fing ohne Anlauf an (Leon 13.08.: "die Vimeo
   Videos kleben so am oberen Rand. Besser zentriert schoener gewichtet
   machen").
   Der Rahmen steht optisch mittig, also eine Spur ueber der echten
   Mitte: oben liegt die Kopfzeile mit Back und Marke, unten nur Luft.
   Gleich viel Abstand haette den Film nach unten kippen lassen.
   Die Hoehe ist gedeckelt ueber max-width — mit aspect-ratio rechnet der
   Rahmen seine Hoehe aus der Breite, ein max-height wuerde stattdessen
   das Bild strecken. */
/* `display: flex` weiter unten schlaegt das HTML-Attribut `hidden`, dessen
   `display: none` nur aus dem Browser-Stylesheet kommt. Projekte ohne
   Vimeo-Fassung (aktuell Aggroberlina) hatten deshalb einen leeren ersten
   Bildschirm in voller Hoehe, vorher weiss, seit dem schwarzen Hintergrund
   ein schwarzes Nichts. Gemessen 03.09.2026: 1440x900 leer. */
.detail-film[hidden] { display: none; }

.detail-film {
  /* Oben und unten gleich viel. Die optische Korrektur nach oben war
     falsch gerechnet: mit gap-s oben und gap-l unten sass der Rahmen
     gemessen bei 63 Bildpunkten Luft oben gegen 135 unten, also sichtbar
     zu hoch (Leon 01.09.: "wenn man die Seite offen hat, ist oben
     weniger Platz als unten"). Jetzt steht der Film in der echten
     Mitte des ersten Bildschirms. */
  padding: max(var(--gap-m), calc(var(--pad) + var(--head-h) + 16px)) var(--pad);
  min-height: 100svh;
  display: flex;
  align-items: center;
  justify-content: center;
}
.film-frame {
  max-width: min(var(--maxw), calc((100svh - clamp(180px, 22vh, 260px)) * var(--film-arn, 1.7778)));
}

/* Cursor-Shield: sitzt unsichtbar ueber dem Vimeo-Iframe.
   Normale Mausbewegungen kommen durch, der benutzerdefinierte Zeiger
   bleibt sichtbar. Beim Klick wird der Shield kurz deaktiviert, damit
   die Vimeo-Steuerung erreichbar bleibt. */
.film-cursor-shield {
  position: absolute; inset: 0; z-index: 2;
  pointer-events: auto; cursor: none;
}
.film-cursor-shield.pass { pointer-events: none; }

/* Die Griffe am Film (Leon 13.08.: "der play button bei den vimeo
   integration ist irgendwie nicht passend zur ci, ich denke vollbild
   sollte gehen, sonst aber keine vimeo funktionen integrieren").
   Vorher stand dort ein Ring — die einzige runde Form auf einer Seite,
   die sonst nur Kanten kennt. Jetzt ein Feld mit Eckwinkeln und einer
   Beschriftung in der Monospace, also dieselbe Bauform wie die Tafeln
   an der Hero. Alles gezeichnet, kein Zeichensatz-Symbol.
   Der Knopf liegt ueber dem Shield, damit der Klick zuerst hier landet. */
.film-play {
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  z-index: 4;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  padding: 0;
  border: 0;
  background: none;
  cursor: none;
  transition: opacity 420ms var(--ease), transform 520ms var(--ease);
}
.film-play-box {
  position: relative;
  display: block;
  width: clamp(64px, 6.6vw, 96px);
  height: clamp(64px, 6.6vw, 96px);
  transition: transform 520ms var(--ease), background-color 420ms var(--ease);
}
/* Eckwinkel statt geschlossenem Rahmen: ein voller Kasten legt sich als
   Fenster ueber das Bild, vier Winkel markieren nur die Stelle. */
.film-corner {
  position: absolute;
  width: 22px; height: 22px;
  border: 1.5px solid rgba(255, 255, 255, 0.92);
  /* Mehrlagig weich und klein, damit die Winkel auf hellem wie dunklem
     Bild stehen ohne als Kasten zu lesen. */
  filter:
    drop-shadow(0 2px 5px rgba(0, 0, 0, 0.45))
    drop-shadow(0 6px 14px rgba(0, 0, 0, 0.35));
}
.film-corner.tl { left: 0; top: 0;    border-right: 0; border-bottom: 0; }
.film-corner.tr { right: 0; top: 0;   border-left: 0;  border-bottom: 0; }
.film-corner.bl { left: 0; bottom: 0; border-right: 0; border-top: 0; }
.film-corner.br { right: 0; bottom: 0; border-left: 0;  border-top: 0; }
.film-play-tri {
  position: absolute;
  left: 53%; top: 50%;
  transform: translate(-50%, -50%);
  width: 0; height: 0;
  border-style: solid;
  border-width: 11px 0 11px 18px;
  border-color: transparent transparent transparent #fff;
  filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.55));
}
.film-play-tag,
.film-tool-tag {
  font-family: var(--mono);
  font-stretch: 75%;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-indent: 0.22em;   /* Sperrung haengt sonst rechts an und kippt die Mitte */
  color: rgba(255, 255, 255, 0.92);
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.6);
}
/* Der Griff endet am Element: die Winkel ruecken nach aussen, das
   Dreieck bleibt stehen. Kein Aufblaehen des ganzen Knopfes. */
.film-play:hover .film-corner.tl { transform: translate(-3px, -3px); }
.film-play:hover .film-corner.tr { transform: translate(3px, -3px); }
.film-play:hover .film-corner.bl { transform: translate(-3px, 3px); }
.film-play:hover .film-corner.br { transform: translate(3px, 3px); }
.film-corner { transition: transform 320ms var(--ease); }

/* Die Griffe sitzen in der Ecke des Bildes, nicht in der Mitte: sie sind
   die zweite Reihe und duerfen dem Film nicht im Weg stehen. Ton und
   Vollbild stehen nebeneinander auf einer Grundlinie, beide in derselben
   Bauform wie die Tafeln an der Hero. */
.film-tools {
  position: absolute;
  right: clamp(14px, 2vw, 28px);
  bottom: clamp(14px, 2vw, 28px);
  z-index: 4;
  display: flex;
  align-items: center;
  gap: 18px;
  /* Nicht permanent im Bild (Leon 02.09.: "dass sie da so permanent
     bleiben, ist nicht super geil"). Am Rechner stehen die Griffe nur
     solange der Zeiger auf dem Film liegt, auf dem Handy holt ein Tipp
     sie fuer ein paar Sekunden herein (project.html setzt .tools-on). */
  opacity: 0;
  transition: opacity 320ms var(--ease);
  pointer-events: none;
}
@media (hover: hover) and (pointer: fine) {
  .film-frame:hover .film-tools { opacity: 1; pointer-events: auto; }
}
.film-frame.tools-on .film-tools { opacity: 1; pointer-events: auto; }
.film-tool {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0;
  border: 0;
  background: none;
  cursor: none;
  opacity: 0.55;
  transition: opacity 320ms var(--ease);
}
.film-tool:hover { opacity: 1; }
.film-tool-ico {
  position: relative;
  display: block;
  width: 20px; height: 20px;
}
/* Nur noch Icons, kein Text (Leon 02.09.: "wir nehmen nur die Icons.
   Also dieses Viereck, quasi diese vier Winkel und dieser Lautsprecher.
   Und keinen Text. Dann werden wir kleiner."). Der Lautsprecher kommt
   zurueck, SOUND/MUTE faellt weg — den Zustand zeigen die Wellen und
   die Mute-Diagonale im Zeichen selbst. Beim Vollbild bleiben die vier
   Winkel als leeres Feld stehen, FULL/SMALL faellt ebenfalls weg. */
.film-sound .film-tool-ico { display: block; }
.film-sound .film-tool-tag { display: none; }
.film-full .film-tool-tag { display: none; }
.film-full { padding: 0; width: 17px; height: 17px; }
.film-tool-ico { width: 17px; height: 17px; }
.film-tool .film-corner { width: 6px; height: 6px; border-width: 1.2px; }
.film-tool:hover .film-corner.tl { transform: translate(-2px, -2px); }
.film-tool:hover .film-corner.tr { transform: translate(2px, -2px); }
.film-tool:hover .film-corner.bl { transform: translate(-2px, 2px); }
.film-tool:hover .film-corner.br { transform: translate(2px, 2px); }
/* Sound-Icon: SVG-Lautsprecher, passend zur Designsprache (nur gerade Linien,
   keine Boegen). Die Wellenlinie und die Mute-Diagonale schalten per Klasse. */
.ico-sound { display: block; width: 100%; height: 100%; overflow: visible; }
.ico-wave-1,
.ico-wave-2 { transition: opacity 280ms var(--ease); }
.ico-mute-line { opacity: 0; transition: opacity 280ms var(--ease); }
.detail-film.is-muted .ico-wave-1,
.detail-film.is-muted .ico-wave-2 { opacity: 0; }
.detail-film.is-muted .ico-mute-line { opacity: 1; }

/* Pause-Balken am Play-Knopf. Sichtbar wenn der Film laeuft,
   versteckt wenn er steht. Das Dreieck kippt gleichzeitig. */
.film-pause-bar {
  position: absolute;
  top: 50%; transform: translateY(-50%);
  width: 4px;
  height: clamp(18px, 2.6vw, 28px);
  background: rgba(255, 255, 255, 0.92);
  filter: drop-shadow(0 3px 8px rgba(0, 0, 0, 0.6));
  opacity: 0;
  transition: opacity 280ms var(--ease);
}
.film-pause-bar.pb-l { left: calc(50% - 8px); }
.film-pause-bar.pb-r { left: calc(50% + 4px); }
.detail-film.is-playing .film-pause-bar { opacity: 1; }
.detail-film.is-playing .film-play-tri { opacity: 0; }
.film-play-tri { transition: opacity 280ms var(--ease); }

/* Nach dem Start wird der Play-Knopf unsichtbar aber klickbar (zum Pausieren).
   Er zeigt Pause-Balken wenn man den Cursor auf den Film bewegt.
   Beim Halten (is-held) bleibt er voll sichtbar. */
/* Laeuft der Film, ist der Knopf unsichtbar und bleibt trotzdem
   klickbar. Vorher blendete er beim Hover ueber dem Bild auf und stand
   dann als zwei senkrechte Balken mitten im Film (Leon 01.09.: "wenn man
   bei den Unterseiten ueber dem Video hovert, sieht man die ganze Zeit
   so zwei Pausenstriche. Das nervt auch uebertrieben. Die soll man nicht
   sehen."). Klick auf das Bild pausiert weiterhin, nur zeigt es nichts
   mehr an. */
.detail-film.is-playing .film-play {
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.92);
  pointer-events: auto;
}
.detail-film.is-playing .film-frame:hover .film-play,
.detail-film.is-playing:hover .film-play,
.detail-film.is-playing.is-held .film-play { opacity: 0; }

/* Container-Vollbild: wenn .film-frame selbst fullscreen ist, fuellt der
   iframe den ganzen Schirm und die Bediengriffe bleiben erreichbar. */
.film-frame:fullscreen,
.film-frame:-webkit-full-screen {
  max-width: none;
  width: 100vw; height: 100vh;
  aspect-ratio: unset;
  display: flex; align-items: center; justify-content: center;
  background: #000;
}
.film-frame:fullscreen iframe,
.film-frame:-webkit-full-screen iframe {
  position: static;
  width: 100%; height: 100%;
  flex: 1 1 auto;
}
.film-frame:fullscreen .film-tools,
.film-frame:-webkit-full-screen .film-tools { z-index: 200; }
/* Im Vollbild rendert der Browser nur den Vollbild-Knoten und alles
   darunter. Der gezeichnete Box-Cursor haengt aber am body, liegt also
   ausserhalb — und weil `body.cursor-on` den echten Zeiger auf `none`
   setzt und der Shield das noch einmal tut, war im Vollbild ueberhaupt
   kein Zeiger mehr zu sehen (Leon 01.09.: "wenn man das Video auf
   Vollbild macht, sieht man den Mauszeiger nicht mehr, das soll nicht so
   sein"). Dort uebernimmt deshalb wieder der Zeiger des Systems. */
.film-frame:fullscreen,
.film-frame:fullscreen *,
.film-frame:-webkit-full-screen,
.film-frame:-webkit-full-screen * { cursor: auto; }
.film-frame:fullscreen .film-tool,
.film-frame:fullscreen .film-play,
.film-frame:-webkit-full-screen .film-tool,
.film-frame:-webkit-full-screen .film-play { cursor: pointer; }

/* BTS — waagerechter Streifen aus Standbildern.
   Die Kacheln haben absichtlich ungleiche Groessen (Leon: Kacheln
   asymmetrisch), jede zweite sitzt tiefer. Der Streifen laeuft rechts
   aus dem Bild, damit er als Fortsetzung lesbar ist. */
.detail-bts { padding: var(--gap-l) 0 var(--gap-m); }
.bts-hd {
  display: flex; align-items: baseline; gap: 14px;
  padding: 0 var(--pad); margin-bottom: clamp(20px, 3vw, 38px);
}
/* Der Wuerfel als Signet vor der Galerie (Leon 11.08.: "hier koennte
   man sich auch den cubus als ci nehmen"). Gezeichnet aus drei Rauten,
   also die Isometrie des Hero-Wuerfels in Strichen — dieselbe Figur,
   nur als Marke. Die obere Flaeche ist heller, wie am echten Koerper.
   Er dreht sich langsam mit dem Streifen mit. */
.bts-cube {
  flex: 0 0 auto; align-self: center;
  width: 26px; height: 30px;
  overflow: visible;
}
.bts-cube .cf {
  fill: currentColor;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linejoin: round;
}
/* Die Aufhellung wandert im Kreis von Flaeche zu Flaeche, versetzt um
   je ein Drittel der Laufzeit. Der Koerper bleibt dabei stehen. */
.bts-cube .cf-top   { animation: cubeFace 5.4s linear infinite; }
.bts-cube .cf-left  { animation: cubeFace 5.4s linear infinite -1.8s; }
.bts-cube .cf-right { animation: cubeFace 5.4s linear infinite -3.6s; }
@keyframes cubeFace {
  0%, 100% { fill-opacity: 0.10; }
  16%      { fill-opacity: 0.85; }
  40%      { fill-opacity: 0.10; }
}
@media (prefers-reduced-motion: reduce) {
  .bts-cube .cf { animation: none; fill-opacity: 0.3; }
}
.bts-label {
  font-size: clamp(22px, 3.4vw, 44px); font-weight: 900; font-style: italic;
  letter-spacing: -0.03em; text-transform: uppercase;
}
.bts-count {
  font-family: var(--mono); font-size: 11px; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--dim);
}
/* Die Spur laeuft von selbst durch, deshalb kein Scrollen und kein
   Rasten: `scroll-snap` zog jede gesetzte Position sofort auf den
   naechsten Rastpunkt zurueck und der Streifen stand still
   (gemessen: 50 gesetzt, 24 gelesen). */
.bts-rail { overflow: hidden; padding: 26px 0 34px; }
.bts-track {
  display: flex; align-items: flex-start;
  gap: clamp(14px, 2vw, 30px);
  padding-left: var(--pad);
  width: max-content;
  will-change: transform;
}
/* Nicht die Breite ist gleich, sondern die HOEHE. Bei gleicher Breite
   waere das Hochformat fast bildschirmhoch und das Breitbild daneben
   ein Schlitz — derselbe Denkfehler wie in der Projektwand. Ueber die
   Hoehe bleibt der Streifen ein Streifen, und die Formate springen
   trotzdem. */
.bts-shot {
  position: relative; flex: 0 0 auto; margin: 0;
  /* Kein Schatten (Leon 02.10.2026, Kacheln tragen keinen). */
  transition: transform var(--t-base) var(--ease);
}
.bts-shot img {
  display: block; width: auto;
  height: clamp(190px, 25vw, 330px);
  aspect-ratio: 16 / 9; object-fit: cover;
}
/* jede zweite Kachel etwas kleiner und tiefer gesetzt */
.bts-shot:nth-child(even) img { height: clamp(140px, 18vw, 240px); }
.bts-shot:nth-child(even) { margin-top: clamp(30px, 5vw, 78px); }
.bts-shot:nth-child(3n) img { aspect-ratio: 4 / 5; }
.bts-shot:nth-child(4n) img { aspect-ratio: 1 / 1; }
.bts-shot figcaption {
  position: absolute; left: 10px; bottom: 8px;
  font-family: var(--mono); font-size: 10px; font-weight: 700;
  letter-spacing: 0.14em; color: #fff;
  text-shadow: 0 1px 4px rgba(0,0,0,0.7);
}
.bts-shot:hover { transform: translateY(-4px); }

/* Mini-Kontaktzeile unter dem BTS-Streifen */
/* Unten nur --gap-s: die Projektliste darunter bringt oben --gap-m mit.
   Vorher addierten sich --gap-l und --gap-m zu rund 190 Punkten Schwarz
   zwischen Mailzeile und Liste (Leon 14.09.2026: "unten auf der seite
   eines films sind die abstaende auch noch komisch"). */
.detail-contact-mini {
  padding: var(--gap-l) var(--pad) var(--gap-s);
  text-align: center;
}
.detail-contact-label {
  display: block;
  font-family: var(--mono);
  font-size: 10px; font-weight: 700;
  letter-spacing: 0.15em; text-transform: uppercase;
  color: var(--dim);
  margin-bottom: 18px;
}
.detail-contact-email {
  font-size: clamp(26px, 4.5vw, 62px);
  font-weight: 900; font-style: italic;
  letter-spacing: -0.025em; text-transform: uppercase;
  color: var(--fg);
  transition: opacity var(--t-base) var(--ease);
}
.detail-contact-email:hover { opacity: 0.55; }

/* Alle Projekte am Seitenende */
/* Unten --gap-s statt --gap-xl, die Fussmarke bringt oben --gap-m mit.
   Vorher standen zwischen Liste und Fussmarke rund 250 Punkte. */
.detail-all-projects {
  padding: var(--gap-m) var(--pad) var(--gap-s);
  border-top: 1px solid rgba(var(--scrim), 0.12);
}
.detail-all-hd {
  font-family: var(--mono);
  font-size: 10px; font-weight: 700;
  letter-spacing: 0.15em; text-transform: uppercase;
  color: var(--dim);
  margin-bottom: 28px;
  max-width: var(--maxw);
  margin-left: auto; margin-right: auto;
}
.detail-all-list {
  max-width: var(--maxw);
  margin: 0 auto;
  display: flex; flex-wrap: wrap;
  gap: 10px 22px;
}
.detail-all-link {
  font-size: clamp(14px, 1.3vw, 17px);
  font-weight: 900; font-style: italic;
  text-transform: uppercase; letter-spacing: -0.01em;
  color: var(--dim);
  transition: color var(--t-base) var(--ease);
}
.detail-all-link.is-current { color: var(--fg); pointer-events: none; }
.detail-all-link:hover { color: var(--fg); }

@media (max-width: 760px) {
  .detail-intro-inner { grid-template-columns: 1fr; gap: 30px; }
  .detail-contact-email { font-size: clamp(20px, 7.5vw, 40px); }
}

/* ============================================================
   Dark Side / Bright Side — Mini-Slider unten rechts
   Eckiger Toggle ohne Text und Symbole (Apple-Settings-Stil).
   ============================================================ */
.mode-switch {
  position: fixed;
  bottom: var(--pad); right: var(--pad);
  z-index: 60;
  /* Klein und zurueckhaltend: der Schalter ist Nebensache, er soll
     nicht mit dem Wuerfel um Aufmerksamkeit konkurrieren. Erst unter
     dem Zeiger nimmt er volle Deckkraft an. */
  width: 34px; height: 18px;
  border: 1px solid var(--fg);
  /* Eigene Flaeche statt durchsichtig, und kraeftiger (01.10.2026).
     Vorher stand der Schalter mit 0.4 Deckkraft ohne Grund direkt auf
     dem, was gerade hinter ihm lag. Unten rechts ist das je nach Seite
     der Lichtabfall des Raums, ein Videobild oder die helle Platte —
     und auf der hellen Seite verschwand der duenne weisse Rahmen dort
     vollstaendig (Leon 01.10.2026: "dieser Schalter unten rechts ist
     manchmal so nicht gut sichtbar, besonders auf der weissen Seite,
     dass er dann so verschwindet, bei so gewissen Kontrastbereichen").
     Mit einer Flaeche in der Seitenfarbe steht der Rahmen immer auf
     seinem eigenen Gegenwert, egal was darunter liegt. Zurueckhaltend
     bleibt er ueber die Groesse, nicht mehr ueber die Durchsicht. */
  background: var(--bg);
  padding: 0;
  overflow: hidden;
  opacity: 0.78;
  transition: opacity var(--t-base) var(--ease);
  -webkit-tap-highlight-color: transparent;
  cursor: pointer;
}
.mode-switch:hover, .mode-switch:focus-visible { opacity: 1; }
.mode-switch .mode-knob {
  position: absolute;
  top: 3px; left: 3px;
  width: 10px; height: 10px;
  background: var(--fg);
  transition: transform var(--t-base) var(--ease);
  transform: translateX(0);
}
html.bright .mode-switch .mode-knob { transform: translateX(16px); }

/* ---------- Reveal on scroll ---------- */
.reveal { opacity: 0; transform: translateY(16px); transition: opacity var(--t-slow) var(--ease), transform var(--t-slow) var(--ease); }
.reveal.in { opacity: 1; transform: none; }

/* ---------- Mobile ---------- */
@media (max-width: 760px) {
  :root { --pad: 16px; }
  .topbar nav a { font-size: 12px; padding: 7px 9px; }
  /* Mobile: nicht auf 100vh zwingen, damit die Titel durchscrollen */
  /* Auslauf unter dem letzten Namen, damit auch er noch in die Mitte
     des Bildes wandern kann und seine Vorschau bekommt. Vorher 30vh —
     das war ein Loch von einem Drittel Bildschirm, obwohl die Sektion
     darunter den Weg laengst mitbringt. */
  .works { height: auto; display: block; overflow: visible; padding-bottom: 12vh; }
  .work-cloud { display: block; padding: var(--gap-s) var(--pad); }
  /* Mobil steht jeder Name auf einer eigenen Zeile, sonst laufen lange
     Titel wie WIR MACHEN STROM aus dem Bild. */
  .work-cloud-inner { font-size: clamp(30px, 10vw, 64px); line-height: 1.06; }
  .work-word { display: block; white-space: normal; }
  .work-sep { display: none; }
  /* Auf dem Handy stehen die Namen untereinander und der Blick hat kein
     Wort unter dem Zeiger, an dem er sich festhaelt. Der Abstand zwischen
     angewaehlt und Rest darf deshalb groesser sein als am Rechner: das
     laufende Werk steht voll in --fg, alles andere faellt deutlich ab,
     aber nicht so weit dass die Liste verschwindet. */
  /* Leon 02.10.2026: "Kannst du die Schrift am Handy auch negativ
     machen? Dann ist sie besser lesbar ueber dem Video." Der angewaehlte
     Name liegt ueber dem Vorschaufenster (z-index 45 gegen 40) und stand
     in --fg, im hellen Modus also schwarz auf einem dunklen Film. Mit
     difference und reinem Weiss dreht sich die Schrift am Grund selbst
     um: ueber der hellen Seite wird sie schwarz, ueber dem dunklen Film
     weiss. Die Graustufe kommt dann aus der Deckkraft statt aus --dim,
     die Werte unten treffen die alten Toene in beiden Modi (0.4 ergibt
     153 auf Weiss gegen --dim 155, und 102 auf Schwarz gegen --dim 111). */
  .work-cloud .work-word {
    color: #fff;
    mix-blend-mode: difference;
    opacity: 0.4;
  }
  .work-cloud.has-hover .work-word { opacity: 0.18; }
  .work-cloud.has-hover .work-word.is-active { opacity: 1; color: #fff; }
  /* Die freigestellte Kopie, die waehrend der Klappen-Animation die
     Schrift traegt, bekommt ihre Farbe per Inline-Style aus main.js und
     bringt dort --fg mit, im hellen Modus also Schwarz. Unter difference
     ist Schwarz die Null und das Wort waere unsichtbar. Hier zaehlt wie
     bei den Woertern selbst nur Weiss, die Graustufe macht die
     Deckkraft des Wortes darueber. */
  .work-cloud .work-flap { color: #fff !important; }
  /* Marke und Angaben untereinander, seitlich ist kein Platz fuer beide */
  .btc-inner { grid-template-columns: 1fr; gap: var(--gap-s); }
  .btc-claim { max-width: none; }
  .btc-stats { gap: 22px 32px; }
  .btc-stats .hstat { min-width: 130px; }
  /* Wuerfel auf Mobile minimal groesser (Leon 07.08.) */
  .hero-cube {
    width: clamp(340px, 80vmin, 820px);
    height: clamp(340px, 80vmin, 820px);
  }
  /* Kacheln untereinander, sonst sind die Vorschauen briefmarkengross.
     Der Versatz des kleineren faellt weg, gestapelt braucht es ihn nicht. */
  .latest-grid { grid-template-columns: 1fr; gap: 48px; }
  .post-side { margin-top: 0; }
  /* Die Flaeche bleibt 16/9 (bei Handybreite ist das 9/16 der Breite und
     damit ohnehin der kleinere der beiden Werte, der Deckel greift nicht).
     Die Titelzeile stapelt: Titel oben, Werte darunter, nebeneinander
     waere beides briefmarkengross. */
  .ftab {
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
    padding: 9px 0;
  }
  /* Auf dem Handy traegt nur die laufende Zeile ihre Werte. Standen sie
     bei allen fuenf, kam Cynical mit seinen drei Kuenstlernamen auf drei
     Zeilen Monospace und die Liste war mehr Kleingedrucktes als Titel. So
     ist zugleich ohne Zeiger klar, welches Werk gerade laeuft. */
  .ftab-meta { display: none; white-space: normal; flex-wrap: wrap; gap: 4px 14px; }
  .ftab.is-on .ftab-meta { display: flex; }

  .latest-intro { margin-bottom: 26px; padding-right: 0; }
  /* Beschriftung neben dem Wuerfel wandert unter ihn, seitlich ist
     auf dem Handy kein Platz. Die Koordinatenzeile faellt weg, sonst
     stossen die beiden Bloecke in der Mitte aneinander. */
  /* Der Panel-Streifen faellt auf Mobile weg — kein Platz */
  .hero-stats { display: none; }
  /* bottom so hoch, dass der Modus-Schalter unten rechts nicht in der
     letzten Zeile sitzt */
  .hero-meta {
    top: auto; bottom: 9vh; transform: none;
    gap: 4px;
  }
  .hero-meta::before { width: 12px; margin-bottom: 2px; }
  .hero-meta-left  { left: var(--pad); right: auto; }
  .hero-meta-right { right: var(--pad); left: auto; }
  /* Auf dem Handy haengen die Bloecke unten in der Hero — auch hier
     klappen die Zusatzzeilen nach oben, sonst laufen sie unten raus. */
  .hero-meta .meta-more { top: auto; bottom: 100%; }
  .hero-meta .meta-more .meta-more-in { padding-top: 0; padding-bottom: 6px; }
  .meta-tag, .meta-sub { font-size: 7.5px; letter-spacing: 0.07em; }
  .meta-val { font-size: 11.5px; }
  .meta-sep { margin: 0 5px; }
  /* Die Herkunftszeile DRKNSD / BERLIN war hier bis 14.09.2026
     ausgeblendet (.meta-origin display none), damit der rechte Block
     nicht in den linken laeuft. Seit der Reduktion traegt er nur noch
     AI+ statt drei Zeichen und LIVE COUNT links ist weg, Platz ist
     reichlich. Mit der Zeile haben beide Bloecke wieder vier Ebenen und
     stehen gleich hoch (Leon: "gucken, dass es wirklich ausbalanciert
     ist"). */
  /* Die Vorschau steht ueber dem Titel in der Bildmitte, Groesse und
     Lage rechnet main.js pro Projekt (Format springt). Kein bottom-Pin
     mehr: der stand am unteren Bildrand ohne Bezug zum Namen
     (Leon 12.08.). Die Werte hier greifen nur bis zum ersten Titel. */
  .preview-float {
    /* auch hier kein max-width, siehe oben */
    width: 56vw;
    left: 0; top: 0; bottom: auto;
  }
  /* Auf dem Handy fuellt ein 16/9 Bild keine Bildschirmhoehe — es
     stuende in einem leeren Feld. Hier traegt der normale
     Sektionsabstand, die Zentrierung faellt weg. */
  .detail-film { min-height: 0; padding: max(var(--gap-l), calc(var(--pad) + var(--head-h) + 16px)) var(--pad) var(--gap-m); }
  .detail-body { grid-template-columns: 1fr; }
  .detail-text-col { padding-bottom: 0; }
}

/* ---------- Tippflaechen am Handy (14.09.2026, Handy-Check) ----------
   Unter 40 Punkten trifft der Daumen daneben. Die Flaeche waechst
   unsichtbar, das Bild bleibt an jeder Stelle gleich.
   Steht bewusst HINTER den Grundregeln von .mode-switch und
   .detail-all-list: gleiche Spezifitaet, also gewinnt die spaetere
   Regel. Weiter oben in der Datei griff die Groesse nicht. */
@media (max-width: 860px) {
  /* Kopfzeile: WORKS und CONTACT sind 30 Punkte hoch. */
  .topbar nav a { position: relative; }
  .topbar nav a::after { content: ""; position: absolute; inset: -6px -2px; }
  /* BACK auf der Projektseite: 36 Punkte. inset rechnet ab der Innenkante
     des 1.5-Punkte-Rahmens, deshalb 6 statt 4. */
  .detail-back::after { content: ""; position: absolute; inset: -6px; }
  /* Werkliste unter der Projektseite: 19 Punkte hohe Namen mit 10 Punkten
     Luft dazwischen. Etwas mehr Zeilenabstand, und jeder Name greift
     ueber die halbe Luft nach oben und unten, damit keine tote Zone
     zwischen zwei Zeilen bleibt. */
  .detail-all-list { gap: 16px 22px; }
  .detail-all-link { position: relative; padding: 8px 0; margin: -8px 0; }
  /* Modus-Schalter: 34x18 sichtbar, das ist als Ziel zu klein. Der
     Knopf selbst wird 58x42, der sichtbare Rahmen zieht als
     Pseudo-Element auf die alte Groesse ein und steht an derselben
     Stelle wie vorher (Versatz gegen den Rand herausgerechnet). Der
     Rahmen darf nicht auf dem Knopf bleiben: overflow hidden wuerde
     jede Beruehrung ausserhalb des Rahmens verwerfen. */
  .mode-switch {
    width: 58px; height: 42px;
    bottom: calc(var(--pad) - 12px); right: calc(var(--pad) - 12px);
    border: 0; overflow: visible; background: transparent;
  }
  /* Die Flaeche in Seitenfarbe sitzt am sichtbaren Rahmen, nicht am
     Knopf: der Knopf ist hier nur die vergroesserte Tippflaeche und
     duerfte nicht als Kasten erscheinen. */
  .mode-switch::before { content: ""; position: absolute; inset: 12px; border: 1px solid var(--fg); background: var(--bg); }
  /* 12 Rand + 1 Rahmen + 3 wie vorher = 16, sonst steht der Knopf einen
     Punkt zu weit oben links (gemessen). */
  .mode-switch .mode-knob { top: 16px; left: 16px; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
  /* Intro bleibt an — das Wuerfel-Intro ist das bewusste Markenerlebnis.
     Auf ?nointro=1 kann man es per URL ueberspringen. */
}

/* Subpixel-Spalt: bei Bruchpixel-Breiten blitzte am rechten Rand der
   Vorschau 1px Seitengrund als weisser Strich auf (Leon 02.09., Cynical
   und Sprite). Im Videofile ist kein Rand, es ist Rundung im Renderer.
   Die Videoebene zeichnet deshalb einen Hauch ueber und der Rahmen
   schneidet ab. 1.004 ist unsichtbar, deckt aber den Spalt. */
.preview-float { overflow: hidden; }
.preview-float video { transform: scale(1.004); }

/* ---------- Nur fuer Vorlesefunktion und Suchmaschine ----------
   Die Startseite hatte keine Ueberschrift erster Ordnung, weil die Hero
   ein WebGL-Wuerfel und die Marke ein Bild ist. `display: none` waere
   hier falsch: dann faellt die Zeile auch aus der Vorlesefunktion und
   aus dem Dokument, das Google bewertet. Also aus dem Bild schieben
   statt ausschalten. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ============================================================
   Creed — Schlusszeile links ab Tablet (Leon 03.09.2026:
   "fuer web b beim Handy a")
   Auf breiten Schirmen steht die Schlusszeile in der linken Spalte
   neben dem Text statt darunter. Der Abschnitt wird dadurch rund ein
   Drittel flacher und die leere Einzugsspalte traegt endlich etwas.
   Auf dem Handy bleibt es bei der Fassung darunter, dort waere die
   Zeile neben dem Text nur noch eine Schlagwortsaeule.
   ============================================================ */
@media (min-width: 861px) {
  .creed-inner {
    /* Die linke Spalte ist jetzt keine Einzugsspalte mehr, sondern
       traegt die Schlusszeile, also braucht sie echte Breite.

       Beide Spalten sind gemessene Breiten statt Anteile (fr), und das
       Raster steht mittig. Mit Anteilen zog die rechte Spalte die volle
       Sektionsbreite auf sich, waehrend der Absatz darin bei 30em endet:
       gemessen bei 1440 stand die Gruppe 24 Bildpunkte vom linken Rand
       und 355 vom rechten, also sichtbar aus der Mitte gerueckt (Leon
       03.09.: "das ganze Ding mittiger"). Die rechte Spalte rechnet
       jetzt dieselben 30em wie .creed-body, damit in ihr kein Loch mehr
       entsteht, und justify-content legt die Gruppe in die Mitte. */
    grid-template-columns:
      minmax(0, clamp(15em, 26vw, 25em))
      minmax(0, calc(30 * clamp(16px, 1.28vw, 22px)));
    justify-content: center;
  }
  .creed-col {
    grid-row: 1;
    /* Gegenstueck zum Versatz der Schlusszeile unten: dieselbe Strecke
       nach rechts, damit die Gruppe wieder mittig steht. Vorher lag sie
       bei 1467 links 173 und rechts 239 Punkte vom Rand (Leon 14.09.:
       "about text kann noch etwas weiter nach rechts. sieht unbalanciert
       aus mit thats darknsd"). `left` statt margin, sonst schrumpft die
       Spalte und der Absatz bricht neu um. */
    position: relative;
    left: clamp(0px, 9vw - 66px, 80px);
  }
  .creed-kick {
    grid-column: 1;
    grid-row: 1;
    /* Ueber die volle Zeilenhoehe, sonst hat sticky keinen Weg. */
    align-self: stretch;
    position: sticky;
    top: clamp(90px, 12vh, 150px);
    /* Ein Stueck weiter nach links, der Text bleibt stehen. Dadurch
       waechst die Luft zwischen Schlusszeile und Absatz, gemessen bei
       1467 von 44 auf 110 Punkte (Leon 14.09.2026: "ein bisschen weiter
       links, beziehungsweise die Luecke zwischen dem Text und diesem
       That's the unstoppable future etwas groesser"). Der Versatz waechst
       mit der Breite und ist bei 861 fast null, dort steht die Spalte
       schon an der Seitenkante. */
    margin: 0 0 0 calc(-1 * clamp(0px, 9vw - 66px, 80px));
    /* Kleiner als in der Fassung darunter, aber nicht so klein wie
       zuerst: bei 2.5vw stand links eine zerhackte Treppe aus fuenf
       kurzen Zeilen gegen einen hohen, hellen Textblock rechts, und
       genau das war die Schieflage (Leon 03.09.: "irgendwas stimmt da
       noch nicht ganz von der Balance von diesem Block"). Die Zeile ist
       das eine starke Zeichen des Abschnitts, sie braucht Gewicht. */
    font-size: clamp(28px, 3.1vw, 50px);
    line-height: 0.94;
    /* Verteilt die Zeilen gleichmaessig, statt die letzte als Rest
       stehen zu lassen. */
    text-wrap: balance;
    /* Sie beginnt nicht an der Oberkante des Textes, sondern etwas
       tiefer. Oben sass sie auf einer Linie mit der ersten Textzeile
       und liess unten links ein Loch ueber die halbe Sektionshoehe. */
    padding-top: clamp(6px, 1.2vw, 22px);
  }
  /* Der Markenname bleibt in einer Zeile. Umgebrochen ("THAT'S" ueber
     "DRKNSD.") liest er sich wie zwei Ansagen statt wie eine. */
  .creed-kick b:last-child {
    margin-left: clamp(10px, 1.4vw, 24px);
    white-space: nowrap;
  }

  /* --- Bewegung beim Scrollen, gesetzt von creed.js -------------
     Fassung "drift": die beiden Zeilen ziehen gegenlaeufig
     auseinander und stehen bei halbem Durchlauf still, also genau
     dann, wenn der Abschnitt mittig im Bild steht und gelesen wird.
     Die Uebergangszeit faengt nur das Nachwippen zwischen zwei
     Scrollschritten ab, gesteuert wird ueber den Scrollweg. */
  .creed-kick[data-creed-scroll="drift"] b {
    display: block;
    will-change: transform;
    transition: transform 220ms cubic-bezier(0.22, 0.61, 0.36, 1);
  }
  .creed-kick[data-creed-scroll="drift"] b:first-child {
    transform: translate3d(var(--creed-a, 0px), 0, 0);
  }
  .creed-kick[data-creed-scroll="drift"] b:last-child {
    transform: translate3d(var(--creed-b, 0px), 0, 0);
  }

  /* Fassung "sweep": die Schrift steht, ein Hell-Dunkel-Verlauf
     wandert beim Scrollen von oben nach unten durch beide Zeilen.
     Der Verlauf sitzt auf dem Absatz, nicht auf den einzelnen Zeilen,
     sonst liefe er zweimal parallel statt einmal durch das Ganze. */
  .creed-kick[data-creed-scroll="sweep"] {
    background-image: linear-gradient(
      170deg,
      color-mix(in srgb, var(--fg) 34%, transparent) 0%,
      color-mix(in srgb, var(--fg) 34%, transparent) 22%,
      var(--fg) 46%,
      var(--fg) 54%,
      color-mix(in srgb, var(--fg) 34%, transparent) 78%,
      color-mix(in srgb, var(--fg) 34%, transparent) 100%);
    background-size: 100% 260%;
    background-repeat: no-repeat;
    background-position: 0 var(--creed-sweep, 0%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    -webkit-text-fill-color: transparent;
  }
  /* Im Sweep tragen beide Zeilen denselben Verlauf, die feste
     Aufteilung grau oben und hell unten faellt weg. */
  .creed-kick[data-creed-scroll="sweep"] b,
  .creed-kick[data-creed-scroll="sweep"] b:last-child {
    color: inherit;
    -webkit-text-fill-color: inherit;
  }
  .creed-kick[data-creed-scroll="sweep"] b { display: block; }
}

/* ---------- Film am Handy quer (Leon 14.09.2026: "wenn man das handy
   quer haelt sieht man das video sehr klein") ----------
   Die Hoehenrechnung oben zieht 180 bis 260 px fuer Luft ab. Quer hat
   ein iPhone nur rund 340 bis 390 px Hoehe, uebrig blieben 150 bis 210 px
   Film. Hier nimmt der Film die ganze Hoehe bis auf einen schmalen Rand
   und bleibt in der Breite zwischen BACK links und Marke rechts. */
@media (orientation: landscape) and (max-height: 540px) {
  .detail-film { padding: 16px var(--pad); min-height: 100svh; }
  .film-frame {
    max-width: min(calc(100vw - 224px), calc((100svh - 32px) * var(--film-arn, 1.7778)));
  }
}

/* Ersatz-Vollbild fuer das iPhone (project.html setzt .is-fake-full):
   Safari laesst dort nur ein <video> ins echte Vollbild, keinen fremden
   Iframe. Der Rahmen legt sich deshalb selbst ueber den ganzen Schirm,
   der Vimeo-Player passt den Film darin ein. */
.film-frame.is-fake-full {
  position: fixed;
  inset: 0;
  z-index: 1000;
  width: 100vw;
  height: 100vh;
  height: 100dvh;
  max-width: none;
  aspect-ratio: auto;
  margin: 0;
  background: #000;
}
.film-frame.is-fake-full .film-tools {
  z-index: 200;
  right: max(14px, env(safe-area-inset-right));
  bottom: max(14px, env(safe-area-inset-bottom));
}
html.film-fake-full, html.film-fake-full body { overflow: hidden; }


/* ---------- About, Aufbau beim Hereinscrollen ----------
   Leon 02.10.2026: "dieser About-Text kann sich noch irgendwie
   reinanimieren, also on scroll, dass da so ein bisschen mehr der Blick
   drauf faellt." Die Woerter stehen von Anfang an an ihrem Platz und
   werden nur aufgedeckt — nichts springt um, die Hoehe steht. creed.js
   setzt .creed-words und staffelt die Verzoegerung je Wort. */
.creed-body.creed-words { opacity: 1; transform: none; }
.creed-word {
  display: inline-block;
  opacity: 0;
  transform: translateY(0.34em);
  transition: opacity 520ms var(--ease), transform 520ms var(--ease);
  transition-delay: var(--wd, 0ms);
}
.creed-body.is-written .creed-word { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .creed-word { opacity: 1; transform: none; transition: none; }
}


/* ---------- Rechtsseiten (imprint.html, datenschutz.html) ----------
   Leon 02.10.2026: "brauchen wir vorher nicht noch ein impressum?"
   Gleiche Schrift, gleiche Kanten, gleicher Rand wie die uebrigen
   Sektionen (eine Breite fuer alles). Kein Rahmen, keine Trennlinien,
   kein Schatten — die Seite fuehrt keine Striche. Die Satzbreite ist
   ueber ch begrenzt, damit der Text auf einem breiten Schirm lesbar
   bleibt statt quer durchzulaufen. */
.legal {
  padding: calc(var(--pad) + 86px) var(--pad) var(--gap-l);
  max-width: var(--maxw);
  margin: 0 auto;
}
.legal-inner { max-width: 70ch; }
.legal-title {
  margin: 0 0 var(--gap-s);
  font-size: clamp(34px, 6vw, 78px);
  font-weight: 900; font-style: italic;
  letter-spacing: -0.03em; text-transform: uppercase;
  line-height: 0.95;
  hyphens: auto;
}
.legal-h2 {
  margin: var(--gap-s) 0 10px;
  font-family: var(--mono);
  font-size: clamp(11px, 1vw, 13px);
  font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--fg);
}
.legal-copy, .legal-adr {
  margin: 0 0 14px;
  font-size: clamp(14px, 1.02vw, 17px);
  line-height: 1.62;
  font-weight: 700;
  color: color-mix(in srgb, var(--fg) 76%, transparent);
}
.legal-adr { line-height: 1.5; }
.legal-mono { font-family: var(--mono); font-size: 0.9em; }
.legal a {
  color: var(--fg);
  text-decoration: underline;
  text-decoration-thickness: 1.5px;
  text-underline-offset: 3px;
}
.legal-foot {
  display: flex; flex-wrap: wrap; gap: 18px;
  margin: var(--gap-m) 0 0;
  font-family: var(--mono);
  font-size: 11px; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase;
}
.legal-foot a { text-decoration: none; color: var(--dim); }
.legal-foot a:hover { color: var(--fg); }

/* Die Fusszeile auf den uebrigen Seiten. Sie traegt nur die beiden
   Pflichtlinks und steht auf derselben Breite wie alles andere. */
.site-foot {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: var(--gap-s) var(--pad) calc(var(--pad) + env(safe-area-inset-bottom));
  display: flex; flex-wrap: wrap; gap: 18px; align-items: baseline;
  font-family: var(--mono);
  font-size: 11px; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--dim);
}
.site-foot a { color: var(--dim); text-decoration: none; }
.site-foot a:hover { color: var(--fg); }
/* Die Kontaktseite fuellt genau einen Schirm, eine Fusszeile im Fluss
   wuerde dort Scrollen erzeugen. Deshalb steht sie dort unten links
   fest, unterhalb des Instagram-Zeichens. */
.site-foot--fix {
  position: fixed;
  left: 0; bottom: 0; z-index: 60;
  padding: 0 var(--pad) calc(14px + env(safe-area-inset-bottom));
  margin: 0;
}
