@font-face {
    font-family: 'PPMori';
    src: url('/PPMori-Regular.woff2') format('woff2'), url('/PPMori-Regular.woff') format('woff');
    font-weight: 400; font-style: normal; font-display: swap;
}

/* ============================================================
   SERAPHIN ERNST
   Aufbau nach dem Vorbild neoneo.ch:
   Weiss, PP Mori, 1px-Haarlinien, Kachelhoehe aus dem
   Bildverhaeltnis. Vier Spalten ab 768px, darunter eine.
   ============================================================ */

html {
    font-size: 18px;
    /* Der Balken bleibt immer reserviert. Sonst wird die Seite beim
       Wechsel auf Index oder About - die kurz genug sind, um nicht zu
       rollen - um die Balkenbreite breiter, und die vier Spalten des
       Kopfes wandern mit: bei 15px Balken rueckt "Index" um 3.75px
       und "About" um 7.5px nach rechts. */
    overflow-y: scroll;
    scrollbar-gutter: stable;
}

:root {
    /* Von app.js aus den tatsaechlichen Leistenhoehen gesetzt. */
    --header-h: 40px;
    --footer-h: 36px;
    /* Hoehe der Einwilligungsleiste, von einwilligung.js gesetzt und
       beim Verschwinden auf Null zurueckgestellt. Muss hier stehen und
       eine Laenge sein: in calc() ist eine leere Variable ein Fehler,
       und die ganze Regel fiele aus. */
    --consent-h: 0px;

    /* Ein einziges Mass, verdoppelt. Jeder Abstand im Blatt ist eines
       dieser vier - keine krummen Zwischenwerte. */
    --s1: 0.2rem;   /*  3.6px */
    --s2: 0.4rem;   /*  7.2px */
    --s4: 0.8rem;   /* 14.4px */
    --s8: 1.6rem;   /* 28.8px */

    /* --rand ist die Textachse: der Abstand jedes Textes von seiner
       Spaltenlinie, auf jeder Ansicht und in jeder Breite derselbe.
       Eine Pille wird um --pille-versatz nach links gezogen: um ihre
       eigene Polsterung samt Rahmenstaerke. Ihre Schrift faellt dadurch
       auf genau dieselbe Achse wie jeder andere Text, der Rahmen legt
       sich nur darum herum und haengt in den Rand hinein. */
    --rand: var(--s4);
    /* Der weisse Rahmen um ein Deckblatt im Raster. Im Studio je
       Projekt abschaltbar - der Schalter steht am Deckblatt selbst:
       app.js haengt dann .ohne-deckblatt-rand an die einzelne Kachel
       und dieses Mass wird dort zu Null. Die Vorgabe steht hier, ein
       Projekt ohne die Angabe behaelt den Rahmen also. */
    --deckblatt-rand: var(--rand);
    /* Die weisse Fuge zwischen zwei Inhalten - waagerecht wie
       senkrecht, am Rechner wie am Handy derselbe Wert. */
    --fuge: var(--s2);
    --pille-x: var(--s2);
    --pille-y: var(--s1);
    --pille-versatz: calc(var(--pille-x) + 1px);
    --pille-versatz-y: calc(var(--pille-y) + 1px);
    /* Durchmesser des Plus-Knopfs auf den Kacheln. Nur der Rueckfall -
       den wirklichen Wert misst measureChrome() an einer Pille. */
    --pille-hoehe: 1.7rem;

    /* DREI SCHRIFTBILDER, MEHR GIBT ES AUF DER GANZEN SEITE NICHT.
       Ein Schnitt (PP Mori Regular), ein Grad im Fliesstext; die
       Rangfolge entsteht aus der Stellung im Raster, nicht aus Grad
       oder Fett. Die drei:
         1 Fliesstext  1rem            / 115%   - jeder Text
         2 Etikett     1rem            / normal - Pille, .underline
         3 Beischrift  --schrift-klein / normal - die Entwurfsmarke
       Zwei und eins unterscheiden sich nur in der Zeilenhoehe: wer
       einen Rahmen oder eine Unterlaenge traegt, braucht 'normal',
       sonst waechst das Element beim Ueberfahren um 1px.
       Ein vierter Grad kam bis hierher nur einmal vor - die Titel im
       Index. Er stand in rem und sprang dadurch nicht ueber zwei,
       sondern ueber drei Stufen (36 / 63 / 77px), weil ab 1200px auch
       die Grundschrift waechst. Der Index laeuft jetzt im Fliesstext. */
    --schrift-klein: 0.8rem;
}

*, *::before, *::after { box-sizing: border-box; }

body {
    margin: 0;
    background: #fff;
    color: #000;
    font-family: 'PPMori', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    font-size: 1rem;
    line-height: 115%;
    /* Ein langes Wort darf die Spalte nicht aufweiten - sonst waeren
       die vier Spalten nicht mehr gleich breit und die Achsen wandern.
       Deshalb steht bei jedem Raster auch minmax(0, 1fr) statt 1fr. */
    overflow-wrap: break-word;
    -webkit-font-smoothing: antialiased;
    -webkit-tap-highlight-color: transparent;
}

img, video { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
ul, ol { list-style: none; margin: 0; padding: 0; }
p { margin: 0; }
[hidden] { display: none !important; }

/* Unterstrichenes Etikett - das einzige Auszeichnungsmittel im Fliesstext. */
.underline { border-bottom: 1px solid #000; line-height: normal; }

/* ============================================================
   PILLE
   Die Schaltflaechen in Kopf- und Fusszeile. Der Rahmen steht
   dauerhaft, beim Ueberfahren und im aktiven Zustand fuellt er sich
   schwarz - es aendern sich nur zwei Farben, nie eine Groesse.
   ============================================================ */
.pill {
    display: inline-block;
    margin-left: calc(-1 * var(--pille-versatz));
    padding: var(--pille-y) var(--pille-x);
    border: 1px solid #000;
    border-radius: 100px;
    line-height: normal;
    white-space: nowrap;
    transition: background-color 120ms linear, color 120ms linear;
}
/* Die Grundregel der ganzen Seite - sie steht nur hier und haengt an
   der Auszeichnung, nicht an einer zusaetzlichen Klasse. Eine kuenftige
   Pille bekommt ihr Aussehen dadurch von selbst.

     a / button   durchgezogener Rand   -> hier kann man hin
                  gefuellt beim Ueberfahren und im aktiven Zustand
     span         gestrichelter Rand    -> das ist eine Auskunft
                  keine Fuellung, keine Rueckmeldung, nie

   Deshalb steht vor jeder Regel unten a bzw. button: eine Pille, die
   nur Auskunft gibt, darf auf nichts reagieren. */
a.pill:hover, button.pill:hover,
a.pill.is-active, button.pill.is-active {
    background: #000;
    color: #fff;
}

.pill:not(a):not(button) {
    border-style: dashed;
}

/* ============================================================
   KOPFZEILE
   ============================================================ */
.header {
    position: fixed; top: 0; left: 0; right: 0; z-index: 100;
    background: #fff;
    border-bottom: 1px solid #000;
}
.menu-top {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    align-items: baseline;
    /* Auch nach oben und unten steht die Schrift der Pille --rand vom
       Rand der Leiste - dieselbe Rechnung wie zur Seite. */
    padding: calc(var(--rand) - var(--pille-versatz-y)) 0;
}
/* Jeder Eintrag beginnt linksbuendig an seiner eigenen Rasterachse:
   Name in Spalte 1, Index in Spalte 2, About in Spalte 3. Spalte 4
   bleibt frei. */
.menu-top-logo { padding-left: var(--rand); }
.menu-top-list {
    grid-column: 2 / -1;
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    align-items: baseline;
}
.menu-top-list li { padding-left: var(--rand); }

/* ============================================================
   RASTER
   ============================================================ */
.grid-wrapper {
    position: relative;
    width: 100%;
    padding-top: var(--header-h);
    padding-bottom: calc(var(--footer-h) + var(--consent-h));
    overflow: hidden;
    /* Das Raster ist eine feste Flaeche: weder Aufziehen mit zwei
       Fingern noch Doppeltippen zoomt hier die Seite. Nur hier - auf
       den Projektseiten bleibt beides, dort sind die Plaene darauf
       angewiesen.
       "pan-y" laesst genau eine Geste uebrig, das senkrechte Rollen.
       Waagerecht rollt das Raster ohnehin nicht.
       Nicht "manipulation": das nimmt nur das Doppeltippen und laesst
       das Aufziehen stehen.
       Die Wirkung reicht ueber die ganze Kette nach unten - die
       Kacheln selbst brauchen die Angabe nicht, ihre berechnete
       Formatierung zeigt weiter "auto". Der Browser verrechnet den Wert
       eines Elements mit dem seiner Vorfahren.
       Was bewusst erreichbar bleibt: Kopf- und Fusszeile liegen
       ausserhalb dieses Kastens, ein Aufziehen von dort zoomt die Seite
       weiterhin - und auf jeder anderen Ansicht ohnehin. Sonst kaeme
       niemand mit schwachen Augen an die Seite heran.
       Nebenwirkung, die man mitnimmt: die 300ms Verzoegerung vor dem
       Antippen faellt weg, die Kacheln reagieren sofort. */
    touch-action: pan-y;
}
.grid-line {
    position: fixed;
    top: var(--header-h);
    bottom: calc(var(--footer-h) + var(--consent-h));
    width: 1px;
    background: #000;
    z-index: 99;
    pointer-events: none;
}
.grid-line--left { left: calc(25% - 1px); }
.grid-line--center { left: calc(50% - 1px); }
.grid-line--right { left: calc(75% - 1px); }

.grid-content { position: relative; width: 100%; }

/* Kachelhoehe folgt dem Bildverhaeltnis - die Bilder werden nie
   verzerrt und das Raster bleibt trotzdem buendig.
   Die Hoehe kommt aus der tatsaechlichen Breite des Deckblatts
   (aspect-ratio), nicht aus einer vw-Rechnung: vw zaehlt den
   reservierten Rollbalken mit, die Breite in Prozent nicht. Aus dieser
   Differenz entstand ein Fehler im Seitenverhaeltnis, den object-fit
   als Anschnitt am Bild auffing - am Rechner 1%, bei zwei Spalten am
   Handy 4.7%.
   Das Verhaeltnis sitzt am Deckblatt, nicht mehr an der Kachel: die
   Kachel ist um ihren weissen Rahmen hoeher als das Bild darin. */
.tile {
    position: absolute; top: 0; left: 0;
    display: flex;
    width: 25%;
    border-top: 1px solid #000;
    border-bottom: 1px solid #000;
    margin-top: -1px;
}
/* Erst nach der ersten Anordnung animieren. Sonst wandern beim
   Seitenaufbau alle Kacheln sichtbar von der linken oberen Ecke an
   ihren Platz. Waehrend eines Groessenwechsels ebenfalls aus. */
.grid-content.is-animated .tile {
    transition: left 350ms ease, top 350ms ease, opacity 200ms ease;
}
/* Der Verweis deckt die ganze Kachel ab. Die Angaben darueber nehmen
   keine Zeigereingaben an - sonst fuehrte ein Tippen auf sie nirgendwo
   hin, obwohl die Kachel ein Verweis ist. */
/* Das Deckblatt steht nicht mehr Kante an Kante, sondern in einem
   gleichmaessigen weissen Rahmen: --rand auf allen vier Seiten, also
   dasselbe Mass, auf dem auch jeder Text der Seite steht. Links faellt
   die Bildkante damit auf die Textachse der Spalte, rechts auf die
   naechste Rasterlinie, oben und unten auf die Haarlinie der Kachel.
   Der Rahmen kommt als Aussenabstand der Flaeche, nicht als Polsterung
   der Kachel: die Kachel wird dadurch um zweimal --rand hoeher, das
   Bildverhaeltnis wandert von der Kachel auf das Deckblatt und bleibt
   unangetastet. Ein Rahmen innerhalb der alten Kachelhoehe haette das
   Bild statt dessen um denselben Betrag angeschnitten.
   Die Hoehe der Kachel misst layoutGrid (app.js) am fertigen Element
   - der hoehere Kasten ordnet sich also von selbst richtig ein. */
.tile__flaeche {
    position: relative; display: flex; flex: 1;
    margin: var(--deckblatt-rand);
    /* Ohne diese Null nimmt die Flaeche als Kind eines flex-Kastens
       nie weniger Breite an, als ihr Bild von Natur aus misst - bei
       zwei Spalten am Handy schob sie sich dadurch ueber die
       Rasterlinie hinaus. */
    min-width: 0;
}
.tile__img {
    position: relative;
    width: 100%; height: auto;
    aspect-ratio: var(--image-ratio);
    object-fit: cover;
}
.tile__hover {
    position: absolute; top: 0; left: 0;
    width: 100%;
    padding: var(--rand);
    z-index: -1;
    opacity: 0;
    pointer-events: none;
    transition: opacity 150ms;
}
/* Die Ersatzkachel traegt ihre Polsterung nur, solange kein Rahmen um
   sie steht: die Schrift soll auf der Textachse stehen, und ob der
   Abstand dorthin vom Rahmen oder von der Polsterung kommt, ist
   gleichgueltig - beides zusammen waere das doppelte Mass. Die
   Rechnung gibt --rand, wenn der Rahmen aus ist, und Null, wenn er
   steht. */
.tile__fallback {
    display: flex; flex-direction: column; gap: var(--s2);
    width: 100%; height: auto;
    aspect-ratio: var(--image-ratio);
    padding: calc(var(--rand) - var(--deckblatt-rand));
}
.tile.is-hidden { opacity: 0; pointer-events: none; }

/* Deckblaetter, die kein Bild sind. Beide fuellen die Kachel wie ein
   Bild und nehmen keine Zeigereingaben an - sonst waere die Kachel
   kein Verweis mehr. */
.tile__pdf { position: relative; width: 100%; height: auto; aspect-ratio: var(--image-ratio); overflow: hidden; }
.tile__pdf iframe { width: 100%; height: 100%; border: 0; pointer-events: none; }
.tile__model { width: 100%; height: auto; aspect-ratio: var(--image-ratio); pointer-events: none; --poster-color: transparent; }

/* Ersatzkachel ohne Deckblatt: Kategorie und Titel, sonst nichts -
   beide untereinander, damit sie auf derselben Achse stehen. */

.grid-empty, .grid-error { padding: var(--rand); }
.grid-error span { display: block; }

/* ============================================================
   INDEX
   Eine gewoehnliche Auflistung im selben Vierer-Raster wie Kopf-
   und Kategorienleiste: Titel auf Achse 1, Kategorie auf Achse 2,
   Spalte 3 und 4 bleiben frei - genau wie Spalte 4 der Kopfzeile.
   Keine laufende Nummer: sie braeuchte eine eigene Achse und
   wiederholt nur die Reihenfolge der Zeilen.
   ============================================================ */
.list-wrapper, .info-wrapper {
    width: 100%;
    padding-top: var(--header-h);
    padding-bottom: calc(var(--footer-h) + var(--consent-h));
}
.list li { border-top: 1px solid #000; }
/* Die Kopfzeile traegt ihre eigene Linie. Bekaeme die erste Zeile
   noch eine, staenden am oberen Rand zwei Haarlinien uebereinander -
   die Kachelansicht loest dasselbe mit margin-top: -1px. */
.list li:first-child { border-top: 0; }
.list li:last-child { border-bottom: 1px solid #000; }
/* Dieselben vier Spalten und dieselbe senkrechte Polsterung wie in
   Kopf- und Kategorienleiste: die Schrift der Pille steht damit auch
   nach oben und unten --rand von der Zeilenkante. */
.list a {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    align-items: baseline;
    padding: calc(var(--rand) - var(--pille-versatz-y)) 0;
}
.list__title { padding-left: var(--rand); }
/* Das Jahr steht auf der Achse der dritten Spalte, die bisher leer
   blieb. Es ist beim Ueberfahren zu sehen und sonst nicht - dieselbe
   Geste wie im Raster, wo das Deckblatt aus- und die Angaben
   einblenden, und dieselben 150ms. Die Zeile aendert dabei ihre
   Hoehe nicht: das Jahr belegt sein Feld auch unsichtbar. */
.list__jahr {
    padding-left: var(--rand);
    opacity: 0;
    transition: opacity 150ms;
}
/* Nur wo es einen Zeiger gibt. Am Handy wird nicht ueberfahren, dort
   bliebe das Jahr fuer immer unsichtbar - deshalb steht es unten im
   Handy-Block dauerhaft. */
@media (hover: hover) {
    .list li:hover .list__jahr,
    .list a:focus-visible .list__jahr { opacity: 1; }
}
/* Als Rasterfeld wuerde die Pille sonst ueber die ganze Spalte
   gezogen - dieselbe Rechnung wie bei der Kategorienleiste. */
.list__marke {
    justify-self: start;
    margin-left: calc(var(--rand) - var(--pille-versatz));
}

/* ============================================================
   ÜBER
   Vier Spalten, vier Abschnitte: Kontakt, Software, Sprachen,
   Praxis. Jeder beginnt auf seiner eigenen Rasterlinie.
   ============================================================ */
/* Die About-Seite ist ein Kasten ueber die volle Hoehe: Zeile oben,
   Spalten darunter, Fuss ganz unten. Ohne die Mindesthoehe saesse der
   Fuss dicht unter den Spalten und mitten auf der Seite - der Inhalt
   fuellt das Fenster nicht aus. */
.info-wrapper {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}
.info-spalten {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    align-items: start;
}
/* margin-top: auto schiebt den Fuss an die Unterkante, egal wie kurz
   der Inhalt darueber ausfaellt. Rechtsbuendig: die Angaben gehoeren
   nicht zu einer der vier Spalten, sondern zur Seite. */
.info-fuss {
    margin-top: auto;
    display: flex;
    justify-content: flex-end;
    align-items: baseline;
    gap: var(--s2);
    padding: var(--rand);
}
/* Die rechte Pille zieht ihren Versatz nach rechts statt nach links -
   ihr Rand faellt damit auf dieselbe Linie wie der Text der Spalte
   darueber. Dieselbe Rechnung wie im Navigationsfuss des Projekts. */
.info-fuss > :last-child { margin-left: 0; margin-right: calc(-1 * var(--pille-versatz)); }
.info-col { padding: var(--rand); }
.line { padding-bottom: var(--s1); }

/* Kontakt und Profil sind nur Schaltflaechen - untereinander, jede
   auf der Achse. Die Adresse darf umbrechen und nie breiter werden
   als ihre Spalte: sonst haengt sie bei schmalen Fenstern oder einer
   laengeren Adresse in die naechste Spalte hinein. Gebrochen wird nur
   an der Stelle, die about-view.js dafuer vorsieht - hinter dem @. */
.kontakt { display: flex; flex-direction: column; align-items: flex-start; gap: var(--s2); }
.kontakt .pill { white-space: normal; max-width: 100%; }

/* Software und Sprachen: Name links auf der Achse, Kenntnisstand
   rechts an der naechsten Rasterlinie. */
.skills { display: flex; flex-direction: column; gap: var(--s1); }
.skill {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: var(--s2);
}
.skill__name { flex: 1 1 auto; }
.skill__stufe { flex: 0 0 auto; }

/* Fuenf Punkte wie auf der alten Website, nur mit der Haarlinie
   dieses Blattes statt 1.5px. */
.dots { display: inline-flex; flex: 0 0 auto; align-self: center; gap: var(--s1); }
.dot {
    width: 0.5em; height: 0.5em;
    border: 1px solid #000; border-radius: 50%;
    box-sizing: border-box;
}
.dot.is-filled { background: #000; }

/* Praxis: je Station ein eigener Absatz, das Jahr als Pille darueber. */
.station + .station { padding-top: var(--s4); }
.station__jahr { margin-bottom: var(--s1); }

/* ============================================================
   PROJEKT
   ============================================================ */
.project-wrapper {
    display: flex; flex-direction: column;
    gap: var(--fuge);
    width: 100%;
    padding-top: var(--header-h);
    padding-bottom: calc(var(--footer-h) + var(--consent-h));
}
/* Dieselben vier Spalten wie Kopf- und Kategorienleiste. Nicht mit
   float und calc(25% - …) arbeiten: das ergibt bei box-sizing:
   border-box keine 25%, die zweite Spalte laege neben ihrer Linie. */
/* Der Fussblock steht am Ende der Seite, nach der letzten Abbildung.
   Der Abstand nach oben ist groesser als die Fuge zwischen zwei
   Abbildungen: er trennt zwei Arten von Inhalt, nicht zwei Bilder. */
.project-info {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    align-items: start;
    width: 100%;
    padding-top: var(--s8);
}
.project-info-col { padding: var(--rand); }

/* Der Beschreibungstext steht ueber der ganzen Breite, wie im Vorbild:
   keine eigene Textspalte, keine Begrenzung des Satzspiegels. Auf einer
   Seite ohne senkrechte Rasterlinien - die gehoeren zum Raster, nicht
   zur Projektseite - gibt es dafuer auch keine Achse.

   Zwei Angaben tragen diese Breite:
   - hyphens: auto. Ohne Trennung reisst eine Zeile ueber 1200px
     Loecher, sobald ein langes Wort umbricht; das Vorbild trennt aus
     demselben Grund. Die Sprache steht im <html>-Element, sonst
     wuesste der Browser nicht, nach welchen Regeln er trennt.
   - etwas mehr Durchschuss als der Rest der Seite. 115% sind fuer
     Etiketten und kurze Zeilen gedacht; ein Fliesstext ueber die volle
     Breite braucht mehr, sonst findet das Auge den Zeilenanfang nicht.
     Das Vorbild steht bei 120%. */
.project-beschreibung {
    padding: var(--rand);
    line-height: 125%;
    hyphens: auto;
    -webkit-hyphens: auto;
}
.project-beschreibung p + p { margin-top: 0.75em; }

/* Das Bildgitter: alle Abbildungen gleich breit, klein nebeneinander -
   erst die Uebersicht, dann per Klick das Vollbild.

   Die Spaltenleiter ist die des Vorbilds, an dessen Seite nachgemessen:
   eine Spalte, ab 500px zwei, ab 875px drei, ab 1250px vier, ab 1625px
   fuenf. Nicht die Breiten der uebrigen Seite (768/1200) - eine
   Uebersicht soll frueher als jedes andere Element mehrspaltig werden,
   sonst ist sie am Handy keine.
   Die Stufen stehen ausserdem in GITTER_SIZES in project-view.js;
   beide muessen zusammenpassen, sonst laedt der Browser die falsche
   Fassung.

   Senkrecht steht mehr Luft als waagerecht - im Vorbild 25 zu 10
   Punkten. Das ist kein Versehen: waagerecht trennt die Fuge zwei
   Bilder derselben Zeile, senkrecht zwei Zeilen, und weil die Zeilen
   unten ausfransen, braucht der Absatz mehr Gewicht als der Abstand
   daneben. Bei gleicher Fuge lesen sich die Bilder als Saeulen statt
   als Zeilen.
   Genommen ist --s4, die naechste Stufe der eigenen Leiter: doppelt
   so viel wie die Fuge. Das Vorbild haelt das Zweieinhalbfache; ein
   Zwischenwert dafuer waere der erste krumme Abstand im ganzen Blatt,
   und die Stufe darueber (--s8) faellt mit dem Vierfachen deutlich zu
   weit aus.

   align-items: start laesst die Bilder einer Zeile oben buendig
   stehen, jedes in seiner eigenen Hoehe. Die Alternative - alle auf
   dieselbe Hoehe zuschneiden - wuerde Plaene beschneiden, und dort
   steckt die Information am Rand. Unten bleibt die Zeile dafuer
   ungleich; das ist der Preis und im Vorbild derselbe.

   Ein Gitter, kein flex-wrap wie im Vorbild: dort verteilt
   space-between eine unvollstaendige letzte Zeile ueber die ganze
   Breite. Das Vorbild faengt das mit fest gerechneten Breiten wieder
   ein - beim Gitter stellt sich die Frage nicht, eine halbe Zeile
   steht von selbst links. */
.project-gitter {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    align-items: start;
    column-gap: var(--fuge);
    row-gap: var(--s4);
    width: 100%;
}
.project-zelle { min-width: 0; }
.project-zelle img, .project-zelle video { width: 100%; height: auto; }
.project-zelle model-viewer { width: 100%; aspect-ratio: 4 / 3; }
/* Nur Bilder lassen sich vergroessern - Video, Modell und PDF bringen
   ihre eigene Bedienung mit. Der Zeiger sagt das voraus; er gilt fuer
   beide Gesten, am Rechner das Vollbild, am Handy das Aufziehen. */
.project-zelle > img { cursor: zoom-in; }

/* ------------------------------------------------------------
   VOLLBILD
   Weiss wie die ganze Seite, das Bild in das Fenster eingepasst.
   Kein Abdunkeln: die Seite kennt zwei Farben, und ein grauer
   Schleier waere die dritte.
   ------------------------------------------------------------ */
.vollbild {
    position: fixed; inset: 0;
    z-index: 6000;
    background: #fff;
    display: flex; align-items: center; justify-content: center;
    padding: var(--rand);
    cursor: zoom-out;
}
/* Eingepasst, nicht gedehnt: max- statt feste Masse. Ein kleines Bild
   bleibt dadurch klein, statt auf Fensterhoehe hochgerechnet und
   unscharf zu werden. Der Platz der Leiste unten bleibt frei. */
.vollbild__bild {
    max-width: 100%;
    max-height: calc(100vh - 2 * var(--rand) - var(--pille-hoehe, 2rem) - var(--s8));
    width: auto; height: auto;
    object-fit: contain;
}
/* Dieselbe Stellung wie der Navigationsfuss des Projekts: zurueck
   links, weiter rechts. Das Schliessen steht in der Mitte - es ist
   der haeufigste der drei Wege hinaus. */
.vollbild__leiste {
    position: absolute; left: 0; right: 0; bottom: 0;
    display: flex; align-items: baseline; justify-content: space-between;
    gap: var(--s4);
    padding: var(--rand);
    cursor: default;
}
/* Die Pille traegt ihren Zug nach links, damit die Schrift auf der
   Textachse sitzt; rechts wird er gespiegelt. Dieselbe Rechnung wie
   im Navigationsfuss. */
.vollbild__weiter { margin-left: 0; margin-right: calc(-1 * var(--pille-versatz)); }
/* Solange ein Bild gross steht, rollt die Seite darunter nicht mit -
   sonst stuende man nach dem Schliessen an einer anderen Stelle. */
body.hat-vollbild { overflow: hidden; }
/* PDFs bringen kein Seitenverhaeltnis mit. A4 hochkant ist der
   sicherste Anhalt; wer die Datei ganz sehen will, oeffnet sie
   ueber den Verweis darunter. */
.media-pdf iframe {
    display: block; width: 100%; border: 0;
    aspect-ratio: 1 / 1.414; max-height: 90vh;
}
.project-link {
    display: inline-block; margin: var(--rand);
    border-bottom: 1px solid #000; line-height: normal;
}
/* Die Fusszeile des Projekts: zurueck ganz links, weiter ganz rechts,
   beide als Pille. Die "Uebersicht" dazwischen ist entfallen - der Name
   in der Kopfzeile fuehrt schon dorthin, ein zweiter Weg an dieselbe
   Stelle ist dieselbe Auskunft zweimal.

   Das "ganz rechts" ist die einzige Stelle der Seite, an der etwas an
   der rechten Kante steht statt an einer Achse zu beginnen - sonst
   faengt jeder Text links an seiner Spaltenlinie an. Bewusste Ausnahme:
   die beiden Pillen sind ein Paar, und ihre Stellung zueinander sagt,
   in welche Richtung es geht.

   Die Pille traegt schon einen Zug nach links, damit ihre Schrift auf
   der Textachse sitzt. Rechts wird das gespiegelt, sonst haette die
   rechte Pille einen anderen Abstand zur Kante als die linke. */
.project-nav {
    display: flex; align-items: baseline; justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--s2) var(--s4);
    padding: var(--s8) var(--rand) var(--rand);
}
.project-nav__weiter {
    margin-left: 0;
    margin-right: calc(-1 * var(--pille-versatz));
}

/* ============================================================
   KATEGORIELEISTE
   ============================================================ */
/* Dieselben vier Spalten wie die Kopfzeile: Schalter in Spalte 1,
   Kategorie in Spalte 2. Die aufgeklappte Liste steht in derselben
   Spalte wie die aktive Kategorie, in einer eigenen Zeile darueber. */
.categories {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 100;
    background: #fff;
    border-top: 1px solid #000;
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    align-items: baseline;
    padding: calc(var(--rand) - var(--pille-versatz-y)) 0;
}
.categories-btn {
    grid-column: 1; grid-row: 2;
    display: inline-flex; align-items: center; gap: var(--s2);
}
/* Als Rasterfeld wuerde eine Pille sonst ueber die ganze Spalte
   gezogen - sie soll aber nur so breit sein wie ihre Schrift. */
.categories-btn, .categories-current, .categories-list li { justify-self: start; }
/* Pillen, die selbst in einer Rasterspalte sitzen, tragen den halben
   Rand als Rest - dieselbe Rechnung wie oben, nur ohne Elternteil,
   das den Rand vorgibt. */
.categories-btn, .categories-current { margin-left: calc(var(--rand) - var(--pille-versatz)); }
.categories-chevron {
    display: inline-block; width: 0.4em; height: 0.4em;
    border-left: 1px solid currentColor; border-top: 1px solid currentColor;
    transform: rotate(45deg) translate(1px, 1px);
    transition: transform 200ms ease;
}
.categories.is-open .categories-chevron { transform: rotate(225deg) translate(1px, 1px); }
.categories-list {
    grid-column: 2; grid-row: 1;
    display: none;
    padding-left: var(--rand);
    padding-bottom: var(--s2);
}
.categories.is-open .categories-list { display: block; }
.categories.is-open .categories-current { visibility: hidden; }
.categories-list li { padding: var(--s1) 0; }

/* Die aktive Kategorie steht in Spalte 2, auf einer Linie mit
   "Index" in der Kopfzeile. */
.categories-current { grid-column: 2; grid-row: 2; }

/* ============================================================
   BIS 767px: EINE SPALTE
   Ohne Rasterlinien gibt es am Handy nur eine Achse - den Seitenrand.
   Auf ihr steht jeder Text: Kopfzeile, Kacheltext, Index, Angaben,
   Projektdaten und Fusszeile.
   ============================================================ */
@media only screen and (max-width: 767px) {
    .menu-top {
        display: flex; align-items: baseline;
        gap: var(--s4);
        padding: calc(var(--rand) - var(--pille-versatz-y)) var(--rand);
    }
    .menu-top-logo { padding-left: 0; }
    .menu-top-list { display: contents; }
    .menu-top-list li { padding-left: 0; }

    /* Zwei Spalten. Von den drei Rasterlinien bleibt die mittlere
       stehen - sie trennt die beiden Spalten, so wie die drei Linien
       es am Rechner tun. Ohne sie stiessen zwei Deckblaetter direkt
       aneinander, die Kacheln haben ja keine seitlichen Kanten.
       Die Spaltenzahl steht ausserdem in layoutGrid (app.js); beide
       muessen zusammenpassen. */
    .grid-line--left, .grid-line--right { display: none; }
    .tile { width: 50%; }

    /* Untereinander trennt die volle Polsterung beider Nachbarn die
       Abschnitte - zweimal --rand, also --s8. */
    .info-spalten { display: block; }

    /* Eine Spalte, wie im Vorbild. Der Fussblock traegt jetzt neun
       moegliche Zeilen; in zwei Spalten zu 187px braechen
       "Bauherrschaft" und "Kategorie" mit ihrem Wert um und die
       Zeilenpaare stuenden versetzt. Untereinander steht jede Angabe
       auf derselben Achse wie jeder andere Text am Handy.
       Die Polsterung der Spalten faellt untereinander doppelt aus -
       einmal von jedem Nachbarn -, deshalb tragen die mittleren
       Spalten oben keine. */
    .project-info { grid-template-columns: minmax(0, 1fr); }
    .project-info-col + .project-info-col { padding-top: 0; }

    /* Angetippt wird das Bild herangeholt, nicht als Vollbild
       aufgelegt. Ein Vollbild braechte hier nichts: die Plaene sind
       querformatig, der Bildschirm hochkant - eingepasst begrenzt sie
       weiter die Breite, und die ist ohnehin schon die volle
       Fensterbreite. Man saehe dasselbe Bild in derselben Groesse,
       nur mit einem Zustand drumherum.

       Die Zelle nimmt dafuer die ganze Zeile - bei zwei Spalten waere
       sie sonst 174px breit und man saehe durch einen Schlitz - und
       rollt waagerecht. Das Bild darin misst das Zweieinhalbfache
       davon: bei 355px Zeilenbreite rund 890 Punkte, auf denen ein
       Grundriss mit Bemassung lesbar wird.

       aspect-ratio bleibt stehen und waechst mit: die Hoehe folgt der
       Breite, das Bild wird groesser statt verzerrt. */
    .project-zelle.is-aufgezogen {
        grid-column: 1 / -1;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
    }
    .project-zelle.is-aufgezogen > img {
        width: 250%;
        max-width: none;
        cursor: zoom-out;
    }

    /* Die Projektseite hat am Handy dieselbe Abfolge wie am Rechner -
       Beschreibung, Bildfolge, Fussblock -, nur steht alles in einer
       Spalte. */

    /* Derselbe Aufbau wie am Rechner - Liste in Zeile 1, Schalter in
       Zeile 2 -, nur ohne Spaltenraster. Stuende die Liste wie zuvor
       hinter dem Schalter, waechst die Leiste beim Oeffnen nach oben
       und schoebe den Schalter mit: gemessen 153px. Jetzt bleibt er
       stehen und die Liste klappt darueber auf. */
    .categories {
        grid-template-columns: auto auto minmax(0, 1fr);
        column-gap: var(--s2);
        padding: calc(var(--rand) - var(--pille-versatz-y)) var(--rand);
    }
    .categories-btn { grid-column: 1; margin-left: calc(-1 * var(--pille-versatz)); }
    .categories-current { grid-column: 2; margin-left: 0; }
    .categories-list {
        grid-column: 1 / -1;
        padding-left: 0; padding-bottom: var(--s2);
    }

    /* Ohne Rasterlinien gibt es auch im Index keine Spaltenachsen.
       Titel und Pille stehen deshalb einfach hintereinander auf der
       einen Achse, im selben Abstand wie die Kopfzeile. Bricht ein
       langer Titel um, rutscht die Pille in die naechste Zeile und
       steht dort durch ihren Versatz wieder genau auf der Achse. */
    .list a {
        display: flex; flex-wrap: wrap; align-items: baseline;
        gap: var(--s2) var(--s4);
        padding: calc(var(--rand) - var(--pille-versatz-y)) var(--rand);
    }
    .list__title { padding-left: 0; }
    .list__marke { margin-left: calc(-1 * var(--pille-versatz)); }
    /* Am Handy gibt es kein Ueberfahren. Das Jahr steht deshalb
       dauerhaft - in derselben Zeile hinter der Pille, wo der
       flex-Kasten es ohnehin hinstellt. Es zu verstecken hiesse, die
       Angabe am Telefon ersatzlos zu streichen. */
    .list__jahr { padding-left: 0; opacity: 1; }
}

/* ============================================================
   768 BIS 1199px: ÜBER IN ZWEI SPALTEN
   Vier Spalten sind hier zu schmal - die Adresse, die Punkte und
   "Muttersprache" ragten in die Nachbarspalte. Die vier Abschnitte
   stehen deshalb zu zweit nebeneinander, auf denselben Achsen.
   ============================================================ */
@media only screen and (min-width: 768px) and (max-width: 1199px) {
    .info-spalten { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ============================================================
   SPALTENLEITER DES BILDGITTERS
   Am Vorbild nachgemessen. Die Grenzen liegen bewusst neben denen der
   uebrigen Seite: eine Uebersicht wird frueher mehrspaltig als der
   Rest, sonst ist sie am Handy keine Uebersicht.
   Dieselben Stufen stehen in GITTER_SIZES in project-view.js.
   ============================================================ */
@media only screen and (min-width: 500px) {
    .project-gitter { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media only screen and (min-width: 875px) {
    .project-gitter { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media only screen and (min-width: 1250px) {
    .project-gitter { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media only screen and (min-width: 1625px) {
    .project-gitter { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}

/* ============================================================
   AB 1200px
   ============================================================ */
@media only screen and (min-width: 1200px) {
    html { font-size: 22px; }
}

/* ============================================================
   HOVER
   Ohne Media-Gate wie im Vorbild: beim Ueberfahren blendet das
   Bild aus und die Beschriftung ein. Auf Touch loest die Regel
   beim Antippen kurz aus - genauso wie beim Vorbild.
   ============================================================ */
/* Beim Ueberfahren tritt das Deckblatt zurueck, gleich ob Bild,
   Video, PDF, Modell oder Ersatzkachel - eine Regel fuer alle. */
.tile > :not(.tile__hover) { opacity: 1; transition: opacity 150ms; }
/* Das Ueberfahren gilt erst ab 768px - darunter macht der Plus-Knopf
   dasselbe, und die beiden vertragen sich nicht: Touch-Geraete lassen
   den Hover-Zustand nach einem Tippen stehen. Er ueberschrieb dann den
   geschlossenen Zustand, das Deckblatt kam nach dem Schliessen nicht
   zurueck und die Angaben blieben sichtbar - der Knopf liess sich
   aufmachen, aber nicht wieder zu.
   Abgeschirmt wird nach **Breite**, nicht nach `hover: hover`, obwohl
   letzteres genauer klaenge. Grund: `hover` laesst sich in headless
   Chromium nicht emulieren - weder ueber emulateMediaFeatures (kennt
   den Namen nicht) noch ueber die Debug-Schnittstelle noch ueber
   --blink-settings. Nach `hover` abgeschirmte Regeln waeren hier also
   ueberhaupt nicht mehr pruefbar; nach Breite abgeschirmte sind es,
   man setzt einfach die Fensterbreite. Und es trifft die Sache: den
   Knopf gibt es nur unter 768px, das Ueberfahren nur darueber - die
   beiden Mechanismen ueberschneiden sich nie. */
@media only screen and (min-width: 768px) {
    .tile:hover > :not(.tile__hover) { opacity: 0; }
    .tile:hover .tile__hover { opacity: 1; z-index: 1; }
}

/* ------------------------------------------------------------
   DER PLUS-KNOPF
   Am Rechner blendet das Ueberfahren die Angaben ein. Am Handy gibt
   es kein Ueberfahren - dort steht unten rechts ein runder Knopf, der
   dasselbe tut. Angetippt dreht er sich um 45 Grad: aus dem Plus wird
   das Kreuz, und er fuellt sich schwarz wie eine Pille im aktiven
   Zustand.
   Sein Durchmesser ist die gemessene Hoehe einer Pille
   (--pille-hoehe), damit er im selben Mass steht wie alles andere.
   ------------------------------------------------------------ */
.tile__mehr { display: none; }

@media only screen and (max-width: 767px) {
    .tile__mehr {
        display: grid; place-items: center;
        position: absolute; right: var(--rand); bottom: var(--rand);
        z-index: 2;
        width: var(--pille-hoehe); height: var(--pille-hoehe);
        border: 1px solid #000; border-radius: 50%;
        background: #fff;
        transition: transform 200ms ease;
    }
    /* Das Zeichen aus zwei Haarlinien - dieselbe Strichstaerke wie
       jede andere Linie der Seite, nicht ein Schriftzeichen. */
    .tile__kreuz {
        position: relative;
        width: 40%; height: 1px; background: #000;
    }
    .tile__kreuz::before {
        content: ''; position: absolute; inset: 0;
        background: #000; transform: rotate(90deg);
    }
    /* Offen dreht sich der Knopf nur - er bleibt weiss. Aus dem Plus
       wird das Kreuz, sonst aendert sich nichts: das Zeichen sagt schon
       alles, ein Farbwechsel dazu waere dieselbe Auskunft ein zweites
       Mal. Plus heisst "Deckblatt zu sehen", Kreuz heisst "Angaben zu
       sehen". */
    .tile.is-offen .tile__mehr { transform: rotate(45deg); }

    /* Offen zeigt die Kachel dasselbe wie am Rechner beim Ueberfahren:
       Deckblatt aus, Angaben ein. Der Knopf bleibt stehen - er ist
       kein Kind der Flaeche, sondern steht daneben. */
    .tile.is-offen .tile__flaeche { opacity: 0; }
    .tile.is-offen .tile__hover { opacity: 1; z-index: 1; }
}


/* Die Zeilen im Index geben beim Ueberfahren keine Rueckmeldung: ihre
   Kategorie ist eine Auskunft, keine Schaltflaeche, und Auskuenfte
   reagieren nach der Grundregel auf nichts. Unterstreichen tut sich der
   Titel auch nicht - das gibt es auf der ganzen Seite nicht. */

/* ============================================================
   ENTWURFSMODUS
   Nur sichtbar, wenn die Seite unveroeffentlichte Inhalte zeigt.
   ============================================================ */
.draft-flag {
    position: fixed; right: var(--rand); z-index: 200;
    bottom: calc(var(--footer-h) + var(--s2));
    padding: var(--s1) var(--s2);
    background: #000; color: #fff;
    font-size: var(--schrift-klein); line-height: normal; letter-spacing: 0.04em;
}

/* Rahmen im Studio abgeschaltet - je Deckblatt, nicht fuers ganze
   Raster: die Kachel stoesst dann Kante an Kante an die Rasterlinien.
   Sie wird dadurch um zweimal --rand niedriger; die Hoehe misst
   layoutGrid am fertigen Element, das Raster ordnet sich also von
   selbst neu, und zwei Kacheln mit und ohne Rahmen stehen problemlos
   nebeneinander. */
.tile.ohne-deckblatt-rand { --deckblatt-rand: 0px; }


/* ============================================================
   EINWILLIGUNG
   Die Leiste ueber der Kategorienleiste. Gebaut wie jede andere
   Leiste dieser Seite: weiss, eine Haarlinie, Text auf der Achse,
   Schaltflaechen als Pillen. Keine Verdunklung, kein Schatten, keine
   Sperre - die Seite bleibt bedienbar, waehrend sie steht. Das ist
   nicht nur eine Frage des Blattes: was man wegklicken muss, um
   weiterzulesen, ist keine freiwillige Einwilligung mehr.

   Beide Schaltflaechen sind dieselbe Pille. Keine traegt is-active,
   keine ist gefuellt, keine ist groesser. Eine hervorgehobene
   Zustimmung waere ein Dark Pattern (EDSA-Leitlinien 03/2022) - und
   sie waere hier zugleich ein Bruch mit der Grundregel der Pillen.
   Der dritte Eintrag ist ein Verweis, der vierte eine Auskunft und
   traegt deshalb von selbst den gestrichelten Rand.
   ============================================================ */
.einwilligung {
    position: fixed; left: 0; right: 0; z-index: 150;
    /* Sie sitzt AUF der Kategorienleiste, nicht darueber: auf Index,
       About und Projektseite ist --footer-h null, dort steht sie
       selbst auf der Unterkante. */
    bottom: var(--footer-h);
    background: #fff;
    border-top: 1px solid #000;
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    align-items: start;
}
/* Polsterung wie bei den Spalten auf About: --rand auf allen vier
   Seiten. Der Text faellt damit auf dieselbe Achse wie jeder andere
   Text der Seite, die Schaltflaechen auf die Achse der dritten
   Spalte - dorthin, wo in der Kopfzeile "About" steht. */
.einwilligung-text { grid-column: 1 / 3; padding: var(--rand); }
.einwilligung-knoepfe {
    grid-column: 3 / -1; padding: var(--rand);
    display: flex; flex-wrap: wrap; align-items: baseline;
    gap: var(--s2) var(--s4);
}
/* Der Zwischenraum faellt um den Versatz der Pille kleiner aus als
   --s4 - genau wie in der Kopfzeile am Handy, wo dieselben drei
   Pillen nebeneinander stehen. Nicht ausgeglichen: dann stuenden hier
   andere Abstaende als dort. */

@media only screen and (max-width: 767px) {
    /* Eine Spalte, eine Achse. Die Polsterung wandert an die Leiste,
       sonst fiele sie zwischen Text und Schaltflaechen doppelt aus. */
    .einwilligung { display: block; padding: var(--rand); }
    .einwilligung-text, .einwilligung-knoepfe { padding: 0; }
    .einwilligung-knoepfe { padding-top: var(--s4); }
}

/* ============================================================
   TEXTSEITE (Datenschutz, spaeter Impressum)
   Eigenstaendige Datei ohne JavaScript-Routing. Die Kopfzeile steht
   dort im Fluss statt fest: --header-h wird von app.js gemessen, und
   app.js laeuft auf diesen Seiten nicht. Mit dem Rueckfallwert 40px
   rutschte der Text ab 1200px unter die Kopfzeile - dort waechst die
   Grundschrift auf 22px und die Leiste mit ihr.
   ============================================================ */
.seite-statisch .header { position: static; }
.text-seite {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    padding-bottom: calc(var(--rand) + var(--consent-h));
}
/* Drei von vier Spalten, die vierte bleibt frei - wie in der
   Kopfzeile. Ueber die volle Breite waere die Zeile bei 1920px
   unlesbar lang. */
.text-seite > * { grid-column: 1 / 4; padding: 0 var(--rand); }
.text-seite h1, .text-seite h2 {
    font-size: 1rem; font-weight: 400; margin: 0;
}
.text-seite h1 { padding-top: var(--s8); }
.text-seite h2 { padding-top: var(--s8); padding-bottom: var(--s1); }
.text-seite p { padding-bottom: var(--s2); hyphens: auto; }
.text-seite a:not(.pill) { border-bottom: 1px solid #000; line-height: normal; }

@media only screen and (max-width: 767px) {
    .text-seite { display: block; }
}


/* ============================================================
   VORAB-LISTE DER STARTSEITE
   Steht im Quelltext, bis app.js das Raster gebaut hat - fuer
   Suchmaschinen und fuer jeden ohne JavaScript. buildGrid() ersetzt
   sie dann. Sie sieht aus wie das Verzeichnis, weil sie dasselbe ist:
   dieselbe Zeile aus index-view.js.
   ============================================================ */
.vorab-liste li { border-top: 1px solid #000; }
.vorab-liste li:first-child { border-top: 0; }
.vorab-liste li:last-child { border-bottom: 1px solid #000; }
.vorab-liste a {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    align-items: baseline;
    padding: calc(var(--rand) - var(--pille-versatz-y)) 0;
}
.vorab-liste .list__title { padding-left: var(--rand); }
.vorab-liste .list__marke { margin-left: calc(var(--rand) - var(--pille-versatz)); }
.vorab-liste .list__jahr { padding-left: var(--rand); }


/* Die Haltungszeile auf About. Ueber alle vier Spalten, auf derselben
   Achse wie jeder andere Text - sie ist eine Aussage, keine Spalte. */
.info-zeile { padding: var(--rand) var(--rand) 0; }
