/* ============================================================
   BoostPixel — Automobilfotografie
   Konzept: KONTAKTBOGEN

   Die Arbeit eines Fotografen ist nach Terminen sortiert, nicht
   nach Einzelbildern. Genau so ist die Seite gebaut: ein Shooting
   ist eine Kachel, alle Kacheln sind gleich groß, weitere Aufnahmen
   liegen als Stapel dahinter und öffnen sich im Dialog. Zwölf
   gleiche Rechtecke lassen sich in Sekunden überblicken, eine
   Mosaikwand aus zwanzig verschieden großen Bildern nicht.

   Vier Schriftgrößen, mehr nicht:
     .t1  Titel und Kontaktzeile
     .t2  Sektionsüberschrift
     .t3  Fließtext, Bildtitel, Formularfelder
     .t4  Auszeichnung: Navigation, Meta, Fußzeile

   Eine Farbwelt für die ganze Seite, hell oder dunkel, umschaltbar
   über die Belichtung im Kopf. Kein Akzentton: die Farbe kommt aus
   den Fotos. Radius überall 0.
   ============================================================ */

/* ---------- Schriften, lokal ---------- */
@font-face {
  font-family: 'Archivo';
  src: url('assets/fonts/archivo.woff2') format('woff2');
  font-weight: 400 800;
  font-stretch: 75% 125%;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Archivo';
  src: url('assets/fonts/archivo-ext.woff2') format('woff2');
  font-weight: 400 800;
  font-stretch: 75% 125%;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+1E00-1E9F, U+2020, U+20A0-20AB, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* ---------- Farbe und Maße ---------- */
:root {
  --bg:    #f4f3f0;
  --bg-2:  #eae8e4;
  --fg:    #121316;
  --fg-2:  #5c5f66;
  --line:  rgba(18, 19, 22, .14);
  --logo-flip: invert(1);
  /* Schatten unter den Abzügen. Im Ton des Untergrunds, nie reines Schwarz. */
  --schatten:      0 0 0 1px rgba(24, 22, 18, .07), 0 .625rem 1.5rem rgba(24, 22, 18, .18), 0 .125rem .375rem rgba(24, 22, 18, .12);
  --schatten-hoch: 0 0 0 1px rgba(24, 22, 18, .07), 0 1.5rem 2.75rem rgba(24, 22, 18, .26), 0 .25rem .5rem rgba(24, 22, 18, .14);

  /* Genau vier Größen. */
  --t1: clamp(2.375rem, 5.4vw, 4.5rem);
  --t2: clamp(1.875rem, 4.6vw, 3.5rem);
  --t3: 1.125rem;
  --t4: 0.9375rem;

  --pad: clamp(1.25rem, 4vw, 3.5rem);
  --sec: clamp(4.5rem, 8.5vw, 8rem);
  --ease: cubic-bezier(.16, 1, .3, 1);
  /* Weicher Verlauf zum Anfassen. Der Anfang muss flach liegen: eine
     Kurve, die gleich zu Beginn steil wird, wirkt schnell, egal wie
     lange sie insgesamt läuft. */
  --weich: cubic-bezier(.35, .06, .22, 1);
  --weich-zeit: .7s;
  --nav-h: 4.5rem;
  --luecke: clamp(1rem, 2vw, 1.75rem);
  --versatz-mobil: 1.75rem;
}

:root[data-theme="dark"] {
  --bg:   #101113;
  --bg-2: #191b1e;
  --fg:   #f2f1ee;
  --fg-2: #9298a0;
  --line: rgba(242, 241, 238, .17);
  --logo-flip: none;
  --schatten:      0 0 0 1px rgba(255, 255, 255, .09), 0 .75rem 1.75rem rgba(0, 0, 0, .55), 0 .125rem .375rem rgba(0, 0, 0, .45);
  --schatten-hoch: 0 0 0 1px rgba(255, 255, 255, .14), 0 1.75rem 3.25rem rgba(0, 0, 0, .65), 0 .25rem .5rem rgba(0, 0, 0, .5);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:   #101113;
    --bg-2: #191b1e;
    --fg:   #f2f1ee;
    --fg-2: #9298a0;
    --line: rgba(242, 241, 238, .17);
    --logo-flip: none;
    --schatten:      0 0 0 1px rgba(255, 255, 255, .09), 0 .75rem 1.75rem rgba(0, 0, 0, .55), 0 .125rem .375rem rgba(0, 0, 0, .45);
    --schatten-hoch: 0 0 0 1px rgba(255, 255, 255, .14), 0 1.75rem 3.25rem rgba(0, 0, 0, .65), 0 .25rem .5rem rgba(0, 0, 0, .5);
  }
}

/* ---------- Grundlagen ---------- */
*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; }

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: 'Archivo', system-ui, sans-serif;
  font-size: var(--t3);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  transition: background-color .4s var(--ease), color .4s var(--ease);
}

img { display: block; max-width: 100%; height: auto; }
a { color: inherit; }
h1, h2, h3 { margin: 0; font-weight: 800; line-height: 1.06; letter-spacing: -.025em; }
p { margin: 0; max-width: 62ch; }

.t1 { font-size: var(--t1); font-stretch: 104%; }
.t2 { font-size: var(--t2); font-stretch: 104%; }
.t3 { font-size: var(--t3); line-height: 1.6; }
/* Die Auszeichnung steht in derselben Familie, versal und weit
   gesperrt. Eine Monospace liest sich technisch, das ist die Sprache
   eines Terminals, nicht die einer Zeitschrift. */
.t4 {
  font-size: var(--t4);
  line-height: 1.5;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: .16em;
}
/* Beschreibungen und Hinweise tragen die Größe der Auszeichnung, aber
   nicht ihre Versalien. Ein Fehlertext in Großbuchstaben schreit. */
.lb__stage figcaption,
.field--check label,
.field__hint,
.field__error {
  text-transform: none;
  letter-spacing: .01em;
  font-weight: 400;
}

.wrap {
  width: 100%;
  max-width: 1360px;
  margin-inline: auto;
  padding-inline: var(--pad);
}

.skip { position: absolute; left: -9999px; background: var(--fg); color: var(--bg); padding: .75rem 1.25rem; z-index: 100; }
.skip:focus { left: 1rem; top: 1rem; }
:focus-visible { outline: 2px solid var(--fg); outline-offset: 3px; }

/* ---------- Einblenden beim Scrollen ----------
   Ordnet die Lesereihenfolge. Ohne JavaScript ist alles sofort da. */
@media (prefers-reduced-motion: no-preference) {
  .js .reveal {
    opacity: 0; transform: translateY(16px);
    transition: opacity .7s var(--ease), transform .7s var(--ease);
  }
  .js .reveal.is-in { opacity: 1; transform: none; }

  /* Die Abzüge tragen beide Bewegungen: das Einblenden in Lesereihenfolge
     und das weiche Anheben unterm Zeiger. Ohne diese Regeln gewinnt die
     reveal-Regel oben per Spezifität, wirft die Übergänge der Abzüge weg,
     und das Anheben springt statt zu gleiten. Die Verzögerung gehört nur
     dem Einblenden: so legen sich die Fotos einer Zeile nacheinander hin,
     das Anfassen reagiert sofort.

     Die Anfass-Übergänge gelten erst ab is-in: beim Laden schreibt das
     Skript jedem Foto seine gewürfelte Lage, und stünde die
     rotate-Transition schon, drehte sich die ganze Wand sichtbar von den
     Ausfallwerten dorthin. */
  .js .reveal.shot, .js .reveal.srv__foto {
    transition:
      opacity .7s var(--ease) var(--reveal-verz, 0s),
      transform .7s var(--ease) var(--reveal-verz, 0s);
  }
  .js .reveal.shot.is-in, .js .reveal.srv__foto.is-in {
    transition:
      opacity .7s var(--ease) var(--reveal-verz, 0s),
      transform .7s var(--ease) var(--reveal-verz, 0s),
      rotate var(--weich-zeit) var(--weich),
      scale var(--weich-zeit) var(--weich),
      box-shadow var(--weich-zeit) var(--weich);
  }
}

/* ---------- Kopfleiste ---------- */
.nav {
  position: sticky; top: 0; z-index: 40;
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line);
}
.nav__inner {
  height: var(--nav-h);
  max-width: 1360px; margin-inline: auto; padding-inline: var(--pad);
  display: flex; align-items: center; gap: clamp(1rem, 3vw, 2.5rem);
}
/* Das B steht schon im Zeichen, der Zug daneben vollendet nur das
   Wort. Dafür schließt er unten mit dem Zeichen ab, auf der Linie, auf
   der auch die Räder stehen, und rückt dicht heran. */
.brand {
  margin-right: auto;
  display: flex; align-items: flex-end; gap: .15rem;
  text-decoration: none;
}
.brand__mark { height: 1.6rem; width: auto; filter: var(--logo-flip); }
/* Unterkante auf Unterkante: bei line-height 1 liegt die Grundlinie
   rund 0,2em über dem Boxrand (der Platz der Unterlängen, die
   »oostPixel« nicht hat). Der Zug rückt um dieses Stück nach unten. */
/* Der Zug gehört zur Marke, nicht zum Satz: seine Größe richtet sich
   nach dem B im Zeichen, nicht nach den vier Textgrößen. */
.brand__name { font-size: 1.3rem; font-weight: 800; letter-spacing: .04em; line-height: 1; translate: 0 .14rem; }

/* Das Zeichen steht immer. Der Namenszug daneben tritt zurück, solange
   die große Marke im Titelbild zu sehen ist: zweimal derselbe
   Schriftzug auf einem Bildschirm liest sich als Fehler. Läuft die
   Marke beim Scrollen aus dem Bild, gleitet der Name aus dem Zeichen
   heraus. Grundstellung ist sichtbar, damit ein Ausfall des Beobachters
   den Namen nicht verschluckt. */
.nav--marke-oben .brand__name {
  opacity: 0; visibility: hidden;
  translate: -.5rem .14rem;
}
@media (prefers-reduced-motion: no-preference) {
  .brand__name {
    transition: opacity .4s var(--ease), translate .4s var(--ease), visibility .4s var(--ease);
  }
}
/* Fehlt die Bilddatei, trägt der Name allein, in jeder Lage. Das B,
   das sonst das Zeichen mitbringt, schreibt er sich dann selbst vor. */
.brand.is-empty .brand__mark { display: none; }
.brand.is-empty .brand__name { opacity: 1; visibility: visible; translate: none; }
.brand.is-empty .brand__name::before { content: "B"; }

.nav__links { display: flex; gap: clamp(1rem, 2.2vw, 2rem); }
.nav__links a { text-decoration: none; color: var(--fg-2); transition: color .2s var(--ease); }
.nav__links a:hover { color: var(--fg); }

.themeswitch { background: none; border: 0; padding: 0; cursor: pointer; color: var(--fg); }
.themeswitch__track {
  position: relative; display: flex; align-items: center; justify-content: space-between;
  width: 4.25rem; height: 2rem; padding: 0 .5rem;
  border: 1px solid var(--line);
}
.themeswitch__knob {
  position: absolute; top: 3px; left: 3px;
  width: calc(50% - 3px); height: calc(100% - 6px);
  background: var(--fg); opacity: .1;
  transition: transform .35s var(--ease);
}
.themeswitch[aria-pressed="true"] .themeswitch__knob { transform: translateX(100%); }
.themeswitch__ico { width: 1.05rem; height: 1.05rem; position: relative; opacity: .4; transition: opacity .3s var(--ease); }
.themeswitch[aria-pressed="false"] .themeswitch__ico--day,
.themeswitch[aria-pressed="true"]  .themeswitch__ico--night { opacity: 1; }

.nav__toggle { display: none; }

/* ---------- Titel: Titelseite ----------
   Das Foto trägt die ganze Fläche, der Satz steht darauf. Vorher stand
   das Bild als Kasten neben dem Text, das war ein Formular, kein
   Aufschlag.

   Der Verlauf über dem Foto ist kein Effekt, sondern die Bedingung
   dafür, dass heller Satz auf einem Foto lesbar bleibt. Er ist unten
   dicht und läuft nach oben aus, dort wo im Bild ohnehin nichts steht.
   ---------- */
.hero {
  position: relative;
  display: grid;
  /* Der Satz spannt über die ganze Höhe: Marke oben, Titel unten. */
  align-items: stretch;
  min-height: min(52rem, 91svh);
  background: #0b0c0d;
  color: #f2f1ee;
  overflow: clip;
}
.hero__bild { position: absolute; inset: 0; }
.hero__bild img {
  width: 100%; height: 100%;
  object-fit: cover;
  /* Der Wagen sitzt rechts der Mitte, unten links bleibt die Straße
     dunkel und frei für den Satz. */
  object-position: 62% 45%;
}
.hero__bild::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(
    to top,
    rgba(8, 9, 10, .90) 0%,
    rgba(8, 9, 10, .74) 30%,
    rgba(8, 9, 10, .34) 58%,
    rgba(8, 9, 10, .08) 100%
  );
}
.hero__satz {
  position: relative;
  display: grid;
  grid-template-rows: auto 1fr;
  gap: clamp(2rem, 5vw, 3.5rem);
  padding-block: clamp(2rem, 4vw, 3rem) clamp(2rem, 4vw, 3.25rem);
}
/* Die Marke als Titel der Ausgabe, oben rechts über dem Foto. Sie steht
   groß wie der Titel einer Zeitschrift, nicht wie ein Wasserzeichen. */
.hero__marke {
  height: clamp(4.25rem, 8vw, 7.5rem);
  width: auto;
  justify-self: end;
}
.hero__unten {
  align-self: end;
  display: grid;
  gap: clamp(1.5rem, 3vw, 2.5rem);
}
.hero__satz .t1 { max-width: 18ch; }
.hero__fuss {
  display: grid;
  gap: clamp(1.25rem, 2.5vw, 2rem);
  justify-items: start;
}
.hero__lead { color: rgba(242, 241, 238, .78); max-width: 34ch; }

@media (min-width: 860px) {
  .hero__fuss {
    grid-template-columns: minmax(0, 34ch) auto;
    align-items: end;
    justify-content: space-between;
    gap: clamp(2rem, 5vw, 5rem);
  }
}

/* Das Foto setzt sich beim Laden einmal, danach steht es still.
   Es lenkt den Blick auf das Bild, bevor der Satz gelesen wird. */
@media (prefers-reduced-motion: no-preference) {
  .js .hero__bild img {
    animation: aufschlag 1.4s var(--ease) both;
  }
  @keyframes aufschlag {
    from { transform: scale(1.06); opacity: .55; }
    to   { transform: none; opacity: 1; }
  }
}

/* ---------- Schaltflächen ---------- */
.btn {
  display: inline-block;
  font-size: var(--t4); font-weight: 500;
  letter-spacing: .16em; text-transform: uppercase; text-decoration: none;
  background: var(--fg); color: var(--bg);
  padding: .9rem 1.75rem; border: 1px solid var(--fg); cursor: pointer;
  transition: transform .15s var(--ease), opacity .2s var(--ease);
}
.btn:hover { opacity: .88; }
.btn:active { transform: translateY(1px); }
.btn--block { width: 100%; }
/* Auf dem Titelfoto ist der Grund immer dunkel, unabhängig von der
   Belichtung der Seite. Die Schaltfläche muss dort hell bleiben. */
.btn--hell { background: #f2f1ee; color: #101113; border-color: #f2f1ee; }
.btn[disabled] { opacity: .5; cursor: progress; }

.link {
  color: inherit; text-decoration: none;
  border-bottom: 1px solid var(--line); padding-bottom: 2px;
  transition: border-color .2s var(--ease);
}
.link:hover { border-color: var(--fg); }

/* ---------- Sektionen ---------- */
.works, .about, .services, .crews, .contact { padding-block: var(--sec); }
/* Die Leistungen enden mit einem Bildblock, der ohnehin unter dem Satz
   ausläuft, und die Werkschau fängt mit einer Überschrift an. Zweimal
   der volle Sektionsabstand wäre an dieser Naht ein Loch. */
.services { padding-bottom: clamp(3rem, 5vw, 4.5rem); }
.works { padding-top: clamp(3rem, 5vw, 4.5rem); }
.about, .services, .crews, .contact { border-top: 1px solid var(--line); }
.sec__head { margin-bottom: clamp(1.75rem, 3vw, 2.75rem); }

/* ---------- Werkschau: Zeilensatz wie im Magazin ----------
   Der Umbruch steht im Quelltext, nicht in einer Spaltenzahl: eine
   Zeile trägt ein, zwei oder drei Fotos. Ein Foto allein bekommt die
   ganze Breite und wird zum Aufschlag, drei nebeneinander sind der
   ruhige Grundtakt.

   Innerhalb einer Zeile teilt sich die Breite nach Seitenverhältnis:
   flex-grow ist das Verhältnis selbst, die Basisbreite null. Dadurch
   sind alle Fotos einer Zeile exakt gleich hoch und behalten trotzdem
   ihr Aufnahmeformat. Beschnitten wird nur, wer als Aufschlag steht.

   Blendet der Filter etwas aus, verteilen die übrigen Fotos die Breite
   unter sich neu. Die Zeile bleibt gesetzt, es entsteht keine Lücke.
   ---------- */
.works__head {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: 1rem 2rem;
  margin-bottom: clamp(1.75rem, 3vw, 2.75rem);
}
.filter { display: flex; gap: 1.25rem; }
.filter button {
  background: none; border: 0; padding: .25rem 0; cursor: pointer;
  font: inherit; color: var(--fg-2);
  border-bottom: 1px solid transparent;
  transition: color .2s var(--ease), border-color .2s var(--ease);
}
.filter button:hover { color: var(--fg); }
.filter button[aria-pressed="true"] { color: var(--fg); border-bottom-color: var(--fg); }

/* Die Wand liegt enger als der Rest der Seite. Zwischen zwei Fotos steht
   der Grundabstand, davon geht die Überlappung ab: die Ecken schieben
   sich übereinander wie bei Abzügen, die jemand hingelegt hat. Die
   Zeile bleibt trotzdem randvoll, weil das Überlappen ein negativer
   Rand ist und kein Verschieben. */
.wand {
  --luecke: clamp(1rem, 1.7vw, 1.5rem);
  display: flex; flex-direction: column; gap: calc(var(--luecke) * .85);
}
.reihe { display: flex; flex-direction: column; gap: var(--luecke); }

/* Auf dem Telefon zählt der Zeilentakt nicht mehr: dort stehen zwei
   Fotos nebeneinander, alle im selben Format. Die rechte Spalte sitzt
   tiefer, damit auch dort kein starres Raster steht. Der Fuß der Zeile
   fängt den Versatz wieder auf. */
@media (max-width: 699px) {
  /* Der Fuß der Zeile fängt nur einen Teil des Versatzes auf: die
     hängende Spalte reicht in die nächste Zeile hinein und legt sich
     dort über die Nachbarn. */
  .wand { gap: calc(var(--luecke) * .65); }
  .reihe {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: calc(var(--luecke) * .25);
    padding-bottom: calc(var(--versatz-mobil) * .45);
    width: auto;
    margin: 0;
  }
  /* Auf dem Telefon liegen die Abzüge schiefer und dichter beieinander:
     zwei Fotos in einer Zeile brauchen mehr Unterschied als vier, sonst
     sieht das Raster wieder wie ein Raster aus. Der Versatz der rechten
     Spalte steht in translate und nicht in transform: transform gehört
     der Bewegung beim Filtern. */
  /* Der Schub in der Höhe bleibt mobil aus: der Spaltenversatz soll
     immer in der unteren Hälfte des linken Bildes beginnen, gewürfelte
     Höhen dazu drückten ihn mal darüber, mal zu tief. */
  .wand { --fak: .9; --fak-y: 0; --kipp-fak: 1.7; }
  .shot { max-width: none; }
  .shot img { aspect-ratio: 3 / 2; }
  .reihe .shot:nth-child(even) { --versatz: var(--versatz-mobil); }
  /* Beide Spalten greifen zur Mitte hin übereinander: der Zug zieht
     jede Box über die Zellgrenze, die Außenkanten bleiben stehen. */
  .reihe .shot:nth-child(odd)  { margin-right: calc(var(--zug, .8rem) * -.7); }
  .reihe .shot:nth-child(even) { margin-left:  calc(var(--zug, .8rem) * -.7); }
}
/* Eine Zeile ohne sichtbares Foto darf keinen Abstand hinterlassen. */
.reihe:not(:has(.shot:not([hidden]))) { display: none; }

/* Abzüge auf dem Tisch: jeder liegt ein wenig schief, die Ecken schieben
   sich übereinander, darunter ein weicher Schatten. Kippung und Schub
   stehen in den eigenen Eigenschaften rotate und translate, nicht in
   transform. Das Schieben beim Filtern läuft über transform und käme
   sonst mit ihnen ins Gehege. */
.shot {
  display: block;
  padding: 0; border: 0; background: none; cursor: pointer;
  overflow: clip;
  position: relative;
  z-index: var(--lage, 1);
  rotate: calc(var(--kippe, 0deg) * var(--kipp-fak, 1));
  translate: calc(var(--schub-x, 0px) * var(--fak, 1))
             calc(var(--schub-y, 0px) * var(--fak-y, var(--fak, 1)) + var(--versatz, 0px));
  box-shadow: var(--schatten);
}
.shot img {
  width: 100%; height: 100%; aspect-ratio: var(--ar, 3 / 2); object-fit: cover;
  background: var(--bg-2);
}

/* Ausfallwerte ohne JavaScript: vier Lagen im Wechsel. Mit JavaScript
   würfelt jedes Foto seine Lage selbst (main.js), diese Regeln decken
   nur den Fall, dass kein Skript läuft. */
.reihe .shot:nth-child(4n + 1) { --kippe: -2.2deg; --schub-x:  .6rem; --schub-y:  .5rem;  --lage: 2; }
.reihe .shot:nth-child(4n + 2) { --kippe:  1.6deg; --schub-x: -.8rem; --schub-y: -.6rem;  --lage: 3; }
.reihe .shot:nth-child(4n + 3) { --kippe: -1.2deg; --schub-x:  .5rem; --schub-y:  .75rem; --lage: 1; }
.reihe .shot:nth-child(4n + 4) { --kippe:  2.6deg; --schub-x: -.7rem; --schub-y: -.35rem; --lage: 4; }
/* Steht ein Foto am Zeilenende auf einer ungeraden Stelle, würde sein
   Schub nach rechts über den Satzspiegel laufen. */
.reihe .shot:last-child:nth-child(odd) { --schub-x: 0; }

/* Angefasst richtet sich der Abzug gerade und kommt nach vorn: eine
   Bewegung, kein Sprung. Der Schub bleibt, wo er ist, sonst zöge das
   Foto zweimal. */
.shot:hover, .shot:focus-visible,
.srv__foto:hover, .srv__foto:focus-visible {
  --lage: 6;
  rotate: 0deg;
  scale: 1.03;
  box-shadow: var(--schatten-hoch);
}
@media (prefers-reduced-motion: no-preference) {
  .shot, .srv__foto {
    transition:
      rotate var(--weich-zeit) var(--weich),
      scale var(--weich-zeit) var(--weich),
      box-shadow var(--weich-zeit) var(--weich);
  }
}
.shot:focus-visible, .srv__foto:focus-visible { outline: 2px solid var(--fg); outline-offset: 4px; }

@media (min-width: 700px) {
  /* Auch die Zeilen liegen nicht auf einer Kante: jede rückt gewürfelt
     ein Stück zur Seite und hebt oder senkt sich (main.js). Über Ränder
     statt translate: translate machte die Zeile zum eigenen
     Stapelkontext, und die Lagen der Abzüge gälten nur noch innerhalb
     ihrer Zeile. */
  .reihe { flex-direction: row; margin-left: var(--reihe-x, 0); margin-top: var(--reihe-y, 0); }
  /* Basisbreite null, Wachstum nach Seitenverhältnis: gleiche Höhe je Zeile. */
  .shot { flex: var(--ar) 1 0; width: 0; }

  /* Der Zug: jedes Foto ab dem zweiten schiebt sich um sein gewürfeltes
     Stück unter den Vorgänger. Ein negativer Rand, kein Verschieben,
     darum bleibt die Zeile randvoll und die Fotos wachsen um den
     gewonnenen Platz. Die Lücke der Zeile ist klein, sonst zehrt sie
     den Zug wieder auf und die Kanten berühren sich bloß. */
  .reihe { gap: calc(var(--luecke) * .35); }
  .reihe .shot + .shot { margin-left: calc(var(--zug, 0px) * -1); }

  /* Jedes Foto behält höchstens seinen Platz in der Zeile. Ohne diese
     Deckel würde ein Foto, das nach dem Filtern allein in seiner Zeile
     steht, auf die ganze Breite wachsen und alles erschlagen. Das
     Polster von 1rem deckt den Platz, den der Zug freigibt, sonst
     endeten volle Zeilen vor dem rechten Rand. */
  .reihe--vier .shot { max-width: calc(25% - var(--luecke) * 0.75 + 1rem); }
  .reihe--drei .shot { max-width: calc((100% - var(--luecke) * 2) / 3 + 1rem); }
  .reihe--paar .shot { max-width: calc((100% - var(--luecke)) / 2 + 1rem); }

  /* Das Paar steht schmaler und wechselt die Seite. Der freie Rand ist
     der Weißraum, den ein Magazin an dieser Stelle hätte. */
  .reihe--paar { width: 84%; }
  .reihe--rechts { margin-left: auto; }
}

/* ---------- Über mich ---------- */
.about__inner { display: grid; gap: clamp(1.75rem, 4vw, 3.5rem); }
.about__img img { width: 100%; aspect-ratio: 4 / 5; object-fit: cover; background: var(--bg-2); }
.about__text { display: grid; gap: 1.5rem; align-content: center; justify-items: start; }
.about__text p { max-width: 42ch; }
/* Initial wie im Zeitschriftensatz: der erste Buchstabe trägt die
   Aufmerksamkeit in den Text hinein. */
.about__auftakt::first-letter {
  float: left;
  font-size: 3em;
  line-height: .8;
  font-weight: 800;
  padding: .1em .1em 0 0;
}
@media (min-width: 860px) {
  .about__inner {
    /* Das Bild steht links vor dem Satz, aber nicht an der Fensterkante:
       ein Abzug braucht Rand, sonst klebt er am Blattschnitt. */
    max-width: none;
    padding-left: clamp(1.5rem, 3vw, 3rem);
    padding-right: max(var(--pad), calc((100vw - 1360px) / 2 + var(--pad)));
    grid-template-columns: minmax(0, 40vw) 1fr;
    gap: clamp(2.5rem, 6vw, 6rem);
    align-items: center;
  }
  .about__img img { aspect-ratio: 5 / 6; }
}

/* ---------- Leistungen: drei Zeilen, eine Linie dazwischen ---------- */
.srv__inner { display: grid; gap: clamp(2rem, 5vw, 4rem); }
.srv__satz { display: grid; gap: clamp(2rem, 3.5vw, 3rem); align-content: center; }

/* Register statt Aufzählung: links der Name in der Auszeichnung,
   rechts der Satz dazu. Eine Linie steht über jedem Eintrag, nicht
   unter jedem. Zwei Linien pro Zeile ergeben ein Datenblatt. */
.srv__liste { margin: 0; display: grid; }
.srv__liste > div {
  display: grid;
  gap: .5rem;
  padding-block: clamp(1.25rem, 2.5vw, 1.75rem);
  border-top: 1px solid var(--line);
}
.srv__liste > div:last-child { padding-bottom: 0; }
.srv__liste dt { align-self: start; padding-top: .2em; }
.srv__liste dd { margin: 0; color: var(--fg-2); max-width: 42ch; }

@media (min-width: 560px) {
  .srv__liste > div {
    grid-template-columns: minmax(0, 11rem) minmax(0, 1fr);
    gap: clamp(1rem, 3vw, 2.5rem);
    align-items: baseline;
  }
}
/* Drei Abzüge, wie hingelegt: zwei rechts untereinander, das
   Hochformat schiebt sich von links darüber. Sie überlappen nur an den
   Ecken, jedes Motiv bleibt lesbar. Das Raster darunter bleibt streng,
   die negativen Ränder rechnen gegen die Spaltenbreite und halten den
   Aufbau in jeder Fenstergröße gleich. */
.srv__stapel {
  display: grid;
  grid-template-columns: minmax(0, 40fr) minmax(0, 60fr);
  column-gap: clamp(.5rem, 1.1vw, .875rem);
}
.srv__foto {
  display: block; width: 100%; aspect-ratio: 3 / 2;
  padding: 0; border: 0; cursor: pointer; overflow: clip;
  background: var(--bg-2); align-self: start;
  box-shadow: var(--schatten);
  position: relative;
  z-index: var(--lage, 1);
  rotate: var(--kippe, 0deg);
  translate: var(--schub-x, 0) var(--schub-y, 0);
}
.srv__foto img { width: 100%; height: 100%; object-fit: cover; }
/* Kippung und Schub liegen in rotate und translate, nicht im Raster:
   die Zeilen bleiben so berechenbar, die Abzüge legen sich nur
   darüber. Das Hochformat liegt obenauf und deckt von den beiden
   anderen je eine Ecke. */
.srv__foto--a { grid-area: 1 / 2; --kippe: -1.1deg; --schub-x:  1%; --schub-y: -.25rem; --lage: 1; }
.srv__foto--b { grid-area: 2 / 1; aspect-ratio: 4 / 5; margin-top: -50%;
                --kippe: 1.5deg;  --schub-x: 14%; --lage: 3; }
.srv__foto--c { grid-area: 3 / 2; margin-top: -46%;
                --kippe: .8deg;   --schub-x: -2%; --schub-y: -1.5rem; --lage: 2; }
@media (prefers-reduced-motion: no-preference) {
  /* Nur das Einblenden ist versetzt. Ein transition-delay griffe auch
     beim Anfassen, und der Abzug hinge dem Zeiger hinterher. */
  .js .srv__foto--b { --reveal-verz: .12s; }
  .js .srv__foto--c { --reveal-verz: .24s; }
}
@media (min-width: 860px) {
  .srv__inner {
    max-width: none;
    /* Kein Anschnitt mehr: ein Abzug, der schief liegt, braucht seine
       vier Ecken. Der Rand rechts ist der Rand des Tisches. */
    padding-right: clamp(1.5rem, 3vw, 3rem);
    padding-left: max(var(--pad), calc((100vw - 1360px) / 2 + var(--pad)));
    grid-template-columns: 1fr minmax(0, 44vw);
    gap: clamp(2.5rem, 5vw, 5rem);
    align-items: center;
  }
}

/* ---------- Communitys ---------- */
.crews__inner { display: grid; gap: 1.5rem; align-items: center; }
.crews__head { color: var(--fg-2); }
.crews__list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: clamp(1.5rem, 5vw, 4rem); }
.crews__list a {
  display: flex; align-items: center; gap: .85rem;
  text-decoration: none; opacity: .82; transition: opacity .25s var(--ease);
}
.crews__list a:hover { opacity: 1; }
.crews__list img { height: 2.25rem; width: auto; filter: var(--logo-flip); }
.crews__list a.is-empty img { display: none; }
/* Ein Zeichen braucht kein Etikett daneben. Die Wortmarke erscheint
   nur, wenn die Bilddatei fehlt. */
.marke__ersatz { display: none; font-weight: 700; }
.crews__list a.is-empty .marke__ersatz { display: block; }
@media (min-width: 760px) {
  .crews__inner { grid-template-columns: auto 1fr; gap: clamp(2rem, 6vw, 5rem); }
}

/* ---------- Kontakt ---------- */
.contact__inner { display: grid; gap: clamp(2.5rem, 5vw, 4.5rem); }
.contact__intro { display: grid; gap: 1.5rem; align-content: start; }
.contact__meta { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; justify-items: start; }
@media (min-width: 860px) {
  .contact__inner { grid-template-columns: 1fr 1fr; }
}

.form { display: grid; gap: 1.25rem; }
.field { display: grid; gap: .5rem; }
.field label { color: var(--fg-2); }
.field input, .field textarea {
  font-family: 'Archivo', system-ui, sans-serif; font-size: var(--t3);
  color: var(--fg); background: var(--bg-2);
  border: 1px solid var(--line); border-radius: 0;
  padding: .8rem 1rem; width: 100%;
  transition: border-color .2s var(--ease);
}
.field input:focus, .field textarea:focus { outline: none; border-color: var(--fg); }
.field textarea { resize: vertical; min-height: 8rem; line-height: 1.6; }
.field__hint { color: var(--fg-2); }
.field__error { color: var(--fg); font-weight: 500; }
.field.is-invalid input, .field.is-invalid textarea { border-color: var(--fg); border-width: 2px; }

.field--check { grid-template-columns: auto 1fr; align-items: start; gap: .75rem; }
.field--check input { width: 1.1rem; height: 1.1rem; margin-top: .3rem; accent-color: var(--fg); }
.field--check .field__error { grid-column: 1 / -1; }

.hp { position: absolute; left: -9999px; }
.form__status { color: var(--fg-2); }
.form__status.is-ok, .form__status.is-error { color: var(--fg); }

/* ---------- Fußzeile ---------- */
.foot { border-top: 1px solid var(--line); padding-block: clamp(2.5rem, 5vw, 4rem); }
.foot__inner { display: grid; gap: 1.5rem; align-items: center; }
.foot__links { display: flex; flex-wrap: wrap; gap: 1.5rem; }
.foot__links a { text-decoration: none; color: var(--fg-2); transition: color .2s var(--ease); }
.foot__links a:hover { color: var(--fg); }
.foot__copy { color: var(--fg-2); }
@media (min-width: 760px) {
  .foot__inner { grid-template-columns: auto 1fr auto; gap: clamp(2rem, 6vw, 4rem); }
}

/* ---------- Bildansicht ----------
   Ein Foto über der ganzen Fläche, dunkel, weil dort das Foto trägt
   und nicht die Oberfläche.

   Ein Bild wird nie größer als das Fenster. Dafür braucht es alle drei:
   der Dialog steht auf position fixed mit inset 0 statt auf 100vh, er
   scrollt selbst nicht, und das Bild liegt absolut im Rahmen, damit die
   Bezugsgröße für max-height sicher definiert ist. ---------- */
.lb {
  display: none;
  position: fixed; inset: 0;
  width: auto; height: auto; max-width: none; max-height: none;
  margin: 0; border: 0; padding: 0;
  background: #0b0c0d; color: #f2f1ee;
  grid-template-rows: auto minmax(0, 1fr);
  overflow: hidden;
  overscroll-behavior: contain;
  --line: rgba(242, 241, 238, .16);
  --fg-2: rgba(242, 241, 238, .68);
}
.lb[open] { display: grid; }
.lb::backdrop { background: rgba(8, 9, 10, .95); }

/* Beim Öffnen wird der Abzug angehoben: das Foto wächst aus seiner
   Lage in der Seite heraus (in main.js gerechnet), der Grund und der
   Rahmen blenden dahinter auf. Käme die schwarze Fläche sofort, wäre
   das Foto im ersten Bild verschwunden statt vergrößert. */
@keyframes lb-grund { from { background-color: rgba(11, 12, 13, 0); } }
@keyframes lb-auf   { from { opacity: 0; } }
@keyframes lb-weg   { to { background-color: rgba(11, 12, 13, 0); } }
@keyframes lb-aus   { to { opacity: 0; } }
@media (prefers-reduced-motion: no-preference) {
  .lb[open] { animation: lb-grund .45s var(--ease) both; }
  .lb[open]::backdrop { animation: lb-auf .45s var(--ease) both; }
  .lb[open] .lb__bar,
  .lb[open] .lb__nav,
  .lb[open] .lb__stage figcaption { animation: lb-auf .4s var(--ease) .2s both; }

  /* Zurücklegen: es landet die Kachel in der Seite (main.js), nicht das
     Dialogbild. Der Grund des Dialogs muss darum sofort weichen, er ist
     deckend und stünde vor dem fliegenden Abzug. Der Backdrop klart
     schnell auf, damit das Aufsetzen unter den Nachbarn im Hellen
     passiert. */
  .lb[open].lb--zu { animation: lb-weg .15s var(--ease) both; }
  .lb[open].lb--zu::backdrop { animation: lb-aus .3s var(--ease) both; }
  .lb[open].lb--zu .lb__bar,
  .lb[open].lb--zu .lb__nav,
  .lb[open].lb--zu .lb__stage figcaption { animation: lb-aus .15s var(--ease) both; }
}

.lb__bar {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: .75rem clamp(.75rem, 2.5vw, 1.25rem);
  border-bottom: 1px solid var(--line);
}
.lb__count { margin: 0; color: var(--fg-2); }

.lb__stage {
  margin: 0; min-height: 0;
  display: grid; grid-template-rows: minmax(0, 1fr) auto; gap: 1rem;
  padding: clamp(1rem, 2vw, 1.5rem) clamp(1rem, 7vw, 5.5rem) clamp(1rem, 2.5vw, 1.75rem);
}
.lb__frame { position: relative; min-height: 0; min-width: 0; }
.lb__frame img {
  position: absolute; inset: 0; margin: auto;
  max-width: 100%; max-height: 100%;
  width: auto; height: auto;
}
.lb__stage figcaption { color: var(--fg-2); text-align: center; }

.lb__close, .lb__nav {
  display: grid; place-items: center;
  background: none; border: 1px solid var(--line);
  color: inherit; cursor: pointer; line-height: 1;
  transition: border-color .2s var(--ease), background-color .2s var(--ease);
}
.lb__close:hover, .lb__nav:hover { border-color: currentColor; }
.lb__close:focus-visible, .lb__nav:focus-visible { outline: 2px solid #f2f1ee; outline-offset: 3px; }
.lb__close { width: 2.5rem; height: 2.5rem; font-size: var(--t3); flex: none; }
.lb__nav {
  position: fixed; top: 50%; transform: translateY(-50%);
  width: 3.25rem; height: 3.25rem; font-size: var(--t3);
}
.lb__nav--prev { left: clamp(.5rem, 2vw, 1.5rem); }
.lb__nav--next { right: clamp(.5rem, 2vw, 1.5rem); }

/* ---------- Fehlende Bilder als saubere Fläche ---------- */
.is-empty img { display: none; }

/* ---------- Rechtstexte ---------- */
.legal { max-width: 46rem; padding-block: clamp(3rem, 7vw, 5rem) var(--sec); }
.legal h1 { font-size: var(--t2); font-stretch: 104%; }
.legal h2 { margin-top: 2.75rem; margin-bottom: .5rem; font-size: var(--t3); font-weight: 700; }
.legal p { margin-top: .85rem; color: var(--fg-2); }
.legal a { color: var(--fg); }
.todo { border-bottom: 1px dashed var(--fg-2); }
.legal__back { margin-top: 3.5rem; }

/* ---------- Telefon ---------- */
@media (max-width: 759px) {
  .nav__links { display: none; }
  .nav__toggle {
    display: grid; gap: 5px; align-content: center; justify-items: center;
    width: 2.5rem; height: 2.5rem; padding: 0;
    background: none; border: 0; cursor: pointer;
  }
  .nav__toggle span { display: block; height: 1px; width: 1.5rem; background: var(--fg); }
  .nav__mobile {
    display: grid; gap: .25rem;
    background: var(--bg); border-bottom: 1px solid var(--line);
    padding: 1rem var(--pad) 1.5rem;
  }
  .nav__mobile a { text-decoration: none; padding: .5rem 0; }
  /* Auf dem Telefon gehören die 60px Seitenrand dem Bild, nicht den
     Pfeilen. Die legen sich stattdessen über den unteren Bildrand,
     dicht genug am Daumen und weit genug von der Bildunterschrift. */
  .lb__stage { padding-inline: 1rem; }
  .lb__nav {
    width: 2.75rem; height: 2.75rem;
    top: auto; bottom: 5.75rem; transform: none;
    background: color-mix(in srgb, #0b0c0d 55%, transparent);
    backdrop-filter: blur(6px);
  }
  .lb__nav--prev { left: 1rem; }
  .lb__nav--next { right: 1rem; }
}
