/* ==========================================================================
   renebedbur.de — ein Stylesheet, kein Framework, kein Preprocessor.
   Aufbau: Schriften → Design-Token → Reset → Layout → Bausteine → Sektionen.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Schriften. Beide Dateien sind Variable Fonts: EIN @font-face deckt den
   gesamten Gewichtsbereich ab (font-weight als Bereich notiert), deshalb
   laedt die Seite zwei Dateien statt fuenf.
   Subset "latin" — deckt Deutsch inkl. Umlauten und "René" ab.
   font-display: swap zeigt sofort die Fallback-Schrift; kein unsichtbarer Text.
   -------------------------------------------------------------------------- */
@font-face {
    font-family: "Roboto";
    src: url("/fonts/roboto-latin-var.woff2") format("woff2");
    font-weight: 100 900;
    font-style: normal;
    font-display: swap;
    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: "Roboto Slab";
    src: url("/fonts/roboto-slab-latin-var.woff2") format("woff2");
    font-weight: 100 900;
    font-style: normal;
    font-display: swap;
    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;
}

/* --------------------------------------------------------------------------
   Design-Token
   -------------------------------------------------------------------------- */
:root {
    /* Farbe. --accent ist die Farbe des bisherigen Auftritts; --accent-ink ist
       die abgedunkelte Variante fuer Text und Links, weil --accent selbst auf
       Weiss nur 2,3:1 erreicht und damit unter WCAG AA liegt. */
    --accent: #7e97ad;
    --accent-ink: #4a6377;
    --accent-soft: #eef2f5;

    --bg: #ffffff;
    --surface: #f6f8f9;
    --text: #1c2126;
    --muted: #5b6771;
    --line: #e2e7ea;

    --shadow: 0 1px 2px rgb(28 33 38 / 6%), 0 8px 24px rgb(28 33 38 / 6%);

    /* Bergpanorama als Liniengeflecht. Sechs Ebenen von fern nach nah, jede
       eine Stufe kräftiger — das ahmt nach, wie Dunst entfernte Ketten
       aufhellt. Die vorderste Ebene ist die Akzentfarbe des Auftritts. Da die
       Linien hinter dem Text liegen, sind alle Töne hell genug gehalten, dass
       Text darüber die AA-Schwelle hält. */
    --sky-top: #e8eef2;
    --sky-bottom: #ffffff;
    --ridge-5: #ccd7de;
    --ridge-4: #bfcdd6;
    --ridge-3: #b0c1cc;
    --ridge-2: #a0b4c2;
    --ridge-1: #8fa6b7;
    --ridge-0: #7e97ad;

    /* Typografie. clamp() skaliert stufenlos zwischen Mobil und Desktop —
       keine Sprünge an Breakpoints. */
    --font-body: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
    --font-display: "Roboto Slab", Georgia, "Times New Roman", serif;

    --step-0: 1.0625rem;
    --step-1: clamp(1.15rem, 1.08rem + 0.35vw, 1.35rem);
    --step-2: clamp(1.35rem, 1.2rem + 0.7vw, 1.75rem);
    --step-3: clamp(1.6rem, 1.32rem + 1.3vw, 2.35rem);
    --step-4: clamp(2.1rem, 1.5rem + 2.9vw, 3.5rem);

    /* Rhythmus */
    --space-2xs: 0.25rem;
    --space-xs: 0.5rem;
    --space-s: 0.875rem;
    --space-m: 1.5rem;
    --space-l: 2.5rem;
    --space-xl: clamp(3rem, 2rem + 4vw, 5.5rem);
    --space-2xl: clamp(4rem, 2.5rem + 6vw, 8rem);

    --measure: 38rem;
    --container: 70rem;
    --radius: 6px;
    --header-h: 4rem;

    color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
    :root {
        --accent: #9db4c7;
        --accent-ink: #b7cbd9;
        --accent-soft: #1f272d;

        --bg: #14181b;
        --surface: #1b2126;
        --text: #e6eaee;
        --muted: #9aa7b1;
        --line: #2b333a;

        --shadow: 0 1px 2px rgb(0 0 0 / 40%), 0 8px 24px rgb(0 0 0 / 30%);

        /* Im Dunkelmodus liegen die Ebenen weiter auseinander als im hellen:
           bei geringem Gesamtkontrast verschwimmen zu eng benachbarte Töne
           sonst zu einer Fläche und die Tiefenstaffelung geht verloren. */
        --sky-top: #1a2229;
        --sky-bottom: #14181b;
        --ridge-5: #2a343d;
        --ridge-4: #32404a;
        --ridge-3: #3b4c58;
        --ridge-2: #445866;
        --ridge-1: #4d6474;
        --ridge-0: #567083;
    }
}

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

html {
    /* Bewusst KEIN scroll-padding-top: es würde sich zu den scroll-margin-Werten
       der Sprungziele addieren, und beide Zahlen gegeneinander zu rechnen macht
       jede spätere Anpassung unübersichtlich. Der Abstand zur klebenden
       Kopfzeile steckt vollständig in den Zielen selbst (siehe :target unten). */
    scroll-behavior: smooth;
    -webkit-text-size-adjust: 100%;
}

body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font-family: var(--font-body);
    font-size: var(--step-0);
    line-height: 1.7;
    font-synthesis-weight: none;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

h1,
h2,
h3 {
    font-family: var(--font-display);
    font-weight: 600;
    line-height: 1.2;
    letter-spacing: -0.015em;
    text-wrap: balance;
    margin: 0;
}

p,
ul,
ol,
dl {
    margin: 0;
}

img {
    max-width: 100%;
    height: auto;
    display: block;
}

a {
    color: var(--accent-ink);
    text-decoration-thickness: 1px;
    text-underline-offset: 0.2em;
}

a:hover {
    text-decoration-thickness: 2px;
}

/* Ein durchgaengiger, gut sichtbarer Fokusring — Voraussetzung fuer
   Tastaturbedienung und selbst Teil des beworbenen Themas Barrierefreiheit. */
:focus-visible {
    outline: 3px solid var(--accent-ink);
    outline-offset: 3px;
    border-radius: 2px;
}

/* Sprungziele der Ankernavigation: so weit unterhalb der Kopfzeile, dass diese
   nichts verdeckt. */
:target,
main[id] {
    scroll-margin-top: calc(var(--header-h) + var(--space-xs));
}

/* Abschnitte brauchen eine Sonderbehandlung: sie tragen oben einen großzügigen
   Innenabstand. Ohne Korrektur landet der Sprung an der Abschnittskante und
   sichtbar ist nur dieser Leerraum — es wirkt, als sei die Seite nicht weit
   genug gescrollt. Der Innenabstand wird deshalb gegengerechnet, sodass die
   Überschrift dicht unter der Kopfzeile steht. Bei kleinen Fenstern ergibt das
   einen positiven Wert, bei großen einen negativen; clamp() rechnet das mit. */
.section[id] {
    scroll-margin-top: calc(var(--header-h) - var(--space-2xl));
}

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

    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }

    /* Seitenübergänge bringen eine eigene Standardanimation mit, die von der
       Regel oben nicht erfasst wird. */
    ::view-transition-group(*),
    ::view-transition-old(*),
    ::view-transition-new(*) {
        animation: none !important;
    }
}

/* --------------------------------------------------------------------------
   Weiche Übergänge zwischen den Seiten. Eine Zeile, die aus vier einzelnen
   Dokumenten einen zusammenhängenden Auftritt macht — ohne Single-Page-App,
   ohne Router, ohne Bündel. Browser ohne Unterstützung navigieren hart, also
   genau wie bisher.
   -------------------------------------------------------------------------- */
@view-transition {
    navigation: auto;
}

/* --------------------------------------------------------------------------
   Einblenden beim Hereinscrollen — als View-Timeline, nicht als
   IntersectionObserver: kein JavaScript im Spiel, die Animation läuft im
   Compositor und ist exakt an die Scrollposition gekoppelt statt an einen
   Schwellwert.

   Wichtig für die Ausfallsicherheit: der sichtbare Zustand ist der
   Grundzustand. Die Regel steckt hinter @supports, damit in Browsern ohne
   Scroll-Timelines nichts unsichtbar bleibt.
   -------------------------------------------------------------------------- */
@supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
        .reveal {
            animation: reveal-in linear both;
            animation-timeline: view();
            /* Abgeschlossen, sobald das Element zu gut zwei Dritteln
               hereingerückt ist. Ein später Endpunkt (etwa cover 22%) sähe auf
               sehr hohen Fenstern so aus, als bliebe der untere Seitenteil
               dauerhaft blass. */
            animation-range: entry 0% entry 70%;
        }
    }
}

@keyframes reveal-in {
    from {
        opacity: 0;
        transform: translateY(1.5rem);
    }
    to {
        opacity: 1;
        transform: none;
    }
}

/* --------------------------------------------------------------------------
   Layout-Hilfen
   -------------------------------------------------------------------------- */
.container {
    width: 100%;
    max-width: var(--container);
    margin-inline: auto;
    padding-inline: clamp(1.25rem, 0.75rem + 2.5vw, 3rem);
}

.section {
    padding-block: var(--space-2xl);
}

.section + .section {
    border-top: 1px solid var(--line);
}

/* --------------------------------------------------------------------------
   Abschnitt mit Foto-Hintergrund.

   Das Porto-Foto liegt ortsfest hinter dem Abschnitt: der Text scrollt darüber
   hinweg, das Bild bleibt stehen. Umgesetzt über background-attachment statt
   über ein positioniertes Bildelement — dadurch ist der Ausschnitt automatisch
   auf den Abschnitt begrenzt und in keinem anderen Bereich zu sehen.

   Auf iOS wird `fixed` von Safari wie `scroll` behandelt; das Bild scrollt dort
   also mit. Der Abschnitt bleibt vollständig lesbar, nur der Effekt entfällt.
   -------------------------------------------------------------------------- */
.section--photo {
    position: relative;
    isolation: isolate;

    /* Schrift durchgehend schwarz statt abgestuft: über einem Foto liest sich
       jede Abstufung als Unruhe, und der Kontrast steigt nebenbei auf über
       15:1. */
    --text: #000000;
    --muted: #000000;
    /* Zwingend, und der Grund ist leicht zu übersehen: `color: var(--text)`
       steht auf dem body und wird DORT ausgewertet. Nachfahren erben den
       fertigen Farbwert, nicht die Variable — ein neues --text weiter unten im
       Baum bliebe ohne Wirkung, solange color nicht erneut zugewiesen wird. */
    color: var(--text);

    /* Der Schleier über dem Foto. So deckend, dass das Bild als ruhige Textur
       wirkt und nicht als Motiv mit Details, die mit dem Text konkurrieren. */
    --photo-veil: rgb(255 255 255 / 88%);

    background-image:
        linear-gradient(var(--photo-veil), var(--photo-veil)),
        image-set(url("/img/porto-1440.webp") type("image/webp"), url("/img/porto-1440.jpg") type("image/jpeg"));
    background-attachment: fixed, fixed;
    background-position: center, center;
    background-size: cover, cover;
    background-repeat: no-repeat, no-repeat;
}

@media (prefers-color-scheme: dark) {
    .section--photo {
        /* Im Dunkelmodus die Gegenrichtung: weiße Schrift, dunkler Schleier. */
        --text: #ffffff;
        --muted: #ffffff;
        --photo-veil: rgb(16 20 23 / 88%);
    }
}

/* Weiche Kanten oben und unten, damit der Abschnitt nicht wie ein
   aufgeklebtes Rechteck wirkt und sauber an die Nachbarflächen anschließt. */
.section--photo::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background: linear-gradient(to bottom, var(--bg) 0%, transparent 14%, transparent 86%, var(--bg) 100%);
}

.section__head {
    margin-bottom: var(--space-xl);
}

.section__head--spaced {
    margin-top: var(--space-xl);
}

.section__eyebrow {
    display: block;
    font-size: 0.8125rem;
    font-weight: 500;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--muted);
    margin-bottom: var(--space-xs);
}

.section__title {
    font-size: var(--step-3);
}

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

.prose > * + * {
    margin-top: var(--space-m);
}

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

.skip-link {
    position: absolute;
    top: var(--space-xs);
    left: var(--space-xs);
    z-index: 100;
    padding: 0.6rem 1rem;
    background: var(--bg);
    color: var(--text);
    border: 2px solid var(--accent-ink);
    border-radius: var(--radius);
    text-decoration: none;
    transform: translateY(-200%);
}

.skip-link:focus {
    transform: translateY(0);
}

/* --------------------------------------------------------------------------
   Kopfzeile und Navigation
   -------------------------------------------------------------------------- */
.site-header {
    position: sticky;
    top: 0;
    z-index: 50;
    /* Ganz oben trägt die Kopfzeile die Farbe des Kopfbereichs und geht
       nahtlos in ihn über — sichtbar wird sie erst, wenn Inhalt darunter
       durchläuft. `transparent` wäre hier falsch: der Kopfbereich beginnt erst
       unterhalb der Zeile, durchscheinen würde also das Weiß des Dokuments und
       eine Kante entstehen. */
    background: var(--sky-top);
    border-bottom: 1px solid transparent;
    transition:
        background-color 180ms ease,
        border-color 180ms ease;
}

/* Gesetzt von menu.js, sobald die Seite nicht mehr ganz oben steht. */
.site-header[data-scrolled] {
    background: color-mix(in srgb, var(--bg) 88%, transparent);
    backdrop-filter: saturate(160%) blur(10px);
    border-bottom-color: var(--line);
}

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

.site-header__inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-m);
    min-height: var(--header-h);
}

.brand {
    font-family: var(--font-display);
    font-size: 1.0625rem;
    font-weight: 600;
    letter-spacing: -0.01em;
    color: var(--text);
    text-decoration: none;
    white-space: nowrap;
}

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

.nav-toggle {
    display: none;
    align-items: center;
    gap: 0.55rem;
    padding: 0.45rem 0.7rem;
    font: inherit;
    font-size: 0.9375rem;
    color: var(--text);
    background: transparent;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    cursor: pointer;
}

.nav-toggle__bars {
    position: relative;
    display: block;
    width: 18px;
    height: 2px;
    background: currentColor;
}

.nav-toggle__bars::before,
.nav-toggle__bars::after {
    content: "";
    position: absolute;
    left: 0;
    width: 18px;
    height: 2px;
    background: currentColor;
}

.nav-toggle__bars::before {
    top: -6px;
}

.nav-toggle__bars::after {
    top: 6px;
}

.nav__list {
    display: flex;
    align-items: center;
    gap: clamp(1rem, 0.4rem + 1.6vw, 2rem);
    list-style: none;
    padding: 0;
    margin: 0;
}

.nav__link {
    position: relative;
    display: inline-block;
    padding-block: 0.35rem;
    font-size: 0.9375rem;
    color: var(--muted);
    text-decoration: none;
}

.nav__link:hover {
    color: var(--text);
}

/* Scrollspy: menu.js setzt aria-current auf den Link der sichtbaren Sektion.
   Die Auszeichnung haengt damit am ARIA-Zustand, nicht an einer Extra-Klasse. */
.nav__link[aria-current="true"] {
    color: var(--text);
}

.nav__link[aria-current="true"]::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: -2px;
    height: 2px;
    background: var(--accent);
}

@media (max-width: 719px) {
    .nav-toggle {
        display: inline-flex;
    }

    .nav {
        position: absolute;
        top: 100%;
        left: 0;
        right: 0;
        background: var(--bg);
        border-bottom: 1px solid var(--line);
        box-shadow: var(--shadow);
    }

    /* Geschlossen wird per hidden-Attribut, nicht per Klasse: das Menue ist
       damit auch fuer Screenreader und die Tabreihenfolge wirklich weg. */
    .nav[hidden] {
        display: none;
    }

    .nav__list {
        flex-direction: column;
        align-items: stretch;
        gap: 0;
        padding: var(--space-xs) clamp(1.25rem, 0.75rem + 2.5vw, 3rem) var(--space-m);
    }

    .nav__link {
        display: block;
        padding-block: 0.7rem;
        font-size: 1.0625rem;
        border-bottom: 1px solid var(--line);
    }

    .nav__link[aria-current="true"]::after {
        display: none;
    }

    .nav__link[aria-current="true"] {
        color: var(--accent-ink);
    }
}

/* Ohne JavaScript: der Umschalter traegt im Markup ein hidden-Attribut und
   bleibt unsichtbar, das Menue steht offen. Die Navigation ist damit auch ohne
   Skript vollstaendig bedienbar — menu.js dreht das Verhaeltnis erst um, wenn
   es tatsaechlich laeuft. */
.nav-toggle[hidden] {
    display: none;
}

/* --------------------------------------------------------------------------
   Hero
   -------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------
   Die Bühne: Kopfbereich und Schwerpunkte auf einem gemeinsamen Hintergrund.
   Das Panorama gehört zu dieser Ebene, nicht zum Kopfbereich allein — nur so
   können die Ketten beim Scrollen zurückbleiben und bis in die Leistungen
   hineinreichen.
   -------------------------------------------------------------------------- */
.stage {
    position: relative;
    isolation: isolate;
    /* `clip` statt `hidden`: ein Element mit overflow:hidden wird zum
       Scroll-Container, an den sich animation-timeline: view() der Kindelemente
       binden würde. clip erfüllt denselben Zweck ohne diese Nebenwirkung. */
    overflow: hidden;
    overflow: clip;

    /* Die Ausdehnung hängt an der Fensterhöhe: auf einem hohen Bildschirm zieht
       sich das Gebirge weiter, auf einem flachen Laptop bleibt es flach — das
       Verhältnis von Panorama zu sichtbarer Seite bleibt dadurch gleich.
       svh statt vh, weil vh auf Mobilgeräten die ausgefahrene Adressleiste
       ignoriert und das Panorama dort zu hoch geriete.
       Die Grenzen fangen die Extreme ab: auf sehr flachen Fenstern bliebe sonst
       kaum etwas übrig, auf sehr hohen würde es den Kopfbereich sprengen. */
    --panorama-h: clamp(19rem, 60svh, 40rem);

    /* Die kleinen Texte stehen über den Linien und brauchen deshalb mehr
       Gewicht als auf glatter Fläche. Nachgemessen an den tatsächlich
       gerenderten Pixeln, nicht geschätzt. */
    --muted: #4a545d;
    --accent-ink: #3d5468;
}

@media (prefers-color-scheme: dark) {
    .stage {
        --muted: #aab7c1;
        --accent-ink: #c6d7e3;
    }
}

.stage__backdrop {
    position: absolute;
    inset: 0;
    z-index: -1;
    /* Himmel: oben ein Hauch Dunst, nach unten ausbleichend. Ersetzt das
       Rein-Weiß und trägt den Kopfbereich auch dann, wenn kein JavaScript
       läuft und die Berge ausbleiben. */
    background: linear-gradient(to bottom, var(--sky-top), var(--sky-bottom) 60%);
}

/* Die Schwerpunkte liegen auf der Bühne und bringen deshalb keine eigene
   Trennlinie und keinen eigenen Hintergrund mit. */
.section--on-stage {
    border-top: 0;
}

.hero {
    position: relative;
    padding-block: var(--space-xl) var(--space-2xl);
    /* Sicherung nach unten, damit das Gebirge noch innerhalb des Kopfbereichs
       endet und nicht in die Leistungen ragt. Knapp bemessen: bei normaler
       Textmenge ist der Kopfbereich ohnehin höher, ein großzügigerer Wert
       erzeugte nur Leerraum unter den Schaltflächen. */
    min-height: calc(var(--panorama-h) + var(--space-m));
}

.panorama {
    /* Haftend statt absolut: das Panorama bleibt am oberen Rand stehen, solange
       die Bühne im Bild ist, und ist dadurch auch bei den Leistungen noch zu
       sehen. Ein reiner Parallax-Versatz würde dafür nicht reichen — er müsste
       so groß werden wie der Scrollweg, und dann wäre es kein Parallax mehr,
       sondern ein festgenageltes Bild. Die Ebenen laufen innerhalb des
       haftenden Rahmens weiter gegeneinander. */
    position: sticky;
    top: 0;
    height: var(--panorama-h);

    /* Bewusst KEIN overflow hier: die Ebenen sollen über den Rahmen hinaus
       wandern dürfen. Begrenzt wird erst an der Bühne (.stage). */

    /* Um so viel dürfen die Ebenen gegeneinander wandern. Als Anteil der
       Panoramahöhe, damit das Verhältnis auf jedem Gerät gleich bleibt. */
    --panorama-reserve: calc(var(--panorama-h) * 0.6);
}

/* Kein Bodennebel mehr: als Linienzeichnung gibt es keine Fläche, die an der
   Unterkante abgeschnitten wirken könnte. Die Linien laufen stattdessen zu den
   Seiten hin aus — das erledigt der Verlauf auf strokeStyle in panorama.js. */

.panorama__layer {
    position: absolute;
    inset-inline: 0;
    /* Oben bündig, unten übersteht die Reserve. panorama.js zeichnet den Grat
       trotzdem am sichtbaren Ausschnitt aus und füllt die Reserve nur auf. */
    top: 0;
    width: 100%;
    height: calc(100% + var(--panorama-reserve));
    display: block;
    /* panorama.js liest diese Farbe per getComputedStyle aus. Dadurch bleibt
       die Farbgebung vollständig im Stylesheet, inklusive Dunkelmodus. */
    will-change: transform;

    /* Dunstmaske: oben stark zurückgenommen, nach unten hervortretend. Das ist
       atmosphärisch richtig — ferne Höhen verlieren sich im Dunst — und
       zugleich der Grund, warum der Kopftext über dem Panorama lesbar bleibt:
       ohne sie drückt die vorderste Kette den gedämpften Text auf 2,3:1.
       Bewusst auf der Ebene und nicht auf dem Rahmen: so wandert die Maske beim
       Parallax mit ihrer Kette mit, statt die auswandernden Linien an einer
       festen Kante abzuschneiden.

       Die Obergrenze von 45 Prozent ist keine Geschmacksfrage: darüber drückt
       die vorderste Kette den Fließtext der Leistungen unter WCAG AA — dort
       liegen die kräftigen unteren Linienabschnitte direkt hinter dem Text.
       Nachgemessen an den gerenderten Pixeln in beiden Farbmodi. */
    -webkit-mask-image: linear-gradient(
        to bottom,
        rgb(0 0 0 / 12%) 0%,
        rgb(0 0 0 / 24%) 40%,
        rgb(0 0 0 / 38%) 62%,
        rgb(0 0 0 / 45%) 82%
    );
    mask-image: linear-gradient(
        to bottom,
        rgb(0 0 0 / 12%) 0%,
        rgb(0 0 0 / 24%) 40%,
        rgb(0 0 0 / 38%) 62%,
        rgb(0 0 0 / 45%) 82%
    );
    -webkit-mask-size: 100% 100%;
    mask-size: 100% 100%;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
}

/* Farbe und Linienstärke liest panorama.js von hier. Ferne Ketten sind blasser
   und feiner gezeichnet, nahe kräftiger — dieselbe Staffelung, die vorher der
   Dunst über die Flächenfarbe geleistet hat. */
.panorama__layer[data-depth="5"] {
    color: var(--ridge-5);
    --line-width: 1;
}
.panorama__layer[data-depth="4"] {
    color: var(--ridge-4);
    --line-width: 1;
}
.panorama__layer[data-depth="3"] {
    color: var(--ridge-3);
    --line-width: 1.15;
}
.panorama__layer[data-depth="2"] {
    color: var(--ridge-2);
    --line-width: 1.3;
}
.panorama__layer[data-depth="1"] {
    color: var(--ridge-1);
    --line-width: 1.5;
}
.panorama__layer[data-depth="0"] {
    color: var(--ridge-0);
    --line-width: 1.75;
}

/* Gestaffelte Bewegung beim Scrollen. Bewusst als Scroll-Timeline und nicht
   als Scroll-Ereignis: die Animation läuft im Compositor, unabhängig vom
   Hauptthread, und bleibt auch dann flüssig, wenn nebenher JavaScript
   arbeitet. Ferne Ebenen bleiben stärker zurück und wirken dadurch weiter weg.
   Browser ohne Scroll-Timelines zeigen das Panorama einfach unbewegt. */
@supports (animation-timeline: scroll()) {
    @media (prefers-reduced-motion: no-preference) {
        .panorama__layer {
            animation: panorama-drift linear both;
            animation-timeline: scroll(root block);
            /* Über gut drei Bildschirmhöhen Scrollweg — die Ketten wandern
               dadurch gemächlich nach oben statt davonzueilen.
               Dieselbe Dehnung steckt in scrollAnteil() in panorama.js: dort
               läuft die Umformung von Gebirge zu waagerechter Linie. Beide
               Werte gehören zusammen, sonst laufen Bewegung und Umformung
               auseinander. */
            animation-range: 0 300vh;
        }

        /* Beim Abwärtsscrollen laufen die Ketten mit nach oben — nur langsamer
           als die Seite. Der Rahmen haftet ohnehin am oberen Rand und bewegt
           sich gar nicht; diese Werte geben den Ebenen die verbleibende, träge
           Eigenbewegung.
           Nahe Ketten laufen schneller als ferne: das ist die Richtung, in der
           man Landschaft aus dem fahrenden Zug sieht. Negative Werte, weil die
           Bewegung nach oben geht.
           Die Spreizung zwischen vorderster und hinterster Ebene beträgt rund
           40 Prozent der Panoramahöhe; deutlich darunter verschmilzt die
           Bewegung optisch zu einem gemeinsamen Verschieben und wird nicht mehr
           als Tiefe gelesen. Alle Werte als Anteil der Panoramahöhe, damit das
           Verhältnis auf jedem Gerät gleich bleibt. */
        .panorama__layer[data-depth="5"] {
            --drift: calc(var(--panorama-h) * -0.06);
        }
        .panorama__layer[data-depth="4"] {
            --drift: calc(var(--panorama-h) * -0.13);
        }
        .panorama__layer[data-depth="3"] {
            --drift: calc(var(--panorama-h) * -0.21);
        }
        .panorama__layer[data-depth="2"] {
            --drift: calc(var(--panorama-h) * -0.3);
        }
        .panorama__layer[data-depth="1"] {
            --drift: calc(var(--panorama-h) * -0.4);
        }
        .panorama__layer[data-depth="0"] {
            --drift: calc(var(--panorama-h) * -0.5);
        }

        /* Der Kopftext zieht leicht mit nach oben und blendet aus, während das
           Panorama zurückbleibt — das verstärkt die Tiefenwirkung. */
        .hero__inner {
            animation: hero-lift linear both;
            animation-timeline: scroll(root block);
            animation-range: 0 80vh;
        }
    }
}

@keyframes panorama-drift {
    to {
        transform: translate3d(0, var(--drift, 0), 0);
    }
}

@keyframes hero-lift {
    to {
        opacity: 0.55;
        transform: translate3d(0, -2.5rem, 0);
    }
}

/* Text und Porträt nebeneinander, sobald genug Breite da ist. Die Bildspalte
   ist auf eine feste Maximalbreite gedeckelt statt auf einen Bruchteil der
   Fläche — sonst wächst das Porträt auf großen Bildschirmen mit und wird
   wieder zum Hauptdarsteller. */
.hero__inner {
    display: grid;
    gap: var(--space-l);
    align-items: center;
}

@media (min-width: 880px) {
    .hero__inner {
        grid-template-columns: minmax(0, 1fr) auto;
        gap: clamp(2rem, 1rem + 4vw, 4rem);
    }
}

.hero__figure {
    margin: 0;
    width: min(100%, 20rem);
}

@media (min-width: 600px) {
    .hero__figure {
        width: 18rem;
    }
}

@media (min-width: 880px) {
    .hero__figure {
        width: 22rem;
    }
}

.hero__figure img {
    width: 100%;
    border-radius: var(--radius);
    border: 1px solid var(--line);
    box-shadow: var(--shadow);
}

.hero__eyebrow {
    display: block;
    font-size: 0.8125rem;
    font-weight: 500;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--accent-ink);
    margin-bottom: var(--space-s);
}

.hero__title {
    font-size: var(--step-4);
}

.hero__lead {
    max-width: 32ch;
    margin-top: var(--space-m);
    font-family: var(--font-display);
    font-size: var(--step-2);
    font-weight: 400;
    line-height: 1.35;
    color: var(--muted);
    text-wrap: balance;
}

.hero__meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-xs) var(--space-m);
    list-style: none;
    padding: 0;
    margin-top: var(--space-l);
    font-size: 0.9375rem;
    color: var(--muted);
}

.hero__meta li {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.hero__meta li::before {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--accent);
    flex: none;
}

.hero__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-s);
    margin-top: var(--space-l);
}

/* --------------------------------------------------------------------------
   Knöpfe
   -------------------------------------------------------------------------- */
.button {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.7rem 1.35rem;
    font-size: 0.9375rem;
    font-weight: 500;
    text-decoration: none;
    border-radius: var(--radius);
    border: 1px solid transparent;
    transition:
        background-color 120ms ease,
        border-color 120ms ease,
        color 120ms ease;
}

.button--primary {
    background: var(--accent-ink);
    color: var(--bg);
}

.button--primary:hover {
    background: var(--text);
}

.button--ghost {
    border-color: var(--line);
    color: var(--text);
}

.button--ghost:hover {
    border-color: var(--accent);
    color: var(--accent-ink);
}

/* --------------------------------------------------------------------------
   Schwerpunkte — nummerierte Textbloecke statt Karten
   -------------------------------------------------------------------------- */
.focus-list {
    display: grid;
    gap: 0;
    list-style: none;
    padding: 0;
    margin: 0;
    counter-reset: focus;
}

@media (min-width: 900px) {
    .focus-list {
        grid-template-columns: 1fr 1fr;
        column-gap: clamp(2.5rem, 1rem + 5vw, 5rem);
    }
}

.focus-item {
    counter-increment: focus;
    padding-block: var(--space-l);
    border-top: 1px solid var(--line);
}

/* Die ersten beiden Elemente stehen in der zweispaltigen Ansicht nebeneinander
   in der ersten Zeile — dort waere die obere Linie eine doppelte Trennung zur
   Ueberschrift. */
.focus-item:first-child {
    border-top: 0;
    padding-top: 0;
}

@media (min-width: 900px) {
    .focus-item:nth-child(2) {
        border-top: 0;
        padding-top: 0;
    }
}

/* --accent-ink statt --accent: die Ziffern sind sichtbarer Text in 13px und
   müssten mit --accent nur 3,04:1 erreichen — unter WCAG AA. Für die rein
   grafischen Akzente (Striche, Punkte, Unterstrich) genügt dieser Wert
   dagegen, dort gilt die 3:1-Schwelle für Nicht-Text. */
.focus-item__number {
    display: block;
    font-size: 0.8125rem;
    font-weight: 500;
    letter-spacing: 0.1em;
    color: var(--accent-ink);
    margin-bottom: var(--space-xs);
}

.focus-item__number::before {
    /* Zweistellig mit fuehrender Null: 01, 02, … */
    content: counter(focus, decimal-leading-zero);
}

.focus-item__title {
    font-size: var(--step-1);
    margin-bottom: var(--space-s);
    hyphens: auto;
}

.focus-item__points {
    list-style: none;
    padding: 0;
    margin: 0;
    color: var(--muted);
}

.focus-item__points li {
    position: relative;
    padding-left: 1.1rem;
    margin-top: var(--space-2xs);
}

.focus-item__points li::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0.75em;
    width: 5px;
    height: 1px;
    background: var(--accent);
}

/* --------------------------------------------------------------------------
   Ueber mich
   -------------------------------------------------------------------------- */
.about__grid {
    display: grid;
    gap: var(--space-xl);
}

@media (min-width: 960px) {
    .about__grid {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        column-gap: clamp(2.5rem, 1rem + 5vw, 5rem);
    }
}

.subhead {
    font-size: var(--step-1);
    margin-bottom: var(--space-m);
}

/* Gilt auch fuer den ersten Block, der auf den Fliesstext folgt — sonst klebt
   dessen Ueberschrift am letzten Absatz. */
.about__block {
    margin-top: var(--space-xl);
}

/* Skills als Definitionsliste: die Kategorie ist der Begriff, die Techniken
   sind die Erlaeuterung. Semantisch praeziser als eine flache Aufzaehlung. */
.skills {
    margin: 0;
}

.skills > div + div {
    margin-top: var(--space-m);
    padding-top: var(--space-m);
    border-top: 1px solid var(--line);
}

.skills dt {
    font-size: 0.8125rem;
    font-weight: 500;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--muted);
    margin-bottom: var(--space-xs);
}

.skills dd {
    margin: 0;
}

/* Ohne Chip-Flächen: über dem Foto ergaben die vielen kleinen Kästchen ein
   unruhiges Raster. Getrennt wird jetzt durch einen Mittelpunkt — das liest
   sich als Aufzählung, ohne eigene Fläche zu beanspruchen. */
.tags {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    column-gap: 0.65rem;
    row-gap: 0.15rem;
    list-style: none;
    padding: 0;
    margin: 0;
}

.tags li {
    font-size: 0.9375rem;
    line-height: 1.6;
    color: var(--text);
}

.tags li:not(:last-child)::after {
    content: "·";
    margin-left: 0.65rem;
    color: var(--accent);
}

.list-dashed {
    list-style: none;
    padding: 0;
    margin: 0;
    color: var(--muted);
}

.list-dashed li {
    position: relative;
    padding-left: 1.1rem;
    margin-top: var(--space-xs);
}

.list-dashed li::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0.75em;
    width: 5px;
    height: 1px;
    background: var(--accent);
}

.credential {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-xs) var(--space-s);
}

.credential__name {
    font-weight: 500;
}

/* --------------------------------------------------------------------------
   Bildband
   -------------------------------------------------------------------------- */
.band {
    margin-block: 0;
}

.band img {
    width: 100%;
    border-radius: var(--radius);
}

.band figcaption {
    margin-top: var(--space-s);
    font-size: 0.875rem;
    color: var(--muted);
}

/* --------------------------------------------------------------------------
   Kontakt
   -------------------------------------------------------------------------- */
.contact__grid {
    display: grid;
    gap: var(--space-m);
}

@media (min-width: 720px) {
    .contact__grid {
        grid-template-columns: 1fr 1fr;
        gap: var(--space-l);
    }
}

.contact-card {
    display: block;
    padding: var(--space-m);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    text-decoration: none;
    color: var(--text);
    transition:
        border-color 120ms ease,
        background-color 120ms ease;
}

.contact-card:hover {
    border-color: var(--accent);
    background: var(--accent-soft);
}

.contact-card__label {
    display: block;
    font-size: 0.8125rem;
    font-weight: 500;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--muted);
    margin-bottom: var(--space-2xs);
}

.contact-card__value {
    display: block;
    font-family: var(--font-display);
    font-size: var(--step-1);
    line-height: 1.35;
    /* Lange E-Mail-Adressen duerfen auf schmalen Displays umbrechen. */
    overflow-wrap: anywhere;
}

/* --------------------------------------------------------------------------
   Fusszeile
   -------------------------------------------------------------------------- */
.site-footer {
    padding-block: var(--space-l);
    border-top: 1px solid var(--line);
    background: var(--surface);
    font-size: 0.9375rem;
    color: var(--muted);
}

.site-footer__inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-s) var(--space-m);
}

.site-footer__nav {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-m);
    list-style: none;
    padding: 0;
    margin: 0;
}

.site-footer a {
    color: var(--muted);
}

.site-footer a:hover {
    color: var(--accent-ink);
}

/* --------------------------------------------------------------------------
   Rechtstexte (Impressum, Datenschutz) und Fehlerseite
   -------------------------------------------------------------------------- */
.page-header {
    padding-block: var(--space-xl) var(--space-l);
}

.page-title {
    font-size: var(--step-3);
}

.legal {
    max-width: var(--measure);
    padding-bottom: var(--space-2xl);
}

.legal h2 {
    font-size: var(--step-2);
    margin-top: var(--space-xl);
    margin-bottom: var(--space-s);
}

.legal h3 {
    font-family: var(--font-body);
    font-size: 1.0625rem;
    font-weight: 500;
    margin-top: var(--space-l);
    margin-bottom: var(--space-xs);
}

.legal p,
.legal ul {
    margin-top: var(--space-s);
}

.legal ul {
    padding-left: 1.2rem;
}

.legal li + li {
    margin-top: var(--space-2xs);
}

.legal address {
    font-style: normal;
    margin-top: var(--space-s);
}

.error-page {
    display: grid;
    place-items: center;
    text-align: center;
    padding-block: var(--space-2xl);
    min-height: 60vh;
}

.error-page__text {
    max-width: var(--measure);
    margin-top: var(--space-m);
    margin-inline: auto;
}

.error-page__action {
    margin-top: var(--space-l);
}
