/* ===================================================================
   COMPONENTS — nav, labels, buttons, cards, grain, footer
   Palette: --orange / --cream / --pink / --red only (+ black/white).
   =================================================================== */

/* ---------- grain: authored texture from a real film scan ---------- */
.grain {
  pointer-events: none;
  position: absolute;
  inset: 0;
  z-index: 5;
  opacity: 0.07;
  mix-blend-mode: overlay;
  background-image: url("../img/grain.png");
  background-size: 160px 160px;
}
.on-ink .grain { opacity: 0.14; }

/* ---------- label ----------
   Was: Geist Mono, 0.14em tracking, semibold — the tracked-out
   typewriter look the artist asked to remove everywhere. Now: the
   main face, bold, near-normal tracking. The leading dash rule went
   with it; it was decoration, not information. */
.label {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  font-family: var(--font-body);
  font-size: 0.8125rem;
  font-weight: var(--w-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--fg-muted);
}
.label--accent { color: var(--fg); }


/* ---------- site nav ----------
   No more mix-blend-mode:difference — that trick turned white
   turquoise-ish over the hero's red tones. Nav is black by default
   (reads on the now-white page base); .site-nav--on-dark (toggled by
   initNavContrast() in main.js while a hero/project-hero is behind it)
   switches it to plain white instead. */
.site-nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-s) var(--gutter);
  pointer-events: none;
  color: var(--black);
  transition: color var(--dur-fast) var(--ease-out);
}
.site-nav a, .site-nav button { pointer-events: auto; }
.site-nav--on-dark { color: var(--white); }

.site-nav__mark {
  display: flex;
  align-items: center;
  color: currentColor;
  transition: opacity var(--dur-fast) var(--ease-out);
}
/* Das Zeichen (quadratisch). Die Höhe muss neben dem MENU-Schriftzug
   gleich bleiben, also wird die Höhe gesetzt und die Breite folgt. */
.site-nav__mark svg { height: 30px; width: auto; display: block; }
/* Leinwand für das Raster beim Hovern, Gehen und Ankommen (chrome.js) */
.site-nav__mark { position: relative; }
.site-nav__raster {
  position: absolute;
  left: 0;
  top: 50%;
  width: 30px;
  height: 30px;
  transform: translateY(-50%);
  pointer-events: none;
}
[data-menu-open="true"] .site-nav__mark { opacity: 0; }
[data-menu-open="true"] .site-nav { color: var(--black); }

.site-nav__toggle {
  font-family: var(--font-display);
  font-weight: var(--w-bold);
  font-size: var(--size-small);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: currentColor;
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}

/* HOVER AUF TEXT (Entscheidung vom 05.10.: Variante F3). Keine Linie.
   Beim Hovern breitet sich die Hover-Farbe kreisförmig von der Stelle
   aus, an der die Maus das Wort berührt, bis das ganze Wort gefärbt
   ist; beim Verlassen zieht sie sich zur Austrittsstelle zurück.
     Wörter auf Weiss oder hellem Grund   -> Pink (Knockout Pink)
     Wörter auf Schwarz oder auf Video    -> Orange (Orange Clown Fish)
   Technik: Die Schrift ist durchsichtig und zeigt einen Hintergrund, der
   auf die Buchstaben zugeschnitten ist (background-clip: text). Unten
   liegt die eigene Schriftfarbe, darüber ein Kreis in der Hover-Farbe,
   der am Mauspunkt sitzt und beim Hovern wächst (main.js setzt dazu
   --hv-px/--hv-py/--hv-d und die Klasse .hv-an).
   Der Kreis wächst über background-size, nicht mehr über eine animierte
   Variable (--hv-r, bis 05.10.): Solange Safari eine registrierte
   Variable animiert, rechnet es rem gegen die Schriftgrösse des
   Elternelements statt der Seite. Die Filter wuchsen dadurch während
   des Übergangs von 15 auf 16,9px und sprangen danach zurück ("das Wort
   wird grösser und schrumpft wieder", 05.10.).
   Gilt für: MENU/CLOSE, Menüeinträge, Menü-Fuss, Filter, All projects/
   See all, Prev/Next, Get in touch, Contact, Sound on, Footer. */
/* "html :is(...)": stärker als die Rücksetzungen "background: none" an
   Knöpfen (button, .stack__btn, .sound-toggle). Ohne Hintergrund bliebe
   die durchsichtige Schrift unsichtbar. */
html :is(.site-nav__toggle, .site-menu__list a, .site-menu__foot a, .filter-pill, .text-link, .stack__btn, .btn, .kontakt__mail, .kontakt__link, .sound-toggle, .site-footer__archive, .site-footer__meta a) {
  --hv-farbe: var(--pink);
  --hv-s: 0px;
  background-image:
    radial-gradient(ellipse closest-side, var(--hv-farbe) calc(100% - 0.5px), transparent 100%),
    linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-size: var(--hv-s) var(--hv-s), 100% 100%;
  background-position: calc(var(--hv-px, 50%) - var(--hv-s) / 2) calc(var(--hv-py, 50%) - var(--hv-s) / 2), 0 0;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  transition: background-size 0.5s cubic-bezier(0.625, 0.05, 0, 1), background-position 0.5s cubic-bezier(0.625, 0.05, 0, 1);
}
/* Maus darauf (nur Maus und Stift: am Telefon bliebe die Farbe nach dem
   Antippen hängen). --hv-d ist so gross, dass der Kreis von jedem Punkt
   aus das ganze Wort deckt. */
html :is(.site-nav__toggle, .site-menu__list a, .site-menu__foot a, .filter-pill, .text-link, .stack__btn, .btn, .kontakt__mail, .kontakt__link, .sound-toggle, .site-footer__archive, .site-footer__meta a).hv-an { --hv-s: var(--hv-d, 40rem); }
/* Ausgangspunkt setzen, ohne dass der Kreis dorthin gleitet */
html :is(.site-nav__toggle, .site-menu__list a, .site-menu__foot a, .filter-pill, .text-link, .stack__btn, .btn, .kontakt__mail, .kontakt__link, .sound-toggle, .site-footer__archive, .site-footer__meta a).hv-sofort { transition: none; }
/* Tastatur: von der Mitte aus */
html :is(.site-nav__toggle, .site-menu__list a, .site-menu__foot a, .filter-pill, .text-link, .stack__btn, .btn, .kontakt__mail, .kontakt__link, .sound-toggle, .site-footer__archive, .site-footer__meta a):focus-visible:not(.hv-an) { background-size: 300% 300%, 100% 100%; background-position: 50% 50%, 0 0; }
/* auf Schwarz und auf Video: Orange */
html :is(.site-nav--on-dark .site-nav__toggle, .hero--abschluss .btn, .sound-toggle, .site-footer__archive, .site-footer__meta a) { --hv-farbe: var(--orange); }
@media (min-width: 901px) { html .kontakt :is(.kontakt__mail, .kontakt__link) { --hv-farbe: var(--orange); } }
@media (prefers-reduced-motion: reduce) { html :is(.site-nav__toggle, .site-menu__list a, .site-menu__foot a, .filter-pill, .text-link, .stack__btn, .btn, .kontakt__mail, .kontakt__link, .sound-toggle, .site-footer__archive, .site-footer__meta a) { transition: none; } }

/* the three hamburger bars are gone — "MENU" / "CLOSE" as plain text
   carries it, per the artist's note */

/* ---------- full-screen menu overlay ----------
   bg: Stalactite · text: Orange Clown Fish, constant — no more hover
   color-swap to Pink, per the artist's later note ("Farbwechsel kann
   komplett weg"). Orange-on-cream is 2.6:1, under the 4.5:1 body-text
   guideline, but these are large bold display words, not running text;
   a soft shadow (no hard outline/stroke — those read as "outlines",
   which the artist asked to drop) keeps them legible in practice.
   Hover is an underline wipe, matching the reference site's nav links,
   instead of a color or scale change. Snappier easing/duration than
   the rest of the site's motion — the reference reads fast and
   confident, not slow and dramatic. */
.site-menu {
  position: fixed;
  inset: 0;
  z-index: 90;
  /* frosted rather than a solid colour: the page stays faintly
     readable behind it. The tint is only there to keep black text
     legible over whatever happens to be underneath. */
  background: rgba(255, 255, 255, 0.72);
  backdrop-filter: blur(22px) saturate(1.1);
  -webkit-backdrop-filter: blur(22px) saturate(1.1);
  color: var(--black);
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* was padding:2xl + justify-content:center with no overflow rule —
     when the list grew taller than the viewport the first item ("Home")
     was clipped off the top with no way to reach it. Scroll + safe top
     padding for the fixed nav fixes it. */
  padding: calc(var(--space-2xl) + 3rem) var(--gutter) var(--space-2xl);
  overflow-y: auto;
  transform: translateY(-100%);
  visibility: hidden;
  transition: transform 0.45s var(--ease-out), visibility 0s linear 0.45s;
}
[data-menu-open="true"] .site-menu {
  transform: translateY(0);
  visibility: visible;
  transition: transform 0.45s var(--ease-out), visibility 0s linear 0s;
}

.site-menu__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}
.site-menu__list a {
  font-family: var(--font-display);
  /* smaller than before (was up to 4.75rem) per the artist's note */
  font-size: clamp(1.75rem, 1.1rem + 2.4vw, 3rem);
  font-weight: var(--w-bold);
  font-stretch: var(--width-display);
  letter-spacing: var(--tracking-display);
  /* HOME, PROJECTS, OTHER WORK … in Grossbuchstaben (05.10.) */
  text-transform: uppercase;
  display: inline-block;
  width: fit-content;
  position: relative;
  /* black, same face and treatment as the rest of the site — the
     orange-on-cream pairing is gone along with its legibility crutches */
  color: var(--black);
  overflow: hidden;
  padding-bottom: 0.06em;
}


.site-menu__foot {
  margin-top: var(--space-2xl);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-m);
  font-family: var(--font-body);
  font-size: var(--size-small);
  font-weight: var(--w-semibold);
  letter-spacing: var(--tracking-label);
  color: var(--black);
}


/* ---------- buttons / links — plain, confident type, no boxy chrome ---------- */
.btn {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-2xs);
  font-family: var(--font-display);
  font-size: var(--size-lede);
  font-weight: var(--w-bold);
  letter-spacing: var(--tracking-display);
  padding-block: var(--space-3xs);
  position: relative;
}
.btn--primary { color: var(--fg); }
/* "Get in touch" steht auf dem dunklen Video: weiss */
.hero--abschluss .btn { color: var(--white); }

.btn--ghost { color: var(--fg); }

.text-link {
  position: relative;
  font-weight: var(--w-semibold);
}


/* ---------- section scaffolding ---------- */
.section { padding-block: var(--space-xl); }
.section--tight { padding-block: var(--space-l); }
.section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-s);
  margin-bottom: var(--space-m);
}
/* The homepage's two section headings carry the same weight as the
   Projects and Other Work page titles they lead to, so they are set the
   same way: large and uppercase, not a smaller sentence-case variant. */
.section-head h2 {
  font-size: clamp(2.75rem, 1rem + 6.4vw, 6.5rem);
  font-weight: var(--w-bold);
  text-transform: uppercase;
  line-height: 0.88;
  letter-spacing: -0.02em;
}

/* ---------- page title ----------
   "Other Work" / "Projects" style headings were rendering at the
   browser's plain h1 default (no size class was ever attached) — this
   is the fix: huge, uppercase, tight leading, matching how the
   reference site sets every top-of-page heading. */
/* bumped well past --size-h1: with the width axis squeezed to 62% at
   rest these titles read far smaller than their nominal size, so the
   nominal size has to carry more */
.page-title {
  /* --title-fit is written by initWidthStretch when the widest point of
     the stretch run would not fit on one line; it is 1 (absent) the rest
     of the time. This is what keeps the title single-line at 390px as
     well as 1440px. */
  font-size: calc(clamp(3.5rem, 1.2rem + 9.5vw, 10rem) * var(--title-fit, 1));
  font-weight: var(--w-bold);
  text-transform: uppercase;
  line-height: 0.86;
  letter-spacing: -0.02em;
  white-space: nowrap;
}
/* Durchlauf (Vorschlag B vom 02.10.): "PROJECTS" und "OTHER WORK" auf
   der Startseite, dieselben Wörter als Seitentitel ihrer eigenen Seiten.

   Das Wort ist mit 27vw breiter als der Bildschirm und wandert beim
   Scrollen von rechts nach links hindurch (initDurchlauf in main.js).
   Bewegt wird nur transform, auf der Grafikkarte; die vorige
   Breiten-Dehnung setzte die Schrift in jedem Frame neu und ruckelte. */
.durchlauf-abschnitt, .kopf-abschnitt { overflow-x: clip; }
.durchlauf {
  margin: 0;
  font-size: 27vw;
  font-weight: var(--w-bold);
  text-transform: uppercase;
  letter-spacing: -0.03em;
  line-height: 0.8;
  white-space: nowrap;
  will-change: transform;
}
.durchlauf-abschnitt__kopf { justify-content: flex-end; margin-top: var(--space-s); }

/* OTHER WORK auf der Startseite: in voller Spaltenbreite (data-einpassen,
   main.js), baut sich beim Hereinscrollen aus Zellen auf wie die Titel
   der Projektseiten (auftritt.js) */
.ow-titel {
  margin: 0;
  /* links bündig mit dem Logo statt mittig in der Inhaltsspalte (05.10.):
     gleiche Grösse, nur an den linken Seitenrand gerückt. Am Telefon ist
     das ohnehin dieselbe Kante. */
  margin-left: calc(50% - 50vw + var(--gutter));
  width: 100%;
  font-weight: var(--w-bold);
  text-transform: uppercase;
  letter-spacing: -0.03em;
  line-height: 0.8;
  white-space: nowrap;
}
.other-work-start { padding-top: var(--space-m); }

/* Welle zwischen zwei Abschnitten (welle.js). Über die volle
   Fensterbreite, auch aus einer Inhaltsspalte heraus. */
main { overflow-x: clip; }
.welle { width: 100vw; margin-inline: calc(50% - 50vw); height: 55vh; min-height: 18rem; pointer-events: none; }
.welle canvas { display: block; width: 100%; height: 100%; }
.welle--projekte { height: 42vh; min-height: 14rem; }
@media (prefers-reduced-motion: reduce) { .welle { display: none; } }
/* Seitentitel PROJECTS und OTHER WORK (04.10.): so gross, wie er ganz ins
   Bild passt, über die volle Fensterbreite (data-einpassen, main.js);
   grösser geht nicht, ohne dass das Wort aus dem Bild ragt */
.durchlauf--oben { padding-inline: 0; }
.durchlauf-abschnitt__filter { margin-top: var(--space-m); }

/* Contact: dasselbe Wort auf die Fensterbreite eingepasst, fährt beim
   Laden herein (data-durchlauf="kopf") */
.durchlauf--kopf {
  /* Schätzwert bis zum Einpassen, damit nichts springt */
  font-size: var(--kopf-groesse, 16vw);
  padding-inline: var(--gutter);
  margin-bottom: var(--space-m);
}
.durchlauf--kopf > span {
  display: inline-block;
  animation: kopf-rein 1.4s cubic-bezier(0.625, 0.05, 0, 1) both;
}
@keyframes kopf-rein {
  from { transform: translate3d(45%, 0, 0); }
}
@media (prefers-reduced-motion: reduce) {
  /* ohne Bewegung passt das Wort ganz ins Bild */
  .durchlauf:not(.durchlauf--kopf) { font-size: 14vw; padding-inline: var(--gutter); }
  .durchlauf { transform: none !important; }
  .durchlauf--kopf > span { animation: none; }
}
.section-title {
  font-size: var(--size-h2);
  font-weight: var(--w-bold);
  text-transform: uppercase;
  line-height: 0.92;
  letter-spacing: -0.01em;
}
/* pulls the label/subtitle up under the title's own baseline instead
   of floating as a separate labelled block above the content */
.section-title + .label { margin-top: -0.3em; }

/* ---------- filter pills (Projects page) ---------- */
.filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs);
}
/* Plain words, no capsules. The active one is marked by weight and a
   rule under it rather than by a filled pill — the rest of the site
   marks state the same way (menu links, text links), and the pills were
   the only bordered chrome left on it.
   The name is kept as-is so nothing else has to be renamed. */
.filter-pill {
  font-family: var(--font-display);
  font-weight: var(--w-semibold);
  font-size: var(--size-small);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  padding: 0.25em 0;
  color: var(--fg-muted);
  position: relative;
  /* Breite der fetten Fassung von vornherein reservieren: Wird ein Filter
     beim Hovern oder Auswählen fett, schiebt er die Nachbarn nicht mehr
     zur Seite (das wirkte verbuggt, Rückmeldung vom 05.10.) */
  display: inline-flex;
  flex-direction: column;
  align-items: center;
}
.filter-pill::after {
  content: attr(data-text);
  font-weight: var(--w-bold);
  height: 0;
  overflow: hidden;
  visibility: hidden;
  pointer-events: none;
}
/* der Vergrößerungseffekt: beim Hovern fliessend fett (nur mit Maus).
   Der Übergang steht hier zusammen mit dem der Hover-Farbe; einzeln
   unter .filter-pill würde ihn die Regel "HOVER AUF TEXT" überschreiben. */
@media (hover: hover) {
  .filter-pill:hover, .filter-pill:focus-visible { font-weight: var(--w-bold); }
}
html .filters .filter-pill {
  transition: background-size 0.5s cubic-bezier(0.625, 0.05, 0, 1), background-position 0.5s cubic-bezier(0.625, 0.05, 0, 1), font-weight 0.35s cubic-bezier(0.625, 0.05, 0, 1);
}
@media (prefers-reduced-motion: reduce) { html .filters .filter-pill { transition: none; } }

/* der gewählte Filter: schwarz und fett, ohne Linie darunter */
.filter-pill[aria-pressed="true"] {
  color: var(--fg);
  font-weight: var(--w-bold);
}

/* ---------- project grid / cards ---------- */
.project-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-m) var(--gutter);
}
@media (max-width: 720px) {
  .project-grid { grid-template-columns: 1fr; }
}

.project-card {
  display: block;
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.9s var(--ease-out), transform 0.9s var(--ease-out);
}
.project-card.is-visible { opacity: 1; transform: translateY(0); }

.project-card__frame {
  position: relative;
  overflow: hidden;
  aspect-ratio: 4/3;
  background: var(--black);
}
/* Freigestelltes Objekt auf der Karte: weisser Rahmen statt schwarzem
   (sonst zeigte sich am Rand eine graue Linie), das Objekt ganz. */
.project-card__frame--freigestellt { background: var(--white); }
/* Eine Haarlinie um jedes Kartenbild (einheitliches Raster, 08.10.):
   Auf Fotos verschwindet sie im Bild, bei den Bildern auf weissem Grund
   (The Other, E-Bin) zeigt sie denselben 4:3-Rahmen wie bei den anderen,
   statt das Objekt frei schweben zu lassen. */
.project-card__frame::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12);
  pointer-events: none;
}
.project-card__frame--freigestellt .project-card__media { object-fit: contain; padding: 4%; box-sizing: border-box; }
/* das zweite Bild (Foto) beim Hovern füllt den Rahmen ganz (16.10.) */
.project-card__frame--freigestellt .project-card__media--b { object-fit: cover; padding: 0; }
/* ... ausser es soll ganz zu sehen sein: die Doppelseite auf der
   Publications-Karte klein und mittig auf weissem Grund (16.10.: "lieber
   die ganze Doppelseite als ein hineingezoomter Ausschnitt") */
.project-card__frame .project-card__media--ganz {
  object-fit: contain;
  object-position: 50% 50%;
  padding: 9% 11%;
  box-sizing: border-box;
  background: var(--white);
}
.project-card__media {
  width: 100%; height: 100%;
  object-fit: cover;
  /* Short: the old 1.1s scale was what made the hover feel soft and
     unresolved long after the pointer had settled. */
  transition: opacity 0.26s var(--ease-out), transform 0.5s var(--ease-out);
}
.project-card:hover .project-card__media { transform: scale(1.04); }
.project-card__media--b {
  position: absolute;
  inset: 0;
  opacity: 0;
}
/* The second image fades in ON TOP of the first, which stays fully
   opaque underneath. Fading the first one out at the same time meant
   that mid-crossfade both were part-transparent over the frame's black
   background, so the card visibly greyed out and looked out of focus.
   Now there is no point in the transition where anything shows through,
   and the resting state is a single image at full opacity. */
.project-card:hover .project-card__media--b { opacity: 1; }

.project-card__meta {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-top: var(--space-xs);
  gap: var(--space-s);
}
.project-card__title {
  font-size: var(--size-h3);
  font-weight: var(--w-bold);
  text-transform: uppercase;
}
.project-card[data-cat] { scroll-margin-top: 6rem; }

/* Karten der Startseite (08.10.): Titel, Kategorie und Text standen
   dicht aufeinander (Titel bis Text 0px). Jetzt 4 % der Kartenbreite
   Luft dazwischen (bei 1440 rund 21px, bei 390 rund 14px; gemessen über
   die Breite des Rasters, cqi, nicht der Karte: eine Karte als eigener
   Container bricht das gemeinsame Raster). Und ein gemeinsames Raster: In jeder Reihe
   beginnen Bild, Titelzeile und Text auf derselben Höhe, auch wenn ein
   Titel zweizeilig ist (ANALOGE KOLLEKTION neben E-BIN). */
.project-grid { container-type: inline-size; --karte: calc((100cqi - var(--gutter)) / 2); }
@media (max-width: 720px) { .project-grid { --karte: 100cqi; } }
.project-card__meta { margin-top: max(var(--space-xs), calc(var(--karte) * 0.03)); }
.project-card > p { margin-top: calc(var(--karte) * 0.04); }
@media (min-width: 721px) {
  .project-card {
    display: grid;
    grid-row: span 3;
    grid-template-rows: subgrid;
    row-gap: 0;
    align-content: start;
  }
  .project-card__meta { align-self: start; }
}

/* ---------- project list (Projects page) ----------
   Layout "C", gewählt am 03.10. aus drei ganzseitigen Vorschlägen:
   je Projekt zuerst der Titel als Durchlauf, genau wie PROJECTS auf der
   Startseite (27vw, über die volle Breite); darunter das Bild links
   (8 von 12 Spalten, 4:3), der Text rechts daneben an der Unterkante
   des Bilds. Zwischen zwei Projekten zieht die Welle (welle.js). */
.project-list {
  display: flex;
  flex-direction: column;
}
.project-row {
  display: block;
  padding-block: var(--space-l) var(--space-xl);
}
.project-row__durchlauf {
  /* aus der Inhaltsspalte heraus auf die volle Fensterbreite */
  margin-inline: calc(50% - 50vw);
  position: relative;
  z-index: 2;
  /* alle Projekttitel gleich gross (data-titelgruppe, main.js); lange
     brechen um statt kleiner zu werden, PUBLICATIONS mit Trennstrich */
  white-space: normal;
  line-height: 0.84;
  /* steht er still, dann bündig mit der Inhaltsspalte */
  padding-inline: var(--gutter);
  /* das Bild rückt ein Stück unter den Titel */
  margin-bottom: -0.1em;
}
.project-row__inhalt {
  display: grid;
  grid-template-columns: minmax(0, 8fr) minmax(0, 4fr);
  gap: var(--gutter);
  align-items: end;
}
.project-row__media {
  position: relative;
  z-index: 1;
  overflow: hidden;
  aspect-ratio: 4 / 3;
  background: var(--black);
}
.project-row__media img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform 0.8s var(--ease-out);
}
.project-row:hover .project-row__media img { transform: scale(1.03); }
/* E-Bin: das freigestellte Objekt ganz und auf Weiss, wie auf der Karte */
.project-row__media--freigestellt { background: var(--white); }
.project-row__media--freigestellt img { object-fit: contain; padding: 4%; box-sizing: border-box; }
.project-row__text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.75rem;
  padding-bottom: 0.25rem;
}
.project-row__blurb {
  max-width: var(--measure);
  margin: 0;
  font-size: var(--size-body);
  color: var(--fg);
}
.project-row__mehr { margin-top: 0.25rem; }
@media (max-width: 760px) {
  .project-row__inhalt { grid-template-columns: 1fr; }
  .project-row__media { aspect-ratio: 4 / 5; }
}
@media (max-width: 640px) {
  .project-row__blurb { font-size: var(--size-small); }
}

/* ===================================================================
   CONTACT — Fassung vom 01.10. (Variante A)

       ┌────────────────────────────────────────────┐
       │ g                                    MENU  │
       │        (Video bildfüllend, gedimmt)        │
       │                                            │
       │ Let's talk. Alexander is based in …        │  Anrede
       │                                            │
       │ alexander.grasl04@gmail.com                │  das Größte
       │ I usually respond within a day or two.     │
       │                                            │
       │ Instagram  Rotterdam, 15:21  Internships…  © │  eine ruhige Zeile
       │ LinkedIn   Willem de Kooning                 │
       └────────────────────────────────────────────┘

   Die Seite ist das Ende des Übergangs von "Get in touch": Dort tritt
   das Video nach vorn und wird auf 1.18 vergrößert, hier beginnt es
   genau so und setzt sich dann auf seine Hintergrund-Deckkraft. Das ist
   der eine Auftritt der Seite; sonst bewegt sich nur das Video selbst.

   Lesbarkeit: Das Video zieht helle Lichtbänder durchs Bild. Bei 0.55
   Deckkraft auf Schwarz wird das hellste davon rund rgb(140) — weisse
   Schrift läge dort bei 3.3:1. Der Verlauf unten (und oben für Logo
   und Menü) drückt das unter jedem Text auf höchstens rgb(50), also
   über 10:1, auch für die kleine Schrift.

   Alles auf eine Fensterhöhe: der Inhalt steht unten, das Video füllt
   den Rest. min-height statt height, damit sehr kleine Telefone
   scrollen dürfen, statt Text abzuschneiden.
   =================================================================== */
.kontakt-seite { background: var(--black); }
.kontakt {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  background: var(--black);
  color: var(--white);
  overflow: hidden;
}
.kontakt__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.55;
  view-transition-name: kontakt-video;
  /* Hell ankommen — so, wie das Video über "Get in touch" hereinwächst —
     und sich erst danach auf seine Hintergrund-Deckkraft setzen. */
  animation: kontakt-dimmen 700ms 560ms var(--ease-out) both;
}
@keyframes kontakt-dimmen {
  from { opacity: 1; }
  to   { opacity: 0.55; }
}
.kontakt__scrim {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0) 18%),
    linear-gradient(0deg, rgba(0,0,0,0.68) 0%, rgba(0,0,0,0.5) 45%, rgba(0,0,0,0) 78%);
  pointer-events: none;
  animation: kontakt-einblenden 700ms 560ms var(--ease-out) both;
}
@keyframes kontakt-einblenden {
  from { opacity: 0; }
  to   { opacity: 1; }
}
/* Fassung vom 02.10., zweiter Durchgang (Wahl des Künstlers aus drei
   Layouts): am Laptop "B" — die Anrede oben, unten eine Zeile mit vier
   Spalten, darunter das Wort CONTACT über die volle Breite, so wie die
   Titel der Projektseiten unten im Bild stehen. Nur ein grosses Element;
   die Adresse steht als Erstes in der Zeile, nicht mehr als zweites
   Riesenwort. Am Telefon "C" (Media Query unten). */
.kontakt__innen {
  position: relative;
  min-height: 100svh;
  box-sizing: border-box;
  padding: clamp(5.5rem, 13svh, 8rem) var(--gutter) clamp(1.25rem, 3svh, 2rem);
  display: grid;
  /* Spalte fest auf die Fensterbreite: sonst weitete der einzeilige
     Titel die Spalte auf seine eigene Breite, und das Einpassen
     (main.js) mass sich selbst */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto 1fr auto auto;
  grid-template-areas: "oben" "." "raster" "titel";
  animation: kontakt-text-auftritt 600ms 800ms var(--ease-out) both;
}
@keyframes kontakt-text-auftritt {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.kontakt__oben { grid-area: oben; display: flex; justify-content: space-between; align-items: flex-start; gap: 2rem; }
.kontakt__intro {
  max-width: 30ch;
  margin: 0;
  font-size: var(--size-lede);
  font-weight: var(--w-semibold);
  line-height: 1.3;
}
.kontakt__copy { margin: 0; font-size: var(--size-small); color: rgba(255, 255, 255, 0.6); text-align: right; }
.kontakt__raster {
  grid-area: raster;
  display: grid;
  grid-template-columns: 2.2fr 1fr 1fr 1.4fr;
  column-gap: var(--gutter);
  padding-bottom: clamp(1.25rem, 3svh, 2rem);
  border-bottom: 1px solid rgba(255, 255, 255, 0.3);
  margin-bottom: clamp(1rem, 2.5svh, 1.75rem);
}
.kontakt__spalte {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.3rem;
  font-size: var(--size-small);
  line-height: 1.5;
}
.kontakt__label {
  margin-bottom: 0.4rem;
  font-size: var(--size-label);
  font-weight: var(--w-semibold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.62);
}
.kontakt__mail {
  color: inherit;
  text-decoration: none;
  /* immer auf EINER Zeile (Wunsch vom 02.10.). Gemessen: die Adresse
     ist 14.7-mal so breit wie ihre Schriftgrösse; die Spalte ist bei
     jeder Breite ab 901px gut 2.2vw x 14.7 breit, 2.1vw passt also mit
     etwas Luft. */
  font-size: min(2rem, 2.1vw);
  white-space: nowrap;
  font-weight: var(--w-bold);
  line-height: 1.1;
  letter-spacing: -0.02em;
}
.kontakt__notiz { margin: 0.35rem 0 0; font-size: var(--size-small); color: rgba(255, 255, 255, 0.72); }
.kontakt__leise { color: rgba(255, 255, 255, 0.72); }
.kontakt__link {
  color: inherit;
  text-decoration: none;
}
/* CONTACT in Orange Clown Fish (Wunsch vom 02.10.) */
.kontakt .durchlauf--kopf { grid-area: titel; padding-inline: 0; margin: 0; color: var(--orange); }
.kontakt .durchlauf--kopf > span { animation-delay: 650ms; }

/* Am Telefon "C": eine weisse Seite wie Projects und Other Work. Oben der
   Titel, dann Anrede und Adresse, das Video als Bild, darunter der Rest.
   Die Hüllen lösen sich auf (display: contents), damit Video und Texte
   eine gemeinsame Reihenfolge bekommen. Die Navigation bleibt hier
   schwarz: data-nav-dark gilt nur ab 901px (chrome.js). */
@media (max-width: 900px) {
  .kontakt-seite { background: var(--white); }
  .kontakt {
    min-height: 0;
    /* seitlich abschneiden: das Wort fährt beim Laden von rechts herein
       und stünde sonst kurz über den Rand (in Safari gemessen: 129px) */
    overflow: visible;
    overflow-x: clip;
    justify-content: flex-start;
    background: var(--white);
    color: var(--black);
    padding: clamp(5.5rem, 13svh, 7rem) var(--gutter) var(--space-l);
  }
  .kontakt__scrim { display: none; }
  .kontakt__innen, .kontakt__oben, .kontakt__raster { display: contents; }
  .kontakt .durchlauf--kopf { order: 1; margin-bottom: var(--space-s); }
  .kontakt__intro { order: 2; max-width: none; font-size: clamp(1.125rem, 1rem + 1vw, 1.375rem); }
  .kontakt__spalte--mail { order: 3; margin-top: var(--space-m); }
  .kontakt__video {
    order: 4;
    position: relative;
    inset: auto;
    height: auto;
    aspect-ratio: 4 / 3;
    margin-top: var(--space-m);
    opacity: 1;
    animation: none;
  }
  .kontakt__spalte--netz { order: 5; margin-top: var(--space-m); }
  .kontakt__spalte--ort { order: 6; margin-top: var(--space-m); }
  .kontakt__spalte--offen { order: 7; margin-top: var(--space-m); }
  .kontakt__copy { order: 8; margin-top: var(--space-l); text-align: left; color: var(--fg-muted); }
  .kontakt__label, .kontakt__notiz, .kontakt__leise { color: var(--fg-muted); }
  /* volle Breite minus Ränder: 390px -> 342px Platz, 5.8vw x 14.7 = 332px */
  .kontakt__mail { font-size: min(2.6rem, 5.8vw); }
  .kontakt__spalte { font-size: var(--size-body); }
  /* Instagram und LinkedIn als Fingerziele: rund 40px hoch statt 24 */
  .kontakt__link { padding-block: 0.4rem; }
}
/* ── Seitenübergang "Get in touch" -> Contact ─────────────────────────
   Beide Videos heissen kontakt-video (hero.css, oben). Der Browser misst
   das Video auf der Startseite (ein 60svh-Streifen) und das auf Contact
   (Vollbild) und lässt das eine in das andere wachsen. Der Rest beider
   Seiten wechselt ohne Überblendung: die Bewegung gehört allein dem
   Video. Freigegeben wird der Übergang nur auf diesem einen Weg, siehe
   die Skripte im <head> von index.html und contact.html. */
::view-transition-old(root),
::view-transition-new(root) { animation: none; }
::view-transition-group(kontakt-video) {
  animation-duration: 800ms;
  animation-timing-function: cubic-bezier(0.65, 0, 0.35, 1);
}
::view-transition-old(kontakt-video),
::view-transition-new(kontakt-video) {
  height: 100%;
  object-fit: cover;
  animation-duration: 800ms;
}

@media (prefers-reduced-motion: reduce) {
  .kontakt__video, .kontakt__scrim, .kontakt__innen { animation: none; }
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
  .kontakt__mail, .kontakt__link { transition: none; }
}

/* ===================================================================
   PROJECT PAGE — coloured ground, cover preview, plates
   The page ground is the project's own palette colour (chosen per
   project to suit its imagery); --accent-fg is the measured-contrast
   text colour for that ground, so nothing here is assumed.
   =================================================================== */
.project-page {
  background: var(--accent);
  color: var(--accent-fg);
}
.project-page[data-theme="orange"] { --fg-muted: rgba(0, 0, 0, 0.68); --line: rgba(0, 0, 0, 0.2); }
.project-page[data-theme="pink"]   { --fg-muted: rgba(0, 0, 0, 0.72); --line: rgba(0, 0, 0, 0.2); }
.project-page[data-theme="cream"]  { --fg-muted: rgba(0, 0, 0, 0.6);  --line: rgba(0, 0, 0, 0.15); }

.project-page .label { color: var(--fg-muted); }

/* ---------- cover preview ----------
   One big image area with the title sitting inside it. The bottom used
   to be masked so the picture dissolved into the page; that ate the
   lower third of every cover, so the mask is gone and the edge is clean.
   The frame is taller than before to compensate. */
.project-cover {
  position: relative;
  height: 96svh;
  min-height: 32rem;
  overflow: hidden;
  color: var(--white);
}
.project-cover__media { position: absolute; inset: 0; background: var(--black); }
.project-cover__media img,
.project-cover__media video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  transition: opacity 1.1s var(--ease-in-out);
}
.project-cover__media img.is-hidden { opacity: 0; }
.project-cover__scrim {
  position: absolute; inset: 0; z-index: 2;
  background: linear-gradient(180deg, rgba(0,0,0,0.42) 0%, rgba(0,0,0,0.05) 20%, rgba(0,0,0,0.05) 42%, rgba(0,0,0,0.72) 88%);
}
.project-cover__content {
  position: absolute; left: 0; right: 0; bottom: 16%;
  z-index: 3;
  padding: 0 var(--gutter);
}
.project-cover__content h1 {
  font-size: var(--size-h1);
  text-transform: uppercase;
  color: var(--white);
  text-shadow: 0 2px 30px rgba(0,0,0,0.4);
}

/* the first text block climbs back up into the image's dissolve zone
   instead of starting below it — but only into the faded tail, not
   over the picture itself, and on the same left edge as everything
   else on the page (it was sitting on the raw gutter before, ~160px
   out of line with the body column) */
.project-intro {
  position: relative;
  z-index: 4;
  margin-top: -5svh;
  max-width: var(--content-max);
  margin-inline: auto;
  padding: 0 var(--gutter) var(--space-l);
}

/* ---------- title reveal: outline floods with ink on scroll ---------- */
.title-reveal {
  position: relative;
  display: block;
  /* a step below the cover title — this is the chapter opener, not a
     second shout of the same size */
  font-size: var(--size-h2);
  text-transform: uppercase;
  line-height: 0.9;
  color: transparent;
  -webkit-text-stroke: 2px var(--accent-fg);
}
.title-reveal__fill {
  position: absolute;
  inset: 0;
  color: var(--accent-fg);
  -webkit-text-stroke: 0;
  clip-path: inset(100% 0 0 0);
  will-change: clip-path;
}
@media (prefers-reduced-motion: reduce) {
  .title-reveal { color: var(--accent-fg); -webkit-text-stroke: 0; }
  .title-reveal__fill { clip-path: none; }
}

/* ---------- plates: every image again, full width ---------- */
.plates { display: flex; flex-direction: column; gap: var(--space-l); }
.plates figure { margin: 0; }
.plates img {
  width: 100%;
  height: auto;
  display: block;
  cursor: zoom-in;
}

/* ---------- lightbox ---------- */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: none;
  align-items: center;
  justify-content: center;
  padding: var(--gutter);
  background: rgba(0, 0, 0, 0.92);
  cursor: zoom-out;
}
.lightbox.is-open { display: flex; }
.lightbox img {
  max-width: 100%;
  max-height: 92svh;
  width: auto;
  height: auto;
  object-fit: contain;
}
.lightbox__close {
  position: absolute;
  top: var(--space-s);
  right: var(--gutter);
  font-family: var(--font-body);
  font-size: 0.75rem;
  font-weight: var(--w-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--white);
  background: none;
  border: 0;
  cursor: pointer;
  opacity: 0.75;
}
.lightbox__close:hover { opacity: 1; }

/* ---------- chapter block ----------
   A full-bleed color section inside an otherwise white/black project
   page — echoes how the reference site drops a full tan/orange block
   between black-and-white photo sections instead of tinting the whole
   page. Uses that page's [data-theme] accent; the swatch+label reads
   as a real chapter marker (this section is one beat in the project),
   not decoration. */
/* On a project page the ground is already the accent colour, so an
   accent-coloured chapter block disappears into it. Invert to black
   there so it still reads as a break. */
.project-page .chapter-block {
  background: var(--black);
  color: var(--white);
}
.project-page .chapter-block p { color: rgba(255, 255, 255, 0.88); }

/* A whole page on the accent ground. Same two tokens as .chapter-block
   below, so the About page and the Available For block on Contact are
   painted from one source and cannot drift apart. Black on #ff6f1b is
   7.5:1, so body text stays comfortably legible. */
body.on-accent {
  background: var(--accent);
  color: var(--accent-fg);
}
body.on-accent .label,
body.on-accent [class*="__bio"] p {
  /* the muted grey is tuned for a white ground and turns muddy here */
  color: var(--accent-fg);
  opacity: 0.72;
}

/* Keywords neben dem Text (16.10., Variante B der Vorschau).

   Vorher ein vollbreiter Farbblock am Ende des Textes, je Seite in
   eigener Farbe (E-Bin Pink, Synergy zuletzt Pantone 2347 C). Er stand
   zu sehr im Vordergrund. Jetzt eine schmale Spalte rechts neben dem
   ersten Absatz, ein Begriff pro Zeile wie ein Register; die Fläche
   daneben war vorher leer. Auf schmalen Fenstern über dem Text, grau
   und fortlaufend. render.js setzt .project-intro--keywords an die
   Sektion und hängt das <aside> an. */
.project-intro--keywords {
  display: grid;
  grid-template-columns: minmax(0, var(--measure)) minmax(0, 15rem);
  column-gap: var(--space-xl);
  align-items: start;
}
.project-intro--keywords > [data-project-body] { grid-column: 1; grid-row: 1; }
.project-keywords { grid-column: 2; grid-row: 1; display: grid; gap: var(--space-2xs); }
.project-keywords ul { list-style: none; margin: 0; padding: 0; }
.project-keywords li { font-size: var(--size-small); line-height: 1.6; }
@media (max-width: 900px) {
  .project-intro--keywords { grid-template-columns: minmax(0, 1fr); row-gap: var(--space-m); }
  .project-keywords { grid-row: 1; grid-column: 1; }
  .project-intro--keywords > [data-project-body] { grid-row: 2; }
  .project-keywords ul { display: flex; flex-wrap: wrap; column-gap: 0.9em; }
  .project-keywords li { color: var(--fg-muted); }
}

.chapter-block {
  background: var(--accent);
  color: var(--accent-fg);
  /* Rund 10 % niedriger als mit --space-xl (Wunsch vom 02.10.), über
     weniger Innenabstand oben und unten. Gemessen: 424 -> 381px bei
     1440, 337/374/411 -> 301/338/375px bei 390. */
  padding-block: clamp(3.25rem, 2rem + 5.6vw, 7.5rem);
  /* full-bleed margins are computed in JS (initFullBleed, main.js) —
     the CSS calc(50% - 50vw) break-out trick only works when the
     element's containing block is already full width, which isn't
     true here (nested inside data-project-body's measure column) */
  padding-inline: var(--gutter);
}
.chapter-block__eyebrow {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  font-family: var(--font-body);
  /* was 12px/600, a third variant of the same small caps label */
  font-size: 0.8125rem;
  font-weight: var(--w-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  margin-bottom: var(--space-s);
}
.chapter-block__eyebrow::before {
  content: "";
  width: 0.9em; height: 0.9em;
  background: currentColor;
  flex: none;
}
.chapter-block__title {
  font-size: var(--size-h2);
  font-weight: var(--w-bold);
  text-transform: uppercase;
  line-height: 0.95;
  max-width: 20ch;
}
.chapter-block p { max-width: var(--measure); font-size: var(--size-lede); margin-top: var(--space-s); }

/* Projektseiten kompakter (Wunsch vom 02.10.: "Abstände zu gross").
   Nur Abstände, kein Umbau: Text oben, darunter Keywords, darunter die
   Bilder, alles getrennt wie zuvor. Gemessen bei 1440 vorher:

     Text -> Keywords-Inhalt     165px  (53 weiss + 112 im Block)
     Keywords -> "Gallery"       248px  (zwei Abschnittsränder à --space-xl)

   Zwischen zwei Abschnitten liegt jetzt zweimal --space-m statt
   zweimal --space-xl; das gilt auf allen fünf Projektseiten gleich,
   also auch zwischen Filmen, Galerie und Flipbooks. Der Abstand vom
   Titelbild zum ersten Text (inline --space-l) bleibt. */
.projektseite > .section { padding-block: var(--space-m); }
.projektseite > .section:first-child { padding-top: var(--space-l); }
.projektseite > .section:last-child { padding-bottom: var(--space-xl); }
/* Keywords-Block: weniger Luft innen und davor.

   Zweiter Durchgang (02.10.): 97px von Text zu Keywords war zu eng.
   Jetzt genau die Mitte zwischen dem ursprünglichen Wert und dem zu
   engen, für die weisse Lücke davor wie für die Polsterung innen:

                         vorher   zu eng   jetzt
     1440  Lücke davor     53       24       38
           innen oben     112       73       93
           zusammen       165       97      131
     390   zusammen        84       64       74  */
.projektseite .chapter-block {
  padding-block: clamp(2.875rem, 1.75rem + 4.5vw, 6.25rem);
  margin-top: calc((var(--space-s) - var(--space-m)) / 2);
}

/* ---------- gallery grid (project pages, other work) ---------- */
.gallery {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-s);
}
@media (max-width: 900px) { .gallery { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .gallery { grid-template-columns: 1fr; } }
/* Photographs keep their own proportions. This used to force
   aspect-ratio: 4/3 with object-fit: cover on every image, which cut each
   portrait frame down to a centred horizontal band — on E-Bin that band
   sat below the object, so the box lost its top edge and the picture was
   mostly lawn. Eight images across the project and Other Work galleries
   were being cropped that way. Nothing is cropped now, so the subject is
   wherever the photographer put it. */
/* One aspect ratio per page, so a gallery reads as a set rather than a
   row of mismatched frames. The value differs from project to project —
   square for the 120mm scans, portrait for E-Bin, 3:2 where the
   originals already are — and is set on the gallery container by
   render.js from `galleryRatio` in projects-data.js.
   Cropping to a shared frame only works if the crop knows where the
   subject is, so initGalleryFraming() in main.js measures each picture
   and writes its object-position. Blind 50% centring is what put the
   E-Bin object against the edge. */
.gallery { align-items: start; }
.gallery img, .gallery video {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: var(--gallery-ratio, 4/3);
  object-fit: cover;
}
/* Ganz gezeigt statt in den Rahmen geschnitten ("| ganz" in gallery.txt,
   16.10.): in der Reihe so hoch wie die Nachbarn, das Bild mittig darin;
   auf dem Telefon, eins pro Zeile, in seinem eigenen Format. */
.gallery img[data-ganz] { object-fit: contain; }
@media (max-width: 560px) { .gallery img[data-ganz] { aspect-ratio: auto; } }
.gallery figure {
  margin: 0;
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.8s var(--ease-out), transform 0.8s var(--ease-out);
}
.gallery figure.is-visible { opacity: 1; transform: translateY(0); }
.gallery figcaption {
  margin-top: var(--space-2xs);
}

/* ---------- footer ---------- */
.site-footer {
  background: var(--black);
  color: var(--white);
  padding-block: var(--space-l);
  position: relative;
}
/* Zwei Blöcke über die volle Breite (15.10.): ARCHIVE links allein,
   rechts eine Gruppe aus Carbon-Badge und Copyright, weil beides Angaben
   zur Seite sind. Das Badge (mit dem Link zur Sustainability-Seite
   darunter) steht links neben dem Copyright, nicht darüber: darüber ging
   es unter. Alles unten bündig mit ARCHIVE, auf den Linien von Logo und
   MENU. Am Telefon untereinander. */
.site-footer .site-footer__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  gap: var(--space-m);
  max-width: none;
}
.site-footer__gruppe {
  display: grid;
  grid-template-columns: auto auto;
  align-items: end;
  column-gap: var(--space-l);
  row-gap: var(--space-2xs);
}
.site-footer__gruppe > .site-footer__co2 { grid-column: 1; grid-row: 1; margin: 0; }
.site-footer__gruppe > .site-footer__nachhaltig { grid-column: 1; grid-row: 2; }
.site-footer__gruppe > .site-footer__meta { grid-column: 2; grid-row: 1 / span 2; }
@media (max-width: 760px) {
  .site-footer .site-footer__row { grid-template-columns: minmax(0, 1fr); gap: var(--space-l); }
  .site-footer__gruppe { grid-template-columns: minmax(0, 1fr); row-gap: var(--space-xs); }
  .site-footer__gruppe > .site-footer__meta { grid-column: 1; grid-row: 3; margin-top: var(--space-s); }
  /* unten Platz für den Zurück-Pfeil, der fest in der Ecke steht */
  .site-footer { padding-bottom: calc(var(--space-l) + 2.5rem); }
}
/* Link zur Sustainability-Seite unter dem Badge, in Orange, damit man ihn
   unten sieht (15.10.); derselbe Satz wie unten auf About */
.site-footer__nachhaltig {
  font-family: var(--font-body);
  font-size: var(--size-small);
  font-weight: var(--w-semibold);
  color: var(--orange);
  text-decoration: none;
  /* Unterstreichung, die beim Hovern von links nach rechts einläuft
     (16.10.; vorher erschien sie schlagartig) */
  background: linear-gradient(currentColor, currentColor) 0 100% / 0 1px no-repeat;
  padding-bottom: 0.15em;
  transition: background-size 0.45s var(--ease-out);
}
.site-footer__nachhaltig:hover,
.site-footer__nachhaltig:focus-visible { background-size: 100% 1px; }
@media (prefers-reduced-motion: reduce) { .site-footer__nachhaltig { transition: none; } }
.site-footer__nachhaltig:focus-visible { outline: 2px solid var(--orange); outline-offset: 3px; }
.site-footer__archive {
  font-family: var(--font-display);
  font-size: clamp(2.5rem, 1.5rem + 5vw, 6rem);
  font-weight: var(--w-bold);
  letter-spacing: 0.01em;
  text-transform: uppercase;
}
.site-footer__meta {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  font-family: var(--font-body);
  font-size: var(--size-small);
  font-weight: var(--w-semibold);
  color: rgba(255, 255, 255, 0.68);
}


/* ---------- project body / bio typography blocks ---------- */
.project-body__block { max-width: var(--measure); }

/* Abstände in den Projekttexten: am 16.10. vergrössert (Abschnitte
   --space-l, Überschrift und Absätze --space-s), am selben Tag auf den
   Stand davor zurückgesetzt ("vorher enger, gefiel besser"). */
.project-body__block .label { margin-bottom: var(--space-2xs); }
.project-body__block p + p { margin-top: var(--space-2xs); }

.project-body__sub {
  padding-top: var(--space-l);
  border-top: var(--hairline);
}
.project-body__sub h3 { font-size: var(--size-h3); margin-bottom: var(--space-2xs); text-transform: uppercase; }
.project-body__sub .label { margin-top: var(--space-s); margin-bottom: var(--space-2xs); }
.project-body__sub p + p { margin-top: var(--space-2xs); }

/* text + its own flipbook as one unit, not two stacked blocks */
.project-body__sub--book {
  max-width: none;
  display: grid;
  /* Das Buch bekommt mehr Platz (16.10.: "darf grösser werden"):
     Spalten 1 : 2 statt 1 : 1,3, die Fläche breiter als sonst
     (.wrap--breit), der Text rückt dafür nach links. Abstand zum Text
     --space-l, rechts bleibt der Seitenrand (--gutter). */
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
  gap: var(--space-l);
  align-items: start;
}
.project-body__sub-text { max-width: var(--measure); }
.wrap--breit { max-width: 90rem; }
/* die Hefte bei Publications höher als die übrigen Bücher */
.project-body__sub-book .flipbook__page { height: min(80svh, 52rem); max-height: none; }
@media (max-width: 900px) {
  .project-body__sub--book { grid-template-columns: 1fr; }
}
/* Neben dem Buch steht jeder Titel auf der Höhe von zwei Zeilen, ein
   einzeiliger unten (16.10.): MOMENTOM beginnt so auf derselben Höhe
   über dem Text wie THE JOURNEY OF A WASTEMAN, und beide Texte setzen
   gleich tief neben ihrem Buch ein. Der Farbaufbau (raster.js) misst
   jeden Buchstaben dort, wo er steht, und folgt mit. */
@media (min-width: 901px) {
  .project-body__sub--book h3 {
    min-height: 2lh;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
  }
}

[data-tools] li, [data-collaborations] li {
  padding-block: var(--space-2xs);
  border-bottom: var(--hairline);
  font-weight: var(--w-semibold);
}
[data-open-to] li {
  font-family: var(--font-display);
  font-weight: var(--w-bold);
}
[data-available-for] li {
  border: 2px solid var(--line);
  border-radius: 999px;
  padding: 0.4em 0.9em;
  font-weight: var(--w-semibold);
}

h2 { font-size: var(--size-h2); }

/* ---------- reveal-on-scroll (generic text/media) ---------- */
[data-reveal] {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.9s var(--ease-out), transform 0.9s var(--ease-out);
}
[data-reveal].is-visible { opacity: 1; transform: translateY(0); }

/* Auftritt von Texten und Überschriften (auftritt.js, 02.10.).
   Texte: gleiten aus etwa einer Zeile Tiefe herauf und werden dabei von
   oben nach unten aufgedeckt. Überschriften warten unsichtbar, bis ihr
   Raster beginnt. Beides nur mit Skript; ohne bleibt alles sichtbar. */
/* Die Maske läuft als Keyframe erst beim Start. Im Wartezustand darf
   das Element nicht weggeschnitten sein: ein ganz durch clip-path
   verdecktes Element meldet der IntersectionObserver nie als sichtbar,
   und es bliebe für immer unsichtbar (so passiert mit den Filtern). */
.auftritt {
  opacity: 0;
  transform: translate3d(0, 0.9em, 0);
}
.auftritt.ist-da {
  opacity: 1;
  transform: none;
  transition: opacity 0.6s linear, transform 0.9s cubic-bezier(0.625, 0.05, 0, 1);
  transition-delay: var(--verzug, 0ms);
  animation: auftritt-maske 0.9s cubic-bezier(0.625, 0.05, 0, 1) var(--verzug, 0ms) both;
}
@keyframes auftritt-maske {
  from { clip-path: inset(0 0 100% 0); }
  to   { clip-path: inset(0 0 0 0); }
}
.raster-wartet { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  .auftritt, .raster-wartet { opacity: 1; transform: none; clip-path: none; }
}

/* Eine Sektion, deren Inhalt leer geblieben ist, darf keinen Platz
   beanspruchen.

   render.js füllt [data-project-body] und [data-project-subentries] aus
   den Textdateien. Bleibt dabei nichts übrig — bei Publications ist der
   Textkörper leer, weil das Projekt seine Inhalte als Untereinträge
   führt — stand die umgebende Sektion trotzdem da und belegte mit ihrer
   Polsterung 224px reinen Leerraum direkt unter dem Titelbild.

   Die Sektionen starten deshalb mit .ist-leer, und render.js nimmt die
   Klasse weg, sobald Inhalt drin ist.

   NICHT über main > section:has(> [data-project-body]:empty) lösen. So
   stand es vom 17.09. bis 01.10. hier, und Safari hat dabei sämtliche
   Projekttexte verschluckt: Die Regel greift beim Laden, solange der
   Textkörper noch leer ist; wird er danach gefüllt, rechnet WebKit den
   Stil zwar neu (display: block), legt die Sektion aber nicht wieder an
   — sie bleibt ohne Fläche, 0x0 Pixel, Texte unsichtbar. Chrome war
   nicht betroffen, deshalb fiel es lange nicht auf. */
main > section.ist-leer {
  display: none;
}

/* Das Artist Statement auf der About-Seite: lesbarer Fliesstext in
   Lede-Grösse, eine Spalte, zwischen Bio und den drei Listen. */
.about-statement__text {
  max-width: var(--measure);
  margin-top: var(--space-s);
  display: flex;
  flex-direction: column;
  gap: 0.9em;
  font-size: var(--size-lede);
  line-height: 1.45;
}
.about-statement__text p { margin: 0; }

/* Die drei Spalten auf der About-Seite (Education, Tools, Collaborations).

   Stand vorher als Inline-Stil im HTML: repeat(3, 1fr), fest, ohne
   jede responsive Rückfallebene. In Safari bei 390px lief die Seite
   dadurch um 116px über — am 17.09. gemessen, sobald die
   Safari-Automatisierung verfügbar war. Chromium fiel nicht auf, weil
   es die Spuren weiter schrumpft; WebKit hält sich an die
   min-content-Untergrenze, die `1fr` implizit mitbringt, und schiebt
   die dritte Spalte aus dem Viewport.

   auto-fit statt einer festen Zahl, und minmax(min(100%, 15rem), 1fr)
   statt 1fr: Das min(100%, …) ist der entscheidende Teil. Ohne es kann
   eine Spur nicht unter 15rem fallen und läuft bei schmalen Fenstern
   wieder über — mit ihm gibt die Spur bei Bedarf bis auf die volle
   Containerbreite nach und bricht dann um. */
.about-spalten {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  gap: var(--space-l);
}
/* Tools & Methods: der Zusatz in Klammern eingerückt in der Zeile
   darunter, kleiner und gedämpft, nie neben dem Begriff (05.10.) */
.about-tools__zusatz {
  display: block;
  padding-left: 1em;
  font-size: 0.8em;
  color: var(--fg-muted);
}

/* ---------- about page: image + headline as one integrated piece ---------- */
.about-hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
  gap: var(--space-l);
}
.about-hero__media {
  position: relative;
  overflow: hidden;
  /* was stretching full-row height against a landscape source photo,
     which forced a heavy crop that read as "too big/zoomed in"; a
     fixed portrait ratio sizes it sensibly instead */
  aspect-ratio: 4 / 5;
  align-self: center;
}
.about-hero__media img {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  /* Mittig, weil die DATEI jetzt schon der Ausschnitt ist.

     Vorher lag hier eine quer aufgenommene Datei, aus der der Browser
     über object-position: 68% den hochformatigen Ausschnitt nahm — von
     2500px Breite kamen dadurch nur 1333 im Bild an. scripts/portrait-neu.py
     schneidet denselben Ausschnitt jetzt aus dem Original (4272x2848) und
     liefert ihn als 1800x2250. Wer den Ausschnitt verschieben will,
     ändert POS_X in diesem Skript, nicht diese Zeile.

     contrast(1.05) ist weg: Belichtung und Kontrast stecken in der Datei.
     Kein grayscale-Filter mehr: das Porträt ist seit dem 28.09. wieder
     farbig, auf Wunsch des Künstlers. Eine Schwarzweiß-Fassung liefert
     scripts/portrait-neu.py --schwarzweiss. */
  object-position: 50% 50%;
}
.about-hero__text {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-m);
  padding: var(--space-xl) var(--gutter);
}
.about-hero__headline {
  /* --size-h1 is tuned for a full-bleed hero; in this half-width column
     it forces single words past the edge (flex items don't shrink below
     content width) — --size-h2 is sized for the column instead */
  font-size: var(--size-h2);
  text-transform: uppercase;
  line-height: 0.98;
  overflow-wrap: break-word;
}
/* Jeder Satz der Bio beginnt in einer neuen Zeile, ohne Abstand
   dazwischen (15.10.; vorher zwei Absätze mit Lücke) */
.about-hero__bio {
  display: flex;
  flex-direction: column;
  gap: 0;
  font-size: var(--size-lede);
  max-width: 34rem;
}

@media (max-width: 900px) {
  .about-hero { grid-template-columns: 1fr; }
  .about-hero__media { min-height: 60svh; }
}

/* ---------- page transition curtain ---------- */
/* Leaving a page: the content softens and fades rather than being
   covered by a black rectangle. The black curtain read as a hard cut
   with nothing behind it; this keeps the page you are leaving visible
   the whole way out, which is what makes the move feel connected
   instead of interrupted. */
/* The blur and fade are applied to the page itself, not to an overlay —
   an overlay can only add something on top, and what we want is the
   page going out of focus. */
.is-leaving {
  filter: blur(9px);
  opacity: 0.35;
  transition: filter 0.26s var(--ease-out), opacity 0.26s var(--ease-out);
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  .is-leaving { filter: none; transition: opacity 0.12s linear; }
}
/* Set for exactly one frame by transition.js when a page comes back
   from the back/forward cache. Going back should be instant — the blur
   belongs to leaving, and playing it in reverse on arrival is an
   animation nobody asked for. !important because it has to beat the
   .is-leaving rule it is undoing. */
.is-restoring,
.is-restoring * {
  transition: none !important;
  animation: none !important;
  filter: none !important;
}

/* ---------- sound toggle (video hero unmute) ---------- */
/* was a chunky bordered pill with a backdrop blur and an emoji —
   now just small plain text; hover turns it Orange (it sits on video) */
.sound-toggle {
  position: absolute;
  right: var(--gutter);
  bottom: var(--space-m);
  z-index: 15;
  font-family: var(--font-body);
  font-size: 0.75rem;
  font-weight: var(--w-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--white);
  padding: 0;
  border: 0;
  background: none;
  opacity: 0.8;
  transition: opacity var(--dur-fast) var(--ease-out);
}
.sound-toggle:hover, .sound-toggle:focus-visible { opacity: 1; }

/* ---------- Zurück-Pfeil unten links (14.10., chrome.js) ----------
   Klein und typografisch wie MENU: Pfeil und Ziel, sonst nichts. */
.zurueck {
  position: fixed;
  left: var(--gutter);
  bottom: calc(var(--gutter) * 0.6);
  z-index: 90;
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  min-height: 2.75rem;
  color: var(--black);
  font-family: var(--font-display);
  font-weight: var(--w-bold);
  font-size: var(--size-small);
  text-transform: uppercase;
  transition: color var(--dur-fast) var(--ease-out), opacity var(--dur-fast) var(--ease-out);
}
.zurueck--auf-dunkel { color: var(--white); }
.zurueck__pfeil { width: 1.2rem; height: 1.2rem; flex: none; }
/* Farbe beim Hovern wie beim Logo: Pink auf hellem, Orange auf dunklem Grund */
@media (hover: hover) {
  .zurueck:hover { color: var(--pink); }
  .zurueck--auf-dunkel:hover { color: var(--orange); }
}
.zurueck:focus-visible { outline: 2px solid currentColor; outline-offset: 3px; }
[data-menu-open="true"] .zurueck { opacity: 0; pointer-events: none; }

/* ---------- Carbon-Badge (Probe 14.10., chrome.js) ---------- */

/* "angepasst": dasselbe offizielle Badge, typografisch wie die Fusszeile */
.co2--angepasst #wcb.carbonbadge { font-family: var(--font-body); font-size: var(--size-small); line-height: 1.4; text-align: left; color: rgba(255, 255, 255, 0.62); }
.co2--angepasst #wcb.carbonbadge #wcb_p { display: block; margin: 0; }
.co2--angepasst #wcb.carbonbadge #wcb_g { display: inline; min-width: 0; padding: 0; border: 0; border-radius: 0; background: none; color: var(--white); font-weight: var(--w-bold); font-size: 1em; }
.co2--angepasst #wcb.carbonbadge #wcb_a { display: inline; margin-left: 0.5em; padding: 0; border: 0; border-radius: 0; background: none; color: rgba(255, 255, 255, 0.62); font-weight: var(--w-regular); font-size: 1em; text-decoration: underline; text-underline-offset: 0.2em; }
.co2--angepasst #wcb.carbonbadge #wcb_2 { display: block; margin-top: 0.15em; padding: 0; text-align: left; color: rgba(255, 255, 255, 0.62); }


/* ---------- Sustainability (endgültiger Text 16.10.) ----------
   Fünf Absätze, jeder für sich; Schrift und Zeilenabstand wie in den
   Projekttexten (--size-body, --leading-body), eine Spalte direkt unter
   dem Titel. Kein Bild, keine Grafik. */
.nachhaltig__text { max-width: var(--measure); margin-top: var(--space-l); padding-bottom: var(--space-xl); }
.nachhaltig__block p { margin: 0; font-size: var(--size-body); line-height: var(--leading-body); }
.nachhaltig__block p + p { margin-top: 0.9em; }
/* Vorschlag statt eines Satzes, der so nicht stimmt: nur in der
   Vorschau, verschwindet mit den [[ ]] in der Textdatei */
mark.vorschlag { background: none; color: inherit; text-decoration: underline wavy var(--pink); text-decoration-thickness: 1px; text-underline-offset: 0.25em; }

/* About: Links auf dem orangen Grund (16.10.: der Link zur
   Sustainability-Seite wurde beim Hovern pink, zu wenig Kontrast).
   Beim Hovern schwarz, dazu unterstrichen, damit man sieht, dass er
   reagiert. */
html .on-accent .text-link { --hv-farbe: var(--black); }
/* Der Link zur Sustainability-Seite (16.10.): etwas grösser und fetter
   als die übrigen Beschriftungen, damit man ihn wahrnimmt; beim Hovern
   noch fetter statt unterstrichen (Archivo ist eine variable Schrift,
   die Stärke geht fliessend über). */
.about-nachhaltig {
  font-size: 0.9375rem;
  font-weight: 800;
  font-stretch: 100%;
  transition: font-weight 0.35s var(--ease-out), font-stretch 0.35s var(--ease-out), background-size 0.5s cubic-bezier(0.625, 0.05, 0, 1);
}
.about-nachhaltig.hv-an,
.about-nachhaltig:hover,
.about-nachhaltig:focus-visible { font-weight: 900; font-stretch: 112%; }
@media (prefers-reduced-motion: reduce) { .about-nachhaltig { transition: none; } }

