@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);


    /* 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. */

.area-index {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1) var(--space-4);
    padding-block: var(--space-3);
    padding-inline: var(--pad-x);
    background: var(--dark);
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.14em;
}

.area-index a { color: var(--on-dark-mute); }
.area-index a:hover { color: var(--light); }

/* 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);
}

/* Ein span und kein Link, solange die Feldseite geparkt ist - deshalb auch
   keine Hover-Farbe: was nicht klickt, soll nicht so tun. */
.area-name {
    display: block;
    color: var(--primary-text);
    margin-bottom: var(--space-1);
}

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

/* 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); }


/* --- 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%;

    --face-top:    #a469ec;
    --face-front:  #9d63e3;
    --face-right:  #9a61df;
    --face-left:   #421c78;
    --face-back:   #401b77;
    --face-bottom: #3f1a75;
    --face-edge:   #b98cff;

    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. */

.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; }
}
