@charset "UTF-8";

/* zynd.at
   =======
   The idea: the page is a sequence of full-width bands. Each one owns the whole
   viewport and declares itself by changing ground colour; the column inside
   stays narrow. Separation comes from the ground changing, not from borders,
   so almost nothing on this site is boxed.

   Where the dark bands go is the argument, not the decoration. #303040 carries
   the material that is somebody else's - the quotes and the client logos. A
   site whose job is verification should give its loudest treatment to the
   parts a reader can check against someone other than me.

   Colours and typeface are unchanged from the original zynd design in
   assets/scss/style.scss: Harmonia, #9a36f8, #f0edf4, #303040 - with #43a8f4
   promoted from an unused SCSS variable to the second accent.

   Everything dimensional is a token in :root. No rule below invents a length:
   horizontal position comes from --pad-x, vertical band height from --band-y*,
   and every other gap and margin picks a step off --space-*. That is what keeps
   the header, the bands, the case strips and the footer on one grid - and it is
   why the narrow-screen section at the bottom is six lines instead of thirty:
   it moves the tokens, not the rules.
*/


/* --- Fonts --------------------------------------------------------------- */

@font-face {
    font-family: "Harmonia";
    src: url("/fonts/Harmonia-Regular.woff2") format("woff2"),
         url("/fonts/Harmonia-Regular.otf") format("opentype");
    font-weight: normal;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "Harmonia";
    src: url("/fonts/Harmonia-Bold.woff2") format("woff2"),
         url("/fonts/Harmonia-Bold.otf") format("opentype");
    font-weight: bold;
    font-style: normal;
    font-display: swap;
}

/* Metric-matched stand-in so the page does not reflow when Harmonia arrives.
   Measured against Arial, weighted by English letter frequency:
     advance  0.4847 vs 0.4775 em  → size-adjust 101.5%
     ascent   0.9590 vs 0.9053 em  → ascent-override 94.5% of the adjusted em
     descent  0.2570 vs 0.2119 em  → descent-override 25.3%
     line gap 0      vs 0.0327 em  → line-gap-override 0%
   Recompute these if the typeface is ever replaced. */

@font-face {
    font-family: "Harmonia Fallback";
    src: local("Arial"), local("Helvetica"), local("Liberation Sans"),
         local("Nimbus Sans"), local("DejaVu Sans");
    size-adjust: 101.5%;
    ascent-override: 94.5%;
    descent-override: 25.3%;
    line-gap-override: 0%;
}


/* --- Tokens ---------------------------------------------------------------
   Colours. Measured contrast on --light (#f0edf4):
     --dark         #303040  →  11.8:1   AA and AAA
     --muted        #615c70  →   6.0:1   AA
     --primary      #9a36f8  →   4.3:1   fills, strokes and the focus ring only
                                       (clears 3:1 for UI, not 4.5:1 for text)
     --primary-text #6d1fbd  →   6.2:1   the one used in text
     --tertiary     #43a8f4  →   1.95:1  strokes only, never a word
   On --dark (#303040) as the ground:
     --light          #f0edf4 →  11.8:1
     --on-dark-mute   #a49dba →   5.9:1  AA
     --on-dark-blue   #7cc4ff →   7.4:1  AA - the blue can carry text here
     --on-dark-violet #b98cff →   5.6:1  AA */

:root {
    --light: #f0edf4;
    --dark: #303040;
    --primary: #9a36f8;
    --primary-text: #6d1fbd;
    --secondary: #31287d;
    --tertiary: #43a8f4;
    --muted: #615c70;

    --on-dark-mute: #a49dba;
    --on-dark-blue: #7cc4ff;
    --on-dark-violet: #b98cff;

    /* Derived surfaces. Named so the same wash cannot drift between the two
       places that use it, which is how the tinted band and the tinted case
       ended up two hundredths apart before. */
    --rule: #ded9e6;
    --rule-dark: rgba(255, 255, 255, 0.14);
    /* Opaque, not translucent: the body under it is dark now, so a see-through
       tint would composite against that instead of against --light. */
    --tint: color-mix(in srgb, var(--dark) 4%, var(--light));
    --tint-strong: rgba(48, 48, 64, 0.07);
    --primary-wash: rgba(154, 54, 248, 0.12);
    --primary-line: rgba(154, 54, 248, 0.35);

    /* Horizontal. One expression places every full-width strip on the page:
       pad inward until the content is --measure wide, and never let the edge
       gap fall below --gutter. Nothing else may position against the viewport. */
    --measure: 76rem;
    --gutter: 2.5rem;
    --pad-x: max(var(--gutter), calc(50vw - var(--measure) / 2));

    /* How wide the content runs inside a band. The bands still span the
       viewport and still start at --pad-x, so every strip stays on the same
       left edge - what this caps is how far right the content reaches, which
       leaves the last third of a normal screen empty. */
    --content-w: 54rem;

    /* Widths inside a band. The reading column is the default for a block;
       --column-wide is for lists that need more room, and --prose is the
       measure running text breaks at. */
    --column: 38rem;
    --column-wide: 54rem;
    --prose: 34em;

    /* Vertical. A band is one of three heights and nothing in between. */
    --band-y: 5.5rem;
    --band-y-lg: 7rem;
    /* The header's height. Static: nothing measures it any more, and the only
       thing that reads it is the hero's optical centring below. Keep it in
       step with .site-header-inner's padding plus the logo. */
    /* Gemessen, nicht geschaetzt: 106px bei 17px Grundschrift. Frueher hat
       js/hero.js den Header vermessen und diesen Wert zur Laufzeit
       ueberschrieben, der CSS-Wert war also nur ein Zwischenstand und durfte
       daneben liegen - er lag um 6px daneben. Ohne das Skript ist er die
       einzige Quelle, und Hero-Polsterung wie Pfeilposition rechnen damit. */
    --header-h: 6.235rem;
    /* Wie hoch der Hero steht. lvh, nicht svh oder dvh:

         svh  ist die kleinste Viewporthoehe, also die mit ausgefahrener
              Browserleiste - der Hero fuellt den Schirm dann nie ganz.
         dvh  passt sich laufend an, springt also in der Hoehe, waehrend die
              Leiste beim Scrollen ein- und ausfaehrt.
         lvh  ist die groesste. Bei eingefahrener Leiste fuellt der Hero den
              Schirm exakt, bei ausgefahrener reicht er ein Stueck darunter -
              und dort steht nur leerer dunkler Grund, weil der Text mittig
              sitzt. Nichts springt.

       Volle Schirmhoehe, ohne den Header abzuziehen: Header und Hero ergeben
       zusammen also einen Schirm plus Headerhoehe, der Hero reicht um genau
       diesen Betrag unter die Falz. Beide tragen --dark, es liest sich als
       eine Flaeche, und der Text bleibt ueber die padding-bottom unten auf
       der Mittellinie des Schirms. */
    --hero-h: 100lvh;

    /* Wie weit der Hero-Text ueber der Mitte steht. Die padding-bottom des
       Heros traegt das Doppelte davon: ein zentrierter Block wandert um die
       halbe Zusatzpolsterung nach oben. In vh, damit der Abstand mit dem
       Schirm mitwaechst statt auf hohen Schirmen zu verschwinden. */
    --hero-lift: 10vh;
    --band-y-sm: 3.5rem;

    /* Type scale. Every font-size on the site is one of these. */
    --text-xs: 0.7rem;
    --text-sm: 0.85rem;
    --text-base: 1rem;
    --text-md: 1.15rem;
    --text-lg: 1.3rem;
    --text-xl: 1.7rem;
    --text-2xl: 2rem;

    /* Surfaces and depth. The bands themselves stay flat - depth is reserved
       for the card grids, which is what carries the added structure. Two
       shadow steps, resting and hover, and nothing in between. */
    --surface: #ffffff;
    --surface-dark: rgba(255, 255, 255, 0.045);
    --radius: 4px;
    --shadow-1: 0 1px 2px rgba(48, 48, 64, 0.05), 0 4px 14px rgba(48, 48, 64, 0.05);
    --shadow-2: 0 2px 4px rgba(48, 48, 64, 0.07), 0 14px 32px rgba(48, 48, 64, 0.10);
    --ease: cubic-bezier(0.2, 0.6, 0.2, 1);


    /* Die Flaechenfarben der beiden Koerper: Wuerfel im Hero und Pyramide
       unter ihm. Keine Schaetzung - sie stammen aus derselben Rechnung, die
       vorher der Fragment-Shader je Pixel gemacht hat: Lambert gegen die
       Lichtrichtung (0.55, 0.75, 0.6), durch smoothstep(0.18, 0.92),
       gemischt zwischen #3f1a75 und #a86cf0.

       Sie standen bis zum 25. September 2026 in der .cube-Regel, wo sie
       niemand ausserhalb des Wuerfels erreichte. Hier stehen sie, weil beide
       Koerper in demselben Licht stehen muessen: eine Pyramide mit eigenen
       Violetttoenen daneben waere ein zweites Objekt und kein zweiter Teil
       desselben. */
    --face-top:    #a469ec;
    --face-front:  #9d63e3;
    --face-right:  #9a61df;
    --face-left:   #421c78;
    --face-back:   #401b77;
    --face-bottom: #3f1a75;
    --face-edge:   #b98cff;

    /* Derselbe Koerper im zweiten Akzent. Die Pyramide laeuft von der Spitze
       zum Sockel durch die beiden Akzentfarben der Seite: oben das Blau
       (--tertiary), unten das Violett des Wuerfels, dazwischen die Mischung.

       Keine frei gewaehlten Blautoene: jede Flaeche behaelt die Helligkeit
       ihrer violetten Entsprechung und bekommt nur Farbton und Saettigung von
       --tertiary (205.8deg, 88.9%). Deshalb steht der Koerper in demselben
       Licht wie der Wuerfel, obwohl er eine andere Farbe hat - ein blau
       eingefaerbter Satz frei geschaetzter Toene waere sofort ein zweites
       Objekt. Die Helligkeit steht je Zeile dabei. */
    --face-alt-top:   #5fb5f6;  /* L 66.9%, wie --face-top    */
    --face-alt-front: #51aff5;  /* L 63.9%, wie --face-front  */
    --face-alt-right: #4cacf4;  /* L 62.7%, wie --face-right  */
    --face-alt-left:  #08538c;  /* L 29.0%, wie --face-left   */
    --face-alt-back:  #08528a;  /* L 28.6%, wie --face-back   */
    --face-alt-edge:  #7cc4ff;

    /* Every gap and margin on the site comes off this scale. */
    --space-1: 0.5rem;
    --space-2: 1rem;
    --space-3: 1.5rem;
    --space-4: 2rem;
    --space-5: 2.5rem;
    --space-6: 3.5rem;
    --space-7: 4.5rem;
}

* { box-sizing: border-box; }

::selection { color: var(--light); background: var(--tertiary); }

/* Keine eigene Hintergrundfarbe hier: der body traegt sie, und von dort
   propagiert sie ohnehin auf den Canvas. Eine zweite Deklaration waere nur
   eine weitere Stelle, die man bei einer Farbaenderung vergisst.

   Auch kein color-scheme. Es stand hier, damit iOS die Seite nicht selbst
   abdunkelt, hat aber nebenbei die Scrollbar hell gezeichnet - die Angabe
   bestimmt saemtliche Browser-eigenen Oberflaechen mit. Ohne sie folgen die
   der Systemeinstellung. */
html {
    scroll-behavior: smooth;
}

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

/* The body's background-color is what Safari 26 samples to tint its toolbar -
   it looks at fixed or sticky elements near the viewport edge first, then falls
   back to here. theme-color does not enter into it. So the body carries --dark,
   the colour the bar should be, and every light band brings its own ground
   instead of inheriting one. */
body {
    margin: 0;
    padding: 0;
    background: var(--dark);
    color: var(--dark);
    font-family: "Harmonia", "Harmonia Fallback", Arial, Helvetica, sans-serif;
    font-size: 17px;
    line-height: 1.5;
    /* Schneidet den Vollbreiten-Ausbruch von .case-cover ab. clip statt
       hidden, weil hidden den body zum Scroll-Container macht - hier ist das
       inzwischen folgenlos, aber es bleibt die harmlosere Variante. Die
       hidden-Zeile ist der Rueckfall fuer Browser ohne clip. */
    overflow-x: hidden;
    overflow-x: clip;
}


/* --- Typography ---------------------------------------------------------- */

/* German compounds run long - "Geschaeftsbedingungen" is 20 characters at a
   clamped 6rem - and a heading that outgrows its column used to overflow into
   the space beside it. With the blocks pushed right that space is the viewport
   edge, so it was being cut off instead. hyphens needs the lang attribute,
   which <html> carries. */
h1, h2, h3, h4, h5, h6 {
    margin-top: 0;
    font-weight: normal;
    overflow-wrap: break-word;
    hyphens: auto;
}

/* Nicht in der obersten Ueberschrift. Automatische Trennung ist bei einem
   deutschen Wortungetuem in einer Zwischenueberschrift richtig und bei einem
   Eigennamen immer falsch - "Christoph Doe-berl" stand so auf der Seite. h1
   traegt hier durchgehend entweder einen Namen oder einen kurzen Satz, beides
   bricht besser zwischen den Woertern. */
h1 { hyphens: manual; }

/* margin-top: 0 stimmt, solange eine Ueberschrift einen Block eroeffnet - und
   das tat sie ueberall, bis auf der Personenseite zwei Bloecke zu einem wurden
   und ein h2 mitten im Text landete. Dort klebt es sonst am Absatz darueber.
   Nur mit Vorgaenger, damit die erste Ueberschrift eines Blocks unberuehrt
   bleibt. */
.block-body > * + :is(h2, h3) { margin-top: var(--space-5); }

/* Die erste Ueberschrift unter einer Abschnittsbeschriftung setzt keinen
   eigenen Vorabstand, die Beschriftung bringt ihren bereits mit. */
.block-body > .section-title + :is(h2, h3) { margin-top: 0; }

h1 {
    font-size: clamp(2.6rem, 8vw, 6rem);
    line-height: 0.98;
    letter-spacing: -0.04em;
    margin: 0 0 var(--space-3);
    max-width: 12em;
}

h2 {
    font-size: clamp(2rem, 4.5vw, 3.2rem);
    line-height: 1.05;
    letter-spacing: -0.03em;
    margin: 0 0 var(--space-2);
}

h3 { font-size: var(--text-xl); letter-spacing: -0.02em; margin: 0 0 var(--space-1); }
h4 { font-size: var(--text-md); }
h5 { font-size: var(--text-base); font-weight: bold; }

h6 {
    font-size: var(--text-xs);
    font-weight: normal;
    text-transform: uppercase;
    letter-spacing: 0.22em;
    margin: 0;
}

p { margin: 0 0 var(--space-2); }
p.large { font-size: var(--text-lg); color: var(--muted); max-width: var(--prose); }

code {
    font-size: 0.92em;
    background: var(--primary-wash);
    padding: 0.1em 0.35em;
    border-radius: 2px;
}

/* Nur color wird ueberblendet, border-color nicht. Die Menuepunkte tragen
   `border-bottom: 2px solid transparent`, und der Initialwert von border-color
   ist currentColor - startet ein Browser beim ersten Stilauflösen eines neuen
   Dokuments doch eine Transition, laeuft sie von der Textfarbe nach
   transparent, und unter allen Eintraegen blitzt kurz ein Unterstrich auf.
   Ohne die Transition kann das nicht passieren; der Unterstrich beim Hover
   schaltet dafuer hart statt zu blenden. */
a { cursor: pointer; color: var(--dark); text-decoration: none; transition: color 0.15s; }

a:focus-visible,
[tabindex]:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 3px;
    border-radius: 2px;
}

/* The small-caps meta line: dates, roles, section labels, the case index.
   One definition, because six places were spelling it out with three
   different letter-spacings. */
.section-title,
.work-meta,
.area-name,
.quiz-tab,
.more-link a,
.case-index,
.facts dt {
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.14em;
}


/* --- The horizontal grid --------------------------------------------------
   Every full-width strip on the site, in one rule. The header used to centre a
   76rem box and pad inside it, which set its content 2.5rem further in than
   every band below - the logo did not line up with the headline under it. */

.site-header-inner,
.block,
.section,
.case,
.case-index,
.site-footer-inner,
.site-footer-bar {
    padding-inline: var(--pad-x);
}


/* --- Bands ----------------------------------------------------------------
   .block and .section are both bands: they span the viewport and pad inward to
   the column. .band-dark and .band-tint flip the ground and are applied in the
   templates, on the sections carrying third-party material. */

.block,
.section {
    display: block;
    width: 100%;
    margin: 0;
    padding-block: var(--band-y);
    background: var(--light);
}

/* Two bands on the same ground have no edge between them, so their facing
   paddings read as one void of double height. The second drops its top
   padding and the pair sits exactly one band-unit apart, like every other
   boundary on the page. Where the ground does change, the full pair stays. */
:is(.block, .section):not(.band-dark, .band-tint) + :is(.block, .section):not(.band-dark, .band-tint),
.band-dark + .band-dark,
.band-tint + .band-tint { padding-top: 0; }

/* No overflow clip: the scene reaches up past the top of this block, behind
   the header, and clipping would cut exactly that part off. */
/* The statement is short, so the hero no longer gets its height from its
   content the way a headline and four paragraphs did. It takes the viewport
   below the header instead, which is also the stage the scene is drawn on.

   svh rather than vh so the height does not jump when a mobile browser's
   toolbar retracts, and the min() keeps the hero from outgrowing a short
   viewport - the header takes its own space in flow above this. */
.hero {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: flex-start;
    padding-top: var(--band-y-lg);
    /* The header stands above this band, so centring within the hero alone
       drops the text below the middle of the screen by half the header. The
       extra bottom padding lifts it back onto the viewport's centre line. */
    padding-bottom: calc(var(--band-y-lg) + var(--header-h) + 2 * var(--hero-lift));
    min-height: 100vh;
    min-height: var(--hero-h);
}

/* The hero copy sits on its own layer above the scene. Scoped to .hero rather
   than reinstated on every band: this is the only place with anything behind
   the text. */
.hero .block-aside,
.hero .block-body { position: relative; z-index: 1; }


/* The statement. Both sentences carry the same weight - the second is the
   turn, not a footnote to the first - and each opens with its own highlighted
   lead-in instead of one of them being scaled up over the other.

   The lead-in sits on its own line as a marker rather than opening the
   sentence, in the same small-caps register the site already uses for
   SELECTED WORK and CONTACT - so it joins the existing labelling system
   instead of inventing a second one. No colon: the label is not introducing
   a clause, it is naming the block above it.

   The measure widens with the size: at this scale 28rem left about four words
   a line. */
.block-body.statement { position: relative; z-index: 1; max-width: 40rem; }

/* Qualified with .block-body because the statement always sits on one, and a
   plain .statement would tie on specificity with any later .block-body rule. */
.block-body.statement > h1,
.block-body.statement > p {
    font-size: clamp(1.4rem, 2.1vw, 1.85rem);
    line-height: 1.35;
    letter-spacing: -0.015em;
    color: var(--dark);
    max-width: none;
    margin: 0 0 var(--space-5);
}

.block-body.statement > h1 { font-weight: normal; }


/* Das Weiter-Signal. Absolut gesetzt statt im Fluss, damit es die Zentrierung
   des Textes nicht verschiebt, und um --header-h vom Boden abgehoben, weil der
   Hero um diesen Betrag unter der Falz endet. */
/* Als `.band-dark a.hero-more` notiert, nicht als `.hero-more`: `.band-dark a`
   weiter unten faerbt jeden Link im dunklen Band in --on-dark-blue und haette
   sonst gewonnen. Der Pfeil ist aber kein Verweis in den Inhalt, sondern ein
   Bedienelement - Linkblau wuerde hier mit der Aussage konkurrieren. */
.band-dark a.hero-more,
.hero-more {
    position: absolute;
    left: var(--pad-x);
    bottom: calc(var(--header-h) + var(--band-y-sm));
    z-index: 1;
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--light);
    font-size: var(--text-sm);
    letter-spacing: 0.08em;
    opacity: 0.65;
    transition: opacity 0.2s var(--ease);
}

.hero-more:hover,
.hero-more:focus-visible { opacity: 1; }

.hero-more svg { width: 1rem; height: 1rem; flex: none; }

/* Ein kurzer Stups alle paar Sekunden, keine Dauerschleife: der Pfeil soll
   sagen, dass es weitergeht, und nicht um Aufmerksamkeit bitten. */
@media (prefers-reduced-motion: no-preference) {
    .hero-more svg { animation: hero-nudge 3.2s var(--ease) infinite; }
}

@keyframes hero-nudge {
    0%, 72%, 100% { transform: translateY(0); }
    84%           { transform: translateY(3px); }
}

/* Der Versatz gilt nur dort, wo der Header klebt, und das ist unter 900px
   (siehe .site-header im Abschnitt fuer schmale Schirme). Am Schreibtisch
   scrollt der Header mit weg: dort liess der Versatz nach dem Sprung einen
   Streifen des dunklen Heros ueber dem ersten Feld stehen. */
#weiter { scroll-margin-top: 0; }

@media (max-width: 900px) {
    #weiter { scroll-margin-top: var(--header-h); }
}

/* Das Statement setzt seine Farbe selbst und ist spezifischer als die
   .band-dark-Regeln, also muss der dunkle Grund hier eigens bedient werden. */
.band-dark .block-body.statement > h1,
.band-dark .block-body.statement > p { color: var(--light); }
.band-dark .statement .lead { color: var(--on-dark-violet); }
.statement .lead {
    display: block;
    font-size: var(--text-xs);
    font-weight: normal;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--primary-text);
    margin-bottom: var(--space-2);
}

.block-body.statement > p:last-child { margin-bottom: 0; }


.band-dark { background: var(--dark); color: var(--light); }
.band-dark h1, .band-dark h2, .band-dark h3 { color: var(--light); }
.band-dark p { color: var(--on-dark-mute); }
.band-dark .section-title,
.band-dark .block-aside { color: var(--on-dark-violet); }
.band-dark a { color: var(--on-dark-blue); }

.band-tint { background: var(--tint); }

/* The rotated side label becomes a plain label above its band. Rotation only
   worked beside a narrow centred column; a full-width band has no margin to
   rotate into. */
/* The aside and the body share a width. Anything that sets one has to set the
   other, or a wide table in the body will outrun the label above it. */
.block-aside,
.block-body { max-width: var(--column); }

/* Nach rechts geschoben, damit der freie Raum links faellt. Beide Kinder
   nehmen den Versatz, sonst fluchten die Beschriftung und der Text, zu dem sie
   gehoert, nicht mehr.

   Eine Ausnahme fuer einzelne Bloecke, keine Grundregel: als sie einmal fuer
   jedes Band galt, wurde der Hero zweigeteilt und das Impressum verlor seine
   Ausrichtung. Gesetzt wird sie mit `columnBlock: true` im Front-Matter einer
   Seite oder mit der Klasse an einem handgeschriebenen .block. */
.block-column > .block-aside,
.block-column > .block-body {
    max-width: var(--content-w);
    margin-inline-start: auto;
}

.block-column > .block-body p { max-width: var(--prose); }

.block-aside { margin-bottom: var(--space-5); color: var(--primary-text); }
.block-aside > div { display: block; }


/* A block that holds several headed parts needs air above each one; h3's own
   margin only separates it from the text underneath it. */
.block-body > h3 { margin-top: var(--space-5); }
.block-body > h3:first-child { margin-top: 0; }

.block-body a { color: var(--primary-text); border-bottom: 1px solid var(--rule); }
.block-body a:hover { border-bottom-color: var(--tertiary); }
.band-dark .block-body a { border-bottom-color: var(--rule-dark); }
.block-body img, .block-body video { width: 100%; height: auto; }

/* Traegt ihre Schriftgroesse selbst, statt sie vom h6-Element zu erben: auf
   der Rechtsseite gliedern zwei Abschnittsbeschriftungen die Seite, und die
   gehoeren in der Gliederung auf die zweite Ebene, nicht auf die sechste. Bei
   den h6, die die Klasse sonst tragen, aendert das nichts. */
.section-title {
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.22em;
    color: var(--primary-text);
    margin-bottom: var(--space-5);
}
.section-lead { max-width: var(--prose); color: var(--muted); margin-bottom: var(--space-5); }
.band-dark .section-lead { color: var(--on-dark-mute); }


/* --- Header ---------------------------------------------------------------
   Not sticky. A band design already tells the reader where they are by ground
   colour, and a bar following them down the page fights that. */

/* Nicht sticky. Ein Balken, der beim Scrollen mitlaeuft, arbeitet gegen das
   Band-Prinzip - der Grundwechsel sagt dem Leser, wo er ist.

   Sticky war eine Zeit lang aus einem technischen Grund noetig: Safari 26
   toent seine Leisten aus fixed und sticky Elementen am Viewport-Rand und
   faellt sonst auf den body zurueck, und solange der Header hell und der body
   dunkel war, brauchte der obere Rand ein eigenes helles Element. Seit Header
   und body beide --dark tragen, liefert der Rueckfall schon das Richtige. */
.site-header {
    position: relative;
    z-index: 10;
    background: var(--light);
}


.site-header-inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-5);
    padding-block: var(--space-4);
}

/* Lower case throughout, matching the wordmark. Done here rather than in
   en.json/de.json because those strings are also the case-index aria-label and
   the footer link text, which stay capitalised. */
/* Dunkler Header. Faellt auf den Seiten an, die mit einem dunklen Hero
   oeffnen - dort gehen Header und Hero in eine Flaeche ueber. Ueberall sonst
   traefe der dunkle Balken direkt auf ein helles Band.

   Gesteuert von `darkHeader: true` im Front Matter, weil damit zugleich die
   Logodatei umgestellt wird: die Farben stecken im SVG, ein <img> kann sie
   nicht umfaerben, also braucht es zwei Dateien und einen Schalter, der beide
   Seiten derselben Entscheidung bedient. */
.dark-header .site-header { background: var(--dark); }
.dark-header .site-header a { color: var(--light); }
.dark-header .site-header .menu-item a[aria-current="page"] { border-bottom-color: var(--on-dark-violet); }

.site-header .menu { display: flex; gap: var(--space-4); font-size: var(--text-base); text-transform: lowercase; }
.site-header .menu-item { white-space: nowrap; }
.site-header .menu-item a { padding-bottom: 3px; border-bottom: 2px solid transparent; }
.site-header .menu-item a:hover { border-bottom-color: var(--tertiary); }
.site-header .menu-item a[aria-current="page"] { border-bottom-color: var(--primary); }

/* Logo right, menu left - the way it sits on the printed stationery. Both
   follow DOM order now, so there are no `order` declarations to keep in sync;
   the logo just claims the free space to its left. */
.site-header .site-logo { display: flex; margin-left: auto; }
/* Sized off the wordmark, not the overall box: in this lockup the mark is
   nearly the full height and the wordmark only about 40% of it, so matching
   the old 1.8rem overall would have set the word at 12px. */
.site-header .site-logo img { height: 2.6rem; width: auto; display: block; }


/* --- Rechtsseiten ---------------------------------------------------------
   Impressum und AGB sind Nachschlagetexte, keine Lesetexte: man sucht darin
   eine Zeile und liest nicht von oben nach unten. In Grundschriftgroesse wird
   das eine Wand. Kleiner gesetzt liest es sich als das, was es ist, und der
   Absatz, den man sucht, ist schneller zu ueberblicken.

   Die Ueberschriften ziehen mit, sonst stuende ueber der Kleinschrift eine
   Zwischenueberschrift in 1.7rem und der Sprung waere groesser als vorher.
   Sie bleiben aber deutlich groesser als der Fliesstext - sie sind der Weg
   hinein, und wenn sie nur noch fett waeren, gaebe es keinen mehr.

   Die 404 traegt utility, aber nicht legal: sie ist kurz und braucht das
   nicht. */

.legal .block-body p,
.legal .block-body li {
    font-size: var(--text-sm);
    line-height: 1.65;
    /* Das Zeilenmass bleibt, wo es war. --prose ist 34em und rechnet gegen die
       eigene Schriftgroesse, am kleineren Absatz waeren daraus 28.9rem statt
       34 geworden - der Block also schmaeler statt nur kleiner gesetzt. 34rem
       ist derselbe Wert, nur gegen die Wurzel gerechnet.

       Nicht am block-body begrenzt, obwohl das ohne zweite Zahl auskaeme:
       block-column richtet den rechtsbuendig aus, eine schmalere Begrenzung
       dort haette die ganze Spalte um 286px nach rechts gezogen. */
    max-width: 34rem;
}

.legal .block-body ul { margin: 0 0 var(--space-2); padding-left: 1.2em; }

/* 1.7rem ueber 0.85rem waere das Doppelte. --text-md haelt den Abstand, der
   auf den anderen Seiten zwischen h3 und Fliesstext steht. Die
   Abschnittsbeschriftung ist davon ausgenommen, sie ist ohnehin --text-xs. */
.legal .block-body :is(h2, h3):not(.section-title) {
    font-size: var(--text-md);
    letter-spacing: 0;
    margin-bottom: var(--space-2);
}


/* --- Arbeitsfelder --------------------------------------------------------
   Vier Kapitel statt einer Projektliste. Der Aufbau je Feld ist immer derselbe:
   Name, Claim, Standortbestimmung, Material, Notizen. Das Material haengt am
   Feld und steht nicht fuer sich - ein Video ohne Erklaerung ist dadurch der
   Beleg zu einer These und keine lose Datei mehr. */

/* Eigener Grund, wie jedes Band. Der body traegt --dark, damit iOS die
   Adressleiste richtig einfaerbt; was seinen Grund nicht selbst setzt, steht
   sonst dunkel auf dunkel - hier waren es die vier Feldnamen. */
.area {
    padding-block: var(--band-y);
    padding-inline: var(--pad-x);
    background: var(--light);
}

/* Zwei Felder auf demselben Grund haetten sonst den doppelten Abstand
   zwischen sich - dieselbe Regel wie zwischen zwei Baendern. */
.area + .area { padding-top: 0; }

.area-head,
.area-material,
.area-notes {
    max-width: var(--content-w);
    margin-inline-start: auto;
}

.area-head { margin-bottom: var(--space-6); }
.area-head h2 { margin-bottom: var(--space-3); }

/* Der Claim traegt das Feld auch dann, wenn wenig Material daran haengt.
   Deshalb im groesseren Grad als der Rest und auf Lesebreite begrenzt. */
.area-claim {
    font-size: var(--text-lg);
    color: var(--muted);
    max-width: var(--prose);
    margin-bottom: var(--space-3);
}

/* Eine Zeile, abgesetzt: wo das Feld gerade steht. */
.area-stance {
    font-size: var(--text-sm);
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--primary-text);
    max-width: var(--prose);
}

.area-material { display: grid; gap: var(--space-6); }

.entry { display: grid; gap: var(--space-4); align-items: start; }

@media (min-width: 56rem) {
    /* Bild links, Text rechts. Der Text bekommt mehr Platz als das Bild, weil
       das Material den Beleg liefert und nicht die Aussage. */
    .entry { grid-template-columns: 16rem 1fr; gap: var(--space-5); }
}

.entry-media { margin: 0; }
.entry-media .case-media { border-radius: var(--radius); }
.entry-body h3 { font-size: var(--text-xl); margin-bottom: var(--space-1); }
.entry-body p { color: var(--muted); max-width: var(--prose); }

/* Kundenstimme am Projekt, nicht in einer eigenen Sektion: hier belegt sie
   etwas, dort wuerde sie nur schmuecken. */
.entry-quote {
    margin: var(--space-3) 0 0;
    padding-left: var(--space-3);
    border-left: 2px solid var(--primary-line);
}

.entry-quote blockquote { margin: 0 0 var(--space-2); font-size: var(--text-md); }
.entry-quote figcaption { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); color: var(--muted); }
.entry-quote figcaption img { height: 1.4rem; width: auto; }

/* Der Einzeiler auf einen Eintrag, der anderswo wohnt. Bewusst schmal
   gehalten: er soll auf die Stelle zeigen und nicht mit ihr konkurrieren. */
.area-crossref {
    font-size: var(--text-sm);
    color: var(--muted);
    margin: 0;
}

.area-notes { margin-top: var(--space-6); }
.area-notes > h3 {
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.22em;
    color: var(--primary-text);
    margin-bottom: var(--space-4);
}

.note { max-width: var(--prose); margin-bottom: var(--space-4); }
.note h4 { font-size: var(--text-md); margin-bottom: var(--space-1); }
.note p { color: var(--muted); margin: 0; }


/* Die Feldliste auf der Startseite. Kein Raster aus Karten: vier Felder sind
   eine Aufzaehlung und keine Auslage. Das Bild steht deshalb frei auf dem
   Bandgrund - kein Rahmen, keine Flaeche, keine abgerundete Ecke, sonst waere
   es wieder eine Karte.

   Zwei mal zwei. Die Ordnung im Eintrag macht das Gewicht: Bild, darunter der
   Name klein als Beschriftung, die Frage als Ueberschrift, ein Absatz. */

.area-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: var(--space-6);
}

.area-list > li { max-width: var(--prose); }

@media (min-width: 56rem) {
    .area-list { grid-template-columns: repeat(2, 1fr); gap: var(--space-7) var(--space-6); }
    /* Im Raster gibt die Spalte das Mass vor; --prose wuerde die zweite
       Spalte auf halber Hoehe abschneiden. */
    .area-list > li { max-width: none; }
}

/* Ein einheitliches Seitenverhaeltnis fuer alle vier, per style-Attribut aus
   den Daten: vier Vorschauen in vier Formaten lesen sich als Versehen. Der
   Grund darunter haelt den Platz, solange das Bild laedt. */
.area-image {
    display: block;
    width: 100%;
    height: auto;
    object-fit: cover;
    background: var(--tint-strong);
    margin-bottom: var(--space-3);
}

/* Wieder ein Link auf die Feldseite, seit diese zurueck im Build ist. Ohne
   Unterstrich: die Zeile ist eine Kapitaelchen-Beschriftung, und eine Linie
   unter gesperrten Versalien liest sich als Trennstrich, nicht als Verweis.
   Die Hover-Farbe traegt das Signal stattdessen. */
.area-name {
    display: block;
    color: var(--primary-text);
    margin-bottom: var(--space-1);
    text-decoration: none;
}

a.area-name:hover { color: var(--tertiary); }

.band-dark .area-name { color: var(--on-dark-violet); }
.band-dark a.area-name:hover { color: var(--light); }

/* Die Frage ist die Ueberschrift des Eintrags und wird auch so gesetzt: h3 in
   der Textfarbe, nicht gedaempft und nicht in Fliesstextgroesse. */
.area-question {
    font-size: var(--text-lg);
    line-height: 1.25;
    color: var(--dark);
    margin: 0 0 var(--space-2);
}

.band-dark .area-question { color: var(--light); }

.area-focus { color: var(--muted); line-height: 1.6; margin: 0; }
.band-dark .area-focus { color: var(--on-dark-mute); }


/* Die Behauptung eines Pfeilers: ein h2, eine Spur kleiner gesetzt als eines.
   Drei davon stehen untereinander, und in voller h2-Groesse waren sie zu dritt
   lauter als die eine Ueberschrift, die der Abschnitt darunter hat. Die
   Spanne bleibt die des h2, nur um eine Stufe nach unten verschoben, damit
   der Abstand zum Fliesstext derselbe bleibt.

   Ohne automatische Trennung: sie bricht sonst mitten im Wort ("Der Unterbau
   dazu: si-cher"). Die drei Saetze sind kurz und kompositumsarm genug, dass
   die Trennung nichts rettet, was ohne sie nicht auch umbraeche - dieselbe
   Ueberlegung wie beim h1. */
.pillar-claim {
    font-size: clamp(1.7rem, 3.4vw, 2.5rem);
    hyphens: manual;
}


/* --- Die drei Pfeiler unter dem Hero --------------------------------------
   Erbt die Bausteine der Felder - .area-image, .area-name, .area-question,
   .area-focus - und setzt nur, was hier anders ist: das Raster und die
   Belegzeile. Die beiden Fassungen dieses Abschnitts sollen sich in der
   Aussage unterscheiden und nicht in der Typografie.

   Untereinander statt nebeneinander. Drei Spalten standen hier zuerst, wie die
   vier Felder in zwei mal zwei stehen - aber ein Feld ist eine Frage und ein
   Absatz, ein Pfeiler sind Beschriftung, Behauptung, Absatz und Belegzeile.
   Drei davon nebeneinander ergeben kurze, gehetzte Zeilen und drei Bilder, die
   zu klein sind, um etwas zu zeigen.

   Jeder Pfeiler ist deshalb eine Zeile ueber die Bandbreite: ab 56rem Bild
   links, Text rechts, darunter Bild ueber Text. Die Bildspalte ist in rem
   gedeckelt und nicht in Prozent - sonst waechst das Bild auf einem breiten
   Schirm weiter, waehrend der Text bei --prose stehenbleibt, und das
   Verhaeltnis kippt.

   Der Abstand zwischen den Zeilen ist groesser als der innerhalb einer Zeile:
   sonst haengt das naechste Bild am vorigen Absatz statt an seinem eigenen
   Text. */

.pillar-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: var(--space-7);
}

.pillar-list > li {
    display: grid;
    gap: var(--space-3);
    max-width: var(--prose);
}

.pillar-text { max-width: var(--prose); }

/* Der Platz, den vorher das Bild hatte. Die drei Schichten sind verschieden
   breit, die Zelle ist es nicht: sie ist so breit wie der Sockel und haelt
   die Textspalte auf einer Linie, egal welche Schicht daneben steht. Die
   Schicht sitzt unten in der Zelle, damit die drei beim Herunterlesen
   uebereinanderstehen und nicht versetzt schweben. */
.pillar-figure {
    /* Die Masseinheit des Koerpers steht hier und nicht an .pyr: die Zelle
       rechnet mit ihr (siehe den Ausgleich auf schmalen Schirmen), und zwei
       Stellen mit derselben Zahl laufen auseinander. */
    --u: 2.3rem;

    display: flex;
    align-items: flex-end;
    justify-content: center;
    min-height: 9rem;
    /* Der Koerper haengt tiefer, als er soll. Er steht unten in seiner Zelle,
       und die Projektion ragt zusaetzlich unter den Kasten - damit stiess die
       Sockelschicht fast an die Kante zum dunklen Band, waehrend ueber der
       Spitze das ganze Bandpolster stand. Gemessen: 166px oben gegen 26px
       unten. Die Polsterung hier hebt die drei um die halbe Differenz an, also
       auf gut 100px auf beiden Seiten - etwa das Mass, das die Textspalte zu
       den Bandkanten haelt.

       Polsterung und nicht ein Versatz per transform: die Zelle ist mit
       min-height hoeher als ihr Inhalt, die Polsterung passt also hinein und
       aendert an der Zeilenhoehe nichts. Ein transform haette den Koerper aus
       seinem Kasten geschoben und die Perspektive mitverzogen. */
    padding-bottom: 4rem;
    perspective: 900px;
    perspective-origin: calc(50% + var(--cam-panX, 0%)) calc(50% + var(--cam-panY, 0%));
    pointer-events: none;
}


/* --- Die Pyramide, waagrecht in drei Schichten geschnitten -----------------
   Dieselbe Bauweise wie der Wuerfel im Hero und dieselben Flaechenfarben:
   beide Koerper stehen in demselben Licht, sonst waeren es zwei Objekte, die
   sich zufaellig aehneln.

   Die Geometrie steht als Zahl da, nicht als Rechnung. Eine Pyramide mit der
   halben Grundbreite 3u und der Hoehe 5.4u, in drei Schichten zu je 1.8u
   geschnitten: die Schnitte liegen bei den halben Breiten 2u und 1u. Damit ist
   die Schraege jeder Schicht dieselbe - jede Seitenflaeche ist 2.0591u lang
   (sqrt(1.8^2 + 1^2)) und lehnt sich um 29.05deg (atan(1/1.8)) nach innen.

   Steiler als eine echte Cheops-Pyramide (die waere 3.8u hoch). Von schraeg
   oben gesehen staucht die Perspektive die Hoehe, und bei der echten
   Proportion sahen die drei Schichten wie flache Platten aus statt wie
   Abschnitte eines Koerpers.
   sqrt() und atan() koennte der Browser rechnen; ausgeschrieben laeuft es
   auch dort, wo er es noch nicht kann, und die Herleitung steht ohnehin hier.

   Eine Seitenflaeche ist ein Rechteck mit clip-path: ein Trapez laesst sich
   nicht als Kasten setzen, wohl aber beschneiden. Der Beschnitt oben ist
   (b - t) / 2b je Seite, also ein Sechstel beim Sockel, ein Viertel in der
   Mitte und die Haelfte bei der Spitze - dort wird aus dem Trapez das
   Dreieck, das es sein soll.

   Gedreht wird um die Unterkante (transform-origin unten): rotateY stellt die
   Flaeche in ihre Himmelsrichtung, translateZ schiebt sie um b nach aussen,
   rotateX legt sie um ihre eigene Unterkante nach innen. Deshalb steht in
   keiner Flaeche ein Winkel, der mit der Kamera zu tun haette - die traegt der
   Koerper. */

.pyr {
    /* --u kommt von .pillar-figure. */
    /* Laenge einer Seitenflaeche und ihre Neigung nach innen. Fuer alle drei
       Schichten dieselbe, weil alle drei aus derselben Pyramide kommen. */
    --slant: 2.0591;
    --lean: 29.05deg;
    /* Durchscheinend wie der Wuerfel im Hero. Bei 92% war der Koerper
       undurchsichtig und damit ein anderes Material; etwas dichter als die 55%
       des Wuerfels steht er hier trotzdem, weil er auf hellem Grund liegt und
       nicht auf dunklem. */
    --pyr-alpha: 70%;

    position: relative;
    display: block;
    width: calc(2 * var(--b));
    height: calc(1.8 * var(--u));
    transform-style: preserve-3d;
    /* Von oben, damit die Schnittflaeche zu sehen ist - sie ist der ganze
       Witz. Ohne Neigung stuende die Pyramide in der Seitenansicht und waere
       ein Dreieck. Der Winkel ist am Schirm gesucht und nicht gerechnet
       worden: zu flach, und das Auge nimmt die Schnittflaeche als Unterseite,
       zu steil, und der Koerper verzieht sich. */
    transform: rotateX(calc(-17deg + var(--cam-tiltY, 0deg)))
               rotateY(calc(38deg + var(--cam-yawX, 0deg)));
}

/* Je Schicht ihre Masse und ihre Farbe. Die Spitze traegt das Blau, der
   Sockel das Violett des Wuerfels, die mittlere Schicht steht Flaeche fuer
   Flaeche genau dazwischen - als color-mix und nicht als dritter Satz Zahlen,
   damit sie die Mitte bleibt, wenn eine der beiden Seiten geaendert wird. */
.pyr-l1 {
    --b: calc(1 * var(--u)); --t: 0rem; --inset: 50%;
    --f-top:   var(--face-alt-top);
    --f-front: var(--face-alt-front);
    --f-right: var(--face-alt-right);
    --f-left:  var(--face-alt-left);
    --f-back:  var(--face-alt-back);
    --f-edge:  var(--face-alt-edge);
}

.pyr-l2 {
    --b: calc(2 * var(--u)); --t: calc(1 * var(--u)); --inset: 25%;
    --f-top:   color-mix(in srgb, var(--face-alt-top)   50%, var(--face-top));
    --f-front: color-mix(in srgb, var(--face-alt-front) 50%, var(--face-front));
    --f-right: color-mix(in srgb, var(--face-alt-right) 50%, var(--face-right));
    --f-left:  color-mix(in srgb, var(--face-alt-left)  50%, var(--face-left));
    --f-back:  color-mix(in srgb, var(--face-alt-back)  50%, var(--face-back));
    --f-edge:  color-mix(in srgb, var(--face-alt-edge)  50%, var(--face-edge));
}

.pyr-l3 {
    --b: calc(3 * var(--u)); --t: calc(2 * var(--u)); --inset: 16.667%;
    --f-top:   var(--face-top);
    --f-front: var(--face-front);
    --f-right: var(--face-right);
    --f-left:  var(--face-left);
    --f-back:  var(--face-back);
    --f-edge:  var(--face-edge);
}

/* Die gezeichnete Kante. --edge-alpha hat keinen Standardwert und keine
   eigene Deklaration an .pyr: der Rueckfall im var() ist der Standard. Stuende
   er als Deklaration da, ueberschriebe er den geerbten Wert genau dort, wo er
   ankommen soll. */
.pyr-face,
.pyr-cut { --f-line: color-mix(in srgb, var(--f-edge) var(--edge-alpha, 100%), transparent); }

.pyr-face {
    position: absolute;
    bottom: 0;
    left: 50%;
    width: calc(2 * var(--b));
    height: calc(var(--slant) * var(--u));
    margin-left: calc(-1 * var(--b));
    transform-origin: 50% 100%;
    clip-path: polygon(var(--inset) 0, calc(100% - var(--inset)) 0, 100% 100%, 0 100%);
    /* Nur die beiden waagrechten Kanten werden gezeichnet. Die schraegen
       liegen im Beschnitt und kaemen als Rechteckkante heraus, also an der
       falschen Stelle; die Silhouette traegt stattdessen der Farbunterschied
       der Nachbarflaeche. Uebrig bleiben die Schnittlinien - genau die, die
       zeigen sollen, dass hier etwas abgeschnitten wurde.

       Die Kante ringsum war einmal da, ueber ein Pseudoelement mit demselben
       Beschnitt: der Koerper bekam damit eine durchgezogene Silhouette und sah
       aus wie ausgeschnittenes Papier. Wieder raus. */
    border-top: 1px solid var(--f-line);
    border-bottom: 1px solid var(--f-line);
}

.pyr-front { background: color-mix(in srgb, var(--f-front) var(--pyr-alpha), transparent); transform: rotateY(   0deg) translateZ(var(--b)) rotateX(var(--lean)); }
.pyr-right { background: color-mix(in srgb, var(--f-right) var(--pyr-alpha), transparent); transform: rotateY(  90deg) translateZ(var(--b)) rotateX(var(--lean)); }
.pyr-back  { background: color-mix(in srgb, var(--f-back)  var(--pyr-alpha), transparent); transform: rotateY( 180deg) translateZ(var(--b)) rotateX(var(--lean)); }
.pyr-left  { background: color-mix(in srgb, var(--f-left)  var(--pyr-alpha), transparent); transform: rotateY( -90deg) translateZ(var(--b)) rotateX(var(--lean)); }

/* Die Schnittflaeche oben, waagrecht auf Schichthoehe. Sie liegt im Licht wie
   die Deckflaeche des Wuerfels und traegt deshalb dieselbe Farbe. Die Spitze
   hat keine - sie ist nicht abgeschnitten. */
.pyr-cut {
    position: absolute;
    bottom: 0;
    left: 50%;
    width: calc(2 * var(--t));
    height: calc(2 * var(--t));
    margin-left: calc(-1 * var(--t));
    margin-bottom: calc(-1 * var(--t));
    background: color-mix(in srgb, var(--f-top) var(--pyr-alpha), transparent);
    border: 1px solid var(--f-line);
    transform: translateY(calc(-1.8 * var(--u))) rotateX(90deg);
}

/* --- Ausgewaehlte Arbeiten -------------------------------------------------
   Bildgefuehrt und wortkarg: nur Bilder, zwei Spalten, kein Rahmen und keine
   Karte. Das Bild steht auf dem Bandgrund wie jedes andere auf der Seite.

   Den Versatz der beiden Spalten macht die Ueberschrift. Sie steht im Kasten
   der linken Spalte, schiebt deren Bilder um ihre eigene Hoehe hinunter, und
   die rechte faengt oben an. Vorher schob ein transform die rechte Spalte um
   einen gegriffenen Betrag nach unten; das sah gleich aus, hatte aber keinen
   Grund und musste den fehlenden Platz unten per Polsterung nachreichen.

   Zwei Kaesten und kein Raster ueber alle vier: in einem Raster laege die
   Ueberschrift in einer Zeile mit dem ersten Bild, die Zeilenhoehe waere die
   des Bildes, und der Versatz betruege eine ganze Bildhoehe. */

.work-cols {
    display: grid;
    gap: var(--space-6);
}

.work-col {
    display: grid;
    gap: var(--space-6);
    /* Sonst zieht das Raster die Bilder auf gleiche Hoehe auseinander, sobald
       die beiden Spalten verschieden hoch sind. */
    align-content: start;
    max-width: var(--prose);
}

@media (min-width: 56rem) {
    .work-cols { grid-template-columns: repeat(2, 1fr); gap: var(--space-5); }
    .work-col { max-width: none; }
}

/* Kapitaelchenzeile, Ueberschrift, Absatz - derselbe Kopf wie ueber den
   anderen Abschnitten.

   Groesse, Zeilenabstand und Laufweite kommen vom h2 und stehen hier nicht
   noch einmal: dieselbe Stufe wie "Christoph Doeberl" und "Wo beginnt
   Innovation?", die beiden anderen Abschnittsueberschriften der Seite. Eine
   eigene, kleinere Stufe stand hier und ist raus - drei Abschnitte, drei
   Groessen.

   Nur der Abstand nach unten ist groesser als der eines h2: darunter folgt
   kein Absatz, sondern ein Bild, und der Versatz der beiden Spalten ist genau
   dieser Abstand plus die Hoehe der Zeile. */
/* Der Kopf ist eine Rasterzelle und kein Stapel aus dreien. Die Spalte ist
   ein Raster mit Bildabstand zwischen ihren Zellen; standen Beschriftung,
   Ueberschrift und Absatz einzeln darin, lag zwischen jeder Zeile der Abstand
   eines Bildes. Im eigenen Kasten setzen die drei ihre Abstaende wieder
   selbst, und nach aussen halten sie zusammen den einen Abstand zum ersten
   Bild. */
.work-head > .section-title { margin-bottom: var(--space-2); }
.work-head > .work-headline { margin-bottom: var(--space-2); }
.work-head > .section-lead { margin-bottom: 0; }

/* Die Kachel ist der Bezugsrahmen fuer das Schild darin. figure und
   figcaption, weil genau das hier steht: ein Medium mit seiner Beschriftung -
   nur liegt sie darauf und nicht darunter. Kein Rand und keine Flaeche, das
   Bild ist die Kachel. */
.work-tile {
    position: relative;
    margin: 0;
}

/* Das Schild in der oberen linken Ecke. Dieselbe Kapitaelchenzeile wie die
   Abschnittsbeschriftungen der Seite, damit es kein zweites Formenrepertoire
   aufmacht - nur eben hell auf dunklem Grund, weil es auf einem Bild liegt,
   dessen Helligkeit niemand kennt.

   Der Grund war eine Fassung lang violett (--primary-text) und ist wieder
   grau: farbig wurde das Schild zum lautesten Punkt der Kachel und stand vor
   dem Bild, statt es zu beschriften. Die Farbe der Seite tragen die
   Beschriftungen ringsum, dieses Schild liegt auf fremdem Material.

   Der Grund ist nicht ganz deckend: das Schild soll auf dem Bild liegen und
   nicht ein Loch hineinschneiden. Deckend genug, dass die Versalien auch auf
   einer hellen Stelle stehen - der Loop mit dem Knotengraph ist fast weiss. */
.work-badge {
    position: absolute;
    top: var(--space-2);
    left: var(--space-2);
    padding: 0.55em 0.85em;
    font-size: var(--text-sm);
    line-height: 1;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--light);
    background: color-mix(in srgb, var(--dark) 86%, transparent);
    border-radius: var(--radius);
    /* Ein Video faengt den Zeiger; das Schild soll ihn durchlassen, sonst
       stockt der Loop, sobald jemand darueberfaehrt. */
    pointer-events: none;
}

/* Stillgelegt, solange die Kachel nirgendwohin fuehrt: work-item.njk gibt das
   Zeichen nicht aus (die Markierung steht dort auskommentiert). Die Regeln
   bleiben stehen, damit beides an einem Tag wieder zusammenfindet, wenn die
   Materialseite offen ist.

   Das Zeichen in der unteren rechten Ecke, gegenueber dem Schild: ein Pfeil im
   Kreis, der unter dem Zeiger aus der Waagrechten in die Diagonale dreht. Es
   sagt, dass die Kachel etwas tut, wenn man sie anfasst - beim Video faengt
   der Loop an zu laufen.

   Derselbe Grund und dieselbe Deckung wie das Schild, damit die beiden Ecken
   zusammengehoeren. Der Kreis ist in em gemessen und haengt damit an der
   Schriftgroesse des Pfeils, nicht an einer zweiten Zahl.

   pointer-events: none, wie beim Schild: beide liegen auf dem Video, und ein
   Zeiger, der ueber sie faehrt, darf den Loop nicht anhalten.

   Gedreht wird 45deg - aus dem waagrechten Pfeil wird der diagonale, der
   ueberall "aufmachen" heisst. Bei :focus-within dasselbe, sonst bliebe das
   Zeichen fuer den, der mit der Tastatur hierher kommt, stumm. */
.work-go {
    position: absolute;
    right: var(--space-2);
    bottom: var(--space-2);
    display: grid;
    place-items: center;
    width: 2.4em;
    height: 2.4em;
    font-size: var(--text-sm);
    color: var(--light);
    background: color-mix(in srgb, var(--dark) 86%, transparent);
    border-radius: 50%;
    pointer-events: none;
}

.work-go svg { width: 1.15em; height: 1.15em; }

.work-tile:hover .work-go svg,
.work-tile:focus-within .work-go svg { transform: rotate(-45deg); }

@media (prefers-reduced-motion: no-preference) {
    .work-go svg { transition: transform 0.25s var(--ease); }
}

/* Hochformat 4:3, also 3:4, mittig beschnitten. Ohne Hoechstbreite: die
   Kachel nimmt die Spalte, wie jedes andere Bild auf der Seite auch. */
.work-image {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 3 / 4;
    object-fit: cover;
    object-position: center;
    background: var(--tint-strong);
}


/* --- Die Pyramide am Stueck ------------------------------------------------
   Probe an der Stelle des Wuerfels im Hero: dieselben drei Schichten,
   aufeinandergesetzt statt auseinandergezogen.

   Der Rumpf traegt die Drehung, die Schicht nur noch ihre Hoehe im Stapel -
   sonst drehte sich jede Schicht um ihre eigene Mitte und der Koerper fiele
   auseinander. Die Regel ist deshalb hoeher spezifiziert als die an .pyr und
   ueberschreibt deren Drehung.

   --level ist die Lage im Stapel, von unten gezaehlt: der Sockel bleibt
   stehen, die Mitte steigt um eine Schichthoehe, die Spitze um zwei. */

.pyr-full {
    --u: 1.6rem;

    position: relative;
    display: block;
    width: calc(6 * var(--u));
    height: calc(5.4 * var(--u));
    transform-style: preserve-3d;
}

.pyr-full .pyr {
    position: absolute;
    left: 50%;
    bottom: 0;
    margin-left: calc(-1 * var(--b));
    transform: translateY(calc(-1.8 * var(--u) * var(--level)));
}

/* Die Schnittflaechen bleiben weg, solange der Koerper zusammengesetzt ist.
   Sie liegen dann innen - sichtbar nur, weil die Flaechen durchscheinen - und
   sie sind es, die das Clipping ausgeloest haben: CSS sortiert 3D-Flaechen als
   ganze Elemente und kennt keinen Tiefenpuffer je Pixel. Eine waagrechte
   Innenflaeche, die genau in der Fuge zweier Schichten liegt, ist gegenueber
   den vier Seitenflaechen mehrdeutig, und bei einem Teil der Kamerawinkel
   entscheidet der Browser falsch: dann schiebt sich ein Keil der Innenflaeche
   ueber eine Aussenflaeche. Nachgesehen ueber das ganze Schwenkfeld der Kamera
   (Gierung 22 bis 54 Grad, Neigung -26 bis -8) - mit den Innenflaechen an
   mehreren Stellen, ohne sie an keiner.

   Unten, wo die Schichten einzeln stehen, bleiben sie: dort sind sie
   Aussenflaechen und zeigen den Schnitt, um den es geht. */
.pyr-full .pyr-cut { display: none; }

.pyr-full .pyr-l3 { --level: 0; --delay:   0ms; }
.pyr-full .pyr-l2 { --level: 1; --delay: 320ms; }
.pyr-full .pyr-l1 { --level: 2; --delay: 640ms; }


/* Der Aufbau. Beim Laden faellt jede Schicht von oben auf ihren Platz, von
   unten nach oben: erst der Sockel, dann die Mitte, dann die Spitze. Der
   Koerper setzt sich also vor dem Leser zusammen, statt fertig dazustehen -
   und nimmt damit die Leserichtung des Abschnitts darunter vorweg, nur in der
   Gegenrichtung.

   Zwei Animationen statt einer, und das ist kein Versehen: opacity an einer
   Schicht wuerde deren preserve-3d aufheben - der Browser gruppiert das
   Element dann und legt seine Flaechen flach. Die Schicht bekommt deshalb nur
   den Fall, ihre Flaechen nur die Blende. Beide laufen auf dieselbe Dauer und
   dieselbe Verzoegerung, --delay steht an der Schicht und wird vererbt.

   Der Fall laeuft ueber translate und nicht ueber transform: transform traegt
   bereits die Lage im Stapel, und die Eigenschaft translate wird davor
   angewandt, also in der Achse des Koerpers. Die Schicht faellt damit entlang
   der Pyramidenachse und nicht schraeg ueber den Schirm.

   Nur hier und nur einmal beim Laden: die drei Abschnitte weiter unten stehen
   in Zeilen und sollen nicht wackeln, wenn jemand scrollt. */

@keyframes pyr-stack {
    from { translate: 0 -2.5rem; }
    to   { translate: 0 0; }
}

@keyframes pyr-appear {
    from { opacity: 0; }
    to   { opacity: 1; }
}

@media (prefers-reduced-motion: no-preference) {
    .pyr-full .pyr {
        animation: pyr-stack 620ms cubic-bezier(0.2, 0.7, 0.3, 1) var(--delay) both;
    }

    .pyr-full .pyr-face,
    .pyr-full .pyr-cut {
        animation: pyr-appear 620ms ease-out var(--delay) both;
    }
}

/* Im Hero folgt der Koerper dem Zeiger wie der Wuerfel, ueber dieselben
   --cam-* Variablen und dasselbe data-camera am .hero-cube. */
.hero-cube .pyr-full {
    --u: 1.9rem;

    /* Auf dem dunklen Band stand die Kante zu hell: sie ist als heller Strich
       auf hellem Grund entworfen, und dort traegt der Grund die Haelfte davon
       weg. Hier zieht sie sich zurueck und die Fuge bleibt eine Fuge. */
    --edge-alpha: 40%;

    transform: rotateX(calc(-17deg + var(--cam-tiltY, 0deg)))
               rotateY(calc(38deg + var(--cam-yawX, 0deg)));
}


/* Auf schmalen Schirmen steht die Schicht ueber dem Text und bekommt keine
   eigene Spalte mehr. Sie wird kleiner, sonst nimmt der Sockel die halbe
   Bildschirmhoehe.

   Mittig statt linksbuendig. Linksbuendig war sie an der Textkante
   ausgerichtet und musste dafuer um den Ueberhang der gedrehten Grundflaeche
   eingerueckt werden (3u * (cos38 + sin38) = 4.21u, also 1.21u je Seite) -
   eine Rechnung, die nur aufgeht, solange Drehung und Grundbreite bleiben, wie
   sie sind. Mittig ist der Ueberhang auf beiden Seiten gleich und niemand muss
   ihn ausgleichen; ausserdem steht der Koerper dort, wo auf einem Telefon
   hingesehen wird, und nicht am Rand.

   Etwas Luft nach unten, damit die Schicht nicht auf ihrer Beschriftung sitzt:
   sie steht unten in ihrer Zelle, und ihre Projektion reicht zusaetzlich unter
   den Kasten. */
@media (max-width: 56rem) {
    .pillar-figure {
        --u: 1.5rem;
        min-height: 7rem;
        justify-content: center;
        padding-left: 0;
        padding-bottom: var(--space-3);
    }
}

@media (min-width: 56rem) {
    .pillar-list > li {
        grid-template-columns: minmax(0, 24rem) minmax(0, 1fr);
        gap: var(--space-6);
        align-items: start;
        max-width: none;
    }
}


/* --- Rasterfassung der Feldseite ------------------------------------------
   Entwurf neben der Listenfassung. Ein Masonry je Projekt.

   Kein Zuschnitt: jede Kachel traegt das echte Seitenverhaeltnis ihrer Datei,
   das Bild fuellt sie ganz und wird nie beschnitten. Eine fruehere Fassung
   setzte die Bilder mit object-fit: cover in Zellen fester Rasterhoehe - der
   Beschnitt fiel damit praktisch zufaellig aus und traf bei Hochformaten das
   Motiv.

   column-count statt grid: die Kacheln fallen in die Spalte mit dem meisten
   Platz, ohne dass ihre Hoehe vorher feststehen muss. Genau das braucht ein
   Feld aus Hoch-, Quer- und Textformaten. */

.field {
    padding-block: var(--band-y);
    padding-inline: var(--pad-x);
    background: var(--light);
}

.field + .field { padding-top: 0; }
.field-head { margin-bottom: var(--space-5); }

.project + .project { margin-top: var(--space-6); }
.project-head { margin-bottom: var(--space-3); }
.project-head h3 { font-size: var(--text-xl); margin: 0 0 var(--space-1); }
.project-head .work-meta { margin: 0; }

.tile-field {
    columns: var(--cols, 2);
    column-gap: var(--space-3);
}

/* Eine Kachel darf nicht ueber den Spaltenumbruch zerrissen werden - bei
   einem Bild waere das Ergebnis genau der Beschnitt, den diese Fassung
   vermeiden soll. */
.tile {
    break-inside: avoid;
    -webkit-column-break-inside: avoid;
    margin-bottom: var(--space-3);
    border-radius: var(--radius);
    overflow: hidden;
}

.tile-media { background: var(--tint-strong); }

.tile-media img,
.tile-media video {
    display: block;
    width: 100%;
    height: auto;
}

/* Wo das Verhaeltnis in den Daten fehlt, bestimmt die Datei selbst die Hoehe.
   Lieber eine ungewohnte Form als ein zerschnittenes Motiv. */
.tile-media[style*="aspect-ratio"] img,
.tile-media[style*="aspect-ratio"] video { height: 100%; object-fit: cover; }

.tile-text {
    background: var(--surface);
    border: 1px solid var(--rule);
    padding: var(--space-3);
    font-size: var(--text-sm);
    line-height: 1.6;
    color: var(--muted);
}

.tile-text p { margin: 0 0 var(--space-2); }
.tile-text p:last-child { margin-bottom: 0; }

.tile-quote {
    background: var(--dark);
    color: var(--light);
    padding: var(--space-3);
}

.tile-quote blockquote { margin: 0 0 var(--space-3); font-size: var(--text-md); }
.tile-quote figcaption { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-xs); color: var(--on-dark-mute); }
.tile-quote figcaption img { height: 1.2rem; width: auto; filter: grayscale(1) invert(1) brightness(1.7); }

.tile-empty { aspect-ratio: 4 / 3; border: 1px dashed var(--primary-line); }

@media (max-width: 60rem) {
    .tile-field { columns: 1; }
}


/* --- Cards ----------------------------------------------------------------
   The structural workhorse. Bands stay flat and unboxed; the density lives
   inside them, in celled grids. One .card definition serves the work grid and
   the quote grid, on light ground and dark, so a new grid anywhere on the site
   is a wrapper class and nothing else. */

.cards { display: grid; gap: var(--space-4); grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr)); }
.cards-2 { grid-template-columns: repeat(2, 1fr); }
.cards-3 { grid-template-columns: repeat(3, 1fr); }

.card {
    display: flex;
    flex-direction: column;
    margin: 0;
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    overflow: hidden;
    box-shadow: var(--shadow-1);
    transition: transform 0.3s var(--ease), box-shadow 0.3s var(--ease), border-color 0.3s var(--ease);
}

.card:hover { transform: translateY(-2px); box-shadow: var(--shadow-2); border-color: var(--primary); }
.card-body { display: flex; flex-direction: column; flex: 1; padding: var(--space-5); }
.card-body p { color: var(--muted); margin-bottom: var(--space-3); }
.card-body h3 { font-size: var(--text-xl); margin-bottom: var(--space-2); }
.card-body h3 a { color: inherit; }
.card-body h3 a:hover { color: var(--primary-text); }

/* One ratio for every card image, whatever the source file is: a grid of
   thumbnails at four different shapes reads as an accident. */
.card-media { display: block; overflow: hidden; aspect-ratio: 16 / 9; background: var(--tint-strong); border-bottom: 1px solid var(--rule); }
.card-media.placeholder { border-radius: 0; }
.card-media img { display: block; width: 100%; height: 100%; object-fit: cover; transition: transform 0.7s var(--ease); }
.card:hover .card-media img { transform: scale(1.04); }

.band-dark .card { background: var(--surface-dark); border-color: var(--rule-dark); box-shadow: none; }
.band-dark .card:hover { border-color: var(--on-dark-violet); box-shadow: none; }
.band-dark .card-body p { color: var(--on-dark-mute); }


/* --- Work list ------------------------------------------------------------
   The uppercase meta line under a project, shared by the home cards and the
   case heads. .more-link is the site's quiet outbound link - under the work
   grid and at the foot of the hero statement. */

.work-meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1) var(--space-3);
    color: var(--primary-text);
    margin: 0;
}

.band-dark .work-meta { color: var(--on-dark-violet); }

/* Declared after .work-meta's `margin: 0`, which would otherwise cancel it:
   the meta line pins to the foot of the card so it aligns across a row. The
   bottom reset is load-bearing too - `.card-body p` outranks `.work-meta` and
   was adding a phantom 1.5rem under every meta line. */
.card .card-meta { margin-top: auto; margin-bottom: 0; }
.work-meta .role::before { content: "\B7"; margin-right: var(--space-3); color: var(--muted); }

.more-link { margin-top: var(--space-6); }

.more-link a {
    color: var(--primary-text);
    border-bottom: 2px solid var(--primary);
    padding-bottom: 4px;
}

.more-link a:hover { border-bottom-color: var(--tertiary); }


/* --- Steps ----------------------------------------------------------------
   The ladder in summary, on the home page. Numbered because the order is the
   argument: commitment rises down the list. The number sits in its own column
   so the titles align regardless of how wide the numerals set. */

.steps {
    list-style: none;
    padding: 0;
    /* Same width and the same push right as a .block-column, so the ladder
       lines up with the blocks above and below it and the empty space stays on
       one side of the page. --content-w rather than --column-wide, which is the
       same number today: this is here to match the blocks, not the facts
       table, and the two should be free to diverge. */
    max-width: var(--content-w);
    margin: 0 0 0 auto;
}

.steps li {
    display: grid;
    grid-template-columns: 3.5rem 1fr;
    gap: var(--space-3);
    padding: var(--space-4) 0;
    border-top: 1px solid var(--rule);
}

.steps li:last-child { border-bottom: 1px solid var(--rule); }

.steps .step-n {
    font-size: var(--text-xs);
    letter-spacing: 0.14em;
    color: var(--primary-text);
    padding-top: 0.45rem;
}

.steps h3 { font-size: var(--text-md); margin: 0 0 var(--space-1); }
.steps p { color: var(--muted); margin: 0 0 var(--space-2); max-width: var(--prose); }
.steps p:last-child { margin-bottom: 0; }
.steps .work-meta { margin-top: var(--space-2); }

/* --- Quotes ---------------------------------------------------------------
   A card like any other, with the mark set as type rather than as an icon:
   one glyph instead of three copies of an inline Font Awesome path. */

.quote .card-body { padding-top: var(--space-4); }
.quote blockquote { margin: 0 0 var(--space-4); font-size: var(--text-md); line-height: 1.5; color: var(--light); flex: 1; }
.quote-mark { display: block; font-size: 3rem; line-height: 0.6; color: var(--on-dark-violet); margin-bottom: var(--space-2); }
.quote-footer { display: flex; align-items: center; gap: var(--space-2); padding-top: var(--space-3); border-top: 1px solid var(--rule-dark); }
.quote-footer img { width: 2.4rem; height: 2.4rem; border-radius: 50%; flex: none; }
.quote-author { font-size: var(--text-sm); }
.quote-affiliation { font-size: var(--text-sm); color: var(--on-dark-mute); }


/* --- Logos ----------------------------------------------------------------
   Inverted onto the dark ground and laufend statt in Zellen. Acht Eintraege,
   nicht zweiundzwanzig: eine Wand aus Logos sagt "grosse Firma", eine kurze
   Reihe sagt "nachpruefbare Geschichte". */

.logo-marquee {
    overflow: hidden;
    /* Blendet die Reihe an beiden Kanten aus, statt sie abzuschneiden - sonst
       erscheinen und verschwinden die Logos hart an der Textkante. */
    -webkit-mask-image: linear-gradient(to right, transparent, #000 4rem, #000 calc(100% - 4rem), transparent);
    mask-image: linear-gradient(to right, transparent, #000 4rem, #000 calc(100% - 4rem), transparent);
}

.logo-track {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    align-items: center;
    width: max-content;
}

/* Der Abstand haengt an den Eintraegen, nicht an einem gap der Spur: bei gap
   liegen zwischen sechsunddreissig Eintraegen fuenfunddreissig Luecken, die
   Haelfte der Gesamtbreite faellt dann nicht auf den Anfang der zweiten Kopie,
   und die Schleife ruckt einmal pro Runde. */
.logo-track li {
    display: flex;
    align-items: center;
    justify-content: center;
    padding-inline-end: var(--space-6);
}

.logo-track img {
    height: 1.9rem;
    width: auto;
    max-width: none;
    filter: grayscale(1) invert(1) brightness(1.7);
    opacity: 0.75;
    transition: opacity 0.3s var(--ease);
}

.logo-track li:hover img { opacity: 1; }

@media (prefers-reduced-motion: no-preference) {
    /* 109s fuer eine Kopie von gemessenen 3005px, also rund 28px/s. Die Dauer
       haengt an der Streckenlaenge, nicht am Tempo - wer Eintraege hinzunimmt
       oder streicht, muss sie mitziehen, sonst aendert sich die Geschwindigkeit
       der Reihe. */
    .logo-track { animation: logo-marquee 109s linear infinite; }

    /* Auf schmalen Schirmen doppelt so schnell. Die Strecke ist dieselbe, das
       Sichtfenster aber ein Drittel so breit - bei gleichem Tempo in Pixeln pro
       Sekunde steht die Reihe dort gefuehlt still, weil pro Sekunde ein viel
       kleinerer Teil des Fensters durchlaeuft. */
    @media (max-width: 60rem) {
        .logo-track { animation-duration: 52s; }
    }
    .logo-marquee:hover .logo-track { animation-play-state: paused; }
}

@keyframes logo-marquee {
    from { transform: translateX(0); }
    to   { transform: translateX(-50%); }   /* genau eine der beiden Kopien */
}


/* --- Register data --------------------------------------------------------
   The block most visitors came for. Rules, no box. */

.facts { display: grid; grid-template-columns: 16rem 1fr; margin: 0; font-size: var(--text-base); max-width: var(--column-wide); }
.facts dt { color: var(--muted); padding: var(--space-2) 0; border-top: 1px solid var(--rule); }
.facts dd { margin: 0; padding: var(--space-2) 0; border-top: 1px solid var(--rule); }
.band-dark .facts dt { color: var(--on-dark-mute); border-top-color: var(--rule-dark); }
.band-dark .facts dd { border-top-color: var(--rule-dark); }


/* --- Footer --------------------------------------------------------------- */

.site-footer { background: var(--dark); color: var(--light); }

.site-footer-inner {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-6);
    padding-block: var(--band-y-sm);
    font-size: var(--text-sm);
}

.site-footer .logo { width: 84px; flex: none; }
.site-footer .logo svg { width: 100%; display: block; }
.site-footer .blocks { display: flex; flex-wrap: wrap; gap: var(--space-6); flex: 1; justify-content: flex-end; }
.site-footer h6 { color: var(--on-dark-violet); margin-bottom: var(--space-2); letter-spacing: 0.16em; }
.site-footer .links span,
.site-footer .links a { display: block; margin-bottom: var(--space-1); }
.site-footer .links a { color: var(--light); }
.site-footer .links a:hover { color: var(--on-dark-blue); }
.site-footer .links span { color: var(--on-dark-mute); }
/* The bar under the footer columns. Its own row so a site-wide item does not
   have to pretend to belong to Contact or Legal. */
.site-footer-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-1) var(--space-4);
    padding-block: var(--space-3);
    font-size: var(--text-xs);
    color: var(--on-dark-mute);
}

/* Small caps on the switcher only: uppercasing the bar as a whole would turn
   the lowercase "zynd" in the copyright into ZYND. */
.site-footer-bar .languages {
    display: flex;
    gap: var(--space-3);
    margin-left: auto;
    text-transform: uppercase;
    letter-spacing: 0.14em;
}
.site-footer-bar .languages a { color: var(--on-dark-mute); }
.site-footer-bar .languages a:hover { color: var(--light); }
.site-footer-bar .languages a[aria-current] { color: var(--light); }


/* --- Placeholder ----------------------------------------------------------
   Unwritten copy, deliberately visible. Deleting this rule at the end of the
   rewrite is a cheap way to find anything still open. */

.placeholder {
    background: var(--primary-wash);
    box-shadow: 0 0 0 1px var(--primary-line);
    border-radius: 2px;
}

.band-dark .placeholder { background: rgba(185, 140, 255, 0.18); box-shadow: 0 0 0 1px rgba(185, 140, 255, 0.4); }


/* --- Project case studies -------------------------------------------------
   The portfolio page: alternating bands, media leading, one case per band.
   A .case is a band and takes the band's measurements. */

.case-index {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1) var(--space-4);
    padding-block: var(--space-2);
    background: var(--dark);
}

.case-index a { color: var(--on-dark-mute); padding-bottom: 2px; border-bottom: 2px solid transparent; }
.case-index a:hover { color: var(--light); border-bottom-color: var(--tertiary); }

.case { padding-block: var(--band-y); scroll-margin-top: var(--space-4); background: var(--light); }
.case:nth-of-type(even) { background: var(--tint); }

.case-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--space-1) var(--space-4); margin-bottom: var(--space-2); }
.case-head h2 { margin: 0; }
.case-body { max-width: var(--prose); margin-bottom: var(--space-5); }
.case-body h3 { color: var(--primary-text); font-size: var(--text-md); }
.case-body p { color: var(--muted); }

.case-media { overflow: hidden; background: var(--tint-strong); }
.case-media img, .case-media video { display: block; width: 100%; height: 100%; object-fit: cover; transition: transform 0.7s cubic-bezier(0.2, 0.6, 0.2, 1); }
.case-media:hover img, .case-media:hover video { transform: scale(1.035); }

/* The lead image, on a dark ground of its own. The band breaks out to the full
   viewport the way the image itself used to; what changed is that the image
   no longer does - it sits contained on the band, square, roughly the way a
   profile picture sits on a cover. */
.case-cover {
    width: 100vw;
    margin-left: calc(50% - 50vw);
    margin-bottom: var(--space-6);
    padding: var(--band-y-sm) var(--pad-x);
    background: var(--dark);
}

.case-avatar {
    width: 13rem;
    max-width: 40vw;
    aspect-ratio: 1;
    border-radius: var(--radius);
    background: var(--surface-dark);
}

.case-gallery { display: grid; grid-template-columns: repeat(auto-fit, minmax(17em, 1fr)); gap: var(--space-2); align-items: start; }
.case-media.portrait { max-width: 20em; margin: 0 auto; }
.case-caption { font-size: 0.75rem; color: var(--muted); margin: var(--space-1) 0 0; }

figure { margin: 0; }

@supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
        .reveal { animation: rise-in linear both; animation-timeline: view(); animation-range: entry 5% cover 28%; }
        @keyframes rise-in {
            from { opacity: 0; transform: translateY(var(--space-5)); }
            to   { opacity: 1; transform: translateY(0); }
        }
    }
}


/* --- Narrow screens -------------------------------------------------------
   Multi-column grids collapse, and below that the tokens shrink. Retuning
   --gutter and --band-y moves the header, every band, the case strips and the
   footer together, which is the whole point of routing them through tokens. */

/* --- Der Wuerfel ----------------------------------------------------------
   Sechs Flaechen um einen Mittelpunkt gedreht, preserve-3d auf dem Elternteil.
   Der Browser perspektiviert und sortiert sie selbst.

   Die Flaechenfarben sind aus dem frueheren Fragment-Shader herausgerechnet,
   nicht nachempfunden: Lambert gegen die Lichtrichtung (0.55, 0.75, 0.6),
   durch smoothstep(0.18, 0.92), gemischt zwischen --shade und --fill. Deshalb
   steht der Wuerfel im selben Licht wie vorher.

   Die Fuellung traegt ihre Durchsichtigkeit ueber color-mix und nicht ueber
   opacity: opacity haette die Kanten mitgenommen, und die waren immer solide. */

.cube {
    --cube-size: 4.6rem;
    --cube-half: calc(var(--cube-size) / 2);
    --cube-alpha: 55%;

    /* Die --face-* Farben stehen in :root, weil die Pyramide dieselben
       braucht. */
    position: relative;
    display: block;
    width: var(--cube-size);
    height: var(--cube-size);
    transform-style: preserve-3d;
    /* Dieselbe Neigung wie zuvor: 0.5, 0.7, 0.2 Radiant. */
    transform: rotateX(28.6deg) rotateY(40.1deg) rotateZ(11.5deg);
}

.cube-face {
    position: absolute;
    inset: 0;
    border: 1px solid var(--face-edge);
}

.cube-top    { background: color-mix(in srgb, var(--face-top)    var(--cube-alpha), transparent); transform: rotateX(  90deg) translateZ(var(--cube-half)); }
.cube-bottom { background: color-mix(in srgb, var(--face-bottom) var(--cube-alpha), transparent); transform: rotateX( -90deg) translateZ(var(--cube-half)); }
.cube-front  { background: color-mix(in srgb, var(--face-front)  var(--cube-alpha), transparent); transform:                  translateZ(var(--cube-half)); }
.cube-back   { background: color-mix(in srgb, var(--face-back)   var(--cube-alpha), transparent); transform: rotateY( 180deg) translateZ(var(--cube-half)); }
.cube-right  { background: color-mix(in srgb, var(--face-right)  var(--cube-alpha), transparent); transform: rotateY(  90deg) translateZ(var(--cube-half)); }
.cube-left   { background: color-mix(in srgb, var(--face-left)   var(--cube-alpha), transparent); transform: rotateY( -90deg) translateZ(var(--cube-half)); }

/* Statt einer Leerlaufdrehung folgt der Wuerfel dem Zeiger. Die --cam-*
   Variablen setzt js/camera.js, ihre Ausschlaege stehen als data-camera am
   Element. Ohne Zeiger und ohne Skript stehen sie auf 0, und der Wuerfel
   nimmt genau die Ruhelage ein, die er vorher als Startbild hatte. */
.hero-cube {
    perspective-origin: calc(50% + var(--cam-panX, 0%)) calc(50% + var(--cam-panY, 0%));
}

.hero-cube .cube {
    transform: rotateX(calc(28.6deg + var(--cam-tiltY, 0deg)))
               rotateY(calc(40.1deg + var(--cam-yawX, 0deg)))
               rotateZ(11.5deg);
}


/* --- Der Wuerfel im Hero -------------------------------------------------- */

/* Auf der Hoehe des Textes, hergeleitet statt geschaetzt. Der Text steht
   mittig im Inhaltskasten des Heros, und dessen Mitte liegt nicht bei 50%: die
   untere Polsterung traegt zusaetzlich --header-h und das Doppelte von
   --hero-lift. Damit sitzt die Mitte bei (100% - --header-h - 2 * --hero-lift)
   / 2, und der Wuerfel bleibt auf der Texthoehe, egal wie hoch der Schirm ist. */
.hero-cube {
    position: absolute;
    /* 16% des Schirms, aber nie weiter aussen als der Inhaltskasten. Der
       Hero polstert auf --pad-x ein, absolute Kinder messen dagegen gegen
       den Polsterkasten, also gegen die volle Schirmbreite: ab etwa 1790px
       liegen 16% weiter aussen als --pad-x, und der Wuerfel stuende neben
       der Spalte im leeren Rand. max() legt ihn dort an deren Kante an. */
    right: max(16%, var(--pad-x));
    top: calc((100% - var(--header-h) - 2 * var(--hero-lift)) / 2);
    transform: translateY(-50%);
    z-index: 0;
    perspective: 900px;
    pointer-events: none;
}

.hero-cube .cube { --cube-size: 8rem; }


/* --- Das Streichholzraetsel ------------------------------------------------
   Dieselbe Bauweise wie der Wuerfel: eine Flaeche in perspective, die Kinder
   in preserve-3d, der Zeiger ueber die --cam-* Variablen aus js/camera.js.

   Die Figur liegt auf einer gekippten Ebene, nicht auf dem Bildschirm: das
   Brett traegt die Neigung, jedes Holz nur seinen Platz darauf. Deshalb steht
   in den Hoelzern kein einziger Winkel, der mit der Kamera zu tun haette.

   --cx, --cy und --ca sind die Stelle, an der ein Holz gerade liegt, --x, --y
   und --a die aus den Daten und --sx, --sy und --sa das Ziel der Hoelzer, die
   sich beim Loesen bewegen. Der Schalter setzt nur um, welche der beiden
   Gruppen gilt. Ueber @property sind die drei als Zahlen angemeldet, damit der
   Browser sie interpolieren kann - ohne @property springt die Figur sofort,
   was die Loesung zeigt und nur den Weg dorthin verschluckt.

   Der Stand haengt an :has(): das Raetsel, dessen Radio ausgewaehlt ist,
   steht, die uebrigen sind weg. Wo :has() fehlt, faellt der ganze Selektor
   aus und alle Raetsel stehen untereinander - unschoen, aber lesbar und
   bedienbar. */

@property --cx { syntax: "<number>"; inherits: false; initial-value: 0; }
@property --cy { syntax: "<number>"; inherits: false; initial-value: 0; }
@property --ca { syntax: "<number>"; inherits: false; initial-value: 0; }

.quiz {
    --cell: clamp(4.4rem, 11vw, 9rem);
    /* Der Abstand, den ein Holz an jedem Ende laesst. Ohne ihn stossen die
       Koepfe an den Ecken aneinander, und aus vier Hoelzern wird ein
       durchgezogener Rahmen statt vier einzelner Staebe. */
    --gap: calc(var(--cell) * 0.14);
    /* Staerke eines Holzes; sie ist auch seine Dicke, sonst laege ein Brett
       und kein Stab. */
    --stick: calc(var(--cell) * 0.075);
    --head-len: calc(var(--cell) * 0.2);
    --wood: #2f2f3b;
    --wood-side: #1b1b24;
}

/* Verborgen wird der Inhalt, nicht der Abschnitt: das Radio steht im Raetsel
   und muss gerendert bleiben, sonst faende der Reiter im anderen Raetsel es
   nicht mehr. Es liegt ohnehin ausserhalb des Flusses. */
.quiz-level:not(:has(.quiz-pick:checked)) > :not(.quiz-pick) { display: none; }

/* Weder Auswahl noch Loesungsschalter werden gezeigt: geklickt werden Reiter
   und Zeichen. Nicht display: none - das naehme beide aus dem Tabweg. */
.quiz-pick,
.quiz-toggle {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* Aufgabe oben, Szene darunter, das Zeichen fuer die Loesung in der Ecke.
   Drei Fuenftel des Schirms je Raetsel: die Szene ist der Grund, warum es die
   Seite gibt. Als min-height notiert, damit ein langer Text darueber
   hinauswachsen darf; die 1fr-Zeile gibt den Zuwachs an die Szene und nicht
   an den Abstand darunter. */
.quiz-main {
    position: relative;
    display: grid;
    grid-template-rows: auto auto 1fr;
    min-height: 60vh;
}

/* Die Reiterzeile. Dieselbe Kapitaelchenschrift wie die uebrigen
   Beschriftungen der Seite, der aktive Reiter unterstrichen und in der
   Textfarbe. Die Linie darunter laeuft durch, damit die Reihe als Leiste
   lesbar ist und nicht als zwei einzelne Woerter. */
.quiz-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-4);
    margin-bottom: var(--space-4);
    border-bottom: 1px solid var(--rule);
}

.quiz-tab {
    cursor: pointer;
    color: var(--muted);
    padding-bottom: var(--space-1);
    margin-bottom: -1px;
    border-bottom: 2px solid transparent;
    transition: color 0.2s var(--ease), border-color 0.2s var(--ease);
}

.quiz-tab:hover { color: var(--primary-text); }
.quiz-tab.is-current { color: var(--dark); border-bottom-color: var(--primary); }
.quiz-pick:focus-visible ~ .quiz-main .quiz-tab.is-current { outline: 2px solid var(--primary); outline-offset: 4px; border-radius: 2px; }

/* Das Zeichen fuer die Loesung: grau, klein, in der unteren rechten Ecke der
   Szene. Es soll zu finden sein, wenn man es sucht, und nicht das Erste, was
   das Auge findet - sonst ist die Abkuerzung um das Raetsel herum der
   auffaelligste Weg auf der Seite. */
.quiz-solve {
    position: absolute;
    right: 0;
    bottom: 0;
    display: inline-flex;
    padding: var(--space-2);
    color: var(--muted);
    cursor: pointer;
    transition: color 0.2s var(--ease);
}

.quiz-solve:hover { color: var(--dark); }
.quiz-toggle:focus-visible ~ .quiz-main .quiz-solve { outline: 2px solid var(--primary); outline-offset: 2px; border-radius: 2px; }
.quiz-icon { width: 1.35rem; height: 1.35rem; }

/* Nur fuer den Vorleser: das Zeichen haette sonst keinen Namen. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

.quiz-stage {
    perspective: 1300px;
    perspective-origin: calc(50% + var(--cam-panX, 0%)) calc(50% + var(--cam-panY, 0%));
    display: grid;
    place-items: center;
    padding-block: var(--space-5);
}

/* Gekippt wie eine Tischplatte, aus der Hoehe eines Stehenden gesehen, und
   leicht gedreht, damit die Figur nicht frontal wie ein Diagramm steht. Die
   Groesse kommt aus den Daten: --cols und --rows stehen am Element. */
.quiz-board {
    position: relative;
    width: calc(var(--cols) * var(--cell));
    height: calc(var(--rows) * var(--cell));
    margin: 0 auto;
    transform-style: preserve-3d;
    transform: rotateX(calc(46deg + var(--cam-tiltY, 0deg)))
               rotateZ(calc(-9deg + var(--cam-yawX, 0deg)));
}

/* Ein Holz: zwei Koerper hintereinander auf einer gemeinsamen Achse, der
   Stab und der Kopf. Beide flach - eine Fassung mit Seitenflaechen, die in
   die Ebene weggeklappt waren, stand hier und sah nach Papierstreifen aus,
   nicht nach Rundholz. Die Tiefe macht jetzt allein die Neigung des Bretts.
   Der Drehpunkt liegt am Fussende, nicht in der Mitte:
   --x und --y sind damit der Gitterpunkt, an dem das Holz anfaengt, und ein
   senkrechtes Holz ist dasselbe wie ein waagrechtes mit --a: 90.

   Das translateX am Ende der Kette wirkt in der Achse des schon gedrehten
   Holzes, nicht in der des Bretts: so ruecken waagrechte und senkrechte
   Hoelzer gleichermassen von ihrem Gitterpunkt weg. */
.match {
    --cx: var(--x);
    --cy: var(--y);
    --ca: var(--a);

    position: absolute;
    left: 0;
    top: calc(var(--stick) / -2);
    width: calc(var(--cell) - var(--gap));
    height: var(--stick);
    transform-origin: 0 50%;
    transform: translate3d(calc(var(--cx) * var(--cell)), calc(var(--cy) * var(--cell)), 0)
               rotate(calc(var(--ca) * 1deg))
               translateX(calc(var(--gap) / 2));
    transition: --cx 0.55s var(--ease), --cy 0.55s var(--ease), --ca 0.55s var(--ease);
}

.match-stick,
.match-head {
    position: absolute;
    top: 0;
    height: 100%;
}

.match-stick {
    left: 0;
    right: calc(var(--head-len) * 0.72);
    background: linear-gradient(180deg, #3e3e4d, var(--wood) 55%, #24242f);
    border-radius: calc(var(--stick) / 2);
}

/* Der Kopf ist eine Kuppe und keine Spitze: dieselbe Rundung wie das Holz,
   nur dicker und farbig. Eine zugespitzte Fassung stand hier kurz und sah
   nach Pfeil aus, nicht nach Zuendholz. Er ueberlappt den Stab um seine
   eigene Rundung, damit zwischen beiden keine Kante steht. */
.match-head {
    right: 0;
    /* Etwas staerker als das Holz, wie an einem echten Zuendholz. Die Zugabe
       geht nach beiden Seiten, damit der Kopf auf der Achse bleibt. */
    top: -14%;
    height: 128%;
    width: var(--head-len);
    background: var(--head, #b98cff);
    border-radius: calc(var(--stick) * 0.64);
}

/* Die Farben wechseln wie in der Schachtel: violett, blau, rosa. */
.match:nth-child(3n + 1) { --head: #b98cff; }
.match:nth-child(3n + 2) { --head: #7cc4ff; }
.match:nth-child(3n + 3) { --head: #f39ac7; }

/* Der Zug. Nur die Hoelzer mit einem Ziel bewegen sich; die uebrigen haben
   kein --sx und behalten ihren Platz, weil der Rueckfall die Vorlage ist. */
.quiz-level:has(.quiz-toggle:checked) .match {
    --cx: var(--sx, var(--x));
    --cy: var(--sy, var(--y));
    --ca: var(--sa, var(--a));
}

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

/* Zeigen oder Verbergen: immer genau eines der beiden Zeichen und einer der
   beiden Namen dazu. */
.quiz-hide { display: none; }
.quiz-level:has(.quiz-toggle:checked) .quiz-show { display: none; }
.quiz-level:has(.quiz-toggle:checked) svg.quiz-hide { display: block; }
.quiz-level:has(.quiz-toggle:checked) span.quiz-hide { display: inline; }


/* --- Portraet im Block ----------------------------------------------------
   Ueber dem Text, quadratisch, auf das Textmass begrenzt. Der Freisteller
   traegt einen Alphakanal und steht deshalb ohne Kasten und ohne Rand auf dem
   Bandgrund; die durchsichtige Flaeche um die Figur ist Luft und kein Rahmen.

   Nicht beschnitten. Ein Zuschnitt auf die Figur (551x885) fuellte die Spalte
   auf 928px Hoehe und trug den Block dann allein; das Quadrat bleibt bei
   578px und laesst dem Text den Vortritt. */

/* Der Hero der Personenseite: Text links, Foto rechts. Der Block nimmt die
   ganze Bandbreite, damit das Foto an derselben rechten Kante steht wie die
   Leistungen darunter und nicht mitten im Raum haengt. Schmal gestapelt steht
   das Foto unter dem Text. */
.block-person > .block-aside,
.block-person > .block-body { max-width: none; }

.person-text { max-width: var(--column); }

/* Kleiner als das h1 der anderen Seiten: neben dem Foto soll der Name auf
   einer Zeile stehen und nicht mit dem Bild um die Hoehe konkurrieren. */
.person-text > h1 { font-size: clamp(2.4rem, 5vw, 4.2rem); }

.person-photo {
    display: block;
    width: 100%;
    max-width: 20rem;
    height: auto;
    margin-top: var(--space-5);
}

@media (min-width: 56rem) {
    .block-person > .block-body {
        display: grid;
        grid-template-columns: minmax(0, var(--column)) minmax(14rem, 20rem);
        justify-content: space-between;
        column-gap: var(--space-6);
        align-items: start;
    }
    .person-photo { margin-top: 0; }
}

.block-portrait .portrait {
    display: block;
    width: 100%;
    max-width: var(--prose);
    height: auto;
    margin-bottom: var(--space-4);
}


@media (max-width: 900px) {
    /* Der Wuerfel steht rechts neben der Textspalte. Auf schmalen Schirmen
       gibt es diese Spalte nicht, er laege hinter der Schrift. */
    .hero-cube { display: none; }

    /* Nur hier klebrig: auf dem Telefon ist das Menue sonst nach dem ersten
       Wisch weg und der Weg zurueck lang. Am Schreibtisch ist er kurz, und
       der Header darf mitscrollen. Die Regel steht nach .site-header und
       .dark-header .site-header, der Hintergrund von dort bleibt. */
    .site-header { position: sticky; top: 0; }

    .cards-2, .cards-3 { grid-template-columns: 1fr; }
    .facts { grid-template-columns: 1fr; }
    .facts dd { border-top: 0; padding-top: 0; padding-bottom: var(--space-2); }
    /* Rechts an den Rand, das Logo bleibt links - der Fuss teilt sich damit
       auf schmalen Schirmen genauso wie auf breiten. text-align dazu, weil die
       Gruppen sonst zwar rechts stehen, ihre kuerzeren Zeilen aber links
       buendig bleiben und die rechte Kante ausfranst. */
    .site-footer .blocks { justify-content: flex-end; text-align: right; gap: var(--space-5); }
    .case-gallery { grid-template-columns: 1fr; }
}

@media (max-width: 560px) {
    :root {
        --gutter: 1.25rem;
        --band-y: var(--band-y-sm);
        --band-y-lg: var(--band-y-sm);
        /* 2 x --space-4 Padding plus das kleinere Logo von unten: 4 + 2.1rem.
           Ohne das rechnet der Hero mit einer Headerhoehe, die es hier nicht
           gibt, und die optische Zentrierung liegt daneben. */
        --header-h: 5.765rem;   /* gemessen: 98px, das Logo ist hier kleiner */
    }

    body { font-size: 16px; }
    .site-header .menu { gap: var(--space-2); font-size: var(--text-sm); }
    .site-header .site-logo img { height: 2.1rem; }
}
