@font-face {
    font-family: 'GT-Era-Text-Light';
    src:
        url('../assets/fonts/GT-Era-Text-Light.woff2') format('woff2'),
        url('../assets/fonts/GT-Era-Text-Light.woff') format('woff');
    font-weight: 300;
    font-style: normal;
    font-display: swap;
}

html {
    --text-color: #000;
    /* body already has this, but body being taken out of flow via
       position:fixed (see _hscrollLockBody in project.js) makes html the
       effective scrolling element while locked — without this here too,
       a downward swipe right at that point can trigger the browser's
       native pull-to-refresh instead of stepping the module. */
    overscroll-behavior: none;
}

html.is-bg-dark {
    --text-color: #fff;
}

html.is-ui-hover {
    --text-color: #000;
}

/* Loading screen is always black on white, regardless of the incoming
   slide's adaptive dark/light detection (is-bg-dark reflects the slide
   that's about to be shown, set before it's actually visible under the
   white blur — without this, a dark first slide would turn the loading
   text white on the still-white loading background). Higher specificity
   than html.is-bg-dark so it wins regardless of source order. */
html.is-blur body.home-page {
    --text-color: #000;
}

body {
    margin: 0;
    font-family: 'GT-Era-Text-Light', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
    font-size: 16pt;
    line-height: 109%;
    color: var(--text-color);
    overscroll-behavior: none;
}

body.home-page {
    background: #fff;
    overflow: hidden;
}



html.is-under-construction-open body {
    overflow: hidden;
}

/* Hide cursor arrow globally; only shown on fine-pointer (mouse) devices via the rule below */
#cursor-arrow {
    display: none;
}

html.is-under-construction-open #cursor-arrow {
    opacity: 0 !important;
}

/* Mobile page */
body.mobile-page {
    background: #fff;
    color: #000;
    /* Slider should not be scrollable away */
    overflow: hidden;
}

body.mobile-page .home-slider.mobile-hero-slider {
    position: fixed;
    inset: 0;
    height: 100vh;
    height: 100dvh;
}

/* Mobile: keep the overlay UI fixed like project pages */
body.mobile-page .project-layout {
    position: fixed;
    inset: 0;
}

/* Burger menu */
.mobile-burger {
    appearance: none;
    background: transparent;
    border: 0;
    padding: 0;
    margin: 0;
    font: inherit;
    color: inherit;
    line-height: 1;
}

.home-mobile-burger {
    display: none;
}

.mobile-menu {
    position: fixed;
    inset: 0;
    z-index: 10000;
    pointer-events: none;
    opacity: 0;
    transition: opacity 160ms ease;
}

html.is-mobile-menu-open .mobile-menu {
    pointer-events: auto;
    opacity: 1;
}

.mobile-menu__panel {
    position: absolute;
    inset: 0;
    background: #fff;
    color: #000;
    box-sizing: border-box;
}

.mobile-menu__close {
    position: absolute;
    top: 9px;
    right: 9px;
    appearance: none;
    background: transparent;
    border: 0;
    padding: 0;
    margin: 0;
    font: inherit;
    /* Explicit black — .mobile-menu__panel already sets color:#000, but this
       avoids ever relying on inherit landing on the slider's adaptive
       --text-color (which can be white) if the panel rule is ever bypassed. */
    color: #000;
}

/* Brand Design / Social Media / Illustration — top-left, left-aligned.
   Close sits top-right, so this no longer needs to clear it vertically. */
.mobile-menu__nav {
    position: absolute;
    top: 9px;
    left: 9px;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
    font-size: 16pt;
    text-align: left;
}

.mobile-menu__nav a,
.mobile-menu__nav a:visited,
.mobile-menu__nav a:hover,
.mobile-menu__nav a:active {
    color: #000;
    text-decoration: none;
}

/* Language switcher — bottom-left, 9px from the edges */
.mobile-menu__lang {
    position: absolute;
    left: 9px;
    bottom: 9px;
    color: #000;
}

/* About — bottom-right, 9px from the edges */
.mobile-menu__about,
.mobile-menu__about:visited,
.mobile-menu__about:hover,
.mobile-menu__about:active {
    position: absolute;
    right: 9px;
    bottom: 9px;
    color: #000;
    text-decoration: none;
    font-size: 16pt;
}
.mobile-root {
    /* Match desktop margin rhythm, but keep clear of fixed top/bottom overlays */
    padding: 9pt;
    padding-top: calc(9pt + 42px);
    padding-bottom: calc(9pt + 64px);
    box-sizing: border-box;
}

/* In fixed-slider mode the list is currently not used (prevents "white" scroll areas).
   Re-enable later if you want a dedicated list view. */
body.mobile-page .mobile-root {
    display: none;
}


.mobile-loading,
.mobile-error {
    padding: 12px 0;
    font-size: 12pt;
}

.mobile-section {
    margin-top: 50px;
}

.mobile-section__title {
    margin: 0 0 12px 0;
    font: inherit;
    font-size: 14pt;
}

.mobile-list {
    display: grid;
    gap: 6px;
}

.mobile-item {
    display: block;
    padding: 10px 0;
    border-bottom: 1px solid rgba(0, 0, 0, 0.12);
    color: #000;
    text-decoration: none;
}

.mobile-item--compact {
    padding: 8px 0;
}

.mobile-item__title {
    font-size: 14pt;
}

.mobile-item__meta {
    margin-top: 4px;
    font-size: 11pt;
    opacity: 0.8;
}

/* Legal pages */
body.legal-page {
    background: #fff;
    color: #000;
}

.legal-layout {
    min-height: 100vh;
    padding: 0;
    box-sizing: border-box;
}

.legal-home {
    color: #000;
    text-decoration: none;
}

.legal-main {
    /* Half-screen gap between header and text */
    margin-top: 50vh;
    max-width: 80ch;
}

/* Fallback before JS sets the Panel-controlled value inline; avoids a layout jump */
#static-modules {
    margin-top: 50vh;
    /* Standard (non-fullbleed) modules get a 9px gutter to the screen edge automatically;
       fullbleed media modules escape it via their own 100vw breakout (except on About, see below). */
    padding-left: 9px;
    padding-right: 9px;
    box-sizing: border-box;
}

/* text-module/list-module bake in a left-only 9px padding for use on project pages;
   on static pages the container already supplies a symmetric 9px gutter. */
#static-modules .text-module,
#static-modules .list-module {
    padding-left: 0;
    padding-right: 0;
}

/* On About only, fullbleed media modules also respect the 9px gutter instead of
   breaking out edge-to-edge — neutralizes the 100vw breakout so they fill the
   container's (already 9px-padded) width like a normal block. */
#static-modules[data-page-slug="about"] .content-module--fullbleed {
    width: auto;
    margin-left: 0;
    margin-right: 0;
}

/* Half Half's own 9px text padding exists so the module still gets a gutter
   once it breaks out to true 100vw on project pages — here that breakout
   is neutralized above, so the container's own 9px already provides the
   gutter and the module's own padding just doubled it to 18px. */
#static-modules[data-page-slug="about"] .half-half-story-module__text {
    padding-left: 0;
    padding-right: 0;
}

.legal-title {
    margin: 0 0 14px 0;
    font: inherit;
    font-size: 16pt;
    font-weight: 300;
}

.legal-content {
    white-space: pre-wrap;
}

/* Text on Picture/Video */
.media-module--text-on-picture .top-item {
    position: relative;
}

.media-module--text-on-picture .top-item img,
.media-module--text-on-picture .top-item video {
    width: 100%;
    height: auto;
    display: block;
}

.media-module--text-on-picture .top-text {
    position: absolute;
    bottom: 9px;
    left: 9px;
    white-space: pre-wrap;
    pointer-events: none;
}

/* Footer — fixed at bottom, revealed by second scroll */
.page-footer {
    height: 0;
    overflow: hidden;
    background: #fff;
    color: #000;
    transition: height 0.45s cubic-bezier(0.4, 0, 0.2, 1);
    pointer-events: none;
    /* Without its own stacking position, this in-flow element paints below
       any position:fixed layer regardless of that layer's z-index (0/auto
       positioned elements always paint above non-positioned in-flow ones).
       The home slider (.home-slider, z-index 0) is opaque and fixed/inset:0,
       so the footer would stay hidden underneath it forever once revealed.
       z-index 2 sits above the slider (0) and .home-blur (1), but still below
       .home-layout's nav overlay (3) so the corner nav text stays on top. */
    position: relative;
    z-index: 2;
}

.page-footer.is-footer-open {
    height: 50vh;
    pointer-events: auto;
}

/* Projektinfos (bottom-left/right) are position:fixed at a higher z-index
   than the footer, so they'd otherwise float visibly over the opened
   footer's own content — confirmed live on desktop (a duplicate-looking
   "current project title/category" block hovering mid-footer). A
   mobile-only version of this already existed (max-width:780px, left side
   only — right side is already display:none there for other reasons) but
   never applied on desktop, where both sides are visible. */
body:has(.page-footer.is-footer-open) .home-bottom-left,
body:has(.page-footer.is-footer-open) .home-bottom-right {
    opacity: 0;
    pointer-events: none;
}

.page-footer__inner {
    box-sizing: border-box;
    height: 50vh;
    padding: 9px;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 18px;
}

.page-footer__left,
.page-footer__col {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.page-footer__spacer {
    flex: 1;
    min-height: 0;
}

.page-footer__right,
.page-footer__col--right {
    display: flex;
    flex-direction: column;
    /* Copyright top-right, contact (Instagram/Email) bottom-right. */
    justify-content: space-between;
    gap: 10px;
    text-align: right;
    align-items: flex-end;
}

.page-footer a,
.page-footer a:visited,
.page-footer a:hover,
.page-footer a:active {
    color: #000;
    text-decoration: none;
}

/* Mobile keeps the exact same corner placement as desktop (2-column grid:
   top-left About / bottom-left legal on the left, top-right Archiv /
   bottom-right contact+copyright on the right) — no stacking override. */

/* Contact links on home: match adaptive text color, no underline */
.home-bottom-right.home-lines a,
.home-bottom-right.home-lines a:visited,
.home-bottom-right.home-lines a:hover,
.home-bottom-right.home-lines a:active {
    color: inherit;
    text-decoration: none;
}

html.is-ui-hover body {
    background: #fff;
}

@media (pointer: fine) {
    /* Never show native cursor (arrow/hand) */
    body,
    a,
    button,
    input,
    select,
    textarea,
    [role='button'],
    [tabindex] {
        cursor: none !important;
    }

    #cursor-arrow {
        position: fixed;
        top: 0;
        left: 0;
        width: 18px;
        height: 26px;
        pointer-events: none;
        /* Must be above cookie banner (10001) and footer (20003) */
        z-index: 20004;
        opacity: 0;
        --cursor-rot: 0deg;
        transform: translate(-50%, -50%) rotate(var(--cursor-rot));
        display: none;
        transform-origin: 50% 50%;
    }

    #cursor-arrow svg {
        display: block;
        width: 100%;
        height: 100%;
    }

    #cursor-arrow .cursor-dot {
        position: absolute;
        top: 50%;
        left: 50%;
        width: 6px;
        height: 6px;
        border-radius: 999px;
        background: currentColor;
        transform: translate(-50%, -50%);
        display: none;
    }

    #cursor-arrow .st0 {
        fill: none;
        stroke: currentColor !important;
        stroke-width: 1px;
        stroke-linecap: square;
        stroke-linejoin: miter;
    }

    html.has-custom-cursor #cursor-arrow {
        display: block;
    }

    /* When hovering menu/Anna Köhler: show dot instead of arrow */
    html.has-custom-cursor.is-cursor-dot #cursor-arrow svg {
        display: none;
    }

    html.has-custom-cursor.is-cursor-dot #cursor-arrow .cursor-dot {
        display: block;
    }


    /* Over selectable elements: show dot as outline (contour) */
    html.has-custom-cursor.is-cursor-dot.is-cursor-outline #cursor-arrow .cursor-dot {
        /* Keep the same size as the filled dot, but show as an inverted ring */
        width: 6px;
        height: 6px;
        background: transparent;
        border: 1px solid #000;
        mix-blend-mode: difference;
    }
}

.home-slider {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    inset: 0;
    z-index: 0;
    overflow: hidden;
    background: #fff;
    transition: opacity 120ms ease, transform 220ms ease;
    transform: translateY(0);
}

/* ── Smooth Slider Mode ──────────────────────────────────────────────────── */

/* Slider becomes a normal document-flow block.
   Height starts at 100vh (CSS default) and is overridden by JS once the
   cover image dimensions are known — so tall/portrait images exceed 100vh
   and can be scrolled through before the project content appears. */
html.is-smooth-mode .home-slider {
    position: relative;
    height: 100vh;
    width: 100%;
    inset: unset;
    z-index: 0;
    transition: none;
    transform: none;
}

/* JS sets background-size/position per slide via inline style:
   - portrait (taller than viewport) → 100% auto + top center (no crop, scrollable)
   - landscape (shorter than viewport) → cover + center (fills viewport, minimal crop) */

/* Nav overlay stays fixed above everything while page scrolls */
html.is-smooth-mode .home-layout {
    position: fixed;
    inset: 0;
    pointer-events: none;
}

html.is-smooth-mode .home-layout > * {
    pointer-events: auto;
}

/* Project content in document flow, directly below the slider */
html.is-smooth-mode #smooth-project {
    background: #fff;
    color: #000;
}

/* Active project rendered with no modules beyond its own cover — see
   renderProject() in main.js. Floor so there's always some room to scroll
   into (and eventually reach the footer) instead of the page having
   nothing to scroll past the slider at all. Must clear vh*0.3 — the
   is-smooth-in-project threshold (see main.js) — with margin, or the
   scrollable range runs out before that state can ever trigger and the
   project still reads as unenterable despite technically being scrollable. */
#smooth-project.is-empty-project {
    min-height: 50vh;
}


/* In project area: bottom overlays are informational only */
html.is-smooth-in-project .home-bottom-left.home-lines,
html.is-smooth-in-project .home-bottom-right.home-lines {
    pointer-events: none;
    cursor: default;
}


/* body.home-page normally has overflow:hidden to prevent scroll (non-smooth mode).
   In smooth mode the page scrolls natively, so we must undo that lock. */
html.is-smooth-mode body.home-page {
    overflow: visible;
}

/* Slider cover shown at natural proportions — fills width, height follows aspect ratio */
.smooth-slide-cover {
    width: 100%;
    background: #fff;
}

.smooth-slide-cover > img,
.smooth-slide-cover > video {
    width: 100%;
    height: auto;
    display: block;
}


/* Legal pages: bottom overlay blocks are not used */
body.legal-page .home-bottom-left,
body.legal-page .home-bottom-right {
    display: none;
}

/* Subpages: footer overlay links must be clickable */
.project-layout .home-bottom-left,
.project-layout .home-bottom-right {
    pointer-events: auto;
}

html.is-ui-hover .home-slider {
    opacity: 0;
}

.home-blur {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 1;
    opacity: 0;
    /* Confirmed live via elementFromPoint(): this fixed, full-viewport layer
       was the topmost hit-test target at screen center at all times — not
       just while actually blurring — since it never had pointer-events
       set and opacity:0 alone doesn't remove something from hit-testing.
       In Safari specifically this silently ate wheel-driven scroll (the
       gesture landed on a non-scrollable fixed element instead of
       bubbling to the document) while leaving clicks/keyboard scroll
       unaffected — matches "wheel dead, spacebar scrolls fine" exactly.
       pointer-events re-enabled only while actually needed (see
       html.is-blur .home-blur below). */
    pointer-events: none;
    transition: opacity 15ms linear, backdrop-filter 15ms linear, -webkit-backdrop-filter 15ms linear;
    backdrop-filter: blur(0px);
    -webkit-backdrop-filter: blur(0px);
    /* tiny alpha so Safari applies backdrop-filter */
    background: rgba(255, 255, 255, 0.001);
}

/* Under construction popup */
.under-construction {
    position: fixed;
    inset: 0;
    z-index: 20001;
    display: block;
    opacity: 0;
    pointer-events: none;
    transition: opacity 160ms ease;
}

.under-construction__backdrop {
    position: absolute;
    inset: 0;
    background: transparent;
}

.under-construction__panel {
    position: fixed;
    z-index: 1;
    background: transparent;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    color: inherit;
    border: 0;
    padding: 0;
    text-align: left;
    box-shadow: none;
    transform: translate(8px, -50%);
}

.under-construction__text {
    font: inherit;
    font-size: inherit;
    line-height: 1.3;
    padding: 0;
}


html.is-under-construction-open .under-construction {
    opacity: 1;
    pointer-events: auto;
}

/* :not(.is-blur) — while the loading screen is still active, hovering the
   top-left name (not pointer-events-gated like the other corners) must
   never expose the slider media behind the blur early. */
html.is-ui-hover:not(.is-blur) .home-blur {
    opacity: 0;
    backdrop-filter: blur(0px);
    -webkit-backdrop-filter: blur(0px);
}

html.is-ui-hover .home-bottom-left.home-lines > div {
    display: none;
}

/* Solid white — not just blurred — so an already-loaded/autoplaying video
   underneath can never show its colors through the loading screen. The
   loading sequence (main.js) controls how long is-blur stays on, so media
   only becomes visible once it's actually finished, however fast it loaded. */
html.is-blur .home-blur {
    opacity: 1;
    background: #fff;
    backdrop-filter: blur(32px);
    -webkit-backdrop-filter: blur(32px);
    /* Only while actually covering the screen should this block
       interaction with the slider underneath — see the base .home-blur
       rule for why pointer-events is none there. */
    pointer-events: auto;
}

/* Loading state: corner UI reveals in a staged sequence (see
   runBasicLoadingSequence / runTextAnimationLoadingSequence in main.js) —
   top-right nav, then bottom-right, then bottom-left — via .is-visible,
   instead of all appearing at once once loading finishes. */
.home-top-right,
.home-bottom-left,
.home-bottom-right {
    transition: opacity 400ms ease;
}

/* Scoped to body.home-page: is-blur is added unconditionally in main.js's
   init() but only ever removed inside the slider-specific code path, which
   early-returns on pages without #home-slider — an unscoped rule here would
   permanently hide the nav on About/Archiv/Datenschutz/Impressum/project. */
html.is-blur body.home-page .home-top-right,
html.is-blur body.home-page .home-bottom-left,
html.is-blur body.home-page .home-bottom-right {
    opacity: 0;
    pointer-events: none;
}

html.is-blur body.home-page .home-top-right.is-visible,
html.is-blur body.home-page .home-bottom-left.is-visible,
html.is-blur body.home-page .home-bottom-right.is-visible {
    opacity: 1;
    pointer-events: auto;
}

/* Nav links (Brand Design/Social Media/Illustration) appear first via the
   container-level fade above; DE/EN stays collapsed a beat longer, then
   grows in from width:0 (main.js measures scrollWidth, same trick as
   .loading-text__secondary). The row is right-anchored (.home-top-right
   has right:9px + justify-content:flex-end), so the links sit flush at the
   9px edge while DE/EN is collapsed, then get pushed left into their
   normal position as it grows — margin-left cancels the flex gap while
   collapsed so no dead space is reserved before DE/EN appears. */
html.is-blur body.home-page .home-nav .lang-switcher {
    width: 0;
    margin-left: -18px;
    overflow: hidden;
    opacity: 0;
    transition: width 0.6s ease, opacity 0.4s ease, margin-left 0.6s ease;
}

html.is-blur body.home-page .home-nav .lang-switcher.is-visible {
    opacity: 1;
    margin-left: 0;
}

/* "Anna" / "Köhler" fade in staggered (see runLoadingIntro in main.js) while
   loading. Default opacity:1 so the name is never accidentally hidden
   outside the home page's loading state (e.g. if JS fails to run). */
.home-top-left__part {
    opacity: 1;
}

html.is-blur body.home-page .home-top-left__part {
    opacity: 0;
    transition: opacity 0.5s ease;
}

html.is-blur body.home-page .home-top-left__part.is-visible {
    opacity: 1;
}

/* Loading-intro center texts (Panel-editable, home page only): text 1
   fades in alone, then text 2 grows in from width:0 next to it — since the
   row is centered as a whole, text 2 growing visually pushes text 1 aside
   without needing to animate text 1's own position. */
.loading-text {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 2;
    display: flex;
    align-items: baseline;
    gap: 9px;
    opacity: 0;
    pointer-events: none;
    transition: opacity 200ms ease;
}

html.is-blur body.home-page .loading-text {
    opacity: 1;
}

.loading-text__primary,
.loading-text__secondary {
    display: inline-block;
    font: inherit;
    font-size: 16pt;
    font-weight: 300;
    white-space: nowrap;
}

.loading-text__primary {
    color: #000;
    opacity: 0;
    transition: opacity 0.5s ease;
}

.loading-text__primary.is-visible {
    opacity: 1;
}

.loading-text__secondary {
    color: rgba(0, 0, 0, 0.3);
    width: 0;
    overflow: hidden;
    opacity: 0;
    transition: width 0.6s ease, opacity 0.4s ease;
}

.loading-text__secondary.is-visible {
    opacity: 1;
}

/* PIN gate (Panel: pin_protected/pin_code) — same visual language as the
   loading screen's center text pair: fixed, centered, "PIN" fully black
   like .loading-text__primary, the field itself dimmed like
   .loading-text__secondary. The underlying page (nav, "Anna Köhler",
   contact) stays visible, blurred, behind it — the locked content itself
   was never sent by the server, so there's nothing else to show. */
.pin-gate {
    position: fixed;
    inset: 0;
    z-index: 500;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(255, 255, 255, 0.001);
    backdrop-filter: blur(32px);
    -webkit-backdrop-filter: blur(32px);
}

.pin-gate__form {
    display: flex;
    align-items: baseline;
    gap: 9px;
}

.pin-gate__label {
    display: inline-block;
    font: inherit;
    font-size: 16pt;
    font-weight: 300;
    color: #000;
    white-space: nowrap;
}

.pin-gate__field {
    position: relative;
    display: inline-flex;
    align-items: center;
    width: 4.5ch;
    height: 1em;
}

/* Real characters stay invisible — only the dots below represent them —
   until the eye toggle reveals them. */
.pin-gate__input {
    font: inherit;
    font-size: 16pt;
    font-weight: 300;
    line-height: 0.72; /* shrinks the caret to roughly cap-height, i.e. as tall as "I" */
    color: transparent;
    caret-color: rgba(0, 0, 0, 0.6);
    background: transparent;
    border: none;
    outline: none;
    padding: 0;
    width: 100%;
    letter-spacing: 0.3em;
}

.pin-gate__field--visible .pin-gate__input {
    color: rgba(0, 0, 0, 0.3);
}

.pin-gate__field--visible .pin-gate__dots {
    opacity: 0;
    pointer-events: none;
}

/* Same look as the custom mouse cursor's dot (see #cursor-arrow .cursor-dot),
   just at 30% opacity — the visible stand-in for the PIN's hidden characters. */
.pin-gate__dots {
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    gap: 6px;
    pointer-events: none;
}

.pin-gate__dot {
    width: 6px;
    height: 6px;
    border-radius: 999px;
    background: #000;
    opacity: 0.3;
}

.pin-gate__field--error .pin-gate__dot {
    background: #b00020;
    opacity: 1;
}

.pin-gate__field--error .pin-gate__input {
    color: #b00020;
}

.pin-gate__toggle {
    align-self: center;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 15pt;
    height: 15pt;
    padding: 0;
    border: none;
    background: transparent;
    cursor: pointer;
    color: rgba(0, 0, 0, 0.3);
}

.pin-gate__toggle svg {
    width: 100%;
    height: 100%;
    fill: none;
    stroke: currentColor;
    stroke-linecap: square;
}

.pin-gate__toggle:hover,
.pin-gate__toggle--active {
    color: #000;
}

.home-slider__layer {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    inset: 0;
    background-position: center;
    background-size: cover;
    opacity: 0;
    transition: none;
}

/* For "free" media where we want to keep the top visible when cropped */
.home-slider__layer.is-focus-top {
    background-position: 50% 0%;
}

.home-slider__video {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: 50% 50%;
    display: block;
    opacity: 0;
    pointer-events: none;
}

.home-slider__layer.is-focus-top .home-slider__video {
    object-position: 50% 0%;
}

.home-slider__layer.is-active {
    opacity: 1;
}

/* Mobile crossfade — no white flash technique:
   Incoming layer fades in (0→1) over 120ms while sitting on top.
   Outgoing layer holds at full opacity for 120ms (stays visible behind
   the incoming image), then instantly drops to 0 once incoming is opaque. */
.home-slider.is-mobile-slider .home-slider__layer.is-active {
    z-index: 1;
    transition: opacity 120ms ease;
}
.home-slider.is-mobile-slider .home-slider__layer:not(.is-active) {
    z-index: 0;
    transition: opacity 0ms 120ms linear;
}


.home-layout {
    min-height: 100vh;
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: auto 1fr auto;
    padding: 9pt;
    box-sizing: border-box;
    position: relative;
    z-index: 3;
    opacity: 1;
    transition: opacity 80ms ease;
}

/* Show pointer cursor when current slide leads to a project page */
html.is-slide-navigable:not(.has-custom-cursor) .home-layout {
    cursor: pointer;
}

/* Project page: keep the Home UI overlay fixed while content scrolls behind */
.project-layout {
    position: fixed;
    inset: 0;
    pointer-events: none;
}

.project-layout .home-top-left,
.project-layout .home-top-left a {
    pointer-events: auto;
}

.project-layout .home-top-left a {
    color: inherit;
    text-decoration: none;
    cursor: default;
}

.project-layout .home-top-left a:hover {
    text-decoration: none;
}

.project-layout .home-nav,
.project-layout .home-nav a {
    pointer-events: auto;
}

/* Mobile burger sits next to .home-nav (not inside it), so the rule above
   never reached it — it stayed pointer-events:none from .project-layout's
   base rule and was unclickable on project/static pages on mobile. */
.project-layout .mobile-burger {
    pointer-events: auto;
}

.project-layout .home-top-right {
    transition: opacity 120ms ease;
}

html.is-project-menu-hidden .project-layout .home-top-right {
    opacity: 0;
    pointer-events: none;
}

.project-layout .home-bottom-left,
.project-layout .home-bottom-right {
    will-change: transform;
}

.home-top-left {
    grid-column: 1;
    grid-row: 1;
    justify-self: start;
    align-self: start;
    position: fixed;
    top: 9px;
    left: 9px;
}

.home-top-right {
    grid-column: 2;
    grid-row: 1;
    justify-self: end;
    align-self: start;
    position: fixed;
    top: 9px;
    right: 9px;
    display: flex;
    align-items: flex-start;
    justify-content: flex-end;
}

.home-bottom-left {
    grid-column: 1;
    grid-row: 3;
    justify-self: start;
    align-self: end;
    position: fixed;
    left: 9px;
    bottom: 9px;
}

.home-bottom-right {
    grid-column: 2;
    grid-row: 3;
    justify-self: end;
    align-self: end;
    text-align: right;
    position: fixed;
    right: 9px;
    bottom: 9px;
}

.home-nav {
    display: flex;
    gap: 18px;
    margin: 0;
}

.home-nav a {
    color: inherit;
    text-decoration: none;
    cursor: default;
}

.home-nav a:hover {
    text-decoration: none;
}

html.is-menu-under-construction .home-nav a.is-menu-hidden {
    display: none;
}

/* Static/category pages (About, Archiv, Datenschutz, Impressum, Brand
   Design, Social Media, Illustration): nav collapses to just the current
   page's own name + language switcher by default, revealing the standard
   Brand Design/Social Media/Illustration nav again on hover. Reveal reuses
   the loading screen's lang-switcher trick (see .home-nav .lang-switcher
   above): grow from max-width:0, margin-left cancels the flex gap while
   collapsed. The row is right-anchored (.home-top-right has
   justify-content:flex-end), so growing links push the already-visible
   ones further left — a right-to-left reveal, same motion as loading. */
body[data-nav-collapsed] .home-nav a:not(.home-nav__current) {
    display: inline-block;
    max-width: 0;
    margin-left: -18px;
    overflow: hidden;
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition: max-width 0.6s ease, opacity 0.4s ease, margin-left 0.6s ease;
}

body[data-nav-collapsed] .home-top-right:hover .home-nav a:not(.home-nav__current) {
    max-width: 240px;
    margin-left: 0;
    opacity: 1;
    pointer-events: auto;
}

html.is-menu-under-construction .home-nav a:not(.is-menu-hidden) {
    position: fixed;
    left: var(--menu-popup-left, 9px);
    top: var(--menu-popup-top, 9px);
    max-width: calc(100vw - var(--menu-popup-left, 9px) - 9px);
    white-space: normal;
    word-break: break-word;
    text-align: left;
}

.home-lines {
    display: grid;
    gap: 6px;
}

/* Project pages (direct visits to project.html — the home slider's own
   in-page scroll-into-project already has its own working reveal via
   html.is-blur body.home-page, untouched here): white cover over the
   still-loading content, mirroring the home loading screen. Starts opaque
   so there's no flash of unstyled/half-loaded content; JS fades it out
   once the first module's media has actually loaded. */
.project-blur {
    position: fixed;
    inset: 0;
    z-index: 300;
    background: #fff;
    opacity: 1;
    transition: opacity 0.4s ease;
    pointer-events: none;
}

.project-blur.is-hidden {
    opacity: 0;
}

/* Same idea: hide the static "—" placeholders baked into project.html
   (shown before any JS runs) and fade in the real text once set, staged
   left-then-right like the loading screen's own staggered reveals. */
body:not(.home-page) .home-bottom-left.home-lines,
body:not(.home-page) .home-bottom-right.home-lines {
    opacity: 0;
    transition: opacity 0.5s ease;
}

body:not(.home-page) .home-bottom-left.home-lines.is-visible,
body:not(.home-page) .home-bottom-right.home-lines.is-visible {
    opacity: 1;
}

.footer-text-button {
    appearance: none;
    background: transparent;
    border: 0;
    padding: 0;
    margin: 0;
    font: inherit;
    color: inherit;
    line-height: inherit;
    cursor: default;
}


@media (max-width: 780px) {
    .home-mobile-burger {
        display: inline-block;
    }

    .home-top-right .home-nav {
        display: none;
    }

    .home-footer__content {
        grid-template-columns: 1fr;
    }

    /* Hide contact info (bottom-right) from slider overlay on mobile */
    .home-bottom-right.home-lines {
        display: none;
    }

    /* Lock scroll only on the home slider page (body.home-page is set globally;
       html overflow is set by JS in isMobileMode — project pages must scroll freely) */

    /* Layout tracks the dynamic visual viewport height */
    .home-layout {
        min-height: 100dvh;
        height: 100dvh;
        /* Push nav/text clear of Dynamic Island (top) and home indicator (bottom).
           viewport-fit=cover extends the slider edge-to-edge; safe-area insets
           keep the overlay UI within the visible safe zone. */
        padding-top: max(9pt, env(safe-area-inset-top));
        padding-bottom: max(9pt, env(safe-area-inset-bottom));
        padding-left: max(9pt, env(safe-area-inset-left));
        padding-right: max(9pt, env(safe-area-inset-right));
    }

    /* Footer open: slide the fixed slider up to reveal the footer below.
       Transition matches the footer's height animation so they move together. */
    .home-slider {
        transition: opacity 120ms ease, transform 0.45s cubic-bezier(0.4, 0, 0.2, 1);
    }

    body:has(.page-footer.is-footer-open) .home-slider {
        transform: translateY(-50vh);
    }

    /* Projektinfos (bottom-left) is fixed to the viewport at a higher
       z-index than the footer, so it would otherwise sit on top of the
       footer's left column once revealed. Fades out with the same opacity
       transition already used for the loading state (see .home-bottom-left
       above). */
    body:has(.page-footer.is-footer-open) .home-bottom-left {
        opacity: 0;
        pointer-events: none;
    }
}

/* Text module */

.text-module {
    padding: 9px 0 9px 9px;
    max-width: 80ch;
}
.text-module--top    { padding-top: 50vh; }
.text-module--bottom { padding-bottom: 50vh; }

/* Runs full width (the 80ch cap is for normal reading-width text blocks) —
   the 9px left/right gutter still applies via .text-module's own padding
   and the #static-modules container padding. */
.text-module--big {
    max-width: none;
}

.text-module--big .text-module__heading,
.text-module--big .text-module__body {
    font-size: 38px;
    line-height: 110%;
}

.text-module__heading {
    margin: 0 0 14px 0;
    font: inherit;
    font-size: 16pt;
    font-weight: 300;
}

/* Mobile project-info-at-end (see renderModule in project.js): each info
   line is its own text-module__heading, stacked directly with no body text
   between them — the 14px above is sized for heading→body spacing, not
   line-to-line, and reads as oversized here. .home-lines (the desktop
   bottom-right this replaces) uses gap:6px for the same kind of stacked
   lines; match that instead. */
.text-module--project-info-mobile .text-module__heading {
    margin-bottom: 6px;
}

.text-module--project-info-mobile .text-module__heading:last-child {
    margin-bottom: 0;
}

.text-module__body p {
    margin: 0 0 1em 0;
}

.text-module__body p:last-child {
    margin-bottom: 0;
}

.text-module__body h1,
.text-module__body h2,
.text-module__body h3,
.text-module__body h4,
.text-module__body h5,
.text-module__body h6 {
    font: inherit;
    font-weight: 400;
    margin: 1.5em 0 0.4em;
}

.text-module__body a {
    color: inherit;
}

.text-module__body ul,
.text-module__body ol {
    padding-left: 1.5em;
    margin: 0 0 1em 0;
}

.text-module__body hr {
    border: none;
    border-top: 1px solid currentColor;
    margin: 2em 0;
    opacity: 0.3;
}

/* Horizontal text list module */

.list-module {
    padding: 9px 0 9px 9px;
    display: grid;
    /* grid-template-columns set via JS based on max cell count */
    column-gap: 18px;
    align-items: baseline;
}

.list-module__cell {
    display: block;
}

/* Media modules (full-bleed like the home slider) */

.content-module {
    box-sizing: border-box;
    margin: 0;
}

/* Full viewport width even inside centered containers */
.content-module--fullbleed {
    width: 100vw;
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
    /* Pull next section 1px up to close sub-pixel height-rounding seams between stacked modules */
    margin-bottom: -1px;
}

/* First rendered element in project-root always fills the viewport */
#project-root > *:first-child {
    min-height: 100vh;
    min-height: 100svh;
}

/* Generic Panel-created static pages (template "page.yml") render into
   project.html's shell via renderAsStaticPageFallback in project.js —
   Projektinfos/Weitere Infos don't apply to them (no project title/
   category/client), and forcing the first module full-viewport like a
   project hero would leave a large empty gap for ordinary content. */
body.static-in-project .home-bottom-left,
body.static-in-project .home-bottom-right {
    display: none;
}

body.static-in-project #project-root > *:first-child {
    min-height: 0;
}

/* Project hero: first media full-viewport like the home slider */
.project-hero {
    width: 100vw;
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
    height: 100vh;
    height: 100svh;
    position: relative;
    overflow: hidden;
    background-position: center;
    background-size: cover;
}

/* Free-form hero assets should dock to the top edge */
.project-hero.project-hero--dock-top {
    background-position: center top;
}

.project-hero__video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    pointer-events: none;
}
/* Transparent overlay blocks native browser video controls/overlays */
.project-hero::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
}

/* No controls, no interaction on all project videos */
.media-tile > video,
.project-hero__video {
    pointer-events: none;
    outline: none;
}
.media-tile > video::-webkit-media-controls,
.project-hero__video::-webkit-media-controls,
.media-tile > video::-webkit-media-controls-panel,
.project-hero__video::-webkit-media-controls-panel,
.media-tile > video::-webkit-media-controls-overlay-play-button,
.project-hero__video::-webkit-media-controls-overlay-play-button,
.media-tile > video::-webkit-media-controls-start-playback-button,
.project-hero__video::-webkit-media-controls-start-playback-button,
.media-tile > video::-webkit-media-controls-play-button,
.project-hero__video::-webkit-media-controls-play-button {
    display: none !important;
    -webkit-appearance: none;
    opacity: 0 !important;
}

.project-hero__img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.project-hero.project-hero--dock-top .project-hero__video {
    object-position: 50% 0%;
}

.project-hero.project-hero--dock-top .project-hero__img {
    object-position: 50% 0%;
}

.media-module {
    display: grid;
    gap: 0;
    background: #ebebeb;
    overflow: hidden;
}

.media-tile {
    position: relative;
    overflow: hidden;
    background: #ebebeb;
}
/* Quelltext overlay — shown on click for tiles with source credit */
.media-tile--has-source {
    cursor: pointer;
}

.media-source {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 2;
    padding: 6px 9px;
    background: none;
    font-size: 10pt;
    line-height: 1.3;
    opacity: 0;
    pointer-events: none;
    transition: opacity 150ms ease;
}

.media-tile--has-source:has(.media-source-icon:hover) .media-source,
.media-tile--has-source:has(.media-source:hover) .media-source {
    opacity: 1;
    pointer-events: auto;
}

.media-source-icon {
    position: absolute;
    bottom: 9px;
    left: 9px;
    z-index: 3;
    font-family: 'GT-Era-Text-Light', serif;
    font-size: 10pt;
    font-style: normal;
    line-height: 1;
    pointer-events: auto;
    cursor: default;
    transition: opacity 150ms ease;
}

.media-tile--has-source:has(.media-source-icon:hover) .media-source-icon,
.media-tile--has-source:has(.media-source:hover) .media-source-icon {
    opacity: 0;
}

/* Transparent overlay blocks all native browser video controls/overlays */
.media-tile::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
}

.media-tile > img,
.media-tile > video {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

img, video {
    -webkit-user-drag: none;
    user-select: none;
    -webkit-user-select: none;
}
video::-webkit-media-controls,
video::-webkit-media-controls-enclosure,
video::-webkit-media-controls-overlay-enclosure,
video::-webkit-media-controls-panel,
video::-webkit-media-controls-overlay-play-button,
video::-webkit-media-controls-start-playback-button,
video::-webkit-media-controls-play-button {
    display: none !important;
    opacity: 0 !important;
}

/* Free-form assets (e.g. "-free-001") should be fully visible (no crop) */
.media-tile.media-tile--contain {
    overflow: visible;
}

.media-tile.media-tile--contain > img,
.media-tile.media-tile--contain > video {
    object-fit: contain;
    object-position: 50% 0%;
}

/* 9:16 single media (module is full-bleed; the tile centers and keeps 9:16) */
.media-module--9x16 {
    display: flex;
    justify-content: center;
}

.media-module--9x16 .media-tile {
    height: 100vh;
    max-height: 100vh;
    aspect-ratio: 9 / 16;
    width: min(100%, calc(100vh * 9 / 16));
}

/* 16:9 single media (full-bleed width, fixed 16:9 ratio) */
.media-module--16x9 {
    display: block;
}

.media-module--16x9 .media-tile {
    width: 100%;
    aspect-ratio: 16 / 9;
}

/* 3:4 single media (full-bleed width, fixed 3:4 ratio) */
.media-module--3x4 {
    display: block;
}

.media-module--3x4 .media-tile {
    width: 100%;
    aspect-ratio: 3 / 4;
}

/* Free single media: use the media's own aspect ratio (no forced tile ratio) */
.media-module--free {
    display: block;
}

.media-module--free .media-tile {
    width: 100%;
}

.media-module--free .media-tile > img,
.media-module--free .media-tile > video {
    width: 100%;
    height: auto;
    display: block;
    object-fit: contain;
    object-position: 50% 0%;
}

/* Dokument Modul: true-size preview (same principle as .media-module--free —
   width:100%/height:auto, no forced ratio), scaled down to a chosen
   percentage of the fullbleed width ("Darstellungsgröße") and showing
   either one item or two side by side ("Darstellung"). View-only —
   right-click/drag saving is blocked globally, see project.js. */
.dokument-module {
    display: block;
}

.dokument-module__row {
    width: 100%;
    margin: 0 auto;
    display: flex;
    gap: 18px;
}

.dokument-module--size-100 .dokument-module__row { width: 100%; }
.dokument-module--size-75  .dokument-module__row { width: 75%; }
.dokument-module--size-50  .dokument-module__row { width: 50%; }
.dokument-module--size-25  .dokument-module__row { width: 25%; }

/* Ausrichtung: only visible once the row is narrower than 100% */
.dokument-module--align-links  .dokument-module__row { margin-left: 0; margin-right: auto; }
.dokument-module--align-mittig .dokument-module__row { margin-left: auto; margin-right: auto; }
.dokument-module--align-rechts .dokument-module__row { margin-left: auto; margin-right: 0; }

.dokument-module__item {
    position: relative;
    display: block;
    flex: 1 1 0;
    min-width: 0;
}

.dokument-module__item .media-tile {
    width: 100%;
}

.dokument-module__item .media-tile > img {
    width: 100%;
    height: auto;
    display: block;
    object-fit: contain;
    object-position: 50% 0%;
}

/* Switch-on-Scroll: scroll-locks when centered, cycles media, then releases */
.media-module--switch-on-scroll {
    display: block;
    position: relative;
}

.switch-scroll-wrapper {
    display: grid;
    width: 100%;
    position: relative;
}

.switch-scroll-wrapper > .switch-item {
    grid-area: 1 / 1;
    opacity: 0;
}

.switch-scroll-wrapper > .switch-item.is-active {
    opacity: 1;
}

.switch-item > img,
.switch-item > video {
    width: 100%;
    height: auto;
    display: block;
}

/* Hover zones: middle third of module height, N equal bands stacked vertically */
.switch-hover-overlay {
    position: absolute;
    left: 0;
    right: 0;
    top: 33.333%;
    height: 33.333%;
    display: flex;
    flex-direction: column;
    z-index: 5;
}

.switch-hover-zone {
    flex: 1;
    cursor: default;
}

/* ── Horizontal Scroll Module ──────────────────────────────────────────── */
.hscroll-module {
    position: relative;
    width: 100%;
    overflow: hidden;
    cursor: ew-resize;
    user-select: none;
    -webkit-user-select: none;
}

.hscroll-module--16x9 { aspect-ratio: 16 / 9; }
.hscroll-module--3x4  { aspect-ratio: 3 / 4; }
.hscroll-module--1x1  { aspect-ratio: 1 / 1; }

.hscroll-module__track {
    display: flex;
    height: 100%;
    width: max-content;
    transform: translateX(0);
    will-change: transform;
    gap: 0;
}

.hscroll-module__item {
    height: 100%;
    flex-shrink: 0;
    display: block;
}

.hscroll-module__item img,
.hscroll-module__item video {
    height: 100%;
    width: auto;
    display: block;
    object-fit: contain;
    pointer-events: none;
}

/* Liste: standalone text-only variant of Fullswitch's meta row — several
   CV-style rows stacked vertically, no media, no scroll-switching. Not
   fullbleed, so it gets the standard 9px side gutter from #static-modules
   automatically like any other standard module. Whitespace above the
   content and title position within it are set per-block in the Panel
   (--module-whitespace custom property + --title-bottom modifier class,
   see renderListeText in project.js); 25vh/top are the fallback for
   already-saved blocks that predate those fields. */
.liste-text-module {
    position: relative;
    display: flex;
    flex-direction: column;
    /* Row spacing follows the text's own line height (1em) rather than the
       fixed 9px gutter, so it scales naturally with font size. */
    gap: 1em;
    padding-top: var(--module-whitespace, 25vh);
    padding-bottom: 9px;
}

/* Not fullbleed (unlike Fullswitch), so the module's own left edge already
   equals 9px from the true screen edge on every static page via
   #static-modules' padding — left:0 here needs no page-specific override. */
.liste-text-module__title {
    position: absolute;
    top: 9px;
    left: 0;
    margin: 0;
    font: inherit;
    font-size: 16pt;
    font-weight: 300;
    z-index: 1;
}

/* "Unten": anchored to the bottom of the whitespace instead of the top,
   9px above where the rows start — mirrors the top case's 9px offset. */
.liste-text-module--title-bottom .liste-text-module__title {
    top: calc(var(--module-whitespace, 25vh) - 9px);
    transform: translateY(-100%);
}

/* Overrides .fullswitch-module__meta's own 9px 0 padding (shared with
   Fullswitch) — the module's own gap: 1em is the sole source of spacing
   between rows here, so it isn't doubled up with row-level padding. */
.liste-text-module__row {
    padding-top: 0;
    padding-bottom: 0;
}

/* Liste mit Medien: same as Liste (whitespace/title behavior identical —
   see renderListeMedia in project.js), but each row can carry a Bild/Video
   that fades in as a fullscreen backdrop on hover, behind the rows/title
   (which stay readable via a higher z-index) but above the rest of the
   page. */
/* Rows sit 9px from the bottom edge and grow upward as more are added
   (min-height keeps them bottom-anchored within at least one viewport;
   beyond that the module simply grows taller, still bottom-anchored). */
.liste-media-module {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    min-height: 100vh;
    /* Row spacing follows the text's own line height (1em) rather than the
       fixed 9px gutter, so it scales naturally with font size. */
    gap: 1em;
    padding-top: var(--module-whitespace, 25vh);
    padding-bottom: 9px;
}

.liste-media-module__title {
    position: absolute;
    top: 9px;
    left: 0;
    margin: 0;
    font: inherit;
    font-size: 16pt;
    font-weight: 300;
    z-index: 2;
}

.liste-media-module--title-bottom .liste-media-module__title {
    top: calc(var(--module-whitespace, 25vh) - 9px);
    transform: translateY(-100%);
}

.liste-media-module__row {
    position: relative;
    z-index: 2;
}

.liste-media-module__overlay {
    position: fixed;
    inset: 0;
    z-index: 1;
    background: #000;
    opacity: 0;
    pointer-events: none;
    transition: opacity 200ms ease;
}

.liste-media-module__overlay.is-active {
    opacity: 1;
}

.liste-media-module__overlay img,
.liste-media-module__overlay video {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* Column order: Kunde/Job, Bereich, Anstellung, Beschreibung, Datum —
   Beschreibung gets the most space as the only long-form field. */
.liste-media-module__meta {
    display: grid;
    grid-template-columns: 5fr 5fr 5fr 15fr 4fr;
    column-gap: 9px;
    align-items: start;
    /* No vertical padding — the module's own gap: 1em is the sole source of
       spacing between rows, so it isn't doubled up with row-level padding. */
    padding: 0;
    box-sizing: border-box;
    line-height: 1.3;
    /* Reset default <a> styling for Projektliste rows (linked to a project). */
    color: inherit;
    text-decoration: none;
}

.liste-media-module__col {
    min-width: 0;
}

.liste-media-module__col--datum {
    text-align: right;
    padding-right: 9px;
    box-sizing: border-box;
}

/* Projektliste (category pages): no Bereich column — the page itself
   already is the Bereich, so one fewer column than Archiv's Liste mit
   Medien. Same proportions minus the Bereich slot. */
.liste-media-module__meta--no-bereich {
    grid-template-columns: 5fr 5fr 15fr 4fr;
}

@media (max-width: 780px) {
    .liste-media-module__meta {
        grid-template-columns: 1fr 1fr;
        row-gap: 9px;
    }

    .liste-media-module__col--beschreibung {
        grid-column: 1 / -1;
    }
}

/* Fullswitch: media + CV-style meta row, stacked as full-width "slides" that
   share the hscroll engine (scroll/drag translates the track horizontally,
   only one slide visible at a time). */
.fullswitch-module {
    /* No own width rule: let .content-module--fullbleed's width/margin pair
       own box sizing so its edge-to-edge math stays internally consistent
       (a lone width override here would fight its matching margins when
       the module sits in a padded container, e.g. #static-modules). */
    position: relative;
    overflow: hidden;
    cursor: ew-resize;
    user-select: none;
    -webkit-user-select: none;
}

.fullswitch-module__track {
    display: flex;
    gap: 9px;
    width: max-content;
    transform: translateX(0);
    will-change: transform;
}

.fullswitch-module__item {
    /* width set via JS (ResizeObserver) to match the module's own rendered
       width exactly — see renderFullswitch in project.js */
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    /* Blank space above the media, part of every block. Size is set per-block
       in the Panel via the --module-whitespace custom property (see
       renderFullswitch in project.js); 25vh is the fallback for
       already-saved blocks that predate that field. */
    padding-top: var(--module-whitespace, 25vh);
}

/* Fixed over the whitespace (not part of the sliding track), same heading
   style used everywhere else on the site. left:9px positions it 9px from
   the module's OWN edge, which only equals 9px from the screen edge on
   Archiv/Datenschutz/Impressum (module is fullbleed/edge-to-edge there).
   On About the module itself already sits inset 9px from the screen edge
   (fullbleed neutralized — see #static-modules[data-page-slug="about"]
   .content-module--fullbleed below), so it needs left:0 there instead to
   still land at 9px from the true screen edge. */
.fullswitch-module__title {
    position: absolute;
    top: 9px;
    left: 9px;
    margin: 0;
    font: inherit;
    font-size: 16pt;
    font-weight: 300;
    z-index: 1;
}

#static-modules[data-page-slug="about"] .fullswitch-module__title {
    left: 0;
}

/* "Unten": anchored to the bottom of the whitespace instead of the top,
   9px above where the media starts — mirrors the top case's 9px offset. */
.fullswitch-module--title-bottom .fullswitch-module__title {
    top: calc(var(--module-whitespace, 25vh) - 9px);
    transform: translateY(-100%);
}

.fullswitch-module__media {
    position: relative;
    width: 100%;
    aspect-ratio: 1902 / 810;
    overflow: hidden;
    background: transparent;
}

/* Hover preview of the Agentur link's website, shown over the media area */
.fullswitch-module__media-preview {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: none;
    display: block;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity 200ms ease;
}

.fullswitch-module__media-preview.is-active {
    opacity: 1;
    visibility: visible;
}

.fullswitch-module__media img,
.fullswitch-module__media video {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    pointer-events: none;
}

/* Fixed-width columns (not content-sized) so each field always starts at the
   same X position across blocks — no shifting as Agentur/Position text
   length varies between blocks. No own font-size: inherits the site's
   default body text size, same as the plain Liste module. Left/right flush
   with the media above (same edge), only vertical breathing room. */
.fullswitch-module__meta {
    /* Height follows content: as tall as the tallest column needs (grid's
       default row-sizing), plus the 9px bottom gap from padding — not a
       fixed ratio, so it grows if e.g. Bereich+Kundinnen wraps to more
       lines. Column order: Agentur, Position, Bereich+Kundinnen,
       Standort+Arbeitsform, Datum. */
    display: grid;
    grid-template-columns: 5fr 6fr 15fr 6fr 4fr;
    column-gap: 9px;
    align-items: start;
    padding: 9px 0;
    box-sizing: border-box;
    line-height: 1.3;
}

.fullswitch-module__col {
    min-width: 0;
}

.fullswitch-module__col--datum {
    text-align: right;
    padding-right: 9px;
    box-sizing: border-box;
}

.fullswitch-module__kundinnen {
    margin-top: 2px;
    color: #000;
    opacity: 0.3;
}

.fullswitch-module__link {
    color: inherit;
}

@media (max-width: 780px) {
    .fullswitch-module__media {
        aspect-ratio: 1 / 1;
    }

    /* Fixed points instead of desktop's single 5-column row: Agentur/Position
       top, Bereich (+ Kundinnen stacked under it, same DOM node) below
       Agentur, Standort/Arbeitsform + Datum at the bottom — same left/right
       corner logic used everywhere else on the site. */
    .fullswitch-module__meta {
        grid-template-columns: 1fr 1fr;
        row-gap: 9px;
        column-gap: 9px;
        /* Explicit normal (H2) size — narrow two-column rows can otherwise
           trigger iOS Safari's text-autosizing, inflating the inherited
           body size past what's actually declared. */
        font-size: 16pt;
        -webkit-text-size-adjust: 100%;
        text-size-adjust: 100%;
    }

    .fullswitch-module__col--agentur {
        grid-column: 1;
        grid-row: 1;
    }

    .fullswitch-module__col--position {
        grid-column: 2;
        grid-row: 1;
        text-align: right;
        padding-right: 9px;
        box-sizing: border-box;
    }

    .fullswitch-module__col--bereich {
        grid-column: 1 / -1;
        grid-row: 2;
        width: 100%;
        padding-right: 9px;
        box-sizing: border-box;
    }

    .fullswitch-module__col--standort {
        grid-column: 1;
        grid-row: 3;
    }

    .fullswitch-module__col--datum {
        grid-column: 2;
        grid-row: 3;
    }
}

/* Overlay text — shared by all modules that support text add-on */
.module-overlay-text {
    position: absolute;
    bottom: 9px;
    left: 9px;
    white-space: pre-wrap;
    pointer-events: none;
    z-index: 10;
}

/* 2x and 3x square grids */
.media-module--grid-2 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

.media-module--grid-3 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
}

.media-module--grid-2 .media-tile,
.media-module--grid-3 .media-tile {
    aspect-ratio: 1 / 1;
}

/* 2-up fixed aspect modules */
.media-module--2-1x1 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

.media-module--2-1x1 .media-tile {
    aspect-ratio: 1 / 1;
}

.media-module--2-4x5 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

.media-module--2-4x5 .media-tile {
    aspect-ratio: 4 / 5;
}

/* 2-up free: show full asset (contain) and dock to top */
.media-module--2-free {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: start;
}

.media-module--2-free .media-tile {
    overflow: visible;
}

.media-module--2-free .media-tile > img,
.media-module--2-free .media-tile > video {
    height: auto;
    object-fit: contain;
    object-position: 50% 0%;
}

/* Half Half Story: text + 9:16 media side by side, order chosen in Panel */
.half-half-story-module {
    display: flex;
    align-items: stretch;
    width: 100vw;
    aspect-ratio: 16 / 9;
    /* Without this, long text content can grow the flex child taller than
       the aspect-ratio height, pushing the whole module past 16:9. */
    overflow: hidden;
    background: #fff;
}

.half-half-story-module--media-left {
    flex-direction: row-reverse;
}

.half-half-story-module__text {
    flex: 0 0 50%;
    max-width: 50%;
    box-sizing: border-box;
    padding: 9px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    text-align: left;
}

/* Defensive — guarantees the module's own 9px padding is the only source
   of edge spacing, regardless of any other margin these shared classes
   might carry elsewhere. */
.half-half-story-module__text .text-module__heading,
.half-half-story-module__text .text-module__body {
    margin-left: 0;
    margin-right: 0;
}

.half-half-story-module__text .text-module__body p:first-child {
    margin-top: 0;
}

/* Media left, text right (desktop only — mobile always stacks left-aligned,
   see the max-width:780px block below): text sits on the outer (screen)
   edge in this variant, so it's right-aligned to match. */
@media (min-width: 781px) {
    .half-half-story-module--media-left .half-half-story-module__text {
        text-align: right;
    }
}

.half-half-story-module__media {
    flex: 0 0 50%;
    max-width: 50%;
    height: 100%;
    display: flex;
    /* Push the tile to the module's outer edge (screen side), not centered */
    justify-content: flex-end;
    align-items: center;
    overflow: hidden;
    background: transparent;
}

.half-half-story-module--media-left .half-half-story-module__media {
    justify-content: flex-start;
}

.half-half-story-module__media .media-tile {
    background: transparent;
}

/* Desktop-only sizing (matches the mobile stacked layout's own rules below,
   kept in its own breakpoint so specificity never has to fight across them).
   Media height is capped at 1062/1350 (~78.67%) of the module's height —
   a fixed ratio requested independent of the module's actual pixel size. */
@media (min-width: 781px) {
    .half-half-story-module__media .media-tile {
        height: 78.67%;
        width: auto;
        aspect-ratio: 9 / 16;
    }

    .half-half-story-module--ratio-1x1 .half-half-story-module__media .media-tile {
        height: 78.67%;
        width: auto;
        aspect-ratio: 1 / 1;
    }

    /* Matches Story/Square's height exactly (same 78.67%), same as the
       requirement they share. A true 4:3 box at that height would overflow
       past the ~8:9 media half's width, so width is capped at 100% of the
       half instead — the source image is cropped (object-fit: cover, same
       as every other media-tile) rather than shown uncropped. */
    .half-half-story-module--ratio-4x3 .half-half-story-module__media .media-tile {
        height: 78.67%;
        width: 100%;
    }
}

@media (max-width: 780px) {
    .half-half-story-module,
    .half-half-story-module--media-left {
        flex-direction: column;
        aspect-ratio: unset;
        /* Text stays on top (DOM order — the row-reverse used for
           media-left's desktop layout no longer applies once stacked).
           25vh whitespace between text and media, and 25vh again below
           the media before the next module. */
        gap: 25vh;
        padding-bottom: 25vh;
    }

    .half-half-story-module__text,
    .half-half-story-module__media {
        flex: 0 0 auto;
        max-width: 100%;
        width: 100%;
    }

    .half-half-story-module__media .media-tile {
        height: auto;
        width: 100%;
    }
}

/* Half Half Connected: multiple .half-half-story-module rows chained
   inside one fullbleed wrapper (content-module--fullbleed sits on the
   outer .half-half-connected-module, not on the individual rows). Rows
   reuse all of Half Half's own styling — only the outer chaining/spacing
   differs, see below. */
.half-half-connected-module {
    display: flex;
    flex-direction: column;
}

.half-half-connected-module__row {
    /* Half Half's own width:100vw is right for a standalone top-level
       module; nested here it would still coincidentally line up (100vw
       inside a zero-padding fullbleed parent), but 100% is the correct,
       non-coincidental way to fill the already-100vw-wide parent. */
    width: 100%;
}

@media (max-width: 780px) {
    /* Only the last row keeps the trailing 25vh gap before whatever comes
       next on the page — between chained rows the usual text/media 25vh
       rhythm continues without also adding each row's own standalone
       "end of module" padding, which would double up the whitespace. */
    .half-half-connected-module__row {
        padding-bottom: 0;
    }

    .half-half-connected-module__row:last-child {
        padding-bottom: 25vh;
    }
}

/* Mobile/touch-notebook step-through (see setupHalfHalfConnectedSwitch in
   project.js): gated by a JS-added class, not a width media query, since
   isMobileMode there also counts wide touch notebooks as mobile — a plain
   @media(max-width) block would miss those.
   Reuses Fullswitch's scroll-lock engagement engine (shared _hscrollEngaged)
   instead of position:sticky — text and media never move. The section's own
   height is computed in JS (updateLayout) so that the instant its top edge
   is aligned under "Anna Köhler" (the engagement trigger), its bottom edge
   also lands exactly 9px above the viewport bottom; a flex column with
   space-between then puts text at the top and media at the bottom of that
   fixed height with no per-element position math needed. Switching is a
   crossfade while a hard scroll lock holds the page frozen in place. */
.half-half-connected-module--mobile-step {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    overflow: hidden;
}

/* #project-root > *:first-child forces min-height:100vh on whatever leads
   the page. The module is now preceded by its own leading spacer (see
   renderHalfHalfConnected), so that spacer — not the section — is what
   ends up :first-child when this module happens to lead a page; either
   way min-height:100vh is taller than intended (25vh for the spacer, or
   the section's own JS-computed height, which would push media past the
   viewport bottom instead of landing 9px above it). */
#project-root > .half-half-connected-module__mobile-before-spacer:first-child,
#project-root > .half-half-connected-module--mobile-step:first-child {
    min-height: 0;
}

.half-half-connected-module--mobile-step .half-half-story-module__text,
.half-half-connected-module--mobile-step .half-half-story-module__media {
    flex: 0 0 auto;
    max-width: 100%;
    width: 100%;
}

.half-half-connected-module--mobile-step .half-half-story-module__media .media-tile {
    height: auto;
    width: 100%;
    /* max-height is set inline via JS (updateLayout) — depends on the
       current block's text height and the viewport, both only known at
       runtime. */
}

.half-half-connected-module__mobile-before-spacer {
    height: 25vh;
}

.half-half-connected-module__mobile-after-spacer {
    height: 25vh;
}

/* 1x square */
.media-module--website-spiegel {
    height: 100vh;
}

.media-module--website-spiegel iframe {
    width: 100%;
    height: 100%;
    border: none;
    display: block;
}

.media-module--1x1 {
    display: flex;
    justify-content: center;
}

.media-module--1x1 .media-tile {
    width: 100%;
    max-width: 100vw;
    aspect-ratio: 1 / 1;
}

/* 2x 3:4 portrait */
.media-module--grid-2-3x4 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* unlimited 3-column 3:4 portrait grid */
.media-module--grid-3-3x4 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
}

.media-module--grid-2-3x4 .media-tile,
.media-module--grid-3-3x4 .media-tile {
    aspect-ratio: 3 / 4;
}

/* Endless 3×1:1: 3 columns, square tiles, unlimited rows */
.media-module--endless-3x1x1 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
}

.media-module--endless-3x1x1 .media-tile {
    aspect-ratio: 1 / 1;
}

/* Endless modules: mobile Einzelansicht (single-column, natural proportions) */
@media (max-width: 780px) {
    .media-module--grid-3-3x4.endless-single,
    .media-module--endless-3x1x1.endless-single {
        grid-template-columns: 1fr;
        overflow: visible;
    }

    .media-module--grid-3-3x4.endless-single .media-tile,
    .media-module--endless-3x1x1.endless-single .media-tile {
        aspect-ratio: unset;
        overflow: visible;
    }

    .media-module--grid-3-3x4.endless-single .media-tile > img,
    .media-module--grid-3-3x4.endless-single .media-tile > video,
    .media-module--endless-3x1x1.endless-single .media-tile > img,
    .media-module--endless-3x1x1.endless-single .media-tile > video {
        height: auto;
        object-fit: contain;
        object-position: 50% 0%;
    }
}

@media (max-width: 780px) {
    .media-module--grid-2,
    .media-module--grid-3,
    .media-module--grid-2-3x4,
    .media-module--2-1x1,
    .media-module--2-4x5,
    .media-module--2-free {
        grid-template-columns: 1fr;
    }

    .media-module--9x16 .media-tile {
        height: auto;
        width: 100%;
    }
}

/* Cookie backdrop — blurs the page behind the banner while consent is pending. */
.cookie-backdrop {
    position: fixed;
    inset: 0;
    z-index: 10000;
    pointer-events: none;
    backdrop-filter: blur(32px);
    -webkit-backdrop-filter: blur(32px);
    transition: opacity 180ms ease;
    opacity: 1;
}

.cookie-consent.is-hiding .cookie-backdrop {
    opacity: 0;
}

/* Cookie banner — four-corner layout (title/actions top, body/settings bottom),
   matching the site's 9px overlay-edge rhythm and standard H2/Copy text sizes. */
.cookie-banner {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 10001;
    height: 25vh;
    background: #fff;
    color: #000;
    border-top: 1px solid rgba(0, 0, 0, 0.18);
    overflow: auto;
    transition: opacity 180ms ease, transform 180ms ease;
    opacity: 1;
    transform: translateY(0);
}

@supports (height: 100dvh) {
    .cookie-banner {
        height: 25dvh;
    }
}

.cookie-banner.is-hiding {
    opacity: 0;
    transform: translateY(10px);
}

/* Cookies — top-left */
.cookie-banner__title {
    position: absolute;
    top: 9px;
    left: 9px;
    font: inherit;
    font-size: 16pt;
    font-weight: 300;
}

/* Cookie copy — bottom-left */
.cookie-banner__body {
    position: absolute;
    left: 9px;
    bottom: 9px;
    font-size: 16pt;
    font-weight: 300;
    line-height: 109%;
    max-width: 60ch;
}

/* Buttons — top-right, in a row */
.cookie-banner__actions {
    position: absolute;
    top: 9px;
    right: 9px;
    display: flex;
    align-items: center;
    gap: 10px;
    justify-content: flex-end;
    flex-wrap: wrap;
    max-width: 60%;
}

.cookie-btn {
    font: inherit;
    font-size: 16pt;
    font-weight: 300;
    padding: 0;
    border: none;
    background: transparent;
    color: inherit;
}

.cookie-btn--primary {
    text-decoration: underline;
}

.cookie-btn--secondary {
    opacity: 0.9;
}

.cookie-btn--link {
    text-decoration: underline;
}

/* Einstellungen — bottom-right */
.cookie-banner__toggle {
    position: absolute;
    right: 9px;
    bottom: 9px;
}

.cookie-banner__details {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: calc(100% - 18px);
    max-width: 60ch;
    border-top: 1px solid rgba(0, 0, 0, 0.14);
    padding-top: 12px;
}

.cookie-toggle {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    font-size: 11pt;
}

.cookie-banner__hint {
    margin-top: 8px;
    font-size: 10.5pt;
    opacity: 0.85;
    line-height: 109%;
}

.cookie-manage {
    position: fixed;
    left: 16px;
    bottom: 16px;
    z-index: 10000;
    font: inherit;
    font-size: 11pt;
    padding: 8px 10px;
    border-radius: 999px;
    border: 1px solid rgba(0, 0, 0, 0.28);
    background: rgba(255, 255, 255, 0.92);
    color: #000;
}

@media (max-width: 780px) {
    /* The four-corner absolute layout above is a desktop-width scheme —
       title and actions both anchored to top:9px inevitably collide once
       the actions (three buttons) don't fit a single row, and body/toggle
       sharing the bottom edge has no real separation either. Mobile
       switches to a plain stacked flow instead: each piece takes the
       width it needs (or all of it, for the actions/body) and normal
       document flow keeps them from ever overlapping regardless of how
       long any piece of text runs. */
    .cookie-banner {
        height: auto;
        max-height: 70vh;
        max-height: 70dvh;
        padding: 9px;
        box-sizing: border-box;
        display: flex;
        flex-direction: column;
        align-items: flex-start;
        gap: 16px;
    }

    .cookie-banner__title,
    .cookie-banner__actions,
    .cookie-banner__body,
    .cookie-banner__toggle {
        position: static;
    }

    .cookie-banner__actions {
        flex-direction: column;
        align-items: flex-start;
        gap: 8px;
        max-width: 100%;
    }

    .cookie-banner__body {
        max-width: 100%;
    }

    .cookie-banner__details {
        position: static;
        transform: none;
        width: 100%;
        max-width: 100%;
    }
}


/* ── Language switcher ───────────────────────────────────────────────────── */
.lang-switcher {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    cursor: default;
    user-select: none;
    -webkit-user-select: none;
}

.lang-switcher [data-lang-sw] {
    cursor: pointer;
    opacity: 0.4;
    transition: opacity 0.15s;
}

.lang-switcher [data-lang-sw].is-active {
    opacity: 1;
}

.lang-switcher [data-lang-sw]:hover {
    opacity: 0.75;
}

.lang-switcher [data-lang-sw].is-active:hover {
    opacity: 1;
}

.lang-switcher__sep {
    opacity: 0.4;
}

/* ── Accessibility: screen-reader-only content ──────────────────────────────
   Visually hidden but readable by assistive tech (unlike display:none). */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ── Accessibility: skip link ────────────────────────────────────────────────
   Hidden off-screen until it receives keyboard focus, then jumps above
   everything so keyboard/screen-reader users can bypass repeated nav. */
.skip-link {
    position: fixed;
    top: -60px;
    left: 8px;
    z-index: 10000;
    background: #fff;
    color: #000;
    padding: 10px 16px;
    text-decoration: none;
    border: 1px solid #000;
    transition: top 120ms ease;
}

.skip-link:focus {
    top: 8px;
}

/* ── Accessibility: visible keyboard focus ───────────────────────────────────
   :focus-visible only triggers for keyboard/AT navigation, so mouse/touch
   users never see this — pure a11y addition, no visual change otherwise. */
a:focus-visible,
button:focus-visible {
    outline: 2px solid #000;
    outline-offset: 2px;
}
