/* Supertrifaller – Website.
 *
 * Inhalt bis 1440 px breit und zentriert; darueber waechst der weisse Rand
 * links und rechts. Seitenrand 40 px, Zeileneinheit 20 px, Zeilenabstand
 * durchgehend Faktor 1, Schriftgroessen absolut und nicht skalierend. Keine
 * Radien, keine Schatten, keine Rahmen.
 *
 * (Die Design-Uebergabe rechnete mit einem Artboard von 1728 px und einem
 * mitskalierenden Rand von 2.315vw. Beides ist bewusst ersetzt.)
 *
 * Die Farben der Wortmarke kommen aus dem Illustrator-Original. Die Uebergabe
 * nennt #ff0000 und #ffe000 aus der Figma-Datei - solange das nicht geklaert
 * ist, gilt das Original, damit Website, Visitenkarte und Marken-Export
 * farbgleich bleiben.
 */

/* --- Schrift -------------------------------------------------------------- */

/* ABC Diatype von Dinamo. Das @font-face steht nicht hier, sondern in
   index.php: dort ist bekannt, welche Datei in assets/fonts/ wirklich liegt.
   Eine Liste mehrerer Dateinamen an dieser Stelle waere bequemer, aber jeder
   Name, der nicht existiert, ist ein 404 in der Konsole - bei vier Kandidaten
   also drei Fehlermeldungen pro Seitenaufruf.

   Liegt keine Datei da, entsteht gar kein @font-face und die Seite laeuft mit
   Helvetica. Nur Webformate, keine otf: eine otf ist eine Druckdatei und auf
   einer Website unnoetig gross. Welche Namen erkannt werden, steht in
   assets/fonts/README.md. */

:root {
  --ground: #ffffff;
  --ink: #000000;
  --accent: #ff1818;        /* Wortmarke, Buchstaben; Link im Hover */
  --contour: #ffb000;       /* Wortmarke, Kontur */
  --mark-highlight: #ffe000;

  --line: 20px;             /* eine Zeile Grundtext */
  /* Lücke zwischen nummeriertem Titel und seinem Text. Nicht der Abstand, den
     man sieht: der Browser stapelt Zeilenkästen, und bei Zeilenabstand 1 sind
     die kleiner als die Buchstaben. Von Grundlinie zu Grundlinie kommen mit
     3,5 px genau 25 px heraus - das 1,25-fache des Grundtextes, dieselbe Zahl
     wie auf der Visitenkarte (dort 10 pt auf 8 pt, siehe WECHSEL in
     card-layout.js). Vorher waren es 2 px und damit 23,5. */
  --titel-luecke: 3.5px;
  /* Abstand zwischen zwei Bloecken, die fuer sich stehen: zwei Zeilen
     Grundtext. Als glatte Zahl und nicht als calc(--line * 2), weil site.js
     den Wert liest: eine benutzerdefinierte Eigenschaft kommt bei JavaScript
     unausgerechnet an, aus "calc(20px * 2)" wird dort NaN. Auf dem Telefon ist
     der Abstand ohnehin ein eigener (30 px), das Verhaeltnis zur Zeile gilt
     also nur hier. */
  --block: 40px;
  --edge: 40px;             /* Rand seitlich und oben */
  --edge-bottom: 20px;      /* Rand unten */
  --intro-bottom: var(--block); /* Introblock zur Unterkante im Startzustand */
  --nav-gap: var(--block);  /* Nav zum Fließtext darunter */
  --mark-gap: var(--block); /* Marke zum Introtext beim Hochschieben (liest site.js) */
  --page: 1440px;           /* groesste Breite des Inhalts, danach waechst der Rand */
  /* Textbreite ergibt sich aus Seitenbreite minus Rand - so bleibt der
     Zusammenhang sichtbar, wenn eine der beiden Zahlen sich aendert. */
  --measure: calc(var(--page) - var(--edge) * 2);
  --font: 'ABC Diatype', 'Helvetica Neue', Helvetica, Arial, sans-serif;

  /* Schriftgroessen. Zeilenabstand durchgehend Faktor 1,0.
     Kontakt hat einen eigenen Wert, weil er auf schmalen Fenstern gross
     bleibt, waehrend der Grundtext kleiner wird. */
  --size-intro: 30px;       /* Introtext im ersten Bildschirm */
  --size-l: 30px;           /* nummerierte Titel */
  --size-m: 20px;           /* Grundtext, Abschnittstitel */
  --size-nav: 20px;         /* Menue */
  --size-s: 15px;           /* Impressum, Datenschutz */
  --size-contact: 20px;     /* Kontakt */
  --menu-row: 16px;         /* Abstand zwischen zwei Menuezeilen */
  --menu-col: 16px;         /* Abstand zwischen zwei Menuepunkten */
  /* Ruhelage der Marke nach der Eingangsanimation. Beides zusammen ergibt die
     Lage: --mark-top ist die Klebehoehe, --mark-shift der Versatz auf die
     eigene Hoehe (fuer echte Mitte). site.js liest nur, was hier steht. */
  --mark-top: var(--edge);
  --mark-shift: 0px;
  --col-no: 60px;           /* Spalte fuer "(1):" */
  --col-lbl: 100px;         /* Spalte fuer "Impressum:" / "Datenschutz:" */
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--font);
  -webkit-font-smoothing: antialiased;
}

/* Kein Scrollbalken. Gescrollt wird weiter – nur die Leiste ist unsichtbar.
   Damit bleibt die Fläche in jedem Zustand gleich breit; im Eingangszustand
   ist die Seite gesperrt, ein auftauchender und verschwindender Balken würde
   das Layout sonst um seine Breite verschieben. */
html { scrollbar-width: none; -ms-overflow-style: none; }
html::-webkit-scrollbar, body::-webkit-scrollbar { width: 0; height: 0; display: none; }

/* Zwei Dinge, die getrennt gehoeren, weil sie zu verschiedenen Zeitpunkten
   enden:

   is-locked  Scrollsperre. Sie haelt bis zum Ende der Eingangsanimation - die
              erste Scrollbewegung startet nur den Eingang, gescrollt wird erst
              mit der naechsten.
   is-intro   Startzustand des Introblocks: unter der Falte. Als Klasse und
              nicht per JS, damit es auch im ersten Frame stimmt. Sie faellt
              mit dem START der Animation weg, sonst wuerde sie --slide
              ueberschreiben und der Block waere nicht am Einfahren, sondern
              erst am Ende ploetzlich da. */
body.is-locked { overflow: hidden; }
body.is-intro .intro { transform: translateY(100%); }

/* Ausserhalb des Sichtbaren, aber nicht ausserhalb des Vorlesbaren: Text, den
   Suchmaschinen und Vorleseprogramme brauchen und das Auge nicht. Absolut
   positioniert und auf einen Punkt beschnitten - display: none waere fuer
   beide unsichtbar, und eine Verschiebung ins Nirgendwo verschiebt bei
   manchen Browsern den Scrollbereich. */
.vh {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

a { color: var(--ink); text-decoration: none; }
a:hover { color: var(--accent); }
::selection { background: var(--mark-highlight); color: var(--ink); }

/* will-change nur waehrend der Eingangsanimation. Dauerhaft ausgelagerte
   Ebenen kosten Speicher, bringen im Ruhezustand nichts und machen die Seite
   fuer Bildschirmaufnahmen unzuverlaessig. */
body.is-moving .piece,
body.is-moving .mark,
body.is-moving .intro { will-change: transform; }

/* --- Bauteil-Fotos ------------------------------------------------------- */

.pieces {
  position: fixed;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  pointer-events: none;
  /* Bis site.js jedem Teil seine Lage gegeben hat, bleiben sie unsichtbar.
     Ohne das stehen sie beim ersten Bild alle auf 50/50 - dem Vorgabewert von
     --l und --t - und rutschen danach sichtbar auseinander. Ohne JavaScript
     bleiben sie weg; sie sind Schmuck, und ein Haufen in der Mitte ist
     schlechter als keiner. */
  visibility: hidden;
}

.pieces.is-ready { visibility: visible; }

.piece {
  position: absolute;
  left: var(--l, 50%);
  top: var(--t, 50%);
  width: var(--w, 160px);
  height: var(--h, 130px);
  transform: translate(-50%, -50%) translate(var(--dx, 0px), var(--dy, 0px)) rotate(var(--r, 0deg));
}

.piece img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

/* Platzhalter, solange die Bibliothek leer ist. */
.piece--empty {
  background: repeating-linear-gradient(135deg, #ededed 0 7px, #e0e0e0 7px 14px);
  display: flex;
  align-items: flex-end;
  padding: 6px;
}
.piece--empty span {
  font: 400 9px/1.2 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: #8c8c8c;
}

/* --- Wortmarke ----------------------------------------------------------- */

/* Die Marke wird nicht per JavaScript geschoben, sie klebt. Dieser Kasten ist
   die Strecke, ueber die sie oben stehen bleibt (--range, von site.js
   gemessen); danach schiebt der Browser sie selbst aus dem Bild - im selben
   Takt wie den Text, der sie schiebt. Ein pro Frame gerechneter Versatz hinkt
   dem nativen Scrollen dagegen immer einen Frame nach, und genau das zittert. */
.header {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: var(--range, 100vh);
  z-index: 6;
  pointer-events: none;
}

/* Ruhelage kommt aus dem CSS: --edge unter der Oberkante, derselbe Wert wie der
   Seitenrand. --ty ist der Versatz darauf, mit dem die Eingangsanimation die
   Marke aus der Mitte nach oben holt. Ohne JavaScript steht sie damit direkt
   oben - also im Zustand, in dem die Seite lesbar ist.

   --mark-top und --mark-shift stehen oben bei den Tokens: auf schmalen
   Fenstern bleibt die Marke mittig stehen, dann ist die Klebehoehe 50svh und
   der Versatz -50% ihrer eigenen Hoehe. */
.mark {
  /* Die Marke ist ein h1 - Innenabstand und Schriftgroesse einer Ueberschrift
     hat sie nicht, sie ist eine Zeichnung. */
  margin: 0;
  font-size: inherit;
  font-weight: inherit;
  position: sticky;
  top: var(--mark-top);
  max-width: var(--page);
  margin: 0 auto;
  padding: 0 var(--edge);
  transform: translateY(calc(var(--mark-shift) + var(--ty, 0px)));
  /* flow-root: die Animation zieht den Rahmen des SVG auf und rechnet die
     Mehrhoehe mit negativen Raendern wieder heraus (mark-animation.js). Ohne
     einen eigenen Formatierungskontext fallen diese Raender mit denen der
     Ueberschrift zusammen - dann wandert nicht der Rahmen, sondern die ganze
     Marke nach oben, und ihre Hoehe bleibt die groessere. */
  display: flow-root;
}

/* Die Eingangslage der Marke - mittig im Fenster - steht schon im
   Stylesheet, nicht erst in JavaScript. Dieselbe Rechnung wie dort
   (tyIntro − markTop): die halbe Fensterhoehe, abzueglich der halben eigenen
   Hoehe und des oberen Randes. site.js schreibt denselben Wert, sobald es
   laeuft, und uebernimmt ihn von da an - sichtbar ist der Uebergang nicht,
   weil beide Zahlen gleich sind.

   Nur mit JavaScript: ohne bleibt die Marke oben, und die Seite ist sofort
   lesbar. */
.js .mark { --ty: calc(50svh - 50% - var(--edge)); }

.mark svg {
  width: 100%;
  height: auto;
  display: block;
  overflow: visible;
}

/* Zwei Gruppen, dieselben vierzehn Pfade: unten als runde Kontur (das ist die
   Huelle), darueber gefuellt. Die Farben stehen auch als Attribute im SVG -
   damit steht das Zeichen ueberall richtig, wo es dieses Stylesheet nicht gibt
   (Vorschau im Admin). Hier gewinnen die CSS-Eigenschaften, weil eine Regel
   auf den Pfad jedes geerbte Attribut schlaegt. */
.mark .envelope path { fill: var(--contour); stroke: var(--contour); }
.mark .glyphs path { fill: var(--accent); }

/* --- Inhalt -------------------------------------------------------------- */

/* main hat bewusst keinen eigenen Hintergrund: ein weisser Grund schiebt sich
   beim Einblenden ueber die Fotos und laesst sie scheinbar ausblenden. */
/* Deckkraft startet bei 1, nicht bei 0: ohne JavaScript oder wenn das Skript
   scheitert, ist die Seite lesbar. Den Eingangszustand blendet site.js
   ausdruecklich aus - eine unsichtbare Seite darf nie der Standard sein. */
main {
  position: relative;
  z-index: 3;
  opacity: var(--mainop, 1);
  padding-top: var(--pt, 40vh);
}

.wrap {
  max-width: var(--page);
  margin: 0 auto;
  padding: 0 var(--edge) var(--edge-bottom);
}

/* Erster Bildschirm: der Introblock sitzt unten, der Weissraum entsteht hier.
   Hoehe in svh, nicht vh: auf dem Telefon ist vh die Flaeche OHNE die Leisten
   des Browsers - der Introblock lag damit hinter der Adressleiste. svh ist die
   kleinste Variante, also die mit ausgeklappten Leisten. Damit steht die Nav
   sichtbar darueber, und weil svh sich beim Scrollen nicht aendert, springt das
   Layout auch nicht, wenn die Leiste einklappt. (vh davor als Rueckfall fuer
   aeltere Browser.) */
.hero {
  min-height: calc(100vh - var(--pt, 40vh));
  min-height: calc(100svh - var(--pt, 40vh));
  display: flex;
  align-items: flex-end;
}

.intro {
  width: 100%;
  /* Abstand zur Unterkante des Bildschirms im Startzustand. */
  padding-bottom: var(--intro-bottom);
  display: grid;
  gap: var(--line);
  transform: translateY(var(--slide, 100%));
}

.intro p {
  margin: 0;
  max-width: var(--measure);
  font: 400 var(--size-intro)/1 var(--font);
  text-wrap: pretty;
}

.menu {
  display: flex;
  flex-wrap: wrap;
  row-gap: var(--menu-row);
  column-gap: var(--menu-col);
  font: 400 var(--size-nav)/1 var(--font);
}

/* Der Abstand von der Nav zum Fließtext soll dem zwischen den Textblöcken
   entsprechen („Unser Ursprung“ → „(1):“). Ein Teil davon steckt schon im
   Innenabstand des Introblocks, der Rest kommt hier dazu – so bleibt der
   Abstand zur Unterkante im Startzustand unverändert. */
/* max(), damit der Rest nie negativ wird: --intro-bottom ist inzwischen so
   gross wie --nav-gap, auf dem Telefon durch den Sicherheitsabstand sogar
   groesser - ohne die Klammer schoebe sich der Text in die Nav. */
.hero + .body-text { margin-top: max(0px, calc(var(--nav-gap) - var(--intro-bottom))); }

/* --- Textblöcke ---------------------------------------------------------- */

/* Der Block steht fuer sich – wie „Unser Ursprung“ zu „(1):“ bekommt er den
   doppelten Zeilenabstand nach unten, nicht den einfachen. */
.body-text {
  margin: 0 0 var(--block);
  max-width: var(--measure);
  font: 400 var(--size-m)/1 var(--font);
  text-wrap: pretty;
}

.section {
  display: grid;
  gap: 2px;
  margin-bottom: var(--line);
  max-width: var(--measure);
}
.section:last-of-type { margin-bottom: var(--block); }

.section h2 {
  margin: 0;
  font: 400 var(--size-m)/1 var(--font);
  text-decoration: underline;
  /* Abstand und Staerke stehen in der Schrift selbst: die post-Tabelle der
     Diatype nennt 50 und -100 von 1000 Einheiten, also 0,05 em Staerke und
     0,10 em unter der Grundlinie (bei 20 px: 1 px Linie, 2 px Abstand).
     Ausgeschrieben und nicht per auto: dann rechnet jeder Browser seine eigene
     Auslegung, und die Visitenkarte, die dieselbe Linie zeichnen muss, waere
     nicht mehr deckungsgleich. Eine Zahl, zwei Medien. */
  text-underline-offset: 0.1em;
  text-decoration-thickness: 0.05em;
  /* Die Linie laeuft durch, auch durch Unterlaengen. Von sich aus spart der
     Browser sie aus (skip-ink: auto) - bei einer Balkenschrift sieht das nach
     Loechern in der Linie aus, nicht nach Ruecksicht. */
  text-decoration-skip-ink: none;
}
.section p {
  margin: 0;
  font: 400 var(--size-m)/1 var(--font);
  text-wrap: pretty;
}

.numbers {
  display: grid;
  gap: var(--line);
  margin-bottom: var(--block);
  max-width: var(--measure);
}
.numbers > div { display: grid; gap: var(--titel-luecke); }
.numbers h3 {
  margin: 0;
  font: 400 var(--size-l)/1 var(--font);
}
/* Feste Spalte, damit die Nummern fluchten. Tabulatoren im Markup gehen nicht,
   HTML fasst sie zu einem Leerzeichen zusammen. Breite mit der Schriftgroesse
   mitgewachsen: "(1):" ist bei 30 px 40,6 px breit, 67 px lassen denselben
   Abstand wie die 60 px bei 27 px Schrift. */
.numbers h3 .no { display: inline-block; width: var(--col-no); }
/* Tabellenziffern nur an der Ziffer: die Diatype macht mit tnum auch Klammern
   und Doppelpunkt ziffernbreit, "(1):" waere dann 72 statt 42 px breit. So sind
   alle vier Beschriftungen gleich breit und die Luecke zum Titel ist ueberall
   dieselbe – bei der 1, die sonst deutlich schmaler ist als die anderen. */
.numbers h3 .no .fig { font-variant-numeric: tabular-nums; }
.numbers p {
  margin: 0;
  font: 400 var(--size-m)/1 var(--font);
  text-wrap: pretty;
}

.contact { display: grid; gap: 2px; }
.contact a, .contact p {
  margin: 0;
  font: 400 var(--size-contact)/1 var(--font);
}

.legal {
  margin: var(--block) 0 0;
  font: 400 var(--size-s)/1 var(--font);
  text-wrap: pretty;
}
.legal + .legal { margin-top: calc(var(--line) * 0.6); }
/* "Datenschutz:" ist bei 15 px 88,1 px breit. 101 px lassen 12,5 px Luft -
   die Haelfte der vorherigen 24,9. */
.legal .lbl { display: inline-block; width: var(--col-lbl); white-space: nowrap; }

/* --- Schmalere Fenster --------------------------------------------------- */

/* Ab 1024 px wird der Rand kleiner. Der Abstand der Wortmarke zur Oberkante
   folgt von selbst mit: site.js liest --edge aus, statt den Wert ein zweites
   Mal zu fuehren. */
@media (max-width: 1024px) {
  :root {
    --edge: 30px;
    --edge-bottom: 15px;
  }
}

@media (max-width: 768px) {
  :root {
    --edge: 15px;
    --edge-bottom: 10px;
    /* Der Introblock haelt zusaetzlich den geschuetzten Bereich frei - auf dem
       iPhone die Zone der Hausbalken, im Browser 0. */
    --block: 30px;
    --intro-bottom: calc(30px + env(safe-area-inset-bottom, 0px));
    /* Der Introtext wird klein wie der Grundtext, das Menue bleibt gross - es
       ist das, was man anfasst. Die nummerierten Titel bleiben bei 20 px, sie
       sind kurz und tragen den Abschnitt. */
    --size-intro: 15px;
    --size-l: 20px;
    --size-m: 15px;
    --size-nav: 20px;
    --size-s: 12.5px;
    /* 20 auf 15 px: 3,25 px Lücke ergeben 18,75 px, wieder das 1,25-fache.
       Im Browser nachgemessen, nicht aus der Metrik gerechnet - Chrome rundet
       die Zeilenkästen, und die halbe Zehntel stimmt nur gemessen. */
    --titel-luecke: 3.25px;
    --menu-row: 5px;
    --menu-col: 10px;
    /* Die Marke bleibt mittig stehen: nach oben zu ruecken hiesse hier, ein
       grosses weisses Feld zwischen Marke und Introtext aufzumachen. 50svh
       plus -50% der eigenen Hoehe ist echte Mitte - und der Browser rechnet
       das bei jeder Aenderung neu, ohne Zutun von JavaScript. */
    --mark-top: 50svh;
    --mark-shift: -50%;
    /* Kontakt bleibt bei 20 px - er ist das, was man auf dem Telefon
       tatsaechlich anfasst. */
    /* Die Label-Spalten wachsen mit der Schrift mit, sonst klafft eine Luecke. */
    --col-no: 45px;
    --col-lbl: 81px;
  }
  /* Hier steht die Marke schon durch --mark-top und --mark-shift in der Mitte -
     ein zusaetzlicher Versatz wuerde sie ein zweites Mal verschieben. */
  .js .mark { --ty: 0px; }
}

/* --- Angesprungener Abschnitt -------------------------------------------- */

/* Die Seite ist kurz, ein Sprung bewegt also wenig. Damit man sieht, worauf man
   geklickt hat, färbt sich der Zielabschnitt rot und geht danach zurück.

   Zwei Dauern, mit Absicht: hinein langsam (die Faerbung soll auffallen),
   heraus schnell (sie hat ihre Aufgabe erledigt, und wer nach oben scrollt, ist
   fertig damit). Die kurze Dauer steht an der Grundregel, die lange an
   .is-target - beim Entfernen der Klasse gilt wieder die Grundregel, also die
   kurze. */
.body-text, .section, .numbers, .contact, .legal,
.body-text *, .section *, .numbers *, .contact *, .legal * { transition: color .12s ease; }

.is-target, .is-target * { color: var(--accent); transition: color .3s ease; }

/* --- Rücksicht ----------------------------------------------------------- */

/* Wer Bewegung reduziert haben will, bekommt den Zielzustand ohne Animation. */
@media (prefers-reduced-motion: reduce) {
  .piece { display: none; }
  .intro { transform: none; }
  main { opacity: 1; }
  /* Ohne Bewegung springt der Anker sofort; die Hervorhebung bleibt, denn
     genau sie zeigt dann, wo man gelandet ist. */
  .body-text, .section, .numbers, .contact, .legal,
  .body-text *, .section *, .numbers *, .contact *, .legal * { transition: none; }
}
