/*
Theme Name: Zuhause
Theme URI: https://zuhauseag.ch
Author: migagentur
Description: Zuhause – Block theme. Design wird nachgereicht.
Version: 1.1.4
Requires at least: 6.0
Tested up to: 7.0
Requires PHP: 8.0
Text Domain: zuhause
*/

/* Base
---------------------------------------- */
*, *::before, *::after {
    box-sizing: border-box;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* The "config-variable" font ships a slant (slnt) axis. Safari applies a
   non-zero slant even though font-style is normal (Chrome keeps it upright),
   rendering everything oblique. Pin the slant to 0 everywhere; font-weight and
   font-stretch still drive the wght/wdth axes. */
html {
    font-variation-settings: "slnt" 0;
}

/* The closed news panel sits off-screen to the right (translateX(100%)). Safari
   lets that off-viewport fixed element create a horizontal scroll region, which
   pushes/clips the right-anchored NEWS tab. Clip horizontal overflow at the root
   (clip, so it doesn't turn html into a vertical scroll container). */
html,
body {
    overflow-x: clip;
}

/* Underlines: the font's own underline metric (text-decoration-thickness: auto)
   renders too heavy — pin every underline to a 1px hairline site-wide, and lift
   it slightly off the baseline so it reads cleaner. */
* {
    text-decoration-thickness: 1px;
    text-underline-offset: 5px;
}

/* iOS Safari's data detectors turn a bare phone number into their own <a> and
   paint it in the system link colour — black, which over the dark team cards
   and the footer means the number simply isn't there. The generated anchor is
   tagged `x-apple-data-detectors`; keep the tap-to-call it gives us and take
   back the typography. `!important` because Safari's own styling of these
   outranks plain author declarations, and `-webkit-text-fill-color` because on
   iOS that is what actually paints the glyphs. */
a[x-apple-data-detectors],
a[x-apple-data-detectors] * {
    color: inherit !important;
    -webkit-text-fill-color: inherit !important;
    font-family: inherit !important;
    font-size: inherit !important;
    font-weight: inherit !important;
    line-height: inherit !important;
    letter-spacing: inherit !important;
    text-decoration: none !important;
}

/* Layout resets
---------------------------------------- */
.site-main {
    margin-top: 0 !important;
}

.entry-content {
    max-width: 100%;
}

.entry-content > * {
    margin-top: 0 !important;
}

.entry-content > .wp-block-group.has-background {
    padding-top: 3.7em;
    padding-bottom: 3.7em;
    padding-left: 3em;
    padding-right: 3em;
    position: relative;
}

h3:first-child {
    margin-top: 0;
}

/* Buttons
---------------------------------------- */
/* Leading arrow, matching the Figma button component (→ + label). */
.wp-block-button__link {
    display: inline-flex;
    align-items: baseline;
    gap: 4px;
    white-space: nowrap;
    transition: background-color 0.2s ease, color 0.2s ease;
}

.wp-block-button__link::before {
    content: "\2192"; /* → */
}

.wp-block-button.is-style-ohne-pfeil .wp-block-button__link::before {
    content: none;
}

/* Negativ: light pill on dark backgrounds. */
.wp-block-button.is-style-negativ .wp-block-button__link {
    background-color: var(--wp--preset--color--off-white);
    border-color: var(--wp--preset--color--off-white);
    color: var(--wp--preset--color--black);
}

/* Hover: fill drops out to an outline (border + text keep the pill's colour). */
.wp-block-button.is-style-negativ .wp-block-button__link:hover {
    background-color: transparent;
    color: var(--wp--preset--color--off-white);
}

.wp-block-button .wp-block-button__link:hover {
    background-color: transparent;
    color: var(--wp--preset--color--black);
}

/* Eyebrow / Kolumnentitel
---------------------------------------- */
.is-style-kolumnentitel {
    font-size: 18px;
    font-weight: 400;
    line-height: 25px;
    letter-spacing: -0.449px;
    text-transform: none;
}

/* Floating menu (header)
---------------------------------------- */
.zh-menu {
    position: fixed;
    bottom: 24px;
    /* Centre with auto margins on a fit-content box rather than
       translateX(-50%): Safari doesn't recompute a %-transform against the
       animating max-width, which made the pill expand rightwards and then snap
       to centre. Auto margins re-centre every frame as the width grows. */
    left: 0;
    right: 0;
    width: fit-content;
    margin-inline: auto;
    z-index: 100;
    background: #fff;
    border-radius: 100px;
    box-shadow: 0 10px 7.5px rgba(0, 0, 0, 0.1), 0 4px 3px rgba(0, 0, 0, 0.1);
    padding: 8px 32px 8px 10px;
    gap: 24px;
    overflow: hidden;
    animation: zh-menu-in 1.2s cubic-bezier(0.22, 1, 0.36, 1) 0.3s both;
}

@keyframes zh-menu-in {
    /* 1. rise up as a circle */
    0% {
        opacity: 0;
        transform: translateY(150%);
        max-width: 72px;
        padding-right: 6px;
    }
    40% {
        opacity: 1;
        transform: translateY(0);
        max-width: 72px;
        padding-right: 6px;
    }
    /* 2. expand into the full pill */
    100% {
        opacity: 1;
        transform: translateY(0);
        max-width: 480px;
        padding-right: 32px;
    }
}

@media (prefers-reduced-motion: reduce) {
    .zh-menu {
        animation: none;
    }
}

/* Once the entrance animation has played, JS swaps it for its end state as
   plain declarations. `zh-menu-in` runs with `fill: both`, so it keeps
   applying `transform: translateY(0)` forever and animation fill styles
   outrank normal author declarations — the scroll behaviour below could
   neither move the pill nor transition it. Two-class selectors so the narrow
   breakpoint's `animation-name` further down the file can't win it back. */
.zh-menu.is-ready {
    animation: none;
    max-width: 480px;
}

.zh-menu__top {
    margin: 0;
}

.zh-menu__top a {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    background: #f3f4f6;
    text-decoration: none;
}

.zh-menu__top a::before {
    content: "\2191"; /* ↑ */
    font-size: 20px;
    color: #000;
}

.zh-menu__nav {
    gap: 32px;
}

.zh-menu__nav .wp-block-navigation-item__content {
    color: var(--wp--preset--color--gray);
    font-size: 20px;
    font-weight: 500;
    letter-spacing: -0.449px;
    text-decoration: none;
}

.zh-menu__nav .wp-block-navigation-item__content:hover {
    color: var(--wp--preset--color--black);
}

@media (max-width: 600px) {
    .zh-menu {
        padding: 6px 20px 6px 8px;
        gap: 14px;
    }
    .zh-menu__top a {
        width: 44px;
        height: 44px;
    }
    .zh-menu__nav {
        gap: 18px;
    }

    /* max-width mirrors the `zh-menu-in-sm` end state, which `is-ready` drops.
       On a phone the pill covers a fifth of the reading width, so it also
       retires below the fold while scrolling down and returns on the way up. */
    .zh-menu.is-ready {
        max-width: min(480px, 100vw - 32px);
        transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.25s ease;
    }
    .zh-scrolled-down .zh-menu.is-ready {
        /* 100% clears the pill, the 24px clears its `bottom` offset. */
        transform: translateY(calc(100% + 24px));
        opacity: 0;
        pointer-events: none;
        /* The transition declared on the target state governs the trip into
           it, so leaving is slower than the (unchanged) return. */
        transition: transform 0.7s cubic-bezier(0.4, 0, 0.6, 1), opacity 0.6s ease;
    }
}

/* Projekt in-place gallery (expanded card)
---------------------------------------- */
.wp-block-post.is-expanded .wp-block-media-text {
    align-items: stretch;
    gap: 1em; /* 20px */
}

/* Projekt copy: short (Übersicht) when closed, full (Detail) when expanded. */
.zh-proj-text--full {
    display: none;
}

.wp-block-post.is-expanded .zh-proj-text--short {
    display: none;
}

.wp-block-post.is-expanded .zh-proj-text--full {
    display: block;
}

.wp-block-post.is-expanded .wp-block-media-text__media {
    width: 68%; /* leave the rest of the row for the text */
    height: auto; /* box hugs the image — height follows its native ratio */
    /* Room for the thumbnail column, which is a fixed 80px inset 16px — so
       the gutter is fixed too. It was 6em, which drifted between 108 and 120px
       with the root size and could not be reused by the close button below
       (a <button> does not inherit font-size, so its own `em` is ~13px). */
    padding-left: 112px;
    cursor: default;
}

.wp-block-post.is-expanded .wp-block-media-text__media::after {
    display: none; /* hide the + while expanded */
}

/* Enlarged view: image spans the full width and its height follows the
   currently selected image's own proportions — no crop, no letterbox.
   The height re-adjusts automatically whenever the gallery src changes,
   because height:auto derives it from each image's natural ratio. */
.wp-block-post.is-expanded .wp-block-media-text__media > img {
    width: 100%;
    height: auto;
    object-fit: contain; /* moot once the box matches the image ratio */
    border-radius: 24px;  /* keep the rounded corners */
}

/* Thumbnails: absolute overlay that rides in from outside the frame — never
   affects the image layout. It used to fade up on the spot, which put it on
   screen while the main image was still travelling to its new size: two
   movements over each other, and the strip briefly sat on top of an image that
   had not arrived yet (Lastenheft 22.9.2026). Now it waits out the 0.6s resize
   parked past the left edge — the media box clips it — and then slides in.
   `-100% - 16px` is its own width plus the inset, i.e. fully outside. */
.zh-pgallery__thumbs {
    position: absolute;
    left: 16px;
    top: 16px;
    bottom: 16px;
    z-index: 2;
    width: 80px;
    display: flex;
    flex-direction: column;
    gap: 12px;
    overflow-y: auto;
    opacity: 0;
    pointer-events: none;
    transform: translateX(calc(-100% - 16px));
    /* Leaving: straight back out alongside the shrinking image, no delay. */
    transition: opacity 0.2s ease, transform 0.35s cubic-bezier(0.45, 0, 0.15, 1);
}

.wp-block-post.is-expanded .zh-pgallery__thumbs {
    opacity: 1;
    pointer-events: auto;
    transform: translateX(0);
    /* The delay is the image's own 0.6s resize, less the overlap that reads as
       one continuous move rather than two queued ones. */
    transition: opacity 0.35s ease 0.5s, transform 0.55s cubic-bezier(0.22, 1, 0.36, 1) 0.5s;
}

@media (prefers-reduced-motion: reduce) {
    .zh-pgallery__thumbs,
    .wp-block-post.is-expanded .zh-pgallery__thumbs {
        transform: none;
        transition: opacity 0.2s ease;
    }
}

.zh-pgallery__thumb {
    flex: 0 0 auto;
    padding: 0;
    border: 0;
    background: none;
    cursor: pointer;
    border-radius: 8px;
    overflow: hidden;
    opacity: 0.5;
    transition: opacity 0.2s ease;
}

.zh-pgallery__thumb.is-active,
.zh-pgallery__thumb:hover {
    opacity: 1;
}

.zh-pgallery__thumb img {
    display: block;
    width: 100%;
    height: auto;
}

.zh-pgallery__close {
    position: absolute;
    bottom: 16px;
    right: 16px;
    z-index: 2;
    width: 41px;
    height: 41px;
    border: 0;
    border-radius: 50%;
    /* Same "+" asset as the enlarge button, rotated 45° so it reads as an "×". */
    background: url("assets/images/projekt-mehr.svg") center / contain no-repeat;
    cursor: pointer;
    opacity: 0;
    pointer-events: none;
    transform: rotate(0deg);
    /* Hold the turn until the open animation (0.6s) has finished, then spin. */
    transition: opacity 0.3s ease, transform 0.4s cubic-bezier(0.45, 0, 0.15, 1) 0.6s;
}

.wp-block-post.is-expanded .zh-pgallery__close {
    opacity: 1;
    pointer-events: auto;
    transform: rotate(135deg);
}

/* Image on the right (even row): the + sits in the image's bottom-left corner
   while closed, so the × it turns into stays there rather than jumping across
   to the right (Lastenheft 22.9.2026). 112px is the thumbnail gutter, +16 puts
   it the same inset inside the image as the + had. */
.wp-block-post-template > li:nth-child(even) .zh-pgallery__close {
    right: auto;
    left: 128px;
}

.wp-block-post.is-expanded .wp-block-media-text__content {
    max-width: none; /* fill the remaining ~38% of the row */
}

.wp-block-post.is-expanded .wp-block-post-title {
    font-size: 1.1em;
}

/* Wir page: hero
---------------------------------------- */
.zh-wirhero {
    position: relative;
    padding: 0 !important;
}

/* The constrained inner wrapper is the positioning context + carries the padding.
   Capped to the design frame and centered so content stays put on large screens. */
.zh-wirhero > .wp-block-group {
    position: relative;
    min-height: min(1139px, 74vw);
    max-width: 1537px;
    margin-left: auto !important;
    margin-right: auto !important;
    padding: 76px 68px;
}

.zh-wirhero__logo {
    position: absolute;
    top: 0;
    right: 0;
    z-index: 2;
    margin: 0;
}

.zh-wirhero__logo img {
    width: 136px;
    height: auto;
}

.zh-wirhero__intro {
    position: relative;
    z-index: 2;
    max-width: 513px;
    margin-left: 0 !important;
    margin-right: auto !important;
    padding-top: 24px;
}

.zh-wirhero__eyebrow {
    color: var(--wp--preset--color--off-white);
    margin: 0 0 4px;
}

.zh-wirhero__title {
    color: var(--wp--preset--color--off-white);
    font-size: 3em; /* 60px */
    font-weight: 400;
    line-height: 1;
    letter-spacing: -0.0206em;
    margin: 0 0 16px;
}

.zh-wirhero__sub {
    color: var(--wp--preset--color--off-white);
    font-size: 20px;
    font-weight: 500;
    line-height: 28px;
    letter-spacing: -0.449px;
    max-width: 513px;
    margin: 0 0 24px;
}

.zh-wirhero__images {
    position: absolute;
    inset: 0;
    z-index: 1;
    margin: 0 !important;
    max-width: none !important;
    pointer-events: none;
}

.zh-wirhero__img {
    position: absolute;
    margin: 0;
}

.zh-wirhero__img img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: 16px;
}

/* The design puts img--2 low on the left, under a short intro. The copy that
   was actually written runs the full height of the column, so the image landed
   on top of it — the two lower images sit side by side on the right instead,
   which is the arrangement the sub-1450 range was already using. */
.zh-wirhero__img--3 { top: 22%; left: 46%; width: 41.4%; aspect-ratio: 637 / 469; }
.zh-wirhero__img--1 { top: 66.8%; left: 42%; width: 26%; aspect-ratio: 496 / 362; }
.zh-wirhero__img--2 { top: 66.8%; left: 71%; width: 26%; aspect-ratio: 415 / 302; }

/* Wir page: Wer wir sind / Team
---------------------------------------- */
.zh-werwirsind__title {
    color: var(--wp--preset--color--off-white);
    margin: 0;
}

/* The Imagevideo, moved here from the home page (22.9.2026).

   The block measures only its button: the poster panel is absolutely placed
   and centred on it, which is exactly what lets it overhang the section below
   on a page where it stands alone. Beside a column of copy that reading is
   wrong — the panel simply landed on the text. Here the teaser *is* the panel:
   a 16:9 box that takes part in the layout, with the play control centred on
   it. Two classes throughout, so these beat the layout rules WordPress prints
   inline for the flex group regardless of which stylesheet lands first. */
.zh-werwirsind .zh-werwirsind__row {
    align-items: center;
    gap: 4em;
}

/* The copy keeps the 513px measure it was written for. */
.zh-werwirsind .zh-werwirsind__row > .wp-block-group {
    flex: 0 1 513px;
}

.zh-werwirsind .zh-werwirsind__video {
    flex: 1 1 auto;
    display: flex;
    justify-content: center;
    min-width: 0;
}

.zh-werwirsind .zh-videopopup__teaser {
    display: grid;
    place-items: center;
    width: min(560px, 100%);
    aspect-ratio: 16 / 9;
}

/* `inset` in place of the base rule's 50%/50% + translate, so the panel fills
   the teaser instead of hanging off the button's centre. */
.zh-werwirsind .zh-videopopup__preview {
    inset: 0;
    width: auto;
    height: auto;
    transform: none;
}

.zh-werwirsind .zh-videopopup__teaser.is-previewing .zh-videopopup__preview {
    transform: scale(1.02);
}

/* The copy holds 513px whatever the column does, so the panel is the only part
   that gives — by ~1025px it is down to a third of the copy's width. They go
   under each other from there instead. */
@media (max-width: 1024.98px) {
    .zh-werwirsind .zh-werwirsind__row {
        flex-direction: column;
        align-items: flex-start;
        gap: 2.5em;
    }

    .zh-werwirsind .zh-werwirsind__row > .wp-block-group,
    .zh-werwirsind .zh-werwirsind__video {
        flex: 0 0 auto;
        width: 100%;
    }

    .zh-werwirsind .zh-werwirsind__video {
        justify-content: flex-start;
    }
}

.zh-team {
    display: flex;
    flex-direction: column;
    gap: 42px;
    margin-top: 52px;
}

.zh-team-card {
    display: flex;
    gap: 28px;
    align-items: flex-start;
    width: fit-content;
    max-width: 100%;
}

/* Horizontal rhythm (Lastenheft 22.9.2026): the photos used to land on a
   different x in every row, because the indent was a per-row percentage *and*
   a mirrored card pushes its photo right by however wide the name happens to
   be. Seven rows, seven positions — the column read as noise. There are now
   three fixed positions and nothing else: left (7%), centred, and right (the
   mirror of left, 7% off the right edge), taken by two, three and two people.

   `--zh-shift` is what a mirrored card parks left of its photo (copy + gap),
   so subtracting it from a position puts the *photo* there rather than the
   card. That is also why the right-hand position is always a mirrored row and
   the left-hand one never is: the other way round the card would need to start
   at a negative margin, or would run off the right edge.

   The indents stay proportional so they shrink with the column, capped at the
   slack actually available — the stagger flattens toward the right edge rather
   than overflowing, since a margin shifts the box after `max-width` applies.
   Below 1300px they are dropped altogether (see the breakpoint further down). */
.zh-team-card {
    --zh-photo-w: 400px;
    --zh-card-w: 788px;  /* photo + 28px gap + the 360px copy column */
    --zh-shift: 388px;   /* copy + gap: what a mirrored row puts left of the photo */
}

/* Photo left, at the left position. */
.zh-team-card:nth-child(7n+2),
.zh-team-card:nth-child(7n+5) {
    margin-left: clamp(0px, 7%, 100% - var(--zh-card-w));
}

/* Photo left, centred. */
.zh-team-card:nth-child(7n+7) {
    margin-left: clamp(0px, 50% - var(--zh-photo-w) / 2, 100% - var(--zh-card-w));
}

/* The two centred rows 7n+3 / 7n+4 follow each other, and with both photos on
   exactly the same x they read as one block with a name either side (client
   feedback 24.9.2026, Nef/Ritschard). So they sit `--zh-nudge` either side of
   centre, and the seam between them gets extra air.

   The direction matters: the normal row goes left and the mirrored one right.
   Nudged the other way, both clamps hit their limits near a 1176px column and
   the two photos would line up again. */
.zh-team-card:nth-child(7n+3),
.zh-team-card:nth-child(7n+4) {
    --zh-nudge: 40px;
}

.zh-team-card:nth-child(7n+3) {
    margin-left: clamp(0px, 50% - var(--zh-photo-w) / 2 - var(--zh-nudge), 100% - var(--zh-card-w));
}

/* Copy left, photo centred. */
.zh-team-card:nth-child(7n+4) {
    flex-direction: row-reverse;
    margin-left: clamp(0px, 50% - var(--zh-photo-w) / 2 - var(--zh-shift) + var(--zh-nudge), 100% - var(--zh-card-w));
    margin-top: 56px;
}

/* Copy left, photo at the right position. */
.zh-team-card:nth-child(7n+1),
.zh-team-card:nth-child(7n+6) {
    flex-direction: row-reverse;
    margin-left: clamp(0px, 93% - var(--zh-photo-w) - var(--zh-shift), 100% - var(--zh-card-w));
}

/* A normal row can put its photo anywhere in [0, column − card]; a mirrored one
   only in [copy + gap, …], because the copy is what stands to its left. Those
   two ranges first overlap at a 1176px column — exactly where the centre
   position (column/2 − photo/2) meets it — so below that no centre exists that
   both kinds of row can reach, and the clamps would quietly split it in two:
   four positions on screen instead of three, which is the thing being fixed.
   The stagger therefore stops here. In its place the rows zigzag strictly —
   photo left, photo right, photo left — so no two photos ever stack on the
   same x (client feedback 24.9.2026: the seven-row pattern left Roth and Nef
   one above the other). A photo-right row is pushed to the right edge, so the
   photos alternate between the two edges of the column. 1300px of viewport is
   that 1176px of column once the section gutter is off it. */
@media (max-width: 1299.98px) {
    .zh-team-card,
    .zh-team-card:nth-child(n) {
        margin-left: 0;
        margin-top: 0;
    }

    .zh-team-card:nth-child(odd) {
        flex-direction: row;
    }

    .zh-team-card:nth-child(even) {
        flex-direction: row-reverse;
        margin-left: auto;
    }
}

/* Narrower than the 460 it was: same height, less width, so the crop keeps
   less of the room either side of the sitter (Lastenheft 22.9.2026 — Fässler,
   Roth and Ritschard were framed too wide). Kept in step with `--zh-photo-w`. */
.zh-team-card__media {
    flex: 0 0 400px;
    aspect-ratio: 400 / 202;
    border-radius: 16px;
    overflow: hidden;
}

.zh-team-card__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    filter: grayscale(1);
    transition: filter 0.4s ease;
}

.zh-team-card:hover .zh-team-card__media img {
    filter: grayscale(0);
}

/* Fixed, not capped: on a mirrored row the copy is what stands between the
   left edge and the photo, so a column that sized itself to the name moved the
   photo with it — see the note on the 1000px breakpoint below. */
.zh-team-card__body {
    padding-top: 9px;
    width: 340px;
    max-width: 340px;
}

.zh-team-card__name {
    color: var(--wp--preset--color--off-white);
    font-size: 36px;
    font-weight: 400;
    line-height: 45px;
    letter-spacing: 0.369px;
    text-transform: uppercase;
    margin: 0 0 17px;
}

.zh-team-card__role {
    color: var(--wp--preset--color--off-white);
    font-size: 20px;
    font-weight: 500;
    line-height: 28px;
    letter-spacing: -0.449px;
    margin: 0 0 3px;
}

.zh-team-card__phone,
.zh-team-card__email {
    color: var(--wp--preset--color--off-white);
    font-size: 20px;
    font-weight: 300;
    line-height: 28px;
    letter-spacing: -0.449px;
    margin: 0;
}

/* The number is a tel: link so it dials on a phone, but it is not presented as
   one — the email below it is the card's only visible link. */
.zh-team-card__phone a {
    color: inherit;
    text-decoration: none;
}

.zh-team-card__email {
    display: inline-block;
    text-decoration: underline;
}

.zh-team-card__vcard {
    display: inline-block;
    margin-top: 11px;
}

/* A name set across two lines reads as two people. Once the card is side by
   side there is room to hold it on one, so stop it breaking. */
@media (min-width: 1000px) {
    .zh-team-card__name {
        white-space: nowrap;
    }

    /* The 340px measure belongs to the copy, not to the name — moved down one
       level, the body can grow to whatever the name needs while the paragraphs
       keep the line length they were set for.

       Fixed rather than intrinsic: on a mirrored row the copy is what stands
       between the left edge and the photo, so a body that sized itself to the
       name moved the photo with it — 343px for MANUEL RITSCHARD against 217px
       for SIMON BERRI, a 126px swing that defeated any fixed position. 360px
       clears the longest name in the CMS today; raise it (and `--zh-card-w`
       and `--zh-shift` with it) if a longer one appears. */
    .zh-team-card__body {
        width: 360px;
        max-width: none;
    }

    .zh-team-card__infos {
        max-width: 340px;
    }
}

/* Narrow the copy column with the viewport. */
@media (max-width: 1450px) {
    /* Drop the base font size 20px -> 18px. Overriding the preset variable
       (not body directly) keeps it the single source of truth: body and all
       the em-based layout scale from it. :root:root out-specifies WP's own
       :root preset definition regardless of stylesheet load order. */
    :root:root {
        --wp--preset--font-size--base: 18px;
    }

    .zh-wirhero__intro {
        max-width: 36vw;
    }
}

/* The collage scales proportionally down to ~900px; below that it would
   overlap the fixed-height copy, so it flows into a stacked column. */
@media (max-width: 900px) {
    .zh-wirhero > .wp-block-group {
        min-height: 0;
    }
    .zh-wirhero__intro {
        max-width: none;
    }
    .zh-wirhero__images {
        position: static;
        display: flex;
        flex-direction: column;
        gap: 1.25em;
        /* !important because the absolute-positioned base rule sets
           `margin: 0 !important`; without it the collage butts straight up
           against the CTA button with a 0px gap. */
        margin: 2.5em 0 0 !important;
        max-width: none !important;
    }
    .zh-wirhero__img {
        position: static !important;
        top: auto;
        left: auto;
        right: auto;
        width: 100% !important;
        max-width: 640px;
        margin: 0;
        /* Stacking makes these flex items, whose `min-height: auto` resolves to
           min-content and silently beats the declared aspect-ratio — the two
           lower images rendered as 344px and 412px portraits at 320 instead of
           ~200px landscape crops. */
        min-height: 0;
        overflow: hidden;
    }
}

@media (max-width: 900px) {
    .zh-team-card,
    .zh-team-card:nth-child(n) {
        flex-direction: column;
        width: 100%;
        max-width: 460px;
        margin-left: 0;
        /* Stacked, every photo sits over its own name; no seam to widen. */
        margin-top: 0;
    }
    .zh-team-card__media { flex-basis: auto; width: 100%; }
    .zh-team-card__body { width: auto; max-width: none; }
}

/* NEWS side tab
---------------------------------------- */
.zh-newstab {
    position: fixed;
    top: 50%;
    right: 0;
    transform: translateY(-50%);
    z-index: 99;
    margin: 0;
    /* Explicit box + grid centering: don't rely on the vertical text's
       intrinsic width, which Safari collapses to ~1px (clipping "NEWS"). */
    display: grid;
    place-items: center;
    width: 44px;
    height: 104px;
    overflow: hidden;
    background: #000;
    border: 1px solid #fff;
    border-right: 0;
    border-radius: 12px 0 0 12px;
    cursor: pointer;
    transition: transform 0.5s cubic-bezier(0.45, 0, 0.15, 1);
}

.zh-news-open .zh-newstab {
    transform: translateY(-50%) translateX(calc(-1 * min(412px, 90vw)));
}

/* Fixed, viewport-sized clip layer: the closed panel sits off-screen at
   translateX(100%); keeping it inside an overflow:hidden fixed wrapper means it
   never adds page-wide horizontal overflow. (Safari counts an off-viewport fixed
   element toward scroll width, which would push the right-anchored NEWS tab off
   the edge — Chrome doesn't.) pointer-events:none lets clicks reach the page. */
.zh-news-viewport {
    position: fixed;
    inset: 0;
    z-index: 98;
    overflow: hidden;
    pointer-events: none;
}

/* News sidebar panel */
.zh-news-panel {
    position: absolute;
    top: 0;
    right: 0;
    width: 412px;
    max-width: 90vw;
    height: 100%;
    padding: 56px 16px 24px;
    background: #f8f8f8;
    overflow-y: auto;
    pointer-events: auto;
    transform: translateX(100%);
    transition: transform 0.5s cubic-bezier(0.45, 0, 0.15, 1);
}

.zh-news-open .zh-news-panel {
    transform: translateX(0);
}

/* Backdrop: dims and blocks the page behind the open panel. It lives inside the
   pointer-events:none viewport and only intercepts clicks once open — a click on
   it is "outside the panel", so the existing document handler closes the panel. */
.zh-news-scrim {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.45);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.5s cubic-bezier(0.45, 0, 0.15, 1);
}

.zh-news-open .zh-news-scrim {
    opacity: 1;
    pointer-events: auto;
}

/* Lock the page behind the panel; the panel scrolls on its own. */
html.zh-news-open {
    overflow: hidden;
}

/* Close control, pinned to the panel's top-right. It's a sibling of the panel
   (not inside its scroll box), so it stays put while the panel content scrolls. */
.zh-news-panel__close {
    position: absolute;
    top: 18px;
    right: 16px;
    z-index: 1;
    width: 34px;
    height: 34px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: transparent;
    cursor: pointer;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s ease, background-color 0.2s ease;
}

.zh-news-open .zh-news-panel__close {
    opacity: 1;
    pointer-events: auto;
}

.zh-news-panel__close:hover {
    background: rgba(0, 0, 0, 0.06);
}

.zh-news-panel__close::before,
.zh-news-panel__close::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 16px;
    height: 2px;
    background: #000;
    transform: translate(-50%, -50%) rotate(45deg);
}

.zh-news-panel__close::after {
    transform: translate(-50%, -50%) rotate(-45deg);
}

.zh-news-panel__inner {
    display: flex;
    flex-direction: column;
    gap: 24px;
}

.zh-news-card {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

/* Image on top, kept compact so the copy leads the card. */
.zh-news-card__media {
    aspect-ratio: 16 / 9;
    border-radius: 16px;
    overflow: hidden;
}

.zh-news-card__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.zh-news-card__title {
    margin: 0;
    font-family: var(--wp--preset--font-family--config);
    font-size: 20px;
    font-weight: 500;
    line-height: 28px;
    letter-spacing: -0.449px;
    text-transform: uppercase;
    color: #000;
}

/* Body copy: always visible, clamped to ~3 lines; expands to full when open. */
.zh-news-card__text {
    max-height: 4.2em; /* ~3 lines */
    overflow: hidden;
    color: var(--wp--preset--color--body-text);
    font-size: 0.9em;
    line-height: 1.4;
    transition: max-height 0.45s cubic-bezier(0.45, 0, 0.15, 1);
}

.zh-news-card__text > * {
    margin: 0 0 0.6em;
}

.zh-news-card__text > *:last-child {
    margin-bottom: 0;
}

.zh-news-card.is-open .zh-news-card__text {
    max-height: 40em;
}

/* "weiterlesen" toggle: shown only when the copy is actually clamped. */
.zh-news-card__more {
    display: none;
    align-self: flex-start;
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
    color: #000;
    font-family: inherit;
    font-size: 0.9em;
    text-decoration: underline;
    cursor: pointer;
}

.zh-news-card.has-more .zh-news-card__more {
    display: inline-block;
}

.zh-newstab a {
    /* Plain horizontal text rotated to vertical — reliable sizing everywhere. */
    white-space: nowrap;
    transform: rotate(-90deg);
    color: #fff;
    text-decoration: none;
    font-size: 16px;
    letter-spacing: 0.1em;
    line-height: 1;
}

/* Outro: CTA + Footer
---------------------------------------- */
footer.wp-block-template-part,
.zh-outro {
    margin-top: 0;
}

.zh-cta {
    text-align: center;
    padding: 128px 20px 80px;
}

.zh-cta__title {
    text-transform: none;
    font-weight: 500;
    font-size: 60px;
    line-height: 1.25;
    letter-spacing: -0.0206em;
    color: var(--wp--preset--color--black);
    margin: 0 auto;
}

.zh-cta__sub {
    color: var(--wp--preset--color--gray);
    font-size: 20px;
    line-height: 1.4;
    margin: 28px auto 46px;
}

.zh-footer {
    background: #1a1a1a;
    color: var(--wp--preset--color--white);
    border-radius: 16px;
    margin: 0 20px 23px;
    padding: 48px 68px;
}

.zh-footer a {
    color: inherit;
}

/* Footer links: no rule at rest; on hover the colour shifts and a hairline
   wipes in from the left. The underline is a background gradient rather than
   text-decoration, because background-size animates and text-decoration does
   not. Scoped to links inside paragraphs, so the social icons and the logo —
   which are also anchors — keep their own behaviour. */
.zh-footer p a {
    text-decoration: none;
    background-image: linear-gradient(currentColor, currentColor);
    background-repeat: no-repeat;
    background-position: 0 100%;
    /* 0% rather than 0: background-size will not interpolate between a length
       and a percentage, so a `0` start makes the underline snap instead of
       wiping in. */
    background-size: 0% 1px;
    /* The address link runs across two lines; `clone` underlines each line
       separately instead of drawing one bar across the whole inline box. */
    -webkit-box-decoration-break: clone;
    box-decoration-break: clone;
    transition: background-size 0.35s cubic-bezier(0.65, 0, 0.35, 1),
                color 0.25s ease;
}

.zh-footer p a:hover,
.zh-footer p a:focus-visible {
    color: var(--wp--preset--color--gray-light);
    background-size: 100% 1px;
}

@media (prefers-reduced-motion: reduce) {
    .zh-footer p a {
        transition: none;
    }
}

.zh-footer__claim {
    text-transform: uppercase;
    font-size: 18px;
    font-weight: 400;
    line-height: 25px;
    letter-spacing: -0.449px;
    color: var(--wp--preset--color--white);
    margin: 0;
    max-width: 20em;
}

.zh-footer__addr-title {
    font-size: 18px;
    font-weight: 500;
    line-height: 28px;
    color: var(--wp--preset--color--white);
    margin: 0 0 16px;
}

.zh-footer__addr {
    color: var(--wp--preset--color--white);
    font-size: 18px;
    line-height: 25px;
    margin: 0;
}

.zh-footer__logo img {
    width: 91px;
    height: auto;
}

.zh-footer__bottom {
    align-items: flex-end;
    margin-top: 32px;
}

.zh-footer__legal {
    font-size: 12.5px;
    margin: 0;
}

.zh-footer__legal a {
    margin-left: 24px;
    text-decoration: none;
}

@media (max-width: 781px) {
    .zh-cta {
        padding: 72px 20px 56px;
    }
    .zh-cta__title {
        font-size: 40px;
    }
    .zh-footer {
        padding: 40px 24px;
    }
}

/* Home: Hero
---------------------------------------- */
body.home .entry-content > .wp-block-group:first-child::after {
    content: "";
    height: 194px;
    flex-shrink: 0;
    align-self: stretch;
    background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.3) 100%);
    display: block;
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    max-width: 100%;
    z-index: 0;
}

body.home .entry-content > .wp-block-group:first-child > * {
    position: relative;
    z-index: 1;
}

.zh-hero__head {
    align-items: flex-start;
    gap: 34px;
    margin-bottom: 34px;
}

.zh-hero__title {
    max-width: 998px;
    margin: 0;
    color: var(--wp--preset--color--black);
    font-weight: 500;
}

/* Hide the title only when JS is present (it splits the words and fades them
   in). Without the flag — e.g. in the block editor — the title stays visible. */
.zh-js .zh-hero__title {
    opacity: 0;
}

.zh-js .zh-hero__title.zh-words {
    opacity: 1;
}

.zh-hero__title .zh-word {
    display: inline-block;
    opacity: 0;
    transform: translateY(100%);
    animation: zh-hero-title-in 0.9s cubic-bezier(0.22, 1, 0.36, 1) both;
    animation-delay: calc(var(--i, 0) * 0.08s);
}

@keyframes zh-hero-title-in {
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

@media (prefers-reduced-motion: reduce) {
    .zh-hero__title,
    .zh-hero__title .zh-word {
        opacity: 1;
        transform: none;
        animation: none;
    }
}

.zh-hero__logo {
    margin: 0;
    flex: 0 0 auto;
}

.zh-hero__logo img {
    width: auto;
    height: 6.75em;
}

.zh-hero__media {
    margin: 0;
    position: relative;
    z-index: 9;
}

.zh-hero__media img {
    display: block;
    width: 100%;
    border-radius: 16px;
}

/* A video in that same slot arrives as a bare `figure.wp-block-video` with no
   theme class, so it never picked up the radius above and sat there with square
   corners (Lastenheft 22.9.2026). Rounded on both the frame and the element:
   Safari will not clip a playing video to an ancestor's border-radius. */
.zh-hero .wp-block-video {
    overflow: hidden;
    border-radius: 16px;
}

.zh-hero .wp-block-video video {
    display: block;
    width: 100%;
    border-radius: 16px;
}

/* Home: slider width in this context */
.zh-vslider.alignfull {
    max-width: none;
    margin: 0;
    padding: 0;
    min-height: 0;
}

/* Home: Titelgrössen (Lastenheft 17.9.2026, Punkt 1)
---------------------------------------- */
/* Three titles read as one series but sit at three sizes: "Alles aus einer
   Hand" is an h3 (1.8em = 32/36px), "Orte die entstehen" an h2 (3em =
   54/60px), and the quote beside the statue a fixed 45px (see
   blocks/vertikaler-slider/style.css). The client wants all three at the
   quote's size. Pinned here rather than rescaled in theme.json, because the
   em ladder is shared with every other h2/h3 on the site.
   Only from 1025px up — that is exactly the range where the quote itself is
   a fixed 45px; below it the quote goes fluid and the tuned mobile rules
   further down already handle these headings. These two are the only h2/h3
   block headings in the front page's content (the project cards use
   .wp-block-post-title), so the selector reaches nothing else. */
@media (min-width: 1025px) {
    .home .entry-content h2.wp-block-heading,
    .home .entry-content h3.wp-block-heading {
        font-size: 45px;
    }
}

/* Home: Projekte
---------------------------------------- */
.zh-projekte {
    background: var(--wp--preset--color--black);
    color: var(--wp--preset--color--off-white);
    padding: 68px 68px 91px;
}

.zh-projekte > * {
    max-width: 1401px;
    margin-left: auto;
    margin-right: auto;
}

.zh-projekte__eyebrow {
    color: var(--wp--preset--color--off-white);
    font-size: 18px;
    font-weight: 400;
    line-height: 25px;
    letter-spacing: -0.449px;
    margin: 0 0 4px;
}

.zh-projekte__title {
    color: var(--wp--preset--color--off-white);
    margin: 0;
}

.zh-projekte__sub {
    color: var(--wp--preset--color--gray-light);
    font-size: 20px;
    line-height: 28px;
    letter-spacing: -0.449px;
    max-width: 510px;
    margin: 16px 0 0;
}

.zh-projekte__filters {
    display: flex;
    gap: 10px;
    align-items: center;
    margin-top: 52px;
}

.zh-filter {
    border: 1px solid var(--wp--preset--color--off-white);
    border-radius: 100px;
    padding: 3px 12px;
    font-size: 16px;
    font-weight: 400;
    line-height: 24px;
    letter-spacing: -0.3125px;
    color: var(--wp--preset--color--off-white);
    background: transparent;
    margin: 0;
    cursor: pointer;
    font-family: inherit;
    transition: opacity 0.2s ease, background-color 0.2s ease, color 0.2s ease;
}

.zh-filter:not(.is-active) {
    opacity: 0.7;
}

/* Active/selected filter: solid white fill with black text. */
.zh-filter.is-active {
    background: var(--wp--preset--color--white);
    color: var(--wp--preset--color--black);
}

/* Hover/focus feedback on any filter (active or inactive): same solid white
   fill with black text. */
.zh-filter:hover,
.zh-filter:focus-visible {
    opacity: 1;
    background: var(--wp--preset--color--white);
    color: var(--wp--preset--color--black);
}

/* Projekte Filter block */
.zh-filter-bar {
    display: flex;
    gap: 10px;
    align-items: center;
    flex-wrap: wrap;
    margin-top: 3.75em;
}

.zh-filter-bar__broschuere {
    margin-left: 2em;
}

.wp-block-post.is-filtered-out,
.wp-block-post.is-beyond-limit {
    display: none;
}

/* "Weitere Referenzen" — reveals items capped by the filter's initial count. */
.zh-projekte-more {
    display: block;
    margin: 3em auto 0;
    padding: 0.55em 1.4em;
    border: 1px solid var(--wp--preset--color--off-white);
    border-radius: 100px;
    background: transparent;
    color: var(--wp--preset--color--off-white);
    font-family: inherit;
    font-size: 16px;
    line-height: 1.5;
    cursor: pointer;
    transition: background-color 0.2s ease, color 0.2s ease;
}

.zh-projekte-more:hover,
.zh-projekte-more:focus-visible {
    background: var(--wp--preset--color--white);
    color: var(--wp--preset--color--black);
}

.zh-projekte__broschuere {
    margin-left: auto;
}

.zh-projekte__list {
    display: flex;
    flex-direction: column;
    gap: 52px;
    margin-top: 52px;
}

.zh-pcard.is-hidden {
    display: none;
}

/* Project cards */
.zh-pcard {
    display: flex;
    gap: 80px;
    align-items: flex-start;
    margin: 0;
    flex-wrap: nowrap;
}

.zh-pcard--right {
    flex-direction: row-reverse;
}

.zh-pcard__media {
    display: block;
    flex: 0 0 540px;
    margin: 0;
    position: relative;
}

.zh-pcard__media img {
    display: block;
    width: 540px;
    height: 400px;
    object-fit: cover;
    border-radius: 24px;
}

.zh-pcard__media::after {
    content: "";
    position: absolute;
    right: 16px;
    bottom: 16px;
    width: 41px;
    height: 41px;
    background: url("assets/images/projekt-mehr.svg") center / contain no-repeat;
}

.zh-pcard__body {
    flex: 1 1 auto;
    max-width: 475px;
    padding-top: 8px;
}

.zh-pcard__title {
    color: var(--wp--preset--color--off-white);
    margin: 0 0 8px;
}

.zh-pcard__text {
    color: var(--wp--preset--color--off-white);
    font-size: 18px;
    line-height: 25px;
    letter-spacing: -0.449px;
    margin: 0 0 24px;
    max-width: 450px;
}

@media (max-width: 900px) {
    .zh-pcard, .zh-pcard--right {
        flex-direction: column;
        gap: 24px;
    }
    .zh-pcard__media, .zh-pcard__media img {
        flex-basis: auto;
        width: 100%;
    }
}

/* Projekte query loop: flex, alternate media side (even = image right)
---------------------------------------- */
li.type-projekt {
    margin-top: 2.5em !important;
}

.wp-block-post-template .wp-block-media-text {
    display: flex;
    align-items: flex-start;
    gap: 4em;
    grid-template-columns: none;
    transition: gap 0.6s cubic-bezier(0.45, 0, 0.15, 1);
}

.wp-block-post-template > li:nth-child(even) .wp-block-media-text {
    flex-direction: row-reverse;
}

.wp-block-post-template .wp-block-media-text__content {
    padding-left: 0;
    padding-right: 0;
}

.wp-block-post-template .wp-block-media-text__media {
    flex: 0 0 auto;
    width: calc(50% - 2em); /* half the row, minus half the 4em gap */
    height: 20em;
    padding-left: 0;
    border-radius: 24px;
    overflow: hidden;
    position: relative;
    cursor: pointer;
    transition: width 0.6s cubic-bezier(0.45, 0, 0.15, 1),
                height 0.6s cubic-bezier(0.45, 0, 0.15, 1),
                padding-left 0.6s cubic-bezier(0.45, 0, 0.15, 1);
    will-change: width, height, padding-left;
}

.wp-block-post-template .wp-block-media-text__media::after {
    content: "";
    position: absolute;
    bottom: 16px;
    right: 16px;
    width: 41px;
    height: 41px;
    background: url("assets/images/projekt-mehr.svg") center / contain no-repeat;
    pointer-events: none;
    transition: transform 0.4s cubic-bezier(0.45, 0, 0.15, 1);
}

/* Image on the right (even): move the + to the bottom-left */
.wp-block-post-template > li:nth-child(even) .wp-block-media-text__media::after {
    right: auto;
    left: 16px;
}

/* Spin the "+" 90° on hover, back on hover-out (the open-state "×" is separate). */
.wp-block-post-template .wp-block-media-text__media:hover::after {
    transform: rotate(90deg);
}

.wp-block-post-template .wp-block-media-text__content {
    flex: 1 1 auto;
    max-width: none; /* fill the remaining half */
    border-bottom: 2px solid #f8f8f8;
    position: relative;
    padding-bottom: 3.5em;
}

.wp-block-post-template .wp-block-media-text__media > img {
    width: 100%;
    object-fit: cover;
    height: 100%;
}

.wp-block-post-template .wp-block-media-text__content > .wp-block-buttons {
    position: absolute;
    left: 0;
    bottom: 1em;
    margin: 0;
}

.wp-block-post-template .wp-block-media-text__content .wp-block-post-excerpt {
    font-size: 0.9em;
}

.wp-block-post-template .wp-block-post-title {
    transition: font-size 0.6s cubic-bezier(0.45, 0, 0.15, 1);
}

/* Hide the content instantly during resize, fade back in when it settles */
.wp-block-post-template .wp-block-media-text__content > * {
    transition: opacity 0.3s ease;
}

.wp-block-post.is-resizing .wp-block-media-text__content > * {
    opacity: 0;
    transition: none;
}

/* Beat core's `.is-vertically-aligned-top ... { align-self: start }` */
.wp-block-post-template .wp-block-media-text.is-vertically-aligned-top > .wp-block-media-text__content {
    align-self: stretch;
}

.wp-block-post-template .wp-block-media-text__media > img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: 24px;
}

/* Full-page navigation transition
----------------------------------------
   On same-origin navigation the incoming page slides in from the right, over
   the outgoing page. Uses the cross-document View Transitions API (Chrome,
   Edge, Safari); browsers without support just navigate normally. Because the
   effect lives on the browser's ::view-transition overlay layer, it doesn't
   transform the real DOM, so fixed elements (menu, news tab/panel) are safe. */
@view-transition {
    navigation: auto;
}

@keyframes zh-page-slide-in {
    from { transform: translateX(100%); }
    to   { transform: translateX(0); }
}

@keyframes zh-page-slide-out-right {
    from { transform: translateX(0); }
    to   { transform: translateX(100%); }
}

/* Forward / new navigations: the incoming page slides in from the right, over
   the outgoing page, which stays still underneath. */
::view-transition-old(root) {
    animation: none;
}

::view-transition-new(root) {
    animation: zh-page-slide-in 0.5s cubic-bezier(0.65, 0, 0.35, 1) both;
    box-shadow: -24px 0 60px rgba(0, 0, 0, 0.35);
}

/* Back traversals (html.zh-nav-back is set from the pagereveal hook in
   functions.php): keep the previous page still underneath and slide the current
   page off to the right to reveal it. Flip the stacking so the outgoing page
   sits on top. */
html.zh-nav-back::view-transition-new(root) {
    animation: none;
    box-shadow: none;
    z-index: 0;
}

html.zh-nav-back::view-transition-old(root) {
    z-index: 1;
    animation: zh-page-slide-out-right 0.5s cubic-bezier(0.65, 0, 0.35, 1) both;
    box-shadow: -24px 0 60px rgba(0, 0, 0, 0.35);
}

@media (prefers-reduced-motion: reduce) {
    ::view-transition-old(root),
    ::view-transition-new(root),
    html.zh-nav-back::view-transition-old(root),
    html.zh-nav-back::view-transition-new(root) {
        animation: none;
    }
}

/* Kontaktformular (Contact Form 7)
   --------------------------------------------------------------------------
   Everything is scoped to .zh-form, which the shortcode puts on the <form> via
   its html_class attribute — nothing keys off the generic .wpcf7 wrapper, so
   the Bewerbungsformular is untouched.

   The form template in CF7 carries no markup of its own: wpautop wraps every
   tag in a paragraph, and those paragraphs are the rows. :has() tells them
   apart (submit row, plain-text label), which keeps the template readable for
   whoever edits it next.
   ========================================================================== */
.zh-form {
    /* Published as variables so the pill geometry is retuned in one place —
       the breakpoints below only override these. */
    --zh-form-width: 1140px;
    --zh-form-field-h: 46px;
    --zh-form-field-pad: 24px;
    --zh-form-row-gap: 26px;
    --zh-form-mark: 40px;
    --zh-form-surface: #ededeb;
    --zh-form-invalid: #ff6b6b;

    max-width: var(--zh-form-width);
    font-size: 18px;
}

.zh-form p {
    margin: 0 0 var(--zh-form-row-gap);
}

.zh-form .wpcf7-form-control-wrap {
    display: block;
}

/* Text fields */
.zh-form input[type="text"],
.zh-form input[type="email"],
.zh-form input[type="tel"],
.zh-form textarea {
    width: 100%;
    height: var(--zh-form-field-h);
    padding: 0 var(--zh-form-field-pad);
    border: 1px solid transparent;
    border-radius: 100px;
    background: var(--zh-form-surface);
    color: var(--wp--preset--color--body-text);
    font: inherit;
    letter-spacing: inherit;
}

.zh-form textarea {
    display: block;
    height: 170px;
    padding: 11px var(--zh-form-field-pad);
    border-radius: 30px;
    resize: vertical;
}

/* The placeholders are the only labels these fields get, so they read as copy
   rather than as the browser's washed-out hint grey. */
.zh-form ::placeholder {
    color: var(--wp--preset--color--body-text);
    opacity: 1;
}

/* Chrome repaints autofilled fields with its own background; an inset shadow
   is the only thing that still covers it. */
.zh-form input:-webkit-autofill,
.zh-form input:-webkit-autofill:focus {
    -webkit-text-fill-color: var(--wp--preset--color--body-text);
    box-shadow: inset 0 0 0 100px var(--zh-form-surface);
}

/* Radios and the acceptance checkbox — same disc, both of them */
.zh-form input[type="radio"],
.zh-form input[type="checkbox"] {
    appearance: none;
    flex: none;
    width: var(--zh-form-mark);
    height: var(--zh-form-mark);
    margin: 0;
    border: 0;
    border-radius: 50%;
    background: var(--wp--preset--color--white);
    cursor: pointer;
}

.zh-form input[type="radio"]:checked,
.zh-form input[type="checkbox"]:checked {
    background-image: radial-gradient(
        circle,
        var(--wp--preset--color--black) 0 32%,
        transparent 33%
    );
}

/* One white ring reads against both the dark section and the light pills. */
.zh-form :is(input, textarea):focus-visible {
    outline: 2px solid var(--wp--preset--color--white);
    outline-offset: 3px;
}

.zh-form .wpcf7-list-item {
    display: block;
    margin: 0;
}

.zh-form .wpcf7-list-item label {
    display: flex;
    align-items: center;
    gap: 16px;
    cursor: pointer;
}

/* CF7 pads its labels with literal spaces; the flex gap does that job now. */
.zh-form .wpcf7-list-item-label::before,
.zh-form .wpcf7-list-item-label::after {
    content: none;
}

/* Anrede is the one group that runs across rather than down. */
.zh-form [data-name="anrede"] .wpcf7-radio {
    display: flex;
    gap: 32px;
}

.zh-form [data-name="aufmerksam"] .wpcf7-radio {
    display: flex;
    flex-direction: column;
    gap: 9px;
}

/* The question and its options read as white like the rest of the form, not
   as the muted grey-blue they were set in (Lastenheft 22.9.2026). */
.zh-form [data-name="aufmerksam"] .wpcf7-list-item-label {
    color: var(--wp--preset--color--off-white);
}

/* The free-text row belongs to the "Andere" option, so it only exists once
   that option is the answer. The radio sits several levels down inside its own
   paragraph, so :has() reaches up out of it and across to the next row. */
.zh-form p:has([data-name="andere"]) {
    display: none;
}

.zh-form p:has(input[value="Andere"]:checked) + p:has([data-name="andere"]) {
    display: block;
}

/* "Wie sind Sie …" is the only paragraph in the template that is plain text,
   and so the only one without an element child. */
.zh-form p:not(:has(*)) {
    margin: 62px 0 24px;
    color: var(--wp--preset--color--off-white);
}

/* Footer row: consent left, Senden right */
.zh-form p:has(.wpcf7-submit) {
    display: flex;
    align-items: center;
    margin: 64px 0 0;
}

.zh-form p:has(.wpcf7-submit) .wpcf7-form-control-wrap {
    margin-right: auto;
}

.zh-form .wpcf7-submit {
    flex: none;
    padding: 0.1875em 0.9em;
    border: 1px solid var(--wp--preset--color--off-white);
    border-radius: 100px;
    background: var(--wp--preset--color--off-white);
    color: var(--wp--preset--color--black);
    font: inherit;
    font-size: 16px;
    line-height: 1.5;
    letter-spacing: -0.0195em;
    cursor: pointer;
    transition: background-color 0.2s ease, color 0.2s ease;
}

/* Same hover as .is-style-negativ: the fill drops out to an outline. */
.zh-form .wpcf7-submit:hover:not(:disabled) {
    background: transparent;
    color: var(--wp--preset--color--off-white);
}

/* CF7 keeps the button disabled until the consent box is ticked. */
.zh-form .wpcf7-submit:disabled {
    opacity: 0.4;
}

/* Feedback */
/* An inset ring rather than border-color: `.zh-form input[type="text"]` above
   already carries an attribute selector and would out-specify a plain
   `.wpcf7-not-valid` border. */
.zh-form .wpcf7-not-valid {
    box-shadow: inset 0 0 0 2px var(--zh-form-invalid);
}

.zh-form .wpcf7-not-valid-tip {
    padding: 7px 0 0 var(--zh-form-field-pad);
    color: var(--zh-form-invalid);
    font-size: 16px;
}

/* Three classes to out-specify `.wpcf7 form .wpcf7-response-output`, which
   ships a 2px blue box. */
.wpcf7 .zh-form .wpcf7-response-output {
    margin: 28px 0 0;
    padding: 0;
    border: 0;
    color: var(--wp--preset--color--off-white);
    font-size: 16px;
}

/* CF7's spinner is dark grey — invisible on a black section. */
.zh-form .wpcf7-spinner {
    margin: 0 0 0 20px;
    background-color: var(--wp--preset--color--off-white);
}

.zh-form .wpcf7-spinner::before {
    background-color: var(--wp--preset--color--black);
}

/* The intro line sits above the form, as a paragraph block on the page, so the
   object it refers to can be edited per page. */
.zh-form__intro {
    margin: 10px 0 44px;
    color: var(--wp--preset--color--gray-light);
    font-size: 18px;
}

/* ==========================================================================
   Responsive
   --------------------------------------------------------------------------
   Everything from here down is scoped to max-width: 1024px and narrower, so
   the desktop layout (which is signed off) is never touched. Reference widths
   used while building: 1000 / 800 / 600 / 320.
   ========================================================================== */

/* 1024.98 rather than 1024: browser zoom and fractional device pixel ratios
   produce viewport widths like 1024.3px, which an integer `max-width: 1024px`
   misses — the page then drops to the desktop layout on, among others, an iPad
   in landscape. The fractional bound catches those without letting any real
   1025px+ viewport through. */
@media (max-width: 1024.98px) {
    /* Section gutters: the desktop 3em (~54px) eats a third of a 320px screen.
       The bottom value is published as a variable because the slider's figure
       has to cancel it exactly to sit on the section boundary. */
    .entry-content > .wp-block-group.has-background {
        --zh-section-pad-bottom: 3em;
        padding-top: 3em;
        padding-bottom: var(--zh-section-pad-bottom);
        padding-left: 2em;
        padding-right: 2em;
    }

    /* Home: hero
    ---------------------------------------- */
    /* Fluid headline, continuous with the desktop size at the 1024px seam
       (7.4vw ≈ 76px there) and bottoming out at 32px on the narrowest phones. */
    .zh-hero__title {
        font-size: clamp(2rem, 7.4vw, 4.25em);
    }

    .zh-hero__logo img {
        height: 5em;
    }

    /* Touch targets
    ----------------------------------------
       These all sit at or below 30px, which is fine for a cursor and not for a
       thumb. The visual sizes stay put — only the hit areas grow. */
    /* Nav labels are 28px tall inside a 56px pill; pad them out to fill it. */
    .zh-menu__nav .wp-block-navigation-item__content {
        padding-top: 8px;
        padding-bottom: 8px;
    }

    .zh-footer__social .wp-block-social-link-anchor {
        padding: 10px;
    }

    /* Every pill control on the site — CTA, "Interesse?", "Zuhause Broschüre",
       V-Card, the projekt filter chips — renders 32px tall, because the
       theme.json button padding (0.1875em) is set for a pointer. */
    .wp-block-button__link,
    .zh-filter,
    .zh-projekte-more {
        padding-top: 10px;
        padding-bottom: 10px;
    }

    /* The CTA's only step-down was WordPress's default 781px breakpoint, so at
       800 and 1000 it kept the full desktop scale — a 492px block on a page
       whose body is ~360px tall. */
    .zh-cta {
        padding-top: 88px;
        padding-bottom: 64px;
    }

    .zh-cta__title {
        font-size: 48px;
    }

    .zh-news-card__more {
        padding: 13px 0;
    }

    .zh-pgallery__close,
    .zh-videopopup__close {
        width: 44px;
        height: 44px;
    }

    /* Inline links and the nav labels can't take padding without disturbing the
       type they sit in, so grow the hit area with an overlay instead — the
       rendered box is untouched. */
    .zh-menu__nav .wp-block-navigation-item__content,
    .zh-footer__legal a,
    .zh-footer__addr a {
        position: relative;
    }

    /* "Wir" is only 30px wide; its neighbours are 18px away. */
    .zh-menu__nav .wp-block-navigation-item__content::after {
        content: "";
        position: absolute;
        inset: 0 -7px;
    }

    .zh-footer__legal a::after,
    .zh-footer__addr a::after {
        content: "";
        position: absolute;
        inset: -15px -4px;
    }

    /* The thumbnail strip's explicit sizing only existed below 900, so between
       901 and 1024 the injected lazy <img>s (no width/height attributes)
       collapsed the column to zero height until they decoded. */
    .zh-pgallery__thumb {
        width: 80px;
        height: 56px;
        flex: 0 0 auto;
    }

    /* Keep the drawer's scroll from chaining to the page behind it. */
    .zh-news-panel {
        overscroll-behavior: contain;
    }

    /* 28px tall, and one of the two things anyone actually taps on the Wir
       page. Tablets at 800/1000 are touch devices too, so this belongs at the
       outer breakpoint rather than the phone one. */
    .zh-team-card__email {
        padding-top: 8px;
        padding-bottom: 8px;
    }

    .zh-news-panel__close {
        width: 44px;
        height: 44px;
    }

    /* Clear the enlarged close button. */
    .zh-news-panel {
        padding-top: 68px;
    }

    .zh-footer__legal {
        font-size: 14px;
    }

    /* Wir: hero
    ---------------------------------------- */
    /* This hero carries its own fixed 68px gutter rather than the section
       padding, which leaves 184px of usable width at 320. Track the section
       gutter instead so the page keeps one margin. */
    .zh-wirhero > .wp-block-group {
        padding: 56px 2em;
    }

    /* Outro: CTA + Footer
    ---------------------------------------- */
    /* The footer's inset rounded card is an accident of the layout on desktop:
       the constrained wrapper caps it at contentSize (1400px) and centres it
       with `margin: auto !important`, which happens to leave 20px a side at
       1440. Below that the card goes edge to edge and the 16px radius stops
       reading. Give it an explicit width so the inset survives. */
    /* The outro lives in the footer template part, outside `.entry-content`, so
       the responsive gutter rule never reached it: at 800 the page had three
       different left edges stacked vertically (20 / 36 / 88). */
    .zh-cta {
        padding-left: 2em;
        padding-right: 2em;
    }

    .zh-footer {
        width: calc(100% - 40px);
        padding-left: 2em;
        padding-right: 2em;
        /* Keep the last row clear of the floating pill (bottom 24px + 72px
           tall), with room to spare. This has to start at 1024 and not 900:
           between those widths the pill is still there and the desktop 48px
           padding left "Disclaimer" permanently buried. */
        padding-bottom: 124px;
    }

    /* The pill sits at z-index 100, above the news panel's z-index 98 layer, so
       it floats over the open panel and covers the last lines of the bottom
       card. The NEWS tab already retires while the panel is open; the pill
       should too. */
    .zh-news-open .zh-menu {
        /* !important because `zh-menu-in` runs with `fill: both`, so its 100%
           keyframe keeps applying opacity:1 forever, and animation fill styles
           outrank normal author declarations. */
        opacity: 0 !important;
        pointer-events: none;
        transition: opacity 0.3s ease;
    }

    /* Kontaktformular
    ---------------------------------------- */
    /* The 1140px cap is a desktop proportion; below it the form just fills the
       section gutters. */
    .zh-form {
        --zh-form-width: 100%;
    }

}

/* Above the stacking breakpoint the collage is still absolutely positioned
   against the section's padding box, so the right-hand image's 71% + 26% ran
   6–9px past the content gutter — visibly out of line with the logo above it. */
@media (min-width: 901px) and (max-width: 1024.98px) {
    .zh-wirhero__img--2 {
        left: auto;
        right: 2em;
    }
}

@media (max-width: 900px) {
    /* Not enough room for headline and lockup side by side: the logo moves
       above the headline but keeps its top-right anchor. */
    .zh-hero__head {
        flex-direction: column-reverse;
        align-items: flex-start;
        gap: 1.25em;
        margin-bottom: 1.75em;
    }

    .zh-hero__logo {
        align-self: flex-end;
    }

    /* Home: Projekte cards
    ---------------------------------------- */
    /* Side by side, the image column shrinks to a sliver (102px at 320). Stack
       image over copy instead; the alternating left/right rhythm is a
       wide-screen device and has no meaning in a single column. */
    .wp-block-post-template .wp-block-media-text,
    .wp-block-post-template > li:nth-child(even) .wp-block-media-text {
        flex-direction: column;
        gap: 1.25em;
    }

    .wp-block-post-template .wp-block-media-text__media {
        width: 100%;
        height: clamp(200px, 56vw, 320px);
    }

    /* The + marker sits bottom-right on every card once they all stack. */
    .wp-block-post-template > li:nth-child(even) .wp-block-media-text__media::after {
        right: 16px;
        left: auto;
    }

    /* Floating UI: menu pill + NEWS tab
    ---------------------------------------- */
    /* The tab keeps its desktop form here — the vertical bar on the right edge
       is the news entry point everywhere, and a pill would read as part of the
       floating menu instead. It only moves up: at 50% it parked over the middle
       of the copy, which runs the full width on a phone. */
    .zh-newstab {
        top: 33%;
        /* The desktop 44x104 box leaves a lot of air around a 16px label, and
           here the bar sits over the copy — take the slack out of it. */
        width: 34px;
        height: 92px;
    }

    .zh-newstab a {
        font-size: 14px;
    }

    /* The panel covers almost the whole screen at this size, so the tab has
       nowhere useful to slide to — retire it while the panel is open and let
       the panel's own close button do the work. The translate stays so it
       fades in place rather than dropping half its height first. */
    .zh-news-open .zh-newstab {
        transform: translateY(-50%);
        opacity: 0;
        pointer-events: none;
    }

    /* Scrolling down means "reading on", so the bar leaves the way it came in —
       sideways off the right edge — and returns the moment the reader scrolls
       back up. The base rule's 0.5s transform transition carries it. */
    .zh-scrolled-down .zh-newstab {
        /* The 1px extra clears the border, which sits outside the width. */
        transform: translateY(-50%) translateX(calc(100% + 1px));
    }

    /* Three columns held in a nowrap row compress the claim to 228px at 800,
       setting it as four heavily ragged lines. */
    .zh-footer__top {
        flex-wrap: wrap;
    }

    /* Expanded gallery: the desktop version reserves a 112px left gutter for a
       vertical thumbnail column, which no longer fits. Full-width image with
       the thumbnails as a horizontal strip across the top instead. */
    .wp-block-post.is-expanded .wp-block-media-text__media {
        width: 100%;
        height: auto;
        padding-left: 0;
    }

    /* Cards all stack, so the + is bottom-right on every one of them (above) —
       and with the gutter gone the × has no left corner to hold either. */
    .wp-block-post-template > li:nth-child(even) .zh-pgallery__close {
        left: auto;
        right: 16px;
    }

    .zh-pgallery__thumbs {
        top: 12px;
        left: 12px;
        right: 12px;
        bottom: auto;
        width: auto;
        /* Explicit height: as a row the container would otherwise collapse to
           the natural height of thumbnails that have not decoded yet. */
        height: 44px;
        flex-direction: row;
        gap: 8px;
        overflow-x: auto;
        overflow-y: hidden;
        scrollbar-width: none;
    }

    .zh-pgallery__thumbs::-webkit-scrollbar {
        display: none;
    }

    .zh-pgallery__thumb {
        width: 64px;
        height: 44px;
    }

    .zh-pgallery__thumb img {
        height: 100%;
        object-fit: cover;
    }
}

/* The entrance animation's 100% keyframe carries the pill's desktop
   `padding-right: 32px`, and animation fill styles outrank normal author
   declarations — so the narrow breakpoint's `padding-right: 20px` could never
   apply, and the pill ended up 300px wide with 10px gutters at 320. Worse, the
   size then depended on an OS setting: `prefers-reduced-motion` disables the
   animation and produced the intended 288px. Give the narrow layout its own
   end state so both paths agree. */
@keyframes zh-menu-in-sm {
    0% {
        opacity: 0;
        transform: translateY(150%);
        max-width: 58px;
        padding-right: 6px;
    }
    40% {
        opacity: 1;
        transform: translateY(0);
        max-width: 58px;
        padding-right: 6px;
    }
    100% {
        opacity: 1;
        transform: translateY(0);
        max-width: min(480px, 100vw - 32px);
        padding-right: 20px;
    }
}

@media (max-width: 600px) {
    .zh-menu {
        animation-name: zh-menu-in-sm;
    }

    .entry-content > .wp-block-group.has-background {
        --zh-section-pad-bottom: 2.25em;
        padding-top: 2.25em;
        padding-bottom: var(--zh-section-pad-bottom);
        padding-left: 1.25em;
        padding-right: 1.25em;
    }

    .zh-hero__logo img {
        height: 4em;
    }

    /* Headings: the desktop scale (h2 3em = 54px, h3 1.8em = 32px) is set for
       a 1400px measure and turns into four- and five-line blocks on a phone.
       Class-carrying selectors so they out-specify theme.json's :root :where().
    ---------------------------------------- */
    .entry-content h2.wp-block-heading {
        font-size: 2.4em;
    }

    .wp-block-post-template .wp-block-post-title {
        font-size: 1.5em;
    }

    /* Once the bar wraps, the separator indent just looks like a stray offset. */
    .zh-filter-bar__broschuere {
        margin-left: 0;
    }

    /* 3.75em is 68px — 21% of a 320px screen, and out of step with the 24px
       gaps elsewhere in this header. The row-gap keeps the wrapped chips'
       enlarged hit areas from touching. */
    .zh-filter-bar {
        margin-top: 2em;
        row-gap: 12px;
    }

    /* Expanding a card at 320 made the image *smaller* (200px closed, 194px
       open) because the closed height sits on its 200px floor while the open
       one is the image's own ratio. */
    .wp-block-post.is-expanded .wp-block-media-text__media > img {
        min-height: 240px;
        object-fit: cover;
    }

    .zh-wirhero > .wp-block-group {
        padding: 40px 1.25em;
    }

    /* 20px copy inside a 275px column averaged ~22 characters a line, with two
       lines of 12. The 513px cap also stopped the sub 42px short of the
       headline's right edge. */
    .zh-wirhero__sub {
        font-size: 18px;
        line-height: 26px;
        max-width: none;
    }

    /* "Wer wir sind" breaks to two lines at 43.2px and fits on one at 42px.
       The class-carrying selector out-specifies the generic h2 rule above. */
    .entry-content h2.zh-werwirsind__title {
        font-size: min(2.4em, 13vw);
    }

    /* Wir: team
    ---------------------------------------- */
    /* The longest name ("Brigitte Sutter") is 289px at the desktop 36px and
       would break onto a second line at 320 while the shorter names stay on
       one — scale it so every card reads the same. The 30px ceiling keeps the
       names on one line from 300px of viewport up, where 10vw alone still let
       the longest one break. */
    .zh-team-card__name {
        font-size: min(30px, 10vw);
        line-height: 1.25;
    }


    .zh-cta {
        padding-top: 56px;
        padding-bottom: 44px;
    }

    .zh-footer {
        width: calc(100% - 24px);
        /* Longhands: the bottom value is the clearance for the floating pill
           set at the 900px breakpoint and must survive. */
        padding-top: 32px;
        padding-left: 20px;
        padding-right: 20px;
    }

    /* The legal links are a single paragraph separated by left margins; at this
       width they need to wrap as a column instead. */
    .zh-footer__legal a {
        margin: 0 20px 0 0;
    }

    /* Kontaktformular
    ---------------------------------------- */
    .zh-form {
        --zh-form-field-h: 44px;
        --zh-form-field-pad: 20px;
        --zh-form-row-gap: 18px;
        --zh-form-mark: 32px;
        font-size: 16px;
    }

    .zh-form textarea {
        height: 140px;
    }

    .zh-form p:not(:has(*)) {
        margin: 40px 0 16px;
    }

    /* Consent and Senden no longer share a line at this width. */
    .zh-form p:has(.wpcf7-submit) {
        display: block;
        margin-top: 32px;
    }

    .zh-form .wpcf7-submit {
        margin-top: 20px;
    }

    .zh-form__intro {
        margin-bottom: 32px;
        font-size: 16px;
    }

}
