/* =========================================================================
   Familienfest - Designsystem "Alpine Elegance"

   Leitidee: Die Einladung soll wie eine gestaltete Drucksache wirken, nicht
   wie eine Web-Anwendung. Deshalb gibt es hier bewusst KEINE Karten, Rahmen
   oder Schatten fuer Bausteine. Getrennt wird ausschliesslich durch
   Weissraum und Typografie.

   Zweite Leitidee: nichts hat eine feste Breite oder Hoehe. Alle Groessen
   sind fliessend (clamp/%/min), Bilder bekommen ein reserviertes
   Seitenverhaeltnis. Dadurch kann bei keiner Fenstergroesse und in keiner
   Ausrichtung etwas ueberlappen oder abgeschnitten werden.

   Dritte Leitidee: Die Einladung ist immer hell - sie ist Papier. Nur die
   Verwaltung folgt dem Farbschema des Systems.

   Aufbau
     1. Schriften
     2. Grundlagen
     3. Farben und Masse
     4. Typografie
     5. Seitengeruest und Abschnitte
     6. Szenen: Titelbild, Split, Foto
     7. Eckdaten
     8. Formularteile der Einladung
     9. Schaltflaechen
    10. Rueckmeldungen
    11. Inhaltsverzeichnis
    12. Bewegung
    13. Verwaltung
    14. Druck
   ========================================================================= */

/* --- 1. Schriften ------------------------------------------------------- */

/* Selbst gehostet: keine Verbindung zu fremden Servern, kein Datenabfluss,
   kein Ausfall wenn ein CDN klemmt. Beide sind variable Fonts - eine Datei
   deckt den ganzen Gewichtsbereich ab.

   Die latin-ext-Schnitte laedt der Browser nur nach, wenn ein Zeichen daraus
   wirklich vorkommt (unicode-range). Deutsche Umlaute stecken schon in latin;
   latin-ext greift erst bei Namen wie Dvorak oder Wisniewski. */

@font-face {
    font-family: 'Cormorant Garamond';
    font-style: normal;
    font-weight: 300 600;
    font-display: swap;
    src: url('/fonts/cormorant-garamond-latin-normal-c47ff12887a36dac62f887a6fdb51066.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
    U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
    U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: 'Cormorant Garamond';
    font-style: normal;
    font-weight: 300 600;
    font-display: swap;
    src: url('/fonts/cormorant-garamond-latin-ext-normal-acc46a2a7785e64a3a761be80754fe79.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
    U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
    U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
    U+A720-A7FF;
}

@font-face {
    font-family: 'Cormorant Garamond';
    font-style: italic;
    font-weight: 300 600;
    font-display: swap;
    src: url('/fonts/cormorant-garamond-latin-italic-aa6bdaf51a40d3bdae4a556e49c6630a.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
    U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
    U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: 'Cormorant Garamond';
    font-style: italic;
    font-weight: 300 600;
    font-display: swap;
    src: url('/fonts/cormorant-garamond-latin-ext-italic-686d9ba10c80b77a3408e04f96cd56bb.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
    U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
    U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
    U+A720-A7FF;
}

@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 400 600;
    font-display: swap;
    src: url('/fonts/inter-latin-normal-260c81a4759baf163c025001c4f27872.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
    U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
    U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 400 600;
    font-display: swap;
    src: url('/fonts/inter-latin-ext-normal-1ad231aac0a8a891b8374aa5526a5813.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
    U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
    U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
    U+A720-A7FF;
}

/* --- 2. Grundlagen ------------------------------------------------------ */

/* Damit app.js die erlaubte Bildhoehe als Zahl lesen kann.

   getComputedStyle gibt eine gewoehnliche Custom Property unaufgeloest zurueck:
   Aus clamp(24rem, 80vh, 50rem) kommt genau diese Zeichenkette und keine Zahl.
   Als Laenge typisiert wird sie dagegen zu Pixeln ausgerechnet.

   Kennt ein Browser @property nicht, liest das Skript nichts und rechnet wie
   zuvor allein mit der Texthoehe. Das Bild wird dann etwas kleiner - nie
   falsch. */
@property --media-h {
    syntax: '<length>';
    inherits: true;
    initial-value: 0px;
}

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

html {
    -webkit-text-size-adjust: 100%;
    /* Ein Sprung aus dem Inhaltsverzeichnis soll gleiten, nicht schneiden -
       sonst weiss man nicht, ob man vor oder zurueck gesprungen ist. Wer
       Bewegung abbestellt hat, bekommt weiter unten wieder den harten Sprung. */
    scroll-behavior: smooth;
}

/* Nur fuer Vorlesesoftware. Was die Marken im Inhaltsverzeichnis zeigen,
   steht damit auch dort zur Verfuegung, wo Zeichnungen nichts nuetzen. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

body,
h1, h2, h3, h4, p, figure, blockquote, dl, dd, ul, ol {
    margin: 0;
}

ul, ol {
    padding: 0;
    list-style: none;
}

img, picture, svg, video {
    display: block;
    max-width: 100%;
}

input, button, textarea, select {
    font: inherit;
    color: inherit;
}

/* --- 3. Farben und Masse ------------------------------------------------ */

:root {
    /* Papier: warmes Creme statt hartem Weiss. Der Ton stammt von
       ungestrichenem Werkdruckpapier, nicht von einem Bildschirm. */
    --paper: #f8f5ef;
    --paper-deep: #f1ece2;
    --paper-sink: #eae3d6;

    /* Tinte mit leichtem Gruenstich - reines Schwarz wirkt auf Creme hart. */
    --ink: #1f2420;
    --ink-soft: #4a5048;
    --ink-faint: #686e66;

    --line: #e5ded1;
    --line-strong: #cec5b4;

    /* Tannengruen traegt die ganze Seite. */
    --accent: #26382f;
    --accent-hover: #354c40;
    --accent-wash: #e9ede8;

    /* Begleitfarben. Messing ausschliesslich fuer Haarlinien und kleine
       Markierungen - als Flaeche wuerde es sofort billig wirken. */
    --taupe: #a08f78;
    --brass: #a3874f;
    --warn: #8a4b2a;
    --ok: #26382f;

    /* Fliessende Abstaende: waechst mit der Fensterbreite, aber gedeckelt.
       Deutlich grosszuegiger als ueblich - der Weissraum ist hier das
       eigentliche Gestaltungsmittel. */
    --space-section: clamp(4rem, 11vw, 9rem);
    /* Fuer schmale Bausteine - Hinweis, Termin, ein einzelner Programmpunkt.
       Sie sollen sich wie Absaetze desselben Textes lesen, nicht wie eine
       Kette einzelner Module. */
    --space-compact: clamp(2rem, 5vw, 3.5rem);
    --space-block: clamp(1.5rem, 3.5vw, 2.5rem);
    --gutter: clamp(1.35rem, 5vw, 4rem);

    /* Die Buehne der Einladung. Jeder Abschnitt sitzt darin, und dadurch
       haben Titelbild, Text, Bildunterschrift und zweispaltiger Baustein
       alle dieselbe linke Kante. Diese durchgehende Kante ist das, was eine
       Seite gestaltet wirken laesst statt zusammengesetzt - vorher hatte
       jeder Abschnittstyp seine eigene, mittig zentrierte Breite. */
    --page-max: 76rem;

    /* Lesbare Zeilenlaenge fuer Fliesstext innerhalb der Buehne. */
    --measure: 34rem;
    --measure-wide: 62rem;

    /* Die Spalte des Inhaltsverzeichnisses am rechten Rand. Sie steht hier,
       weil zwei Regeln sie brauchen: die Leiste selbst und der Platz, den die
       Seite dafuer aussparen muss. */
    --toc-w: 15rem;

    --radius: 2px;
    --font-display: 'Cormorant Garamond', 'Iowan Old Style',
    'Palatino Linotype', Palatino, Georgia, serif;
    --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI',
    Roboto, 'Helvetica Neue', Arial, sans-serif;

    /* Eine einzige Kurve fuer alle Bewegungen. Sie startet zuegig und laeuft
       weich aus - das wirkt ruhig statt mechanisch. */
    --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
}

/* Die Einladung bleibt immer hell. Nur Verwaltung und Anmeldung duerfen dem
   System folgen. :has() traegt das an den Rumpf; kann der Browser das nicht,
   greifen die Regeln immer noch auf .admin selbst - dann bleibt nur der
   Bereich hinter dem Inhalt hell, was niemandem auffaellt. */
@media (prefers-color-scheme: dark) {
    body:has(.admin),
    .admin {
        --paper: #151815;
        --paper-deep: #1c201c;
        --paper-sink: #232823;
        --ink: #e8e4db;
        --ink-soft: #a9afa6;
        --ink-faint: #868c84;
        --line: #2a2f2a;
        --line-strong: #3c423c;
        --accent: #93b3a2;
        --accent-hover: #a9c5b6;
        --accent-wash: #1e2620;
        --warn: #d09070;
    }
}

/* --- 4. Typografie ------------------------------------------------------ */

body {
    background: var(--paper);
    color: var(--ink);
    font-family: var(--font-sans);
    /* Fliessende Schriftgroesse, aber nie kleiner als bequem lesbar. */
    font-size: clamp(1rem, 0.96rem + 0.25vw, 1.09rem);
    line-height: 1.7;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    /* Rettet lange Woerter und Links auf schmalen Bildschirmen. */
    overflow-wrap: break-word;
}

/* Kleines Etikett ueber einer Ueberschrift - der "Hotel"-Anteil der Optik. */
.eyebrow {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    font-size: 0.7rem;
    letter-spacing: 0.26em;
    text-transform: uppercase;
    color: var(--ink-faint);
    font-weight: 500;
    margin-bottom: 1.1rem;
}

/* Die Linienmarke des Bausteins - das einzige Ornament der Seite.

   Sie steht bewusst hier und nicht als Farbflaeche hinter dem Abschnitt:
   Haette jeder Bausteintyp einen eigenen Hintergrund, saehe man der Seite
   sofort an, dass sie aus Bausteinen zusammengesetzt ist. Eine Marke von
   knapp einem Zentimeter setzt denselben Akzent, ohne die Flaeche zu
   zerschneiden. */
.eyebrow__mark {
    display: inline-flex;
    flex: none;
    color: var(--accent);
}

.eyebrow__mark svg {
    width: 1.6rem;
    height: 1.25rem;
    /* Haarfein: die Marke soll wie ein Federstrich wirken, nicht wie ein
       Symbol aus einer Anwendung. */
    opacity: 0.85;
}

.display {
    font-family: var(--font-display);
    /* 300 statt 400: Cormorant ist fein gezeichnet und wirkt in grossen
       Graden erst dann wirklich elegant. */
    font-weight: 300;
    /* Die vh-Komponente verhindert, dass die Zeile auf niedrigen Fenstern
       (Handy quer) hoeher wird als der Platz, der da ist. */
    font-size: clamp(2.6rem, min(9vw, 10.5vh), 5.5rem);
    line-height: 1.04;
    letter-spacing: -0.005em;
}

.title {
    font-family: var(--font-display);
    font-weight: 400;
    font-size: clamp(1.85rem, 4.2vw, 2.9rem);
    line-height: 1.16;
}

/* Drei Grade statt einem.

   Vorher waren zehn von fuenfzehn Ueberschriften exakt 46,4px gross: "Berghof
   Schoenalm" wog genauso schwer wie "Gibt es etwas zu beachten?". Wo alles
   gleich laut ist, ist nichts betont - und die Seite liest sich als Liste
   gleichwertiger Kacheln statt als Einladung mit einem Anfang, einer Mitte und
   ein paar Fragen dazwischen.

   Welcher Grad gilt, sagt die Betonung des Bausteins. Sie steht in Java an
   genau einer Stelle je Baustein und entscheidet zugleich ueber das
   Spaltenverhaeltnis - Schriftgrad und Layout koennen dadurch nicht
   auseinanderlaufen. */
.section--scene .title {
    font-size: clamp(2.3rem, 5.2vw, 3.6rem);
    line-height: 1.08;
    letter-spacing: -0.008em;
}

.section--quiet .title {
    font-size: clamp(1.5rem, 2.6vw, 1.95rem);
    line-height: 1.25;
}

/* Der Untertitel folgt mit, sonst ueberholt er im sachlichen Baustein die
   Ueberschrift. */
.section--quiet .subtitle {
    font-size: clamp(1.05rem, 1.8vw, 1.2rem);
}

/* Fuer schmale Bausteine. Ein einzelner Programmpunkt oder ein Hinweis soll
   dem Auge nicht dieselbe Wucht entgegensetzen wie ein Kapitelanfang - sonst
   liest sich eine Folge kurzer Bausteine wie eine Gliederung statt wie ein
   fortlaufender Text. Gestuft wird also ueber den Grad, nicht ueber Kaesten. */
.title--small {
    font-size: clamp(1.35rem, 2.6vw, 1.75rem);
}

.subtitle {
    font-family: var(--font-display);
    font-weight: 400;
    font-style: italic;
    font-size: clamp(1.25rem, 2.6vw, 1.6rem);
    line-height: 1.4;
    color: var(--ink-soft);
}

.lead {
    font-size: clamp(1.05rem, 1rem + 0.3vw, 1.2rem);
    color: var(--ink-soft);
    line-height: 1.6;
}

.muted {
    color: var(--ink-soft);
}

.faint {
    color: var(--ink-faint);
    font-size: 0.9rem;
}

.prose > * + * {
    margin-top: 1.1em;
}

/* Absatzumbrueche aus einem Textfeld: white-space erhaelt die Leerzeilen. */
.prose--raw {
    white-space: pre-line;
}

/* Ein Hinweis, der etwas lauter sprechen darf - "denkt an feste Schuhe",
   "Treffpunkt ist der Parkplatz".

   Hervorgehoben wird ausschliesslich ueber die Schrift: Serife statt Grotesk,
   ein Grad groesser als der Fliesstext. Kein Kasten, kein farbiger Grund, kein
   Rahmen - das ist die eigentliche Entscheidung hier. Ein hinterlegter Kasten
   waere sofort als eingesetztes Modul zu erkennen und wuerde die durchgehende
   Flaeche der Einladung zerschneiden. Den farbigen Akzent traegt die Marke in
   der Zeile darueber. */
.note {
    font-family: var(--font-display);
    font-size: clamp(1.2rem, 2.4vw, 1.5rem);
    line-height: 1.45;
    white-space: pre-line;
}

a {
    color: var(--accent);
    text-decoration-thickness: 1px;
    text-underline-offset: 0.2em;
    text-decoration-color: var(--line-strong);
    transition: text-decoration-color 0.2s ease, color 0.2s ease;
}

a:hover {
    color: var(--accent-hover);
    text-decoration-color: currentColor;
}

:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
}

/* --- 5. Seitengeruest und Abschnitte ------------------------------------ */

.page {
    /* Kein max-width am Rumpf: Full-Bleed-Bausteine sollen die volle Breite
       nutzen koennen. Begrenzt wird erst innen, in .measure. */
    min-height: 100vh;
    min-height: 100svh;
    display: flex;
    flex-direction: column;
}

/* Die Buehne. Jeder Abschnitt der Einladung sitzt darin - deshalb beginnen
   Titelbild, Ueberschrift, Fliesstext und Bildunterschrift alle an derselben
   Kante. Das ist die tragende Entscheidung des ganzen Layouts.

   Die Buehne ist eine reine Inhaltsbox: kein Innenabstand, keine Kante, die
   irgendjemand zeichnet - nur eine Satzkante, an der sich alles ausrichtet.

   Vorher trug sie ihren Seitenrand als padding-inline. Das klingt harmlos, hat
   aber zwei Kanten erzeugt statt einer: Der Text sass in der Inhaltsbox, das
   Seitenbild griff mit einem negativen Rand wieder heraus bis an die
   Rahmenkante. Gemessen bei 1600px waren das 248px fuer jede Ueberschrift,
   jede Haarlinie und jede Antwortflaeche - und 184px fuer jedes Bild. Ein
   Versatz von 64px, quer durch die ganze Einladung, bei dem das Auge nirgends
   Halt fand.

   Jetzt bestimmt die Breite selbst den Rand: Bis 76rem plus zweimal Gutter
   bleibt der Gutter als Sicherheitsabstand zum Fensterrand stehen, darueber
   deckelt --page-max. Zwischen Buehne und Fensterrand ist auf grossen
   Bildschirmen ohnehin genug Luft; der Text muss deshalb nicht noch einmal
   zusaetzlich einruecken. */
.canvas {
    width: min(100% - 2 * var(--gutter), var(--page-max));
    margin-inline: auto;
}

/* Fliesstext bleibt lesbar schmal - aber linksbuendig an der Kante der
   Buehne, nicht noch einmal fuer sich zentriert. Genau dieses zweite
   Zentrieren hat die Seite vorher schwimmen lassen.

   Gilt nur noch fuer das Titelbild. Der Baustein ohne Bild hatte dieselbe
   Klasse und bekam dadurch jedes Kind auf 34rem gedeckelt - auch die
   Eckdatenzeile und die Antwortflaechen, die deshalb 144px vor der
   Aussenkante endeten. Wie er es stattdessen macht, steht weiter unten. */
.canvas--text > * {
    max-width: var(--measure);
}

/* Im Baustein ohne Bild wird der laufende Text gedeckelt, nicht der Baustein.

   Das ist der Unterschied, um den es geht. Ein Deckel auf jedem direkten Kind
   trifft eben auch die Eckdatenzeile, die Aufzaehlung und die drei
   Antwortflaechen - und die sollen die Breite fuellen, nicht Text sein. Ein
   Deckel auf Absaetzen trifft genau das, was schwer zu lesen wird, wenn es zu
   lang laeuft.

   Gilt in jeder Tiefe: Die Fragmente packen ihre Absaetze teils in ein div
   ohne Klasse - Adresse und Link beim Ort, der Shuttle bei der Anreise. Ein
   Deckel nur auf direkten Kindern haette die ausgelassen.

   Der Kopf bekommt seinen Deckel getrennt: Auf schmalen Bildschirmen steht er
   im laufenden Satz und braucht ihn, im Raster daneben hebt ihn die
   Spaltenbreite wieder auf. */
.canvas--sidehead p,
.canvas--sidehead .prose,
.canvas--sidehead .note,
.canvas--sidehead > :first-child {
    max-width: var(--measure);
}

/* Die Marginalspalte: Kopf links, Inhalt rechts.

   Ein Baustein ohne Bild nutzte vorher 544 von 1216 Pixeln - und das bei zehn
   von fuenfzehn Abschnitten. Der Rest der Zeile war nicht Weissraum, sondern
   Rest.

   Den Text einfach breiter laufen zu lassen waere die naheliegende Loesung und
   die falsche: 1088px sind gut 130 Zeichen je Zeile, das liest niemand gern.
   Stattdessen wandert der Kopf - Etikett und Ueberschrift - nach links daneben.
   Der Fliesstext behaelt seine lesbare Breite, die Zeile ist trotzdem gefuellt,
   und die linke Kante bleibt, wo sie war.

   Zwei Bedingungen, beide mit Absicht:

   - Ab 62rem, also derselben Schwelle, ab der auch Bild und Text nebeneinander
     ruecken. Darunter bleibt alles untereinander.
   - Nur wenn es ueberhaupt einen zweiten Inhalt gibt (:has). Ein Baustein, der
     nur aus Etikett und Ueberschrift besteht - ein Termin ohne Zusaetze etwa -
     haette sonst eine leere rechte Spalte, und das Loch waere zurueck, nur
     schmaler.

   Was hier lange gefehlt hat: Die Inhaltsspalte war auf --measure gedeckelt.
   Kopf (21rem) plus Spalte (34rem) plus Abstand ergaben 944 von 1088 Pixeln -
   rechts blieben 144 uebrig, an denen nichts endete. Zusammen mit der linken
   Kante der Inhaltsspalte bei 648 folgte der Baustein damit sichtbar einem
   dritten Raster, und auf einem breiten Bildschirm sah er aus, als haette
   jemand das Bild vergessen.

   Jetzt laeuft die Inhaltsspalte bis an die Aussenkante der Buehne, und
   gedeckelt wird nur noch der laufende Text (siehe die Regel darueber).
   Strukturiertes - Eckdaten, Aufzaehlungen, Antwortflaechen, Formular - fuellt
   die Breite und zieht seine Haarlinien bis an dieselbe Kante, an der auch die
   Bilder der anderen Bausteine enden. Der Baustein ohne Bild ist dadurch keine
   halbe Zeile mehr, sondern ein zweispaltiger Satzspiegel. */
@media (min-width: 62rem) {
    .canvas--sidehead:has(> :nth-child(2)) {
        display: grid;
        grid-template-columns: minmax(0, 22rem) minmax(0, 1fr);
        column-gap: clamp(2rem, 4vw, 4rem);
        row-gap: var(--space-block);
        align-items: start;
    }

    /* Alles in die rechte Spalte ... */
    .canvas--sidehead:has(> :nth-child(2)) > * {
        grid-column: 2;
    }

    /* ... ausser dem Kopf. Der steht links und bleibt dort oben stehen,
       waehrend der Inhalt daneben laenger werden darf. Die Spalte gibt ihm
       seine Breite, der Deckel von oben wuerde nur dagegenhalten. */
    .canvas--sidehead:has(> :nth-child(2)) > :first-child {
        grid-column: 1;
        grid-row: 1;
        max-width: none;
    }

    /* Im Raster macht row-gap die Abstaende. Der Aussenabstand aus
       .section__inner wuerde sich sonst dazuaddieren. */
    .canvas--sidehead:has(> :nth-child(2)) > * + * {
        margin-top: 0;
    }
}

/* Die alte Textspalte. Bleibt fuer Verwaltung und Anmeldung, wo eine
   zentrierte Spalte richtig ist. */
.measure {
    width: 100%;
    max-width: var(--measure);
    margin-inline: auto;
    padding-inline: var(--gutter);
}

.measure--wide {
    max-width: var(--measure-wide);
}

/* Ein Baustein. Nur Abstand, sonst nichts - keine Kante, kein Hintergrund. */
.section {
    padding-block: var(--space-section);
    /* Sprungziel des Inhaltsverzeichnisses.

       Zwei Summanden, und beide sind noetig: die schmale Zeile oben, die dem
       Abschnitt sonst auf der Ueberschrift saesse, und --sticky-top - das ist
       in der Vorschau die Leiste der Verwaltung, die ebenfalls oben klebt
       (app.js misst sie). In der Einladung des Gastes ist der zweite Wert 0. */
    scroll-margin-top: calc(var(--sticky-top, 0px) + 3.75rem);
}

/* Am rechten Rand steht die eigene Leiste nicht im Weg - was oben klebt,
   bleibt aber auch dort zu beruecksichtigen. */
@media (min-width: 90rem) {
    .section {
        scroll-margin-top: calc(var(--sticky-top, 0px) + 1.5rem);
    }
}

/* Zwei aufeinanderfolgende Abschnitte wuerden sonst den doppelten Abstand
   ergeben; so bleibt der Rhythmus ueber die ganze Seite gleichmaessig. */
.section + .section {
    padding-top: 0;
}

.section--bleed {
    padding-block: 0;
}

.section--bleed + .section {
    padding-top: var(--space-section);
}

/* Ein schmaler Baustein: Hinweis, Termin, ein einzelner Programmpunkt.

   Den Abstand zwischen zwei Abschnitten traegt immer der obere - der untere
   hat oben ja nichts mehr. Deshalb muss fuer einen schmalen Baustein auch
   sein Vorgaenger zurueckstecken; sonst saesse ein Zweizeiler weit unter dem
   vorigen Abschnitt und dicht ueber dem naechsten und wirkte dadurch wie ein
   eingeschobenes Modul statt wie ein Absatz.
   Kann ein Browser :has() nicht, bleibt der Abstand davor gross - das sieht
   dann etwas luftiger aus, aber nie kaputt. */
.section--compact,
.section:not(.section--bleed):has(+ .section--compact) {
    padding-bottom: var(--space-compact);
}

/* Nach einem Titelbild setzt die Regel oben den vollen Abstand wieder ein. */
.section--bleed + .section--compact {
    padding-top: var(--space-compact);
}

.section__inner > * + * {
    margin-top: var(--space-block);
}

/* --- 6. Szenen ---------------------------------------------------------- */

/* Titelbild ------------------------------------------------------------- */

.hero {
    position: relative;
    display: grid;
    align-items: end;
    /* svh statt vh: auf dem Handy zaehlt sonst die eingeblendete Adressleiste
       mit und der untere Rand wird abgeschnitten. */
    min-height: min(92vh, 52rem);
    min-height: min(92svh, 52rem);
    isolation: isolate;
    overflow: hidden;
}

.hero--plain {
    min-height: min(74vh, 40rem);
    /* Statt eines Fotos ein sehr ruhiger Verlauf aus den Papiertoenen -
       keine Farbexplosion, nur eine Ahnung von Tiefe. */
    background:
            radial-gradient(120% 85% at 50% 0%, var(--paper) 0%, var(--paper-sink) 100%);
}

.hero__media {
    position: absolute;
    inset: 0;
    z-index: -2;
}

.hero__media img {
    width: 100%;
    height: 100%;
    /* Zuschnitt passiert hier, in der Anzeige - nie durch Verzerren. */
    object-fit: cover;
    object-position: center;
}

/* Der Verlauf ueber dem Foto.
   Zwei Lagen, und das aus einem konkreten Grund: Bei einem hellen Motiv -
   Schnee, Nebel, weisser Himmel - reichte der reine Verlauf von unten nicht,
   die weisse Schrift verschwand darin. Die zweite Lage von links legt sich
   zusaetzlich unter den Text, wo er tatsaechlich steht. */
.hero__scrim {
    position: absolute;
    inset: 0;
    z-index: -1;
    background:
            linear-gradient(to top,
            rgba(14, 18, 15, 0.86) 0%,
            rgba(14, 18, 15, 0.58) 28%,
            rgba(14, 18, 15, 0.24) 58%,
            rgba(14, 18, 15, 0.08) 100%),
            linear-gradient(to right,
            rgba(14, 18, 15, 0.42) 0%,
            rgba(14, 18, 15, 0.12) 45%,
            rgba(14, 18, 15, 0) 70%);
}

.hero__content {
    padding-block: clamp(2.5rem, 9vh, 6rem);
}

/* Auf einem Bild steht der Text immer hell - unabhaengig vom Farbschema.
   Der Schatten ist die letzte Sicherung fuer Motive, mit denen niemand
   gerechnet hat. */
.hero--image .hero__content,
.hero--image .hero__content .eyebrow,
.hero--image .hero__content .lead {
    color: #f7f4ee;
    text-shadow: 0 1px 20px rgba(10, 14, 11, 0.4);
}

.hero--image .hero__content .eyebrow {
    color: rgba(247, 244, 238, 0.85);
}

/* Auf dem Foto traegt die Marke kein Gruen - dort waere es nicht zu sehen. */
.hero--image .hero__content .eyebrow__mark {
    color: rgba(247, 244, 238, 0.8);
}

/* Das Datum ist bei einem Save-the-Date die eigentliche Nachricht. Es bekommt
   deshalb die Serifenschrift und Gewicht, statt als weitere graue Zeile
   unterzugehen. */
.hero__when {
    font-family: var(--font-display);
    font-size: clamp(1.35rem, 2.6vw, 1.95rem);
    line-height: 1.3;
    margin-top: clamp(1.25rem, 3vh, 2rem);
}

/* Dasselbe Gewicht wie im Titelbild, nur im laufenden Text: Beim
   Termin-Baustein ist das Datum die ganze Nachricht und darf deshalb genauso
   auftreten wie beim Auftakt - sonst waere es nur eine weitere graue Zeile. */
.date-line {
    font-family: var(--font-display);
    font-size: clamp(1.6rem, 3.4vw, 2.4rem);
    line-height: 1.25;
}

.hero__where {
    margin-top: 0.35rem;
    font-size: clamp(1rem, 1.6vw, 1.1rem);
    letter-spacing: 0.02em;
}

.hero__note {
    margin-top: clamp(1.25rem, 3vh, 2rem);
    padding-top: clamp(1rem, 2.5vh, 1.5rem);
    border-top: 1px solid rgba(247, 244, 238, 0.28);
    font-size: 0.95rem;
    max-width: 26rem;
}

/* Handy quer: der Titelbereich darf nicht die ganze Hoehe fressen. */
@media (orientation: landscape) and (max-height: 34rem) {
    .hero,
    .hero--plain {
        min-height: 100svh;
    }

    .hero__content {
        padding-block: 1.5rem;
    }
}

/* Bild und Inhalt nebeneinander ------------------------------------------ */

/* Die Grundform der meisten Bausteine: links das Bild, rechts der Inhalt -
   oder umgekehrt. Welche Seite, entscheidet der Server (abwechselnd, per
   Baustein uebersteuerbar) und setzt die Klasse.

   Auf schmalen Bildschirmen faellt das Raster von selbst in eine Spalte.
   Weil das Bild im Quelltext vor dem Inhalt steht, entsteht dabei genau die
   gewuenschte Reihenfolge: Bild, Ueberschrift, Text, Aktion. */
/* Die Betonung eines Bausteins steht als Klasse am Abschnitt und wird hier zu
   drei Groessen: dem Spaltenverhaeltnis, der Bildhoehe und dem Bildformat auf
   dem Handy. Die Ueberschrift folgt weiter unten derselben Klasse.

   Warum ueber Variablen und nicht ueber drei Regelsaetze: Die Bildseite
   wechselt ab, es gaebe also je Betonung zwei Regeln (links/rechts) mal drei
   Werte. Mit Variablen steht das Verhaeltnis einmal da und die beiden
   Seitenregeln lesen es. */
/* Das Bild bestimmt, wie breit seine Spalte wird.

   Vorher stand die Aufteilung je Betonung fest - 52/48, 58/42, 40/60. Damit
   musste dasselbe Verhaeltnis fuer jedes Motiv herhalten: Ein Hochformat in
   einer breiten Spalte wurde oben und unten abgeschnitten, ein Querformat in
   einer schmalen an den Seiten. Man sah es nicht als Fehler, sondern als
   schlecht ausgesuchtes Foto.

   Jetzt liefert der Server das Seitenverhaeltnis des tatsaechlich
   eingestellten Bildes als --img-ratio mit, und daraus ergibt sich alles
   Weitere:

     Breite = Hoehe x Verhaeltnis, gedeckelt durch das, was der Text uebrig
              laesst
     Hoehe  = Breite / Verhaeltnis, gedeckelt durch --media-h

   Ein Hochformat bekommt dadurch eine schmale hohe Spalte, ein Panorama eine
   breite flache - und beide zeigen ihr ganzes Motiv, ohne Beschnitt und ohne
   Stauchen.

   Die Betonung steuert weiterhin, aber nur noch die Groesse: wie hoch das Bild
   hoechstens werden darf und wie viel Platz dem Text mindestens bleibt. */
.split {
    /* Bezugshoehe fuer die Breite der Bildspalte - NICHT die Hoehe des Bildes.
       Die richtet sich nebeneinander nach der Spalte (siehe unten). */
    --media-h: clamp(18rem, 58vh, 36rem);
    /* Und wie breit darf die Bildspalte hoechstens werden? */
    --media-w-max: 36rem;
    /* Und wie hoch, wenn es einspaltig darueber steht?
       Deutlich mehr, und das aus einem klaren Grund: Nebeneinander teilt sich
       das Bild die Breite mit dem Text und ist entsprechend schmal - eine
       grosse Hoehe waere dort ein Streifen. Einspaltig laeuft es ueber die
       volle Breite, und ein gewoehnliches Hochformat aus dem Handy ist dann
       von selbst hoch. Mit dem engen Deckel von nebenan wurde davon ein
       Drittel abgeschnitten. */
    --media-h-narrow: 76vh;
    /* Und wie viel muss der Text auf jeden Fall behalten? */
    --body-min: 26rem;
    /* Und wie viel von der Zeile darf die Bildspalte hoechstens nehmen?

       Der Anteil gehoert zur Betonung wie alles andere hier. Er stand vorher
       als feste 0,55 in app.js und galt damit fuer jeden Baustein gleich -
       ausgerechnet bei einer Szene, wo das Bild fuehren soll, deckelte er
       dadurch, obwohl der Text sein Mindestmass noch lange nicht erreicht
       hatte. */
    --media-share: 0.55;
    /* Ersatzwert, falls kein Bild eingestellt ist - das gewoehnliche
       Querformat einer Kamera. Der Server ueberschreibt ihn am Element. */
    --img-ratio: 1.5;

    display: grid;
    gap: clamp(1.75rem, 4vw, 3rem);
}

/* Szene - hier gibt es etwas zu sehen. Das Bild darf fast bis an den oberen
   und unteren Fensterrand reichen; wie breit es dabei wird, entscheidet das
   Motiv. */
.section--scene .split {
    /* 84vh statt kleiner: Beim Kleben bleibt der Rest des Fensters als Rand
       ueber und unter dem Bild stehen, je zur Haelfte. Bei 84 sind das rund
       70px - genug, dass es nach Absicht aussieht, und wenig genug, dass die
       Spalte gefuellt wirkt. Hoeher zu gehen brachte nichts mehr: Ab etwa 90
       stoesst das Bild an die Kopfzeile der Vorschau. */
    --media-h: clamp(24rem, 80vh, 50rem);
    --media-h-narrow: 82vh;
    --media-w-max: 42rem;
    --body-min: 26rem;
    /* Hier gibt es etwas zu sehen, also bekommt das Bild die groessere
       Haelfte. Gemessen beim Ort: 728 statt 629 Pixel, und der Text behaelt
       immer noch seine 26rem. */
    --media-share: 0.64;
}

/* Sachlich - hier gibt es etwas zu tun. Das Bild begleitet nur, und der Text
   behaelt mehr Platz, weil dort die Antwortflaechen stehen. */
.section--quiet .split {
    --media-h: clamp(12rem, 34vh, 22rem);
    /* Einspaltig zurueckhaltender als eine Szene, aber nicht so eng, dass ein
       Hochformat auf die Haelfte zusammengeschnitten wird - und Hochformat ist
       hier genau das Empfohlene, weil das Formular daneben hoch baut. Mit 42vh
       waren es auf dem Tablet gemessene 52 Prozent Beschnitt. */
    --media-h-narrow: 68vh;
    --media-w-max: 28rem;
    --body-min: 28rem;
}

/* Ein schmaler Baustein - Termin, Hinweis, ein einzelner Programmpunkt.

   Er darf ein Bild haben, aber keins, das ihn ueberstimmt. Der ganze Sinn von
   compact() ist, dass zwei Zeilen Text sich wie ein Absatz lesen und nicht wie
   ein Kapitel; ein bildschirmhohes Motiv daneben macht daraus wieder eins.

   Empfohlen wird deshalb ein Panorama (siehe ImageShape am Bildfeld): ein
   flaches Band neben zwei, drei Zeilen. Ein Hochformat wuerde hier auf
   Bandhoehe zusammengeschnitten und nichts mehr zeigen - deshalb steht die
   Empfehlung am Feld, wo der Admin sie beim Auswaehlen sieht.

   Steht nach .section--quiet .split: Beide haben dieselbe Spezifitaet, und
   die drei kompakten Bausteine sind zugleich QUIET. */
.section--compact .split {
    /* Deutlich niedriger als ein Kapitel, aber hoch genug, dass ein Bild
       Gegenwart hat. Mit 20vh wurde aus einem gewoehnlichen Querformat eine
       256 Pixel breite Briefmarke neben 888 Pixeln Text - gemessen 21 Prozent
       der Buehne. Das ist kein Begleiten mehr, das ist ein Loch mit einem Bild
       darin. */
    --media-h: clamp(11rem, 28vh, 19rem);
    --media-h-narrow: 40vh;
    --media-w-max: 40rem;
    --body-min: 22rem;
}

.split__media {
    margin: 0;
}

.split__media img {
    width: 100%;
    /* Das eigene Verhaeltnis des Bildes, nicht ein vorgegebenes. Zusammen mit
       der gedeckelten Hoehe heisst das: Das Bild wird nie verzerrt und im
       Regelfall auch nicht beschnitten - der Ausschnitt hat ja genau seine
       Form. Beschnitten wird nur da, wo die Deckel greifen, und dann
       entscheidet weiterhin der Fokuspunkt, was stehenbleibt. */
    aspect-ratio: var(--img-ratio);
    /* Einspaltig gilt der grosszuegigere Deckel; die Regel fuer nebeneinander
       steht unten in der Breiten-Abfrage. */
    max-height: var(--media-h-narrow);
    object-fit: cover;
}

.split__body > * + * {
    margin-top: var(--space-block);
}

@media (min-width: 62rem) {
    /* Bewusst nicht 50/50, und je nach Betonung verschieden: Eine Seite aus
       lauter gleich geteilten Abschnitten sieht gerastert aus, egal wie gut
       die einzelnen sind. */
    /* Die Bildspalte ist so breit, wie das Bild bei voller Hoehe waere - aber
       nie so breit, dass dem Text weniger als --body-min bleibt.

       minmax(0, ...) ist dabei das Entscheidende: Die Obergrenze ist der
       Wunsch, die Untergrenze 0 erlaubt dem Raster, die Spalte zu stauchen,
       wenn der Text sein Minimum einfordert. Ohne die 0 liefe ein Panorama
       ueber den Fensterrand hinaus.

       Der Seitenrand wird hier nicht mehr abgezogen: Das Bild sass frueher um
       genau diesen Betrag weiter aussen als jeder Text und musste deshalb aus
       der Rechnung herausgenommen werden. Jetzt sitzt es auf derselben Kante,
       und die Spaltenbreite ist wieder schlicht Hoehe mal Verhaeltnis. */
    /* Die Breite der Bildspalte.

       Das Verhaeltnis des Bildes gibt sie vor - ein Hochformat bekommt eine
       schmale Spalte, ein Querformat eine breite. Boden und Deckel fangen die
       Enden ab: Ohne Boden wurde aus einem hochkanten Foto in einem sachlichen
       Baustein eine 173px schmale Briefmarke neben 915px Text, ohne Deckel
       waere fuer den Text nichts uebrig geblieben. */
    .split {
        --media-w: clamp(
            16rem,
            calc(var(--media-h) * var(--img-ratio)),
            var(--media-w-max));

        grid-template-columns: minmax(0, var(--media-w)) minmax(var(--body-min), 1fr);
        gap: clamp(2.5rem, 5vw, 4.5rem);
        /* stretch statt center - siehe die lange Begruendung unten. */
        align-items: stretch;
    }

    .split--media-right {
        grid-template-columns: minmax(var(--body-min), 1fr) minmax(0, var(--media-w));
    }

    /* Der Quelltext bleibt unveraendert (Bild zuerst) - nur die Anzeige
       dreht sich. Vorlesesoftware und Tastatur behalten die Reihenfolge. */
    .split--media-right > .split__media {
        order: 2;
    }

    /* Hier lag der schwerste Fehler des bisherigen Layouts.

       Das Bild hatte ein festes Seitenverhaeltnis, seine Hoehe hing also
       allein an der Spaltenbreite - nie am Inhalt daneben. Bei 1440px waren
       das immer 760px, egal was rechts stand. Zusammen mit align-items:center
       ergab das:

         Uebernachtung   Bild 760, Text 1655  ->  448px Leere ueber UND unter
         Ort             Bild 760, Text  349  ->  411px Leere neben dem Text
         Dresscode       Bild 760, Text  255  ->  504px Leere neben dem Text

       Das Bild schwebte in der Mitte einer Spalte, die es nicht fuellte. Kein
       Abstand, den jemand gesetzt haette - einfach uebriggebliebener Platz.

       Jetzt traegt die Bildspalte die Hoehe des Inhalts (stretch), und das
       Bild darin klebt beim Scrollen. Dadurch steht bei jeder Scrollposition
       Bild neben Text, statt dass eine Spalte irgendwo ins Leere laeuft. Bei
       kurzen Bausteinen faellt das gar nicht auf - da reicht die Bildhoehe
       ohnehin fast bis zum Rand und es gibt nichts zu kleben. */
    .split__media {
        align-self: stretch;

        /* Das Bild darunter ist auf 100% Hoehe gestellt und traegt damit
           nichts mehr zur Zeilenhoehe bei - eine Prozenthoehe in einer Zeile
           unbestimmter Hoehe zaehlt nicht mit. Ohne diese Untergrenze wuerde
           ein Baustein mit wenig Text auf Texthoehe zusammenfallen, und aus
           einem grossen Bild wuerde eine Briefmarke.

           Sie ist zugleich die Groesse, an der app.js die Bildbreite ausrichtet:
           erst steht die Hoehe fest, dann folgt die Breite daraus. */
        min-height: min(
            calc(var(--media-w) / var(--img-ratio)),
            var(--media-h));
    }

    /* Der Text steht mittig zum Bild - und das ist die andere Haelfte
       desselben Problems.

       Ist das Bild hoeher als der Text, klebte der Text bisher oben und
       darunter blieb die halbe Spalte leer. Bei einem hohen Motiv neben drei
       Zeilen Beschreibung war das ein Loch von ueber 400px. Mittig gesetzt
       wird daraus eine Komposition: kurzer Textblock auf der Achse des Bildes.

       Fuer den umgekehrten Fall - Text hoeher als Bild, etwa beim Hotel -
       aendert die Regel nichts: Wer die Zeilenhoehe selbst bestimmt, kann
       darin nicht zentriert werden. Eine Regel deckt damit beide Faelle ab. */
    .split__body {
        align-self: center;
    }

    .split__media img {
        /* Das Bild fuellt die Spalte auf ganzer Hoehe. Das ist die Regel, um
           die es hier geht.

           Vorher stand das Bild in seinem eigenen Verhaeltnis in einer Spalte,
           die der Text bestimmt - und war fast immer kleiner als sie. Was
           uebrig blieb, war kein Abstand, sondern ein Loch: gemessen 428px bei
           "Habt ihr Zeit und Lust?", 458px bei der Teilnehmerzahl, 365px beim
           Essen. Das Bild schwebte darin als Streifen.

           Jetzt gibt die Spalte die Hoehe vor und das Bild fuellt sie. Wo die
           Breite dafuer nicht reicht, wird beschnitten - der Fokuspunkt
           entscheidet, was stehenbleibt. Ein etwas beschnittenes Bild ist
           allemal besser als ein winziges in einem Loch. */
        height: 100%;
        aspect-ratio: auto;

        /* Wird die Spalte hoeher als das Fenster - beim Hotel sind es 1580px -,
           waere das Bild ein senkrechter Streifen aus einem schmalen
           Bildausschnitt. Dann bleibt es fensterhoch und klebt: So ist die
           sichtbare Spalte an jeder Scrollposition trotzdem ganz gefuellt.

           Etwas kleiner als das Fenster, nicht genau so hoch: Ein Bild, das
           oben und unten exakt an den Rand stoesst, wirkt angeschnitten wie
           ein Fehler statt gesetzt. Die verbleibenden 12 Prozent teilen sich
           als Rand ueber und unter das Bild - deshalb klebt es auf halber
           Hoehe davon und nicht bei null. */
        max-height: 88vh;
        position: sticky;
        top: 6vh;
    }

}

/* Ein Band bleibt ein Band.

   Die Regel oben zieht das Bild auf die Hoehe der Zeile und schneidet zu - fuer
   einen erzaehlenden Baustein genau richtig, denn dort ist das Bild die Szene.
   Bei einem kompakten Baustein kehrt sie sich um: Gemessen wurde ein Panorama
   von 2,4 neben einem Termin mit An- und Abreise auf 519 x 389 gestreckt, also
   auf 1,33 - 44 Prozent des Motivs weg, und aus dem flachen Band wurde wieder
   ein Kapitelbild. Genau das soll compact() verhindern.

   Hier fuehrt deshalb das Bild seine Form mit: Es behaelt sein Verhaeltnis,
   bleibt unter dem Bandhoehen-Deckel und steht oben in seiner Spalte. Dass der
   Text daneben etwas tiefer reicht, ist kein Loch, sondern der gewoehnliche
   Satz einer Abbildung neben ihrem Text - und allemal besser als ein Motiv,
   von dem die Haelfte fehlt.

   Das ist zugleich der Grund, warum am Bildfeld Panorama empfohlen steht: Die
   Breite folgt hier der Hoehe mal dem Verhaeltnis, und die Hoehe ist gedeckelt.
   Ein liegendes Motiv wird dadurch breit, ein stehendes bleibt schmal - daran
   kann kein Layout etwas aendern, ohne das Motiv zu zerschneiden. Der Versuch,
   ein Hochformat von 0,76 auf Bandbreite zu ziehen, kostete gemessen 74 Prozent
   des Bildes.

   Kein Kleben: Ein 220 Pixel hohes Band hat nichts, wohin es wandern koennte. */
@media (min-width: 62rem) {
    .section--compact .split__media {
        align-self: start;
    }

    .section--compact .split__media img {
        height: auto;
        aspect-ratio: var(--img-ratio);
        max-height: var(--media-h);
        position: static;
    }
}

/* Wer Bewegung abbestellt hat, bekommt auch kein wanderndes Bild. Sticky ist
   streng genommen keine Animation, wirkt aber wie eine - und wer die Einstellung
   setzt, meint genau das. Ohne Kleben steht das Bild oben in seiner Spalte. */
@media (min-width: 62rem) and (prefers-reduced-motion: reduce) {
    .split__media img {
        position: static;
    }

    .split__media {
        align-self: start;
    }
}

/* Foto-Baustein ---------------------------------------------------------- */

.photo {
    margin: 0;
}

.photo--bleed {
    width: 100%;
}

.photo__img {
    width: 100%;
    /* Ohne festes Verhaeltnis behaelt das Bild sein eigenes - dank
       height:auto bleibt es dabei immer unverzerrt. */
    height: auto;
}

/* Die Bildunterschrift sitzt auf derselben Kante wie aller uebrige Text.
   Vorher lief sie in einer eigenen, enger zentrierten Spalte und stand
   dadurch sichtbar versetzt unter dem Bild. */
.photo figcaption {
    margin-top: 0.85rem;
}

/* Feste Formate schneiden per object-fit zu, statt zu verzerren.
   Die Namen kommen ohne doppelte Unterstriche aus, weil sie im Template
   dynamisch zusammengesetzt werden und Thymeleaf __...__ selbst deutet. */
.photo-breit {
    aspect-ratio: 16 / 9;
    object-fit: cover;
}

.photo-panorama {
    aspect-ratio: 21 / 9;
    object-fit: cover;
}

/* Auf schmalen Bildschirmen wird ein Panorama sonst zu einem Streifen. */
@media (max-width: 34rem) {
    .photo-panorama {
        aspect-ratio: 3 / 2;
    }
}

/* Ein Bild innerhalb eines Textbausteins - etwa der Blick ins Haus unter der
   Beschreibung. Festes Verhaeltnis, damit beim Nachladen nichts springt; was
   beim Zuschneiden stehenbleibt, bestimmt der Fokuspunkt des Bildes. */
.block-photo {
    width: 100%;
    aspect-ratio: 3 / 2;
    object-fit: cover;
}

/* --- 7. Eckdaten -------------------------------------------------------- */

.factlist {
    display: grid;
    /* auto-fit + minmax: bricht selbstaendig auf eine Spalte um,
       ohne dass dafuer eine Breakpoint-Regel noetig waere. */
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
    gap: clamp(1.5rem, 3vw, 2.5rem);
    /* Zwei Haarlinien fassen die Eckdaten ein, ohne eine Karte zu bauen. */
    padding-block: clamp(1.5rem, 3vw, 2.25rem);
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
}

.factlist__term {
    font-size: 0.68rem;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--ink-faint);
    margin-bottom: 0.5rem;
}

.factlist__value {
    font-family: var(--font-display);
    font-size: 1.3rem;
    line-height: 1.35;
}

/* Eine ruhige Aufzaehlung aus "Stichwort - Text": Anreisewege, was der Koffer
   braucht, was das Haus zu bieten hat.

   Bewusst untereinander statt im Raster wie die Eckdaten. Die Eckdaten sind
   drei feste Angaben und vertragen Spalten; hier weiss niemand vorher, ob drei
   oder acht Zeilen kommen und wie lang sie werden - ein Raster daraus haette
   Restplaetze und saehe nach Tabelle aus. Getrennt wird nur durch Weissraum,
   Halt gibt die kleine Versalzeile, die auch die Eckdaten benutzen. */
.deflist > * + * {
    margin-top: 1.15rem;
}

.deflist__term {
    display: block;
    font-size: 0.68rem;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--ink-faint);
    margin-bottom: 0.2rem;
}

/* --- 8. Formularteile in der Einladung ---------------------------------- */

.question + .question {
    margin-top: var(--space-block);
}

.question__label {
    display: block;
    font-family: var(--font-display);
    font-size: 1.35rem;
    margin-bottom: 0.85rem;
}

.question__help {
    color: var(--ink-soft);
    font-size: 0.92rem;
    margin: -0.5rem 0 0.85rem;
}

/* Auswahl als grosse antippbare Flaechen - fuer Daumen und fuer Grosseltern.

   Flex statt Raster, und zwar wegen der Restzeile: Ein festes Raster laesst
   bei drei Optionen in zwei Spalten die dritte allein und halbbreit stehen.
   Hier waechst stattdessen jede Flaeche mit, sodass eine angebrochene Zeile
   immer ausgefuellt wird und gewollt aussieht. */
.choices {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
}

/* Die Grundbreite bestimmt die Beschriftung selbst - das ist der Kern.

   Vorher stand hier eine feste Grundbreite von 8rem, und die kann nur fuer
   eine Sorte Antwort richtig sein. "Ja" und "Nein, wir schlafen woanders"
   bekamen denselben Platz: Drei lange Antworten blieben nebeneinander stehen
   und schrumpften auf gut 6rem, von denen nach Innenabstand und Haekchen keine
   3rem fuer den Text uebrig blieben - die Beschriftung brach dann vier Zeilen
   tief um. Eine groessere feste Breite kehrt den Fehler nur um: Dann steht
   auch "Ja / Nein / Vielleicht" untereinander und die Seite verliert ihren
   Rhythmus.

   Mit auto ist die Grundbreite die ungebrochene Beschriftung, und
   flex-shrink: 0 verhindert, dass sie darunter gedrueckt wird. Kurze Antworten
   stehen dadurch nebeneinander, lange ruecken von selbst in die naechste
   Zeile, und weil jede Flaeche weiter waechst, wird die angebrochene Zeile
   trotzdem ausgefuellt.

   max-width haelt den Ausnahmefall ab: Eine einzelne Antwort, die laenger ist
   als die Spalte, bricht dann innerhalb ihrer Flaeche um, statt ueber den Rand
   hinauszulaufen. */
.choice {
    position: relative;
    display: flex;
    flex: 1 0 auto;
    max-width: 100%;
}

/* Auf dem Handy nebeneinander zu quetschen bringt nichts: Die Beschriftungen
   brechen dann unterschiedlich um und die Flaechen werden schmaler als der
   Daumen. Untereinander ist hier eindeutig besser.

   34rem statt 30rem, weil dazwischen der unangenehmste Bereich lag: zwei
   Flaechen nebeneinander, beide zu schmal fuer ihre Beschriftung, dazu eine
   dritte allein darunter. */
@media (max-width: 34rem) {
    .choice {
        flex-basis: 100%;
    }
}

/* Der Radiokreis wird nur unsichtbar, nicht ersetzt: Tastatur,
   Vorlesesoftware und das Absenden ohne JavaScript haengen daran. */
.choice input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}

/* Eine Antwort ist kein Knopf.

   Vorher war jede Auswahl ein umrandeter Kasten, der beim Antippen komplett
   tannengruen wurde. Drei gefuellte Flaechen sind das Lauteste, was auf dieser
   Seite passieren kann - lauter als das Titelbild -, und in einer Einladung,
   die sonst nur aus Schrift und Weissraum besteht, sieht das aus wie ein
   Formular, das jemand hineinkopiert hat.

   Jetzt traegt eine Haarlinie die Antwort, so wie die Haarlinien die Eckdaten
   tragen. Ausgewaehlt wird ueber Farbe und einen gesetzten Punkt, nicht ueber
   eine Flaeche. */
.choice__face {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    gap: 0.1rem;
    /* Grosszuegig antippbar - das ist hier kein Schmuck. Die Einladung wird
       auch von Grosseltern auf dem Handy benutzt. */
    min-height: 3.6rem;
    padding: 0.7rem 0 0.8rem;
    border-bottom: 1px solid var(--line-strong);
    cursor: pointer;
    transition: border-color 0.2s ease, color 0.2s ease;
}

.choice__word {
    display: flex;
    align-items: baseline;
    font-size: 0.86rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    font-weight: 500;
    line-height: 1.3;
}

/* Der gesetzte Punkt - dasselbe Zeichen, das die Marke der Rueckmeldung
   traegt. Sein Platz ist immer reserviert, auch unausgewaehlt, sonst
   ruecken beim Antippen alle Beschriftungen zur Seite. */
.choice__word::before {
    content: '';
    flex: none;
    width: 0.4rem;
    height: 0.4rem;
    margin-right: 0.55rem;
    border-radius: 50%;
    background: currentColor;
    opacity: 0;
    transition: opacity 0.2s ease;
}

/* Die leise Zeile: der ganze Satz, den das Wort darueber abkuerzt. */
.choice__hint {
    font-family: var(--font-display);
    font-style: italic;
    font-size: 1.02rem;
    line-height: 1.3;
    color: var(--ink-faint);
    transition: color 0.2s ease;
}

.choice input:checked + .choice__face {
    border-bottom-color: var(--accent);
    color: var(--accent);
}

.choice input:checked + .choice__face .choice__word::before {
    opacity: 1;
}

.choice input:checked + .choice__face .choice__hint {
    color: var(--accent);
}

.choice input:focus-visible + .choice__face {
    outline: 2px solid var(--accent);
    outline-offset: 4px;
}

.choice input:not(:checked) + .choice__face:hover {
    border-bottom-color: var(--accent);
    color: var(--accent);
}

.choice input:not(:checked) + .choice__face:hover .choice__hint {
    color: var(--ink-soft);
}

.field {
    display: block;
}

.field > span {
    display: block;
    font-size: 0.8rem;
    letter-spacing: 0.06em;
    color: var(--ink-soft);
    margin-bottom: 0.45rem;
}

.input,
.textarea,
.select {
    width: 100%;
    padding: 0.75rem 0.9rem;
    background: var(--paper);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius);
    /* Unter 16px zoomt iOS beim Antippen in die Seite hinein. */
    font-size: max(1rem, 16px);
    line-height: 1.5;
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.textarea {
    min-height: 7rem;
    resize: vertical;
}

/* Freitext in der Einladung: eine Linie, kein Kasten.

   Bewusst nur hier, innerhalb einer Frage. Die Verwaltung behaelt ihre
   umrandeten Felder - sie ist dicht und tabellarisch, dort ist der Kasten
   richtig. In der Einladung stehen zwei, drei Felder auf einer ansonsten
   leeren Seite, und dort wirkt jeder Rahmen wie ein Fremdkoerper. */
.question .input--line,
.question .textarea--line {
    padding: 0.6rem 0.1rem;
    background: transparent;
    border: 0;
    border-bottom: 1px solid var(--line-strong);
    border-radius: 0;
    transition: border-color 0.2s ease;
}

.question .textarea--line {
    /* Deutlich flacher als der alte Kasten: Eine hohe leere Flaeche fordert
       einen Aufsatz an. Zwei Zeilen laden zu einem Satz ein, und mehr will
       hier ohnehin niemand schreiben. Wer doch mehr tippt, bekommt mehr -
       das Feld waechst mit (siehe app.js). */
    min-height: 3.4rem;
    resize: none;
    overflow: hidden;
}

.question .input--line:focus,
.question .textarea--line:focus {
    border-bottom-color: var(--accent);
    box-shadow: none;
    outline: none;
}

/* Zahl mit zwei Flaechen statt Browserpfeilen ---------------------------- */

.stepper {
    display: inline-flex;
    align-items: stretch;
    border-bottom: 1px solid var(--line-strong);
}

.stepper__key {
    /* Ohne JavaScript gibt es keine Knoepfe - dann bleiben die Pfeile des
       Browsers die einzige Bedienung, und die darf man nicht auch noch
       wegnehmen. Sichtbar werden sie erst durch die .js-Klasse. */
    display: none;
    width: 2.75rem;
    min-height: 2.75rem;
    align-items: center;
    justify-content: center;
    border: 0;
    background: transparent;
    color: var(--ink-faint);
    font-size: 1.25rem;
    line-height: 1;
    cursor: pointer;
    transition: color 0.2s ease;
}

.js .stepper__key {
    display: inline-flex;
}

.stepper__key:hover {
    color: var(--accent);
}

.stepper__key:disabled {
    opacity: 0.3;
    cursor: not-allowed;
}

.stepper__value {
    width: 3.5rem;
    padding: 0.5rem 0;
    border: 0;
    background: transparent;
    text-align: center;
    font-family: var(--font-display);
    /* Die Zahl ist hier die Antwort - sie darf so gross sein wie eine
       Ueberschrift, nicht so klein wie ein Formularwert. */
    font-size: 1.75rem;
    line-height: 1.2;
}

.stepper__value::placeholder {
    color: var(--line-strong);
    opacity: 1;
}

.stepper__value:focus {
    outline: none;
}

.stepper:focus-within {
    border-bottom-color: var(--accent);
}

/* Die Pfeile des Browsers verschwinden nur, wenn es einen Ersatz gibt. */
.js .stepper__value {
    appearance: textfield;
    -moz-appearance: textfield;
}

.js .stepper__value::-webkit-outer-spin-button,
.js .stepper__value::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

.input:focus,
.textarea:focus,
.select:focus {
    border-color: var(--accent);
    outline: none;
    box-shadow: 0 0 0 3px var(--accent-wash);
}

.switch {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    min-height: 2.75rem;
    cursor: pointer;
}

.switch input {
    width: 1.15rem;
    height: 1.15rem;
    accent-color: var(--accent);
    flex: none;
}

/* --- 9. Schaltflaechen -------------------------------------------------- */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    min-height: 3rem;
    padding: 0.7rem 1.6rem;
    border: 1px solid var(--accent);
    border-radius: var(--radius);
    background: var(--accent);
    color: var(--paper);
    font-size: 0.92rem;
    letter-spacing: 0.04em;
    text-decoration: none;
    cursor: pointer;
    transition: background-color 0.2s ease, border-color 0.2s ease;
}

.btn:hover {
    background: var(--accent-hover);
    border-color: var(--accent-hover);
    color: var(--paper);
}

.btn--ghost {
    background: transparent;
    color: var(--accent);
    border-color: var(--line-strong);
}

.btn--ghost:hover {
    background: var(--accent-wash);
    border-color: var(--accent);
    color: var(--accent);
}

.btn--small {
    min-height: 2.4rem;
    padding: 0.35rem 0.85rem;
    font-size: 0.85rem;
}

.btn--danger {
    background: transparent;
    color: var(--warn);
    border-color: var(--line-strong);
}

.btn--danger:hover {
    background: transparent;
    border-color: var(--warn);
    color: var(--warn);
}

.btn[disabled] {
    opacity: 0.5;
    cursor: not-allowed;
}

.btn-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.7rem;
}

/* --- 10. Rueckmeldungen ------------------------------------------------- */

.notice {
    padding: 0.9rem 1.15rem;
    border-left: 2px solid var(--accent);
    background: var(--accent-wash);
    font-size: 0.95rem;
}

.notice--warn {
    border: 1px solid var(--line-strong);
    border-left: 2px solid var(--warn);
    background: transparent;
}

/* Der Speicherstand einer Antwort. Bewusst leise: er soll bestaetigen,
   nicht feiern. Die Hoehe ist reserviert, damit beim Wechsel des Textes
   nichts springt. */
.savestate {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    min-height: 1.5rem;
    font-size: 0.86rem;
    color: var(--ink-faint);
    transition: color 0.2s ease;
}

.savestate::before {
    content: '';
    width: 0.45rem;
    height: 0.45rem;
    border-radius: 50%;
    background: currentColor;
    opacity: 0;
    transition: opacity 0.3s ease;
}

.savestate--saving::before,
.savestate--saved::before,
.savestate--error::before {
    opacity: 0.65;
}

.savestate--saved {
    color: var(--accent);
}

.savestate--error {
    color: var(--warn);
}

/* Wird noch getippt: der Punkt pulsiert ganz langsam. */
.savestate--saving::before {
    animation: pulse 1.4s ease-in-out infinite;
}

@keyframes pulse {
    50% {
        opacity: 0.2;
    }
}

.saved-hint {
    color: var(--ink-faint);
    font-size: 0.88rem;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}

/* Baustein, den nur der Admin in der Vorschau sieht. */
.hidden-marker {
    display: inline-block;
    font-size: 0.68rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--warn);
    border: 1px dashed var(--line-strong);
    padding: 0.2rem 0.6rem;
    margin-bottom: 1rem;
}

/* --- Fusszeile der Einladung -------------------------------------------- */

.invitation-foot {
    margin-top: auto;
    padding-block: clamp(3rem, 7vw, 5rem);
    border-top: 1px solid var(--line);
    color: var(--ink-faint);
    font-size: 0.85rem;
}

.invitation-foot a {
    color: var(--ink-faint);
}

/* --- Anmeldung ---------------------------------------------------------- */

.login {
    min-height: 100vh;
    min-height: 100svh;
    display: grid;
    place-items: center;
    padding: var(--gutter);
    background: var(--paper);
}

.login__inner {
    width: 100%;
    max-width: 23rem;
}

.login__inner > * + * {
    margin-top: 1.15rem;
}

/* --- 11. Inhaltsverzeichnis --------------------------------------------- */

/* Die Abschnitte dieser einen Einladung, zum Nachschlagen und Anspringen.

   Es soll ein Detail sein, kein Bedienelement. Die Einladung und vor allem
   ihre Bilder bleiben das Wichtigere; deshalb kleine Schrift, viel Abstand,
   Haarlinien statt Flaechen, und Tannengruen ausschliesslich fuer die eine
   Zeile, auf der man gerade steht.

   Zwei Gestalten, dasselbe Element:

     ab 90rem   senkrecht am rechten Rand. Die Einladung bekommt den Platz
                dafuer ausgespart, statt dass die Leiste ueber ihr liegt -
                sonst staende sie irgendwann auf einem Foto und waere weder zu
                lesen noch zu uebersehen.
     darunter   eine schmale Zeile oben, die sagt, wo man gerade ist, und sich
                zur ganzen Liste aufklappen laesst. Sie faehrt erst herein,
                wenn der erste Abschnitt durch ist: Ueber dem Titelbild soll
                nichts stehen.

   Die senkrechte Haarlinie durch die Liste traegt die Marken wie Stationen an
   einem Weg. Sie ist zugleich die Stelle, an der spaeter das Storytelling
   ansetzen kann - aus Punkt, Linie und Ring koennen Weg, Haus und Gipfel
   werden, ohne dass am Markup etwas zu aendern waere.

   Aufgeteilt ist der Abschnitt nach Gestalt, nicht nach Eigenschaft: erst was
   beiden gemeinsam ist, dann je eine Abfrage fuer die Zeile oben und die
   Spalte rechts. Anfangs stand das Verhalten der Zeile - hereinfahren,
   zuklappen - als Grundzustand da und die Spalte nahm es Eigenschaft fuer
   Eigenschaft zurueck. Das ist zweimal dieselbe Angabe an zwei Stellen, und
   eine davon war die Transition: Beim Wechsel ueber die Schwelle glitt die
   Leiste sichtbar herein, und blieb der Uebergang stehen, stand sie ausserhalb
   des Bildes. Jetzt kennt jede Gestalt nur ihre eigenen Angaben. */

.toc {
    position: fixed;
    z-index: 20;
    font-size: 0.82rem;
    line-height: 1.5;
    color: var(--ink-faint);
    /* Fuer den Wechsel auf das Titelbild und wieder herunter. */
    transition: color 0.35s ease, text-shadow 0.35s ease;
}

.toc::before {
    transition: opacity 0.35s ease;
}

.toc__now {
    display: none;
    width: min(100% - 2 * var(--gutter), var(--page-max));
    margin-inline: auto;
    align-items: center;
    gap: 0.7rem;
    min-height: 2.9rem;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--ink-soft);
    font-size: 0.68rem;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    text-align: left;
    cursor: pointer;
}

/* Ein langer Abschnittsname darf die Zeile nicht zweizeilig machen - die
   Leiste soll immer gleich hoch bleiben. */
.toc__now-text {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Ein Winkel aus zwei Strichen, gedreht. Dieselbe Haltung wie die Marken der
   Bausteine: eine Strichstaerke, keine Fuellung, kein Symbolsatz. */
.toc__now-sign {
    flex: none;
    width: 0.42rem;
    height: 0.42rem;
    border-right: 1px solid currentColor;
    border-bottom: 1px solid currentColor;
    transform: translateY(-0.12rem) rotate(45deg);
    transition: transform 0.3s var(--ease-out);
}

.toc[data-open] .toc__now-sign {
    transform: translateY(0.06rem) rotate(-135deg);
}

/* Die Marke steht rechts vom Namen, nicht links.

   Die Leiste sitzt am rechten Rand der Seite; ein Weg an ihrer Innenkante
   liefe mitten zwischen Einladung und Text. Aussen wird daraus eine
   Marginallinie am Blattrand, und die Namen ruecken an die Einladung heran.
   Der Quelltext bleibt unveraendert - die Marke steht dort weiter vorn, weil
   sie beim Vorlesen zum Namen gehoert; gedreht wird nur die Anzeige. */
.toc__link {
    position: relative;
    display: grid;
    grid-template-columns: 1fr 0.7rem;
    align-items: center;
    gap: 0.85rem;
    padding-block: 0.42rem;
    color: inherit;
    text-decoration: none;
    text-align: right;
    transition: color 0.25s ease;
}

.toc__text {
    min-width: 0;
    grid-column: 1;
    grid-row: 1;
}

.toc__mark {
    grid-column: 2;
    grid-row: 1;
}

/* Der Weg zwischen den Stationen. Je Eintrag ein Stueck, dadurch passt er sich
   jeder Zeilenhoehe von selbst an. Beim ersten und letzten Eintrag beginnt
   beziehungsweise endet er an der Marke, sonst finge er im Nichts an.

   Am rechten Rand wird er weiter unten durch eine durchgehende Linie ersetzt,
   die ueber die ganze Fensterhoehe laeuft. Hier, im aufgeklappten Zustand der
   schmalen Zeile, sollen die Enden dagegen an den Marken sitzen: Die Liste ist
   dort nur so hoch wie ihr Inhalt. */
.toc__link::before {
    content: '';
    position: absolute;
    right: calc(0.35rem - 0.5px);
    top: 0;
    bottom: 0;
    width: 1px;
    background: var(--line);
}

.toc__list li:first-child .toc__link::before {
    top: 50%;
}

.toc__list li:last-child .toc__link::before {
    bottom: 50%;
}

.toc__link:hover,
.toc__link.is-current {
    color: var(--accent);
}

/* Wo man gerade ist, steht fett. Farbe allein ist auf Creme sehr leise - und
   diese eine Zeile ist das, wofuer es die Leiste ueberhaupt gibt. */
.toc__link.is-current .toc__text {
    font-weight: 600;
}

/* Die Marken. Gezeichnet aus einem Kreis und nichts weiter - in dieser Groesse
   waere von einem Symbol nur ein Fleck uebrig. Der Hintergrund deckt die
   Haarlinie ab, damit der Weg nicht durch die Station laeuft.

   Die drei Zustaende heissen toc-mark-* und nicht toc__mark--*: Ihr Name wird
   im Template zusammengesetzt, und Thymeleaf deutet doppelte Unterstriche als
   Praeprozessor-Syntax. Dieselbe Vorsichtsmassnahme wie bei photo-breit. */
.toc__mark {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 0.7rem;
    height: 1.2rem;
    background: var(--paper);
}

.toc__mark::before {
    content: '';
    display: block;
    border-radius: 50%;
    transition: background-color 0.25s ease, border-color 0.25s ease;
}

/* Hier steht etwas: ein gesetzter Punkt. */
.toc-mark-info::before {
    width: 0.28rem;
    height: 0.28rem;
    background: var(--line-strong);
}

/* Hier wird etwas von euch erwartet: ein offener Ring. */
.toc-mark-frage::before {
    width: 0.5rem;
    height: 0.5rem;
    border: 1px solid var(--line-strong);
}

/* Und ihr habt schon geantwortet: derselbe Ring mit gesetztem Punkt - genau
   das Zeichen, das unten in der Einladung eine gewaehlte Antwort traegt. */
.toc-mark-beantwortet::before {
    width: 0.5rem;
    height: 0.5rem;
    border: 1px solid var(--accent);
    background: radial-gradient(circle, var(--accent) 0 38%, transparent 40%);
}

.toc__link.is-current .toc-mark-info::before {
    background: var(--accent);
}

.toc__link.is-current .toc-mark-frage::before {
    border-color: var(--accent);
}

/* Zu wenig Platz daneben: die schmale Zeile oben. */
@media (max-width: 89.999rem) {
    .toc {
        /* Normalerweise null. In der Vorschau steht darueber noch die Leiste
           der Verwaltung; app.js misst deren Hoehe und traegt sie als
           --sticky-top ein, damit die Zeile sich darunter schiebt statt den
           Rueckweg in den Baukasten zu verdecken. Denselben Wert liest der
           Sprungabstand an .section. */
        top: var(--sticky-top, 0px);
        left: 0;
        right: 0;
        background: var(--paper);
        border-bottom: 1px solid var(--line);
    }

    /* Sie erscheint erst, sobald der erste Abschnitt vorbei ist. Ueber dem
       Titelbild soll nichts stehen - dort ist die Einladung noch ganz Bild.

       Aufblenden statt herausfahren: Ein Weg von "minus der eigenen Hoehe"
       muesste wissen, wo die Zeile anfaengt, und in der Vorschau faengt sie
       eben nicht bei null an - dort blieb ein Streifen davon stehen. Blenden
       ist von der Position unabhaengig und passt ohnehin besser zu einer
       Seite, auf der sonst auch nichts hereingeschoben wird.

       Ohne JavaScript steht die Zeile von Anfang an da und die Liste ist
       offen: Der Knopf koennte sie nicht aufklappen, und eine Bedienung
       anzubieten, die nichts tut, ist schlechter als keine. */
    .js .toc {
        opacity: 0;
        visibility: hidden;
        transform: translateY(-0.5rem);
        transition: opacity 0.4s var(--ease-out),
        transform 0.4s var(--ease-out),
        visibility 0.4s;
    }

    .js .toc.is-visible {
        opacity: 1;
        visibility: visible;
        transform: none;
    }

    .js .toc__now {
        display: flex;
    }

    .toc__list {
        width: min(100% - 2 * var(--gutter), var(--page-max));
        margin-inline: auto;
        padding-block: 0.5rem 1.1rem;
    }

    .js .toc__list {
        display: none;
    }

    .js .toc[data-open] .toc__list {
        display: block;
    }
}

/* Genug Platz: die Liste steht senkrecht am rechten Rand. */
@media (min-width: 90rem) {
    /* Der Platz wird ausgespart, nicht ueberlagert. Dadurch liegt die Leiste
       nie auf einem Bild, und die Einladung bleibt in dem, was uebrig ist,
       mittig - bei 1600px ist das wieder die volle Bahnbreite.

       Ausgespart wird nur, wenn es das Verzeichnis wirklich gibt. Eine
       Einladung aus zwei Abschnitten bekommt keins (siehe blocks/_nav.html),
       und ohne die Bedingung stuende dort ein 15rem breiter Streifen, den
       niemand bestellt hat und der nach einem Fehler aussieht. */
    .page:has(> .toc) {
        padding-right: var(--toc-w);
    }

    /* Das Titelbild ist die Ausnahme: Es laeuft unter der Leiste durch.

       Full Bleed heisst volle Breite, und ein Foto, das kurz vor dem Rand
       aufhoert, weil daneben eine Navigation steht, ist kein Auftakt mehr
       sondern ein Bild in einem Rahmen. Die Leiste legt sich stattdessen
       darauf - dort ist sie ein Detail auf dem Motiv, und weiter unten kehrt
       sie in ihre Spalte zurueck.

       Der Text darin bleibt trotzdem auf der gemeinsamen Kante: Der Rand wird
       nur am Abschnitt zurueckgenommen, der Inhalt bekommt ihn wieder
       aufgeschlagen. Ohne das rutschte die Ueberschrift des Titelbilds um die
       halbe Leistenbreite nach rechts - und ausgerechnet der Auftakt stuende
       dann als einziger Abschnitt neben der Kante.

       Nur der Auftakt, nicht jeder Baustein ueber die volle Breite: Ein
       Foto-Baustein mitten in der Seite ist niedriger als das Fenster. Die
       Leiste laege dann halb auf dem Bild und halb auf Papier, und es gaebe
       keine Schriftfarbe, die auf beidem zu lesen waere. */
    .page:has(> .toc) .section--bleed:has(.hero) {
        margin-right: calc(-1 * var(--toc-w));
    }

    .page:has(> .toc) .section--bleed .hero__content {
        padding-right: var(--toc-w);
    }

    /* Und es reicht bis an den unteren Fensterrand.

       Sonst endete das Motiv bei 52rem, die Leiste aber erst bei 100vh -
       gemessen standen die letzten beiden Eintraege hell auf cremefarbenem
       Papier und waren damit unsichtbar. Ein Auftakt, der genau einen
       Bildschirm fuellt, ist ohnehin das, was er sein will. */
    .page:has(> .toc) .section--bleed .hero {
        min-height: 100vh;
        min-height: 100svh;
    }

    .toc {
        --toc-pad: clamp(1rem, 2vw, 2rem);

        right: 0;
        top: 0;
        bottom: 0;
        width: var(--toc-w);
        display: flex;
        align-items: center;
    }

    /* Der Weg laeuft ueber die ganze Fensterhoehe, nicht nur von der ersten
       bis zur letzten Station.

       Deshalb haengt er hier an der Leiste und nicht mehr an den Eintraegen:
       Die Leiste ist fenster hoch, die Liste darin nur so hoch wie ihre
       Eintraege. Eine Linie aus Teilstuecken koennte oben und unten nicht
       weiterlaufen. */
    .toc::before {
        content: '';
        position: absolute;
        top: 0;
        bottom: 0;
        right: calc(var(--toc-pad) + 0.35rem - 0.5px);
        width: 1px;
        background: var(--line);
    }

    .toc__link::before {
        display: none;
    }

    /* Hier gibt es nichts aufzuklappen - die Liste steht ohnehin ganz da, und
       der Knopf bleibt weg (er ist von Haus aus display:none).

       Die Eintraege verteilen sich ueber die ganze Hoehe, ihr Abstand ist aber
       gedeckelt: Bei vier Abschnitten sollen sie nicht ueber einen halben
       Meter auseinanderstehen. Waechst jeder Eintrag bis zu seinem Deckel und
       wird die Gruppe danach mittig gesetzt, gilt beides zugleich - und der
       Weg laeuft trotzdem von ganz oben nach ganz unten. */
    .toc__list {
        display: flex;
        flex-direction: column;
        justify-content: center;
        height: 100vh;
        /* Bei sehr vielen Abschnitten schrumpfen die Eintraege bis zu ihrer
           Untergrenze und die Liste scrollt danach in sich. Der Balken dazu
           bliebe als graue Kante stehen und waere das Lauteste an der ganzen
           Leiste. */
        overflow-y: auto;
        scrollbar-width: none;
        padding-inline: 0 var(--toc-pad);
    }

    .toc__list::-webkit-scrollbar {
        display: none;
    }

    .toc__list li {
        display: flex;
        flex: 1 1 auto;
        max-height: 4.5rem;
        min-height: 1.9rem;
    }

    .toc__link {
        flex: 1;
        padding-block: 0;
    }

    /* Auf dem Bild spricht die Leiste hell.

       Dieselbe Loesung wie beim Text des Titelbilds: helle Schrift und ein
       Schatten als letzte Sicherung fuer Motive, mit denen niemand gerechnet
       hat. Der Verlauf ueber dem Foto ist oben am schwaechsten, und genau dort
       stehen die ersten Eintraege - ohne Schatten verschwaenden sie in einem
       hellen Himmel.

       Der Weg verschwindet dabei. Seine Stationen tragen sonst ein Stueck
       Papierfarbe, damit die Linie nicht durch sie hindurchlaeuft; auf einem
       Foto waeren das ein Dutzend heller Kaestchen. Ohne Linie braucht es sie
       nicht - und ruhiger ist es ueber einem Motiv ohnehin. */
    .toc--auf-bild {
        color: rgba(247, 244, 238, 0.78);
        text-shadow: 0 1px 12px rgba(10, 14, 11, 0.75);
    }

    .toc--auf-bild::before {
        opacity: 0;
    }

    .toc--auf-bild .toc__mark {
        background: transparent;
    }

    .toc--auf-bild .toc__link:hover,
    .toc--auf-bild .toc__link.is-current {
        color: #f7f4ee;
    }

    .toc--auf-bild .toc-mark-info::before,
    .toc--auf-bild .toc__link.is-current .toc-mark-info::before {
        background: rgba(247, 244, 238, 0.72);
    }

    .toc--auf-bild .toc-mark-frage::before,
    .toc--auf-bild .toc__link.is-current .toc-mark-frage::before {
        border-color: rgba(247, 244, 238, 0.72);
    }

    .toc--auf-bild .toc-mark-beantwortet::before {
        border-color: rgba(247, 244, 238, 0.72);
        background: radial-gradient(circle,
        rgba(247, 244, 238, 0.9) 0 38%, transparent 40%);
    }
}

/* --- 12. Bewegung ------------------------------------------------------- */

/* Ruhiges Erscheinen beim Scrollen. Die Regeln haengen an .js, das ein
   winziges Skript im <head> setzt - ohne JavaScript ist alles von Anfang an
   sichtbar und nichts kann unsichtbar haengenbleiben.

   Bewegt werden nur opacity und transform. Beides erledigt der Compositor,
   deshalb bleibt das Scrollen auch auf aelteren Handys fluessig. */
.js .reveal {
    opacity: 0;
    transform: translateY(1.75rem);
}

.js .reveal.is-in {
    opacity: 1;
    transform: none;
    transition: opacity 1.1s var(--ease-out),
    transform 1.1s var(--ease-out);
}

/* Das Titelbild fuellt den Bildschirm - es soll nur ruhig aufblenden, nicht
   von unten hereinrutschen. */
.js .section--bleed.reveal {
    transform: none;
}

/* Innerhalb einer Szene erscheinen die Teile leicht versetzt. Das gibt der
   Seite ihren erzaehlenden Zug, ohne dass es nach Animation aussieht.
   Bewusst nur eine Ebene tief - sonst staffelt sich Gestaffeltes. */
.js .reveal.is-in .split__media,
.js .reveal.is-in .section__inner > * {
    animation: rise 1s var(--ease-out) backwards;
}

.js .reveal.is-in .section__inner > :nth-child(2) {
    animation-delay: 0.08s;
}

.js .reveal.is-in .section__inner > :nth-child(3) {
    animation-delay: 0.16s;
}

.js .reveal.is-in .section__inner > :nth-child(4) {
    animation-delay: 0.24s;
}

.js .reveal.is-in .section__inner > :nth-child(n + 5) {
    animation-delay: 0.3s;
}

/* Im zweispaltigen Baustein bekommt das Bild einen kleinen Vorsprung: erst
   die Szene, einen Wimpernschlag spaeter der Text dazu. Das ist dieselbe
   Reihenfolge, in der man den Abschnitt ohnehin liest, und es macht aus zwei
   gleichzeitig erscheinenden Spalten eine kleine Abfolge.

   Zwoelf Hundertstel - mehr braucht es nicht. Alles darueber sieht man als
   Animation, und dann wartet man darauf, statt zu lesen. */
.js .reveal.is-in .split__body > :nth-child(1) {
    animation-delay: 0.12s;
}

.js .reveal.is-in .split__body > :nth-child(2) {
    animation-delay: 0.2s;
}

.js .reveal.is-in .split__body > :nth-child(3) {
    animation-delay: 0.28s;
}

.js .reveal.is-in .split__body > :nth-child(n + 4) {
    animation-delay: 0.34s;
}

@keyframes rise {
    from {
        opacity: 0;
        transform: translateY(0.85rem);
    }
}

.is-busy {
    opacity: 0.55;
    pointer-events: none;
}

/* Wer Bewegung abbestellt hat, bekommt keine. Ohne Ausnahme. */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }

    .js .reveal {
        opacity: 1;
        transform: none;
    }
}

/* --- 13. Verwaltung ----------------------------------------------------- */

/* Hier gilt bewusst eine andere Haltung als in der Einladung: dicht,
   sachlich, tabellarisch. Sie erbt nur Farben, Schriften und Abstaende. */

.admin {
    min-height: 100vh;
    min-height: 100svh;
    display: flex;
    flex-direction: column;
    background: var(--paper);
    color: var(--ink);
}

.topbar {
    border-bottom: 1px solid var(--line);
    background: var(--paper);
    position: sticky;
    top: 0;
    z-index: 10;
}

.topbar__inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem 1.25rem;
    padding-block: 0.85rem;
}

.topbar__brand {
    font-family: var(--font-display);
    font-size: 1.25rem;
    text-decoration: none;
    color: var(--ink);
    margin-right: auto;
}

.topbar nav {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 1rem;
}

.topbar nav a {
    color: var(--ink-soft);
    text-decoration: none;
    font-size: 0.92rem;
    padding-block: 0.25rem;
}

.topbar nav a:hover,
.topbar nav a.is-active {
    color: var(--accent);
}

.admin__body {
    flex: 1;
    padding-block: clamp(1.75rem, 4vw, 3rem);
}

.admin__body > * + * {
    margin-top: clamp(1.5rem, 3vw, 2.25rem);
}

.pagehead {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: 1rem;
}

.panel {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--paper-deep);
    padding: clamp(1.1rem, 3vw, 1.75rem);
}

.panel > * + * {
    margin-top: 1rem;
}

.panel__title {
    font-family: var(--font-display);
    font-size: 1.3rem;
}

.cards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 17rem), 1fr));
    gap: 1rem;
}

.form-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
    gap: 1rem;
}

.form-grid--single {
    grid-template-columns: 1fr;
}

.stack > * + * {
    margin-top: 1rem;
}

.stack--tight > * + * {
    margin-top: 0.55rem;
}

/* Tabellen sind der einzige Ort, an dem Inhalt breiter werden kann als das
   Fenster. Deshalb scrollt hier der Kasten - nie die Seite. */
.table-scroll {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    border: 1px solid var(--line);
    border-radius: var(--radius);
}

.table {
    border-collapse: collapse;
    width: 100%;
    font-size: 0.9rem;
    white-space: nowrap;
}

.table th,
.table td {
    padding: 0.6rem 0.85rem;
    text-align: left;
    border-bottom: 1px solid var(--line);
    vertical-align: top;
}

.table thead th {
    position: sticky;
    top: 0;
    background: var(--paper-deep);
    font-size: 0.7rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ink-soft);
    font-weight: 500;
    z-index: 1;
}

.table tbody tr:last-child td {
    border-bottom: none;
}

.table tbody tr:hover td {
    background: var(--accent-wash);
}

.table td.wrap {
    white-space: normal;
    min-width: 14rem;
}

/* Erste Spalte beim horizontalen Scrollen stehen lassen. */
.table th:first-child,
.table td:first-child {
    position: sticky;
    left: 0;
    background: var(--paper);
}

.table thead th:first-child {
    background: var(--paper-deep);
    z-index: 2;
}

.tag {
    display: inline-block;
    font-size: 0.68rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    padding: 0.15rem 0.55rem;
    border: 1px solid var(--line-strong);
    border-radius: 999px;
    color: var(--ink-soft);
}

.tag--open {
    border-color: var(--accent);
    color: var(--accent);
}

.tag--hidden {
    border-style: dashed;
}

.tag--closed {
    border-color: var(--warn);
    color: var(--warn);
}

/* Ein Baustein in der Bauliste des Admins. */
.blockrow {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.6rem 1rem;
    padding: 0.85rem 0;
    border-bottom: 1px solid var(--line);
}

.blockrow:last-child {
    border-bottom: none;
}

.blockrow__main {
    flex: 1 1 14rem;
    min-width: 0;
}

.blockrow__name {
    font-weight: 500;
}

.blockrow__type {
    font-size: 0.78rem;
    color: var(--ink-faint);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

.credentials {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.95rem;
    background: var(--paper);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius);
    padding: 0.75rem 1rem;
    overflow-wrap: anywhere;
}

.media-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 10rem), 1fr));
    gap: 0.85rem;
}

.media-tile {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--paper-deep);
}

.media-tile img {
    width: 100%;
    /* Feste Kachelform, damit das Raster ruhig bleibt - der Zuschnitt
       passiert per object-fit, das Bild wird nie verzerrt. Die Kachel zeigt
       damit zugleich, wie das Bild anderswo zugeschnitten aussieht. */
    aspect-ratio: 4 / 3;
    object-fit: cover;
}

/* Der Klickbereich zum Setzen des Bildausschnitts. */
.media-tile__pick {
    position: relative;
    display: block;
    cursor: crosshair;
}

/* Fadenkreuz an der gemerkten Stelle. Ein heller Ring mit dunklem Kern -
   damit es auf hellen wie auf dunklen Motiven sichtbar bleibt. */
.media-tile__focus {
    position: absolute;
    width: 1.1rem;
    height: 1.1rem;
    margin: -0.55rem 0 0 -0.55rem;
    border-radius: 50%;
    border: 2px solid rgba(255, 255, 255, 0.9);
    background: rgba(20, 26, 21, 0.55);
    box-shadow: 0 0 0 1px rgba(20, 26, 21, 0.45);
    pointer-events: none;
}

/* Solange niemand etwas gewaehlt hat, nur andeuten - sonst sieht es aus,
   als waere schon etwas eingestellt. */
.media-tile__focus.is-default {
    opacity: 0.35;
}

.media-tile__meta {
    padding: 0.5rem 0.65rem;
    font-size: 0.75rem;
    color: var(--ink-faint);
    overflow-wrap: anywhere;
}

/* Ein Eintrag, dessen Datei nicht mehr da ist. Dieselbe Sprache wie der
   Hinweis auf einen versteckten Baustein: gestrichelt, warnfarben, klein. */
.media-tile__lost {
    margin-bottom: 0.4rem;
    padding: 0.25rem 0.5rem;
    border: 1px dashed var(--line-strong);
    color: var(--warn);
    font-size: 0.72rem;
    line-height: 1.3;
}

/* Namensfeld einer Bildkachel. Das Feld nimmt sich den Platz, der Knopf
   bekommt, was er braucht - in einer 10rem schmalen Kachel bleibt sonst vom
   Namen nichts uebrig. */
.media-tile__rename {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    margin-bottom: 0.4rem;
}

.media-tile__rename .input {
    flex: 1 1 6rem;
    min-width: 0;
    padding: 0.4rem 0.55rem;
    /* Die Schriftgroesse bleibt bei den 16px aus .input - darunter zoomt iOS
       beim Antippen in die Seite hinein. Lieber rutscht der Knopf in einer
       schmalen Kachel eine Zeile tiefer. */
}

.empty {
    padding: clamp(1.5rem, 5vw, 3rem);
    text-align: center;
    color: var(--ink-soft);
    border: 1px dashed var(--line-strong);
    border-radius: var(--radius);
}

/* Vorlagen-Auswahl beim Hinzufuegen eines Bausteins. */
.preset {
    display: block;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 0.7rem 0.9rem;
    cursor: pointer;
    transition: border-color 0.2s ease, background-color 0.2s ease;
}

.preset:hover {
    border-color: var(--line-strong);
    background: var(--paper);
}

.preset input {
    margin-right: 0.5rem;
    accent-color: var(--accent);
}

.preset__name {
    font-weight: 500;
}

.preset__meta {
    display: block;
    margin-top: 0.2rem;
    margin-left: 1.4rem;
    font-size: 0.82rem;
    color: var(--ink-faint);
}

/* --- 14. Druck ---------------------------------------------------------- */

@media print {
    :root {
        --paper: #fff;
        --paper-deep: #fff;
        --paper-sink: #fff;
        --ink: #000;
        --ink-soft: #333;
    }

    .topbar,
    .btn,
    form,
    .toc,
    .invitation-foot {
        display: none !important;
    }

    /* Der Platz fuer die Leiste wird auf Papier nicht gebraucht. */
    .page {
        padding-right: 0 !important;
    }

    .hero {
        min-height: auto;
    }

    .section {
        padding-block: 1.5rem;
        break-inside: avoid;
    }

    /* Beim Drucken darf nichts unsichtbar bleiben, was noch nicht
       eingeblendet wurde. */
    .js .reveal {
        opacity: 1 !important;
        transform: none !important;
    }
}
