/* ==========================================================================
   Section layouts
   ==========================================================================
   THE RULE: no rule in this file may set padding-top, padding-bottom,
   margin-top or margin-bottom on a <section>. Vertical space between
   sections comes from the "Space below" dropdown; vertical space inside a
   coloured section comes from --space-inset in style.css (above and below
   the content). Everything else uses the six
   tokens via gap, or the .stack-* helpers in style.css.

   One block per layout, in the same order as the branches in
   shortcodes/sections.php.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Structural rules that apply to every section, whatever the layout.
   -------------------------------------------------------------------------- */

/* The header sits over the top of the page, so the first section clears it
   plus a --space-128 gap. This is the one systemic exception to the
   no-vertical-padding rule. Full-bleed sections (.is-bleed) run underneath
   the header on purpose. */
.site-main > section:first-child:not(.is-bleed) {
    padding-top: calc(var(--header-height) + var(--space-128));
}

/* Bare containers outside the section system (index.php, the editor-only
   notices) clear the header the same way. */
.site-main > .container {
    padding-block: calc(var(--header-height) + var(--space-128)) var(--space-64);
}

/* A page without any sections still has to push the "Like to know more?"
   block below the header. */
.site-main:not(:has(> *)) {
    padding-top: var(--header-height);
}

/* Images in layouts fill their frame. */
.site-main figure img,
.site-main picture img { width: 100%; }

/* Parallax (gsap_scripts.js enlarges and moves the image): the frame clips
   it, so the image never grows past its box. */
.has-parallax :is(figure, .milestone_card-media, .banner-media) { overflow: hidden; }

/* Shared by the full-bleed layouts with text over a photo. */
.hero,
.banner {
    position: relative;
    overflow: hidden;
    color: var(--color-paper);
}

.hero-inner,
.banner-inner {
    position: absolute;
    inset-inline: 0;
    z-index: 1;
}

/* Edge-to-edge photos keep their frame's shape, but never grow taller than
   1.2 screen heights on wide, low screens; the photo is cropped instead. */
:is(.hero-img, .banner-img, .image--bleed .image-img) { max-height: 120vh; }

/* Vimeo video over a poster (dme_video_layer(); scripts.js adds the
   player). The layer covers its positioned, clipping frame; the player
   covers the layer at the video's own shape — --video-ratio, or on phones
   --video-ratio-mobile when a phone video is set — cropped like
   object-fit: cover, using container query units (the layer is a size
   container). It fades in once the video actually plays; until then, and
   with reduced motion or without JS, the poster shows. Used by the hero,
   the image banner, the edge-to-edge image and the Video element. */
.bg-video {
    --_video-ratio: var(--video-ratio, 1.7778);
    position: absolute;
    inset: 0;
    overflow: hidden;
    container-type: size;
    opacity: 0;
    transition: opacity 1.2s var(--ease);
    pointer-events: none;
}

.bg-video.is-playing { opacity: 1; }

/* Hero, image banner and edge-to-edge image with a background video: no
   photo at all (decided). The frame is a solid --color-ground-dark and the
   video fades in over it; if the video can't play (reduced motion, Low Power
   Mode, a refused domain) the frame simply stays that colour. The empty
   .bg-video-still block (dme_media_still()) carries the photo's class, so
   the frame keeps the photo's shape. */
.has-bg-video { background: var(--color-ground-dark); }

.bg-video-still {
    display: block;
    width: 100%;
}

.bg-video iframe {
    position: absolute;
    top: 50%;
    left: 50%;
    width: max(100cqw, calc(100cqh * var(--_video-ratio)));
    height: max(100cqh, calc(100cqw / var(--_video-ratio)));
    border: 0;
    transform: translate(-50%, -50%);
}

@media (max-width: 767px) {
    .bg-video { --_video-ratio: var(--video-ratio-mobile, var(--video-ratio, 1.7778)); }
}

/* --------------------------------------------------------------------------
   HERO
   -------------------------------------------------------------------------- */
.hero-img {
    aspect-ratio: 9 / 5;
    object-fit: cover;
}

/* The wordmark sits --hero-inner-bottom above the bottom of the photo. On
   wide, low screens the photo is taller than the screen (it may grow to
   120vh), so the distance grows by the part of the photo below the fold:
   the wordmark always stays inside the first screen. */
.hero-inner {
    --hero-inner-bottom: var(--space-64);
    bottom: max(var(--hero-inner-bottom), calc(100% - 100svh + var(--hero-inner-bottom)));
    display: flex;
    justify-content: flex-end;
}

/* With the light navigation over the photo, a mild shade at the top of the
   photo, fading to transparent, keeps the logo and menu readable. */
.hero-media { position: relative; }

.hero[data-header-style="dark"] .hero-media::after {
    content: "";
    position: absolute;
    inset: 0 0 auto;
    height: calc(var(--header-height) * 2);
    background: linear-gradient(to bottom, rgb(26 26 26 / 0.35), rgb(26 26 26 / 0));
    pointer-events: none;
}

/* With Media "Vimeo video" the frame is a solid colour until the video
   layer (.bg-video, above) plays; no photo (.has-bg-video, above). The top
   shade and the wordmark stay above the video. */

/* The wordmark only fades, slower than other reveals and without the glide
   upwards; the delay is set on the element (data-reveal-delay). */
.hero .hero-inner.sectionfadein { transform: none; }

.hero .hero-inner.sectionfadein.is-visible {
    transition: opacity 6s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

/* 15% smaller than first set on larger screens (asked for): at most 61px
   instead of 72px. Phones keep 56px (below). */
.hero-wordmark {
    font-size: clamp(2.975rem, 4.25vw, 3.825rem);
    border-color: currentColor;
}

@media (max-width: 767px) {
    .hero-img { aspect-ratio: 385 / 733; }

    .hero-wordmark { font-size: 3.5rem; }

    .hero-inner {
        /* Two steps above the bottom, then 62px lower (asked for: 30px,
           then 32px more). */
        --hero-inner-bottom: calc(var(--space-128) * 2 - 62px);
        justify-content: center;
    }
}

/* --------------------------------------------------------------------------
   PAGE TITLE
   -------------------------------------------------------------------------- */
.page_title-inner {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
}

.page_title-inner > .label + * { margin-top: var(--space-16); }
.page_title-inner > .label + .title { margin-top: var(--space-32); }

/* Very large capitals sit a full step below their label (asked for: 64px
   between e.g. MILESTONES and THE JOURNEY). */
.page_title-inner > .label + .display { margin-top: var(--space-64); }

.page_title-title.title { max-width: 50rem; }

/* A milestone's title is set 20% smaller than the sentence style elsewhere. */
.single-milestone .page_title-title.title { font-size: clamp(2rem, 4vw, 3.6rem); }

/* On a milestone, a very subtle line as wide as the text column sits halfway
   down the gap below the title (its Space below, --space-below from
   style.css), so it adds no space of its own. Not when the title is the
   last section. */
.single-milestone .page_title:not(:last-of-type) { position: relative; }

.single-milestone .page_title:not(:last-of-type)::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    bottom: calc(var(--space-below, 0px) / -2);
    width: min(var(--text-column), 100% - 2 * var(--container-pad));
    height: 1px;
    margin-inline: auto;
    background: var(--color-line-soft);
}

@media (max-width: 767px) {
    .single-milestone .page_title:not(:last-of-type)::after {
        width: calc(100% - 2 * var(--container-pad-mobile));
    }
}

/* A project's name is set 28% smaller than the display style elsewhere
   (10%, then 20% more): at most 93.6px. On phones a long single word
   ("Sarphatistraat" is 8.5em wide in capitals) could run off the screen,
   so on phones the size scales with the screen width: 65px on a 430px
   iPhone Pro Max, proportionally smaller below (asked for), never more
   than 65px. scripts.js shrinks a title whose longest word still doesn't
   fit the screen. */
.single-project .page_title-title.display { font-size: clamp(2.52rem, 6.48vw, 5.85rem); }

/* A project's title arrives slowly: after a short pause (data-reveal-delay)
   a long, soft fade with a shorter glide upwards. */
.single-project .page_title-inner.sectionfadein { transform: translateY(var(--space-16)); }

.single-project .page_title-inner.sectionfadein.is-visible {
    transform: none;
    transition:
        opacity 3.2s cubic-bezier(0.25, 0.46, 0.45, 0.94),
        transform 3.6s cubic-bezier(0.16, 1, 0.3, 1);
}

@media (max-width: 767px) {
    .single-project .page_title-title.display {
        font-size: min(4.0625rem, calc(100vw * 65 / 430));
    }
}

/* --------------------------------------------------------------------------
   INTRO
   -------------------------------------------------------------------------- */
.intro--column .intro-inner {
    max-width: var(--text-column);
    margin-inline: auto;
}

.intro--left .intro-inner { max-width: 42.5rem; }

.intro-inner > .label + * { margin-top: var(--space-16); }

/* --------------------------------------------------------------------------
   TEXT COLUMN
   -------------------------------------------------------------------------- */
.rich_text-inner {
    max-width: var(--text-column);
    margin-inline: auto;
}

/* --------------------------------------------------------------------------
   IMAGE + TEXT
   Beside: a 770px portrait and a 500px text column; the text starts a step
   below the top of the image. Above: a wide content-width image with the
   text below it.
   -------------------------------------------------------------------------- */
.image_text-grid {
    display: grid;
    column-gap: var(--gutter);
    align-items: start;
}

.image_text--image-left .image_text-grid  { grid-template-columns: minmax(0, 77fr) minmax(0, 50fr); }
.image_text--image-right .image_text-grid { grid-template-columns: minmax(0, 50fr) minmax(0, 77fr); }

.image_text--image-above .image_text-grid {
    grid-template-columns: minmax(0, 1fr);
    row-gap: var(--space-32);
}

.image_text-figure img {
    aspect-ratio: 5 / 7;
    object-fit: cover;
}

.image_text--image-above .image_text-figure img { aspect-ratio: 1312 / 922; }

.image_text-body {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    margin-top: var(--space-48);
}

.image_text--image-above .image_text-body {
    max-width: 53.125rem; /* 850px (was 600px) */
    margin-top: 0;
}

/* Beside the image, the text stays in view while the image scrolls past:
   it sticks at the same distance from the top as the services scroll text
   (clear of the compact header bar) and stops at the bottom of the image,
   the end of its grid row. Text too tall for the screen gets a smaller
   --sticky-top from scripts.js, so it sticks once its last line is in view.
   Phones stack, so no sticking there. */
@media (min-width: 768px) {
    .image_text--image-left .image_text-body,
    .image_text--image-right .image_text-body {
        position: sticky;
        top: var(--sticky-top, var(--space-128));
    }
}

.image_text-body > * + * { margin-top: var(--space-16); }
.image_text-body > .heading + * { margin-top: var(--space-8); }

@media (max-width: 767px) {
    .image_text .image_text-grid {
        grid-template-columns: minmax(0, 1fr);
        row-gap: var(--space-stack-mobile);
    }

    .image_text .image_text-body { margin-top: 0; }
    .image_text .image_text-figure img { aspect-ratio: 5 / 8; }
}

/* --------------------------------------------------------------------------
   TEXT + STAGGERED IMAGES
   -------------------------------------------------------------------------- */
.text_images-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--gutter);
    align-items: start;
}

/* The text starts a step below the top of the right-hand image. */
.text_images-main { margin-top: var(--space-32); }

.text_images-body {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    max-width: 32rem;
}

.text_images-body > * + * { margin-top: var(--space-16); }
.text_images-body > .heading + * { margin-top: var(--space-8); }

/* Body text at the body size (21px), like every other layout. */
.text_images-text { line-height: 1.45; }

.text_images-figure img {
    aspect-ratio: 2 / 3;
    object-fit: cover;
}

.text_images-figure--one { margin-top: var(--space-64); }

@media (max-width: 767px) {
    .text_images-grid { grid-template-columns: minmax(0, 1fr); }
    .text_images-main { margin-top: 0; }
    .text_images-figure--one { margin-top: var(--space-stack-mobile); }
    .text_images-figure--one img { aspect-ratio: 5 / 8; }
    .text_images-figure--two { display: none; }
}

/* --------------------------------------------------------------------------
   TWO IMAGES
   -------------------------------------------------------------------------- */
.two_images-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--gutter);
}

/* Text column width: the pair lines up with the text column, e.g. between
   the paragraphs of a milestone. */
.two_images--column .two_images-grid {
    max-width: var(--text-column);
    margin-inline: auto;
}

.two_images-figure img {
    aspect-ratio: 2 / 3;
    object-fit: cover;
}

@media (max-width: 767px) {
    .two_images-grid {
        grid-template-columns: minmax(0, 1fr);
        row-gap: var(--space-64);
    }

    .two_images-figure img { aspect-ratio: 5 / 8; }
}

/* --------------------------------------------------------------------------
   IMAGE
   Edge to edge it is cropped to the design's frame (landscape on desktop,
   tall on phones); at content or column width the photo keeps its shape.
   -------------------------------------------------------------------------- */
.image--bleed .image-img {
    aspect-ratio: 16 / 10;
    object-fit: cover;
}

/* The frame for Media "Vimeo video" (.bg-video). */
.image--bleed .image-figure { position: relative; }

.image--column .image-figure {
    max-width: var(--text-column);
    margin-inline: auto;
}

@media (max-width: 767px) {
    .image--bleed .image-img { aspect-ratio: 385 / 800; }
}

/* --------------------------------------------------------------------------
   IMAGE BANNER
   -------------------------------------------------------------------------- */
.banner-media { position: relative; }

/* Darken the photo, most towards the text at the bottom. */
.banner-media::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(to bottom, rgb(26 26 26 / 0.12) 40%, rgb(26 26 26 / 0.45));
    pointer-events: none;
}

.banner-img {
    aspect-ratio: 16 / 10;
    object-fit: cover;
}

.banner-inner {
    bottom: var(--space-64);
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
}

.banner-title {
    font-family: var(--font-serif);
    font-size: clamp(2.5rem, 3.4vw, 3rem);
    line-height: 1.1;
    text-transform: uppercase;
}

.banner-text {
    font-size: clamp(1.375rem, 2vw, 1.75rem);
    line-height: 1.3;
}

.banner-inner .btn { margin-top: var(--space-32); }

@media (max-width: 767px) {
    .banner-img { aspect-ratio: 385 / 800; }
    .banner-inner { bottom: var(--space-128); }
}

/* --------------------------------------------------------------------------
   VIDEO
   A Vimeo video at its own shape (a phone video can have another), edge to
   edge or at content width. A still image fills the frame until the video
   plays (.bg-video fades in over it). Edge to edge the frame never grows
   taller than 1.2 screen heights, like the edge-to-edge photos; the video
   is cropped instead. A subtle round pause/play button sits bottom right
   and appears once the video plays (or straight away, as play, with
   reduced motion).
   -------------------------------------------------------------------------- */
.video-frame {
    position: relative;
    overflow: hidden;
    aspect-ratio: var(--video-ratio, 16 / 9);
    background: var(--color-dark);
}

.video--bleed .video-frame { max-height: 120vh; }

.video-poster {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.video-toggle {
    position: absolute;
    right: var(--space-16);
    bottom: var(--space-16);
    z-index: 2;
    display: grid;
    place-items: center;
    width: 40px;
    height: 40px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: rgb(26 26 26 / 0.35);
    color: var(--color-paper);
    cursor: pointer;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.4s var(--ease), background-color 0.3s var(--ease);
}

.video-frame:is(.has-video, .is-waiting) .video-toggle {
    opacity: 0.75;
    pointer-events: auto;
}

.video-frame .video-toggle:is(:hover, :focus-visible) {
    opacity: 1;
    background: rgb(26 26 26 / 0.6);
}

.video-toggle:focus-visible {
    outline: 2px solid var(--color-paper);
    outline-offset: 2px;
}

.video-toggle-icon {
    width: 14px;
    height: 14px;
    fill: currentColor;
}

.video-toggle-icon--play,
.video-frame.is-paused .video-toggle-icon--pause { display: none; }

.video-frame.is-paused .video-toggle-icon--play { display: block; }

/* Edge to edge the button lines up with the content's margin. */
.video--bleed .video-toggle {
    right: var(--container-pad);
    bottom: var(--space-32);
}

@media (max-width: 767px) {
    .video-frame { aspect-ratio: var(--video-ratio-mobile, var(--video-ratio, 16 / 9)); }

    .video--bleed .video-toggle { right: var(--container-pad-mobile); }
}

/* --------------------------------------------------------------------------
   DETAILS
   -------------------------------------------------------------------------- */
/* The heading and the lines use the Listing's classes (.listing-heading,
   .listing-items, see LISTING) so both always look the same; here the rule
   runs across the whole content width, and the statement follows 64px
   below. */
.details-inner {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
}

.details-heading { align-self: stretch; }
.details-heading + .details-list { margin-top: var(--space-16); }

.details-row a:hover { text-decoration: underline; }

.details-statement { max-width: var(--text-column); }

.details-heading + .details-statement,
.details-list + .details-statement { margin-top: var(--space-64); }

/* --------------------------------------------------------------------------
   LISTING
   Design: two columns a gutter apart, each a stack of lists 48px apart; a
   list is a letter-spaced capital heading in the sans, a thin rule 16px
   below it and the items 16px below that. Phones stack the columns, left
   first, and drop an empty column.
   -------------------------------------------------------------------------- */
.listing-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--gutter);
    row-gap: var(--space-48);
    align-items: start;
}

.listing-column,
.listing-group {
    display: flex;
    flex-direction: column;
}

.listing-column { gap: var(--space-48); }
.listing-group  { gap: var(--space-16); }

.listing-heading {
    display: flex;
    flex-direction: column;
    gap: var(--space-16);
    margin: 0;
    font-family: var(--font-sans);
    font-size: 1rem;
    font-weight: 400;
    line-height: 1;
    letter-spacing: 0.15em;
    text-transform: uppercase;
}

/* The thin rule under the heading, as wide as the column. */
.listing-heading::after {
    content: "";
    height: 1px;
    background: var(--color-rule);
}

.listing-items {
    margin: 0;
    padding: 0;
    list-style: none;
    font-family: var(--font-sans);
    font-size: 1rem;
    font-weight: 300;
    line-height: 1.8;
}

[element-theme="2"] .listing-heading::after { background: var(--color-line-dark); }

@media (max-width: 767px) {
    .listing-grid { grid-template-columns: minmax(0, 1fr); }
    .listing-column:not(:has(.listing-group)) { display: none; }
}

/* --------------------------------------------------------------------------
   CONTACT DETAILS
   Design: a 2:3 image and the text column side by side, 64px apart.
   -------------------------------------------------------------------------- */
.contact_details-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--space-64);
    align-items: start;
}

.contact_details-figure { grid-column: 1; grid-row: 1; }

.contact_details-body {
    grid-column: 2;
    grid-row: 1;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
}

.contact_details-figure img {
    aspect-ratio: 2 / 3;
    object-fit: cover;
}

.contact_details-body > .label + * { margin-top: var(--space-16); }

.contact_details-blocks {
    display: flex;
    flex-direction: column;
    gap: var(--space-48);
    margin-top: var(--space-64);
}

.contact_details-block .heading + * { margin-top: var(--space-8); }

/* The credits line: below the image, in the small light sans of the footer
   credit. */
.contact_details-credits {
    grid-column: 1;
    grid-row: 2;
    margin-top: var(--space-64);
    font-family: var(--font-sans);
    font-size: 0.9375rem; /* 15px, a bit bigger than the 13px first set */
    font-weight: 300;
}

.contact_details-credits a { text-decoration: underline; text-underline-offset: 0.15em; }
.contact_details-credits a:hover { text-decoration: none; }
.contact_details-lines a:hover { text-decoration: underline; }

@media (max-width: 767px) {
    .contact_details-grid { grid-template-columns: minmax(0, 1fr); }

    .contact_details-body { grid-column: 1; }

    .contact_details-figure {
        grid-row: 2;
        margin-top: var(--space-stack-mobile);
        margin-inline: calc(-1 * var(--container-pad-mobile));
    }

    .contact_details-figure img { aspect-ratio: 385 / 800; }

    .contact_details-credits { grid-row: 3; }
}

/* --------------------------------------------------------------------------
   PROJECTS GRID
   Cards share the banner's type and darkening; the whole card is the link.
   -------------------------------------------------------------------------- */
.projects_grid-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--gutter);
    row-gap: var(--space-32);
}

.project_card {
    position: relative;
    display: block;
    overflow: hidden;
    color: var(--color-paper);
}

.project_card-media { position: relative; }

.project_card-media::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(to bottom, rgb(26 26 26 / 0.1) 40%, rgb(26 26 26 / 0.45));
    pointer-events: none;
}

.project_card-img {
    aspect-ratio: 2 / 3;
    object-fit: cover;
}

/* The hover zoom works on the frame, so it never fights the parallax that
   moves the image inside it. */
.project_card-media { transition: transform 0.8s var(--ease); }
.project_card:hover .project_card-media { transform: scale(1.03); }

.project_card-body {
    position: absolute;
    inset-inline: 0;
    bottom: var(--space-64);
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding-inline: var(--space-32);
    text-align: center;
}

/* Every card lines up (asked for): the name on one line, the introduction
   in a fixed four-line area — longer text ends in "…", shorter or no text
   leaves the rest empty — and the button right below it. So names, texts
   and buttons sit at the same height on every card. */
.project_card-title {
    max-width: 100%;
    overflow: hidden;
    font-family: var(--font-serif);
    font-size: clamp(2.5rem, 3.4vw, 3rem);
    line-height: 1.1;
    text-overflow: ellipsis;
    text-transform: uppercase;
    white-space: nowrap;
}

.project_card-text {
    display: -webkit-box;
    height: calc(4 * 1.15em);
    max-width: 29rem;
    overflow: hidden;
    font-size: clamp(1.125rem, 2vw, 1.75rem);
    line-height: 1.15;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 4;
    line-clamp: 4;
}

.project_card-button { margin-top: var(--space-32); }

.project_card:is(:hover, :focus-visible) .project_card-button {
    background: var(--color-paper);
    border-color: var(--color-paper);
    color: var(--color-dark);
}

@media (max-width: 767px) {
    .projects_grid-grid {
        grid-template-columns: minmax(0, 1fr);
        row-gap: var(--space-16);
    }

    .project_card-img { aspect-ratio: 5 / 8; }
}

/* --------------------------------------------------------------------------
   MILESTONES LIST
   Two columns with rows aligned at the top; each photo keeps its own shape,
   which gives the staggered look. The featured item spans both columns.
   -------------------------------------------------------------------------- */
.milestones_list-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--space-64);
    row-gap: var(--space-128);
    align-items: start;
}

.milestone_card {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
}

.milestone_card-media { display: block; width: 100%; }

.milestone_card-body {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    margin-top: var(--space-64);
}

.milestone_card-body > * + * { margin-top: var(--space-16); }
.milestone_card-body > .btn { margin-top: var(--space-32); }

.milestone_card--featured {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: minmax(0, 772fr) minmax(0, 476fr);
    column-gap: var(--space-64);
    align-items: start;
}

.milestone_card--featured .milestone_card-body { margin-top: 0; }

.milestone_card--featured .milestone_card-img {
    aspect-ratio: 772 / 551;
    object-fit: cover;
}

@media (max-width: 767px) {
    .milestones_list-grid { grid-template-columns: minmax(0, 1fr); }

    .milestone_card--featured {
        display: flex;
        flex-direction: column;
    }

    .milestone_card .milestone_card-body,
    .milestone_card--featured .milestone_card-body { margin-top: var(--space-stack-mobile); }
}

/* --------------------------------------------------------------------------
   SERVICES SCROLL
   Desktop: 635×900 portraits stacked in the right column, 128px apart (the
   gap is reading time for the text beside each image, see scripts.js). All
   texts share one sticky cell in the left column that spans every image
   row; only the active one is visible (scripts.js sets .is-active).
   Mobile: each image followed by its own text, all visible.
   -------------------------------------------------------------------------- */
.services_scroll-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--gutter);
    row-gap: var(--space-128);
    align-items: start;
}

.services_scroll-figure {
    grid-column: 2;
    grid-row: var(--row-desktop);
}

.services_scroll-figure img {
    aspect-ratio: 635 / 900;
    object-fit: cover;
}

.services_scroll-text {
    grid-column: 1;
    grid-row: 1 / span var(--count);
    align-self: start;
    position: sticky;
    top: var(--space-128);
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    max-width: 32rem;
    margin-top: var(--space-64);
    opacity: 0;
    visibility: hidden;
    transform: translateY(var(--space-16));
    /* Leaving: fade out quickly, then hide. */
    transition:
        opacity 0.4s var(--ease),
        transform 0.4s var(--ease),
        visibility 0s linear 0.4s;
}

/* Texts of images already scrolled past leave upwards; the others wait
   below, so the movement follows the scroll direction. */
.services_scroll-text.is-past { transform: translateY(calc(-1 * var(--space-16))); }

/* Arriving: wait until the previous text has faded out, then fade up. One
   text at a time, never two overlapping. */
.services_scroll-text.is-active {
    opacity: 1;
    visibility: visible;
    transform: none;
    transition:
        opacity 0.7s var(--ease) 0.5s,
        transform 0.7s var(--ease) 0.5s,
        visibility 0s linear 0.5s;
}

/* Desktop with JS: scripts.js sets each text's opacity, visibility and
   movement straight from the image positions while scrolling (.is-scrubbed),
   so the classes' timed fades above are switched off. */
.services_scroll-grid.is-scrubbed .services_scroll-text { transition: none; }

.services_scroll-text > * + * { margin-top: var(--space-16); }
.services_scroll-text > .btn { margin-top: var(--space-32); }

/* Slideshow on scroll (desktop; gsap_scripts.js adds .is-slideshow and pins
   the section). Every text shares the left cell and every image the right
   one. The texts keep their fades from above; the active image fades in
   over the previous one, which drops out only once it is covered. The
   slide fits the screen while pinned. */
.services_scroll-grid.is-slideshow .services_scroll-text {
    grid-row: 1;
    position: relative;
    top: auto;
}

/* The pinned section is solid and sits above the page, so if a fast scroll
   ever gets ahead of the pin, nothing around it shows through or over it.
   Sections with their own background keep it. */
.services_scroll:has(> .is-slideshow) {
    z-index: 2;
    background: var(--color-bg);
}

.services_scroll.bg-beige:has(> .is-slideshow) { background: var(--color-beige); }
[element-theme="2"].services_scroll:has(> .is-slideshow) { background: var(--color-dark); }

/* One service at a time, text and image together: the outgoing pair fades
   out (0.6s), then after a short empty beat the next pair fades in (1s). */
.services_scroll-grid.is-slideshow .services_scroll-text {
    transition:
        opacity 0.6s var(--ease),
        transform 0.6s var(--ease),
        visibility 0s linear 0.6s;
}

.services_scroll-grid.is-slideshow .services_scroll-text.is-active {
    transition:
        opacity 1s var(--ease) 0.8s,
        transform 1s var(--ease) 0.8s,
        visibility 0s linear 0.8s;
}

.services_scroll-grid.is-slideshow .services_scroll-figure {
    grid-row: 1;
    opacity: 0;
    transform: none;
    transition: opacity 0.6s var(--ease);
}

.services_scroll-grid.is-slideshow .services_scroll-figure.is-active {
    z-index: 1;
    opacity: 1;
    transition: opacity 1s var(--ease) 0.8s;
}

.services_scroll-grid.is-slideshow .services_scroll-figure img {
    max-height: calc(100vh - 2 * var(--space-64));
}

@media (max-width: 767px) {
    .services_scroll-grid {
        grid-template-columns: minmax(0, 1fr);
        row-gap: 0;
    }

    .services_scroll-figure {
        grid-column: 1;
        grid-row: var(--row-mobile);
    }

    .services_scroll-figure--later { margin-top: var(--space-128); }

    .services_scroll-figure img { aspect-ratio: 7 / 5; }

    /* Doubled class so it outranks .is-past / .is-active above. */
    .services_scroll .services_scroll-text {
        grid-column: 1;
        grid-row: var(--row-mobile);
        position: static;
        margin-top: var(--space-stack-mobile);
        opacity: 1;
        visibility: visible;
        transform: none;
        transition: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    .services_scroll .services_scroll-text {
        transform: none;
        transition: none;
    }
}

/* --------------------------------------------------------------------------
   STATEMENT
   A screen-high block with its content centred both ways.
   -------------------------------------------------------------------------- */
.statement_block {
    position: relative;
    display: grid;
    align-items: center;
    min-height: 100svh;
}

.statement_block-inner {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
}

.statement_block-inner > * + * { margin-top: var(--space-32); }
.statement_block-inner > .statement_block-frame + * { margin-top: var(--space-64); }

.statement_block-frame { width: 202px; }

.statement_block-figure img {
    aspect-ratio: 3 / 4;
    object-fit: cover;
}

/* Scroll animation (gsap_scripts.js adds .is-zoom and pins the section).
   The frame keeps the image's final size in the layout; the figure is
   lifted out of it, starts covering the whole section and is scaled down
   into the frame. The image keeps covering its box, so it never
   stretches. */
.statement_block.is-zoom .statement_block-frame { aspect-ratio: 3 / 4; }

.statement_block.is-zoom .statement_block-figure {
    position: absolute;
    z-index: 1;
    margin: 0;
    overflow: hidden;
}

.statement_block.is-zoom .statement_block-figure img {
    height: 100%;
    aspect-ratio: auto;
}

.statement_block-text { max-width: 43.75rem; /* 700px */ }

@media (max-width: 767px) {
    .statement_block-text { line-height: 1.43; }
}

/* ==========================================================================
   Previous / next (template-parts/post-nav.php)
   ==========================================================================
   Printed after the sections on single projects and milestones. The last
   section has no "Space below", so the gap above comes from here.
   Desktop: arrow + label at both ends. Mobile: arrows at the edges with a
   short label in the middle.
   ========================================================================== */
.site-main > .post-nav { margin-top: var(--space-128); }
.site-main > .post-nav:first-child { padding-top: var(--header-height); }

.post-nav-inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-32);
    font-family: var(--font-sans);
    font-weight: 300;
    font-size: 1rem;
    line-height: 1.3;
}

.post-nav-link {
    display: inline-flex;
    align-items: center;
    gap: var(--space-16);
    transition: opacity 0.3s var(--ease);
}

.post-nav-link:hover { opacity: 0.6; }

.post-nav-arrow {
    flex-shrink: 0;
    width: 34px;
    height: 12px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1;
}

.post-nav-mobile { display: none; }

@media (max-width: 767px) {
    .post-nav-inner { font-weight: 400; }
    .post-nav-label { display: none; }
    .post-nav-mobile { display: block; }
}
