/* ---------------------------------------------------------------
   HEADER BREAKPOINT — single source of truth.

   1024px. Above it: the three-column desktop header (nav split
   either side of the centred wordmark) and Barba page transitions.
   Below it: the mobile menu trigger and full page loads.

   Chosen because the desktop header's fixed content — 350px
   wordmark, two nav groups, 2.5rem padding — physically collapses
   at roughly 810px, and 1024 lands on iPad landscape, so portrait
   tablets get touch-sized navigation.

   CSS cannot read a custom property inside a media query
   condition, so 1024 is repeated literally below. If you change
   this value, change it in all three places:
     1. --bp-header, here
     2. the "HEADER BREAKPOINT" @media block further down this file
     3. nothing else — footer.php reads it from here via JS

   Page content (galleries, .show-when-small, body overflow) keeps
   its own 700px breakpoint. Do not merge the two.
   --------------------------------------------------------------- */
:root {
    --bp-header: 1024px;

    /* Header light/dark switch. One number, referenced everywhere the
       switch touches, so the wordmark, the nav and the mobile trigger
       cannot drift apart. Was 600ms in three separate places.

       The mobile panel keeps its own 600ms — see the note on
       .is-menu-open .site-header. */
    --header-fade: 400ms;
    --ease-header: cubic-bezier(0.25, 0.1, 0.25, 1);
}

html,
html a {
    -webkit-font-smoothing: antialiased !important;
}
* {
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
}
body{
    background-color: #F9F8F2;
    color:#101014;
    height:100vh;
    overflow:hidden;
    transition: background-color 600ms cubic-bezier(0.25, 0.1, 0.25, 1), cursor 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
    -o-transition: background-color 600ms cubic-bezier(0.25, 0.1, 0.25, 1), cursor 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
    -ms-transition: background-color 600ms cubic-bezier(0.25, 0.1, 0.25, 1), cursor 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
    -moz-transition: background-color 600ms cubic-bezier(0.25, 0.1, 0.25, 1), cursor 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
    -webkit-transition: background-color 600ms cubic-bezier(0.25, 0.1, 0.25, 1), cursor 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
    will-change: background-color, cursor;
    transition-delay: 500ms;
}

img, video {
   width: 100%; height: auto;
}

.splide__spinner{
    display:none !important;
}

.logo-path{
    transition: stroke 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
    -o-transition: stroke 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
    -ms-transition: stroke 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
    -moz-transition: stroke 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
    -webkit-transition: stroke 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
    will-change: stroke;
    transition-delay: 500ms;
    stroke:#000000;
    fill:none;
}

.text-path{
    transition: fill 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
    -o-transition: fill 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
    -ms-transition: fill 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
    -moz-transition: fill 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
    -webkit-transition: fill 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
    will-change: fill;
    transition-delay: 500ms;
    fill:#000000;
}

.menu-item, .menu-item a, a, h1, .mobile-header .menu-item a, .sub-menu-container .sub-menu a, #hideMenu a{
    transition: color 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
-o-transition: color 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
-ms-transition: color 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
-moz-transition: color 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
-webkit-transition: color 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
transition-delay: 500ms;
}

#headerBackground, #mobileheaderBackground, .bottom-menu{
    transition: background 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
-o-transition: background 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
-ms-transition: background 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
-moz-transition: background 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
-webkit-transition: background 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
will-change: background;
}

#mobile-logo-bg {
   position: fixed;
   display: none;
   width: 100%; height: 140px;
   top: 0; left: 0;
   z-index: 4;
   background: rgb(239,237,228);
   background: linear-gradient(180deg, rgba(239,237,228,1) 0%, rgba(239,237,228,1) 45%, rgba(239,237,228,0.6) 70%, rgba(239,237,228,0) 100%);
   pointer-events: none;
}

@media only screen and (max-width:700px){
   #mobile-logo-bg {
      display: block;
   }
}

a{
    text-decoration: none;
}

.show-when-small{
    display:none;
}

.hide-when-small{
    display:block;
}

.fade-in-title{
    transition-delay: 500ms !important;
}

.fade-in-text{
    transition-delay: 750ms !important;
}

.fade-in-slider{
    transition-delay: 750ms !important;
}

.bottom-logo-container, .animation-container, .logo-container, .fade-in-credit{
    transition-delay: 1000ms !important;
}

.fade-in-overlay{
    transition: opacity 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
    -o-transition: opacity 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
    -ms-transition: opacity 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
    -moz-transition: opacity 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
    -webkit-transition: opacity 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
    will-change: opacity;
    transition-delay: 1000ms !important;
}

.fade-in-logo{
    transition-delay: 500ms !important;
    transition: opacity 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
    -o-transition: opacity 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
    -ms-transition: opacity 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
    -moz-transition: opacity 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
    -webkit-transition: opacity 600ms cubic-bezier(0.25, 0.1, 0.25, 1);
    will-change: opacity;
}

.fullscreen{
    height:100vh;
}

.fade-in {
    pointer-events: none;
    transition: opacity 1000ms ease-in-out;
    -o-transition: opacity 1000ms ease-in-out;
    -ms-transition: opacity 1000ms ease-in-out;
    -moz-transition: opacity 1000ms ease-in-out;
    -webkit-transition: opacity 1000ms ease-in-out;
    will-change: opacity;
    opacity: 0;
}

.fade-in.visible {
    opacity: 1;
    pointer-events: all;
}

/* FONTS */

/* The clamp had its floor and ceiling the wrong way round: min 2.25rem,
   max 1.5rem. clamp() resolves to the minimum whenever max < min, so this
   rendered a flat 36px at every width and the fluid scale never ran.

   Swapped back, which is what the preferred value was always solving for —
   1.2214rem + 1.1429vw is exactly 1.5rem at a 390px viewport and exactly
   2.25rem at 1440px. Nothing at or above 1440 changes; below it the h1 now
   scales down to 24px on a phone instead of holding at 36px. Visible on
   404, page-text, page-services and page-about — the only templates with an
   unstyled h1. Every P4 template sets its own size.

   line-height moves from 2.625rem to the unitless equivalent at the top of
   the range (42/36), or a fixed 42px would sit at 1.75 against a 24px face.
   The min-width:1920px block below overrides both in vw and is untouched. */
h1{
    font-family:'Orleans Roman';
    font-size: clamp(1.5rem, 1.2214rem + 1.1429vw, 2.25rem);
    font-weight:400;
    line-height: 1.1667;
    padding-bottom:3rem;
    color:#000000;
}

h2{
    font-family:'Orleans Light';
    font-size: 1.25rem;
    line-height:clamp(1.75rem, 1.9214rem + -0.1905vw, 1.875rem);
    font-weight:400;
}

h3{
    font-family:'Orleans Roman';  
    font-weight:400;
    font-size:1rem;
    line-height:1.5rem;
}

h4{
    font-family:'Orleans Roman'; 
    font-weight:400; 
    font-size:0.75rem;
}

body{
    font-family:'Orleans Light';
    font-size: 0.875rem;
    line-height:clamp(1.375rem, 1.5464rem + -0.1905vw, 1.5rem);
    font-weight:400;
}

p{
    font-family:'Orleans Light';
    font-size: 0.875rem;
    line-height:clamp(1.375rem, 1.5464rem + -0.1905vw, 1.5rem);
    margin-bottom:1rem;
    font-weight:400;
}

p:last-child{
    margin-bottom:0px;
}


/* MENU */

.header{
    display:flex;
    justify-content:space-between;
    align-items: center;
    padding:2.5rem;
    position:fixed;
    width:100%;
    top:0;
    left:0;
    z-index:3;
}

.menu, .sub-menu{
    list-style:none;
}

.sub-menu li{
    opacity:0;
    transition: opacity 200ms cubic-bezier(0.25, 0.1, 0.25, 1);
    -o-transition: opacity 200ms cubic-bezier(0.25, 0.1, 0.25, 1);
    -ms-transition: opacity 200ms cubic-bezier(0.25, 0.1, 0.25, 1);
    -moz-transition: opacity 200ms cubic-bezier(0.25, 0.1, 0.25, 1);
    -webkit-transition: opacity 200ms cubic-bezier(0.25, 0.1, 0.25, 1);
    will-change: opacity;
    white-space: nowrap;
}

.sub-menu{
    display:none;
    position:absolute;
    width: fit-content;
    right: 0;
    padding-bottom:100px;
    transition: opacity 600ms ease-in-out;
    -o-transition: opacity 600ms ease-in-out;
    -ms-transition: opacity 600ms ease-in-out;
    -moz-transition: opacity 600ms ease-in-out;
    -webkit-transition: opacity 600ms ease-in-out;
    will-change: opacity;
}

.sub-menu-container, .mobile-header{
    transition: opacity 600ms ease-in-out;
    -o-transition: opacity 600ms ease-in-out;
    -ms-transition: opacity 600ms ease-in-out;
    -moz-transition: opacity 600ms ease-in-out;
    -webkit-transition: opacity 600ms ease-in-out;
    will-change: opacity;
}

#menu-item-103{
    position:relative;
}

.inner-header{
    width:50%;
}

.inner-header-left svg {
    max-width: 350px;
    width: 100%;
    height: auto;
}

.inner-header-center{
    display:flex;
    justify-content: center;
}

#menu-main-menu{
    display:flex;
    justify-content: flex-end;
    gap:3.75rem;
}

.logo{
    width:12.875rem;
    height:auto;
    object-fit:contain;
}

.menu-item{
    font-family:'Orleans Roman';  
    color:#bcb5ac;
    text-decoration:none;
    line-height:1;
}

.menu-item a{
    font-family:'Orleans Roman';  
    color:#bcb5ac;
    text-decoration:none;
    font-size: 0.875rem;
}

.bottom-menu{
    position:fixed;
    bottom:0;
    left:0;
    width:100%;
    z-index:5;
}


/* ===============================================================
   P4 HEADER — SOW 2
   ===============================================================

   Only rendered when a menu is assigned to the P4 Header Menu
   location, so none of this affects live until Stage 5.

   Values measured from Sophie's comps (5120px exports of a 2560px
   frame, so design px / 2). Recorded here rather than in a commit
   message so the next developer can check the build against the
   design without re-measuring:

     wordmark width      730 ->  365px
     nav item gap        273 ->  136px  (8.5rem)
     side padding        ---     see note below
     header padding-top   65 ->   32px  (2rem)
     nav type             --  -> 21.3px at the 2560 frame — see note
     nav baseline lift    18 ->    9.2px above the wordmark baseline
     colour on Home           #ffffff, sampled from the comp
     colour elsewhere         #000000, sampled from the comp

   Note on nav type: the earlier figure here was 18px, taken by
   measuring ink height and reading it as the face size. Orleans
   Roman has a 750/1000 cap with a -10 overshoot, so the ink of a
   cap-height word is 0.77em, not 1em. Re-measured against the real
   metrics, three words agree:

     Studio    33 raw ink / 0.780 em ratio -> 21.15px
     Contact   33 raw ink / 0.770 em ratio -> 21.40px
     Projects  42 raw ink / 0.984 em ratio -> 21.30px  (j descender)

   So 21.3px at 2560 = 0.832vw, not the 0.729vw the theme uses for
   body copy above 1920. Set below via --nav-font-size.

   Note on the baseline: the comp puts the nav baseline 9.2px ABOVE
   the wordmark baseline at the 2560 frame — 0.432em of the nav
   face. align-items: baseline alone sits them level; align-items:
   center overshoots the other way by ~7.5px. Neither matches, so
   the lift is applied explicitly on .site-nav.

   Note on side padding: the comp has 99px on the left and 39px on
   the right. The right matches the theme's existing 2.5rem, the
   left does not, and the whole nav sits ~30px left of true centre
   as a result. Treated as a slip in the comp and built symmetric
   at 2.5rem. Raised with Sophie — if it was deliberate, change
   --header-pad-inline and nothing else.

   OPEN — scaling basis. Every value above is fixed px taken from a
   2560 frame, and there is no comp at any other width. At 1440 the
   comp-equivalent wordmark is 205px, not 365px, so on a laptop the
   wordmark ships ~78% oversized relative to everything around it.
   Fixing that is a design decision for Sophie, not a bug: either
   commit to fixed sizing at the 2560 values, or go fluid on both
   (--wordmark-width: 14.26vw alongside the --nav-font-size clamp
   below). Left fixed pending her answer.
   =============================================================== */

.site-header {
    --wordmark-width: 265px;
    --header-pad-inline: 2.5rem;
    --nav-gap: 8.5rem;

    /* 0.832vw is the comp value (21.3px at the 2560 frame). Clamped
       at both ends: the floor keeps the theme's existing 14px on a
       laptop, which nothing in the comps contradicts, and the
       ceiling stops the nav growing past 2560 while the wordmark
       beside it is pinned at 365px. Continuous through the range,
       so the separate 1920 media query is no longer needed. */
    --nav-font-size: clamp(0.875rem, 0.832vw, 1.33rem);

    /* Distance the nav baseline sits above the wordmark baseline in
       the comp, in ems of the nav face. See the note above. */
    --nav-baseline-lift: 0.432em;

    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    z-index: 3;

    display: grid;
    grid-template-columns: 1fr auto 1fr;

    /* Baseline, then lifted. Baseline alignment is the stable half
       of this — it holds whatever --wordmark-width becomes — and
       the lift below carries the offset the comp actually shows. */
    align-items: baseline;

    padding: 2rem var(--header-pad-inline);

    /* Everything visual inherits from here: the wordmark SVG is
       fill="currentColor" and the nav links are color: inherit, so
       the light/dark switch below is one declaration, not a set of
       per-element overrides. */
    color: #000;

    /* Declared once, in the Reveal block below, alongside the entry
       animation. Both used to set `transition` on this same selector
       and the later one silently won, so editing this one did
       nothing. Do not reintroduce a transition here. */
}

/* SOW 2 — white over the full-bleed imagery on Home, black on the
   cream pages. body_class() already emits .home, so no custom body
   class is needed. See the Barba note in assets/js/mirahston.js:
   client-side navigation does not swap the body class on its own. */
.home .site-header {
    color: #fff;
}

.site-header__home {
    display: block;
    line-height: 0;
    color: inherit;
}

.site-header .wordmark {
    display: block;
    width: var(--wordmark-width);
    max-width: 100%;
    height: auto;
}

/* The face is set on the nav, not the link, for two reasons: the
   baseline lift below is in em and has to resolve against the nav
   type rather than whatever body copy the header inherits, and the
   mobile panel then needs one override instead of two. */
.site-nav {
    font-size: var(--nav-font-size, 0.875rem);
}

/* position/bottom rather than a transform: a transform would promote
   the nav to its own compositor layer and soften the type, and there
   is nothing in the header whose layout this can disturb. */
.site-header > .site-nav {
    position: relative;
    bottom: var(--nav-baseline-lift, 0);
}

.site-nav__list {
    display: flex;
    gap: var(--nav-gap);
    list-style: none;
    margin: 0;
    padding: 0;
}

.site-nav--right .site-nav__list {
    justify-content: flex-end;
}

.site-nav__link {
    position: relative;
    display: inline-block;
    font-family: 'Orleans Roman';
    font-size: inherit;
    line-height: 1;
    text-decoration: none;
    color: inherit;

    /* The legacy rule near the top of this file matches bare `a` and
       gives it `transition: color 600ms` with a 500ms DELAY. These
       links set no transition of their own, so they inherited it and
       finished 500ms after the wordmark — the wordmark landed, then
       the nav caught up. That stagger was the "slow" switch.

       Overridden here rather than at the legacy rule, which still
       serves the legacy header wherever mirahston_p4_nav_active()
       is false. */
    transition: color var(--header-fade) var(--ease-header);
    transition-delay: 0s;
}

/* Keyboard affordance. The theme sets a custom cursor image on
   html, body and a, which gives a mouse user a hover cue and a
   keyboard user nothing at all — so focus visibility is the only
   thing a keyboard user has to go on. */
.site-nav__link:focus-visible,
.site-header__home:focus-visible {
    outline: 2px solid currentColor;
    outline-offset: 4px;
}

.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 100;
    padding: 0.75rem 1.25rem;
    background: #101014;
    color: #f9f8f2;
    font-family: 'Orleans Roman';
    font-size: 0.875rem;
}

.skip-link:focus {
    left: 0;
}

/* --- Reveal ---------------------------------------------------

   One mechanism, and no JavaScript. The resting state is visible;
   the off-screen state exists only inside the animation, so a
   failed script or stylesheet still leaves the nav reachable.

   The header sits outside [data-barba="container"], so this runs
   once per document load and not on client-side navigation.
   -------------------------------------------------------------- */
@keyframes header-enter {
    from {
        transform: translateY(-100%);
    }

    to {
        transform: translateY(0);
    }
}

.site-header {
    transform: translateY(0);

    /* The only place the light/dark switch is timed. */
    transition: color var(--header-fade) var(--ease-header);

    /* backwards, not forwards: holds the off-screen state through
       the 600ms delay, but leaves the resting value declared above
       rather than animation-filled.

       Deliberately NOT on --header-fade. This is the one-per-load
       entry reveal, not the colour switch; they share a curve but
       not a purpose, and tying them together means retuning the
       reveal every time the switch is adjusted. */
    animation: header-enter 800ms var(--ease-header) 600ms backwards;
}

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

/* --- Mobile panel ----------------------------------------------

   Built from menu_mobile.jpg and home_mobile.jpg, supplied 24 Aug.
   Both are 1206px wide artboards at @3x, so a 402px frame. Every
   value below was measured off those files and divided by three;
   the raw measurement is quoted so it can be rechecked without
   reopening the comps.

   Why #mobile-menu and not .mobile-header for the structural
   properties: the legacy panel rules further down this file set
   display, opacity, height and flex on .mobile-header, and they
   come later in source order, so a class here would lose. The ID
   wins on specificity regardless of position, which keeps the P4
   panel in the P4 section instead of stranding half of it at the
   bottom of the file. When the legacy header is deleted at Stage
   5 these can all become .mobile-header and the ID can go.
   ---------------------------------------------------------------- */

#mobile-menu {
    position: fixed;
    inset: 0;
    z-index: 2;

    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;

    /* dvh so the panel does not sit under a collapsing mobile
       address bar. .fullscreen elsewhere is still sized in JS —
       see sizeFullscreenElements() — but nothing here needs it. */
    height: 100dvh;

    background: #f9f8f2;
    color: #101014;

    opacity: 0;
    visibility: hidden;

    /* This is the CLOSE transition — the .is-open rule below carries
       the open one. They are deliberately different lengths now.

       Close is pulled to 400ms to match --header-fade. On close the
       header goes back to white on Home over 400ms; at the old 600ms
       the panel was still a third opaque when it got there, so the
       wordmark went white against cream and vanished for ~200ms.
       Matching the two means the cream leaves as the white arrives.

       Open stays at 600ms because the staggered item delays below
       are measured against it. */
    transition:
        opacity var(--header-fade) var(--ease-header),
        visibility 0s linear var(--header-fade);
}

#mobile-menu.is-open {
    opacity: 1;
    visibility: visible;
    transition:
        opacity 600ms cubic-bezier(0.25, 0.1, 0.25, 1),
        visibility 0s;
}

/* The panel is cream on every template, so the wordmark above it
   has to be black even on Home, where the header is otherwise
   white over the carousel. */
.is-menu-open .site-header {
    color: #101014;

    /* Held at the panel's 600ms, NOT --header-fade.

       This colour change has to stay behind the cream panel arriving
       underneath it. At 400ms against the panel's 600ms fade the
       wordmark turns black while the carousel is still showing
       through — black type on bright photography for ~200ms, which
       reads as a rendering fault rather than a transition.

       If the panel fade above ever changes, change this with it. */
    transition-duration: 600ms;
}

/* Nav items. Re-measured against the real Orleans metrics — the
   earlier 16px came from reading ink height as face size, the same
   error as the desktop nav:

     About     18.33px ink / 0.784 em ratio -> 23.4px
     Projects  22.67px ink / 0.984 em ratio -> 23.0px
     Contact   18.00px ink / 0.770 em ratio -> 23.4px

   so ~23.3px on the 402 frame. Baseline pitch measures 68.7px, and
   on line-height 1 that is face + gap, so the gap has to come DOWN
   from 52px to ~45px as the face goes up. The old pair (16 + 52)
   happened to give the right pitch with the wrong type.

   Fluid to match --wordmark-width, capped so a small tablet below
   the 1024 breakpoint does not get phone-sized-times-two type. */
#mobile-menu .site-nav__list {
    flex-direction: column;
    gap: 2.85rem;

    /* Labels are LEFT-aligned to each other in menu_mobile.jpg, not
       centred. Correcting for left side bearings all three start at
       169.8px on the 402 frame: About 169.8, Projects 170.0,
       Contact 169.8, while the right edges vary by 47px.

       Sophie appears to have centred the first item and aligned the
       rest to it, which cannot be expressed as a rule that survives
       the client renaming a menu item. Centring the block on its
       widest label is the content-independent equivalent and lands
       within ~6px of the comp on a 402 frame. */
    align-items: flex-start;
    text-align: left;
}

#mobile-menu .site-nav {
    font-size: min(5.8vw, 1.4375rem);
}

#mobile-menu .site-nav__link {
    color: inherit;
}

/* Current-page marker. Mobile only — everything under #mobile-menu is
   display:none above 1024, so this needs no media query of its own.

   Measured off menu_mobile.jpg (1206px @3x, 402px frame), Projects row:

     arrow ink box   x 765-798, y 974-1012 raw -> 11.3 x 13.0px
     cap height (P)  y 962-1013 raw          -> 17.3px, baseline 337.7
     gap after "s"   729 -> 765 raw          -> 12.0px

   Nav face there is min(5.8vw, 1.4375rem) = 23.3px on a 402 frame, so
   the box is 0.49 x 0.56em and the gap 0.51em. The arrow bottom sits on
   the baseline (1012 vs 1013 raw), which is what vertical-align:baseline
   on an inline-block gives for free.

   Mask rather than <img> so the arrow takes the panel's text colour —
   Arrow.svg is hard-coded #bcb5ac, which is the wrong grey here and
   would need a second file to change. Mask means no PHP change either,
   so nav.php stays a plain list. */
#mobile-menu .site-nav__link[aria-current="page"]::after {
    content: "";
    display: inline-block;
    width: 0.49em;
    height: 0.56em;
    margin-left: 0.51em;
    vertical-align: baseline;

    background-color: currentColor;

    -webkit-mask: url("../../assets/svgs/Arrow.svg") no-repeat center / contain;
    mask: url("../../assets/svgs/Arrow.svg") no-repeat center / contain;
}

/* --- Staggered item fade ---------------------------------------

   Items fade in one after another once the panel opens. Delays are
   on the .is-open rule only, so closing takes them all out together
   with the panel rather than replaying the stagger backwards.

   200ms base starts them a third of the way into the panel's own
   600ms fade — the panel reads as arriving first, the items follow.
   Delays are nth-child because the menu is client-editable and the
   item count is not known here. The 8th item and anything after it
   all share 760ms, so a very long menu ends on a flat fade instead
   of an ever-growing wait.
   ---------------------------------------------------------------- */

#mobile-menu .site-nav__item {
    opacity: 0;
    transition: opacity 450ms cubic-bezier(0.25, 0.1, 0.25, 1);
}

#mobile-menu.is-open .site-nav__item {
    opacity: 1;
    transition-delay: 760ms;
}

#mobile-menu.is-open .site-nav__item:nth-child(1) { transition-delay: 200ms; }
#mobile-menu.is-open .site-nav__item:nth-child(2) { transition-delay: 280ms; }
#mobile-menu.is-open .site-nav__item:nth-child(3) { transition-delay: 360ms; }
#mobile-menu.is-open .site-nav__item:nth-child(4) { transition-delay: 440ms; }
#mobile-menu.is-open .site-nav__item:nth-child(5) { transition-delay: 520ms; }
#mobile-menu.is-open .site-nav__item:nth-child(6) { transition-delay: 600ms; }
#mobile-menu.is-open .site-nav__item:nth-child(7) { transition-delay: 680ms; }

/* --- Menu / Close triggers -------------------------------------

   Both sit on the horizontal centre. In menu_mobile.jpg the Close
   BASELINE is 28.4px off the bottom of the 658px frame (1889.6 raw
   of 1975). 1.75rem was that figure applied to the element's bottom
   edge, which is not the same thing: the 0.5rem padding below the
   text plus the font's descent metric pushed the baseline to 38px,
   ~10px high.

   1.125rem puts it back — 18 + 8 padding + 2.03 of descent-minus-
   half-leading = 28.0px to the baseline. The padding stays, because
   it is the only thing giving these a usable tap target.

   Menu on home_mobile.jpg reads slightly higher again, but it is
   white type over a bright table top and could not be measured
   reliably. Worth eyeballing against the comp on staging.
   ---------------------------------------------------------------- */

.mobile-menu-toggle {
    position: fixed;
    left: 0;
    bottom: 1.125rem;
    z-index: 4;

    width: 100%;
    padding: 0.5rem 0;

    appearance: none;
    background: none;
    border: 0;

    font-family: 'Orleans Roman';
    font-size: 0.9375rem;
    line-height: 1;
    text-align: center;
    color: inherit;
}

.mobile-menu-toggle:focus-visible {
    outline: 2px solid currentColor;
    outline-offset: 4px;
}

/* Over the full-bleed carousel on Home, the same white as the
   header — and back to black the moment the cream panel is up. */
.mobile-menu-toggle--open {
    color: #101014;
    opacity: 1;
    visibility: visible;
    /* Colour on --header-fade: this word changes light/dark with the
       wordmark on the same page switch, so it has to move with it.
       At 600ms against the header's 400ms the wordmark landed first
       and the trigger followed — two halves of one header changing
       at different times. */
    transition:
        color var(--header-fade) var(--ease-header),
        opacity 400ms var(--ease-header),
        visibility 0s;
}

.home .mobile-menu-toggle--open {
    color: #fff;
}

.mobile-menu-toggle--close {
    color: #101014;
}

/* Only one of the two is ever present. Hiding the open trigger
   with visibility rather than display keeps its width stable, so
   nothing reflows behind the fade. */
.is-menu-open .mobile-menu-toggle--open {
    opacity: 0;
    visibility: hidden;
}

/* Above the header breakpoint the nav is in the header itself and
   neither trigger has any job. */
@media only screen and (min-width: 1024px) {
    #mobile-menu,
    .mobile-menu-toggle {
        display: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    /* Delay has to go as well as duration — a 0ms fade that still
       waits 680ms is a nav item that pops in late for no reason. */
    #mobile-menu .site-nav__item,
    #mobile-menu.is-open .site-nav__item,
    #mobile-menu.is-open .site-nav__item:nth-child(1),
    #mobile-menu.is-open .site-nav__item:nth-child(2),
    #mobile-menu.is-open .site-nav__item:nth-child(3),
    #mobile-menu.is-open .site-nav__item:nth-child(4),
    #mobile-menu.is-open .site-nav__item:nth-child(5),
    #mobile-menu.is-open .site-nav__item:nth-child(6),
    #mobile-menu.is-open .site-nav__item:nth-child(7) {
        transition: none;
        transition-delay: 0s;
    }

    #mobile-menu,
    .mobile-menu-toggle--open {
        transition: none;
    }
}

@media only screen and (max-width: 1023px) {
    .site-header {
        /* Wordmark alone, centred. The nav moves into the panel
           behind the Menu trigger. */
        grid-template-columns: 1fr;
        justify-items: center;
        align-items: center;
    }

    .site-header > .site-nav {
        display: none;
    }

    .site-header {
        /* 483px raw of a 1206px @3x artboard — 161px on a 402px
           frame, so 40% of the viewport. Was 220px, which is 55%
           and noticeably wider than either mobile comp. Capped so
           it stops growing on a landscape phone or small tablet,
           where 40vw would overshoot the desktop 365px. */
        --wordmark-width: min(40vw, 240px);

        /* Comp puts the cap line 39.3px down (118px raw). */
        padding-top: 2.5rem;
    }
}


/* ---------------------------------------------------------------
   HEADER BREAKPOINT — must match --bp-header at the top of this file.

   Previously the desktop nav was hidden at 700px while Barba
   switched at 768px, so between 701px and 768px the site ran with
   no visible navigation and desktop transitions. This block closes
   that gap.
   --------------------------------------------------------------- */
@media only screen and (max-width: 1023px) {

    .header{
        justify-content:center;
        z-index:5;
    }

    .inner-header-right{
        display:none !important;
    }

    /* The Menu trigger is marked .show-when-small in the markup, which
       is a 700px page-content class. Drive it from the header
       breakpoint instead so navigation never disappears entirely. */
    .bottom-menu{
        display:block;
    }
}


.bottom-menu .menu-item{
    position:fixed;
    bottom:1.75rem;
    text-align:center;
    width:100%;
}

.sub-menu .menu-item{
    padding-top:23px;
    line-height:1;
    text-align:right;
}

.mobile-header{
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    margin: auto;
    display: flex;
    align-items: center;
    height: 100vh;
    justify-content: center;
    flex-direction: column;
    display:none;
    opacity:0;
}

.mobile-header .menu-item{
    text-align:center;
    padding-bottom:2.5rem;
}

.mobile-header .menu-item a{
    color:#bcb5ac !important;
    font-size: 1.375rem;
font-style: normal;
font-weight: 400;
line-height: normal;
}

.menu-item a {
	position: relative;
	display: inline-block;
	text-decoration: none;	
}

.menu-item a:after {
	content: "";
	display: block;
	position: absolute;
	width: 0px;
	height: 1px;
	left: 0;
	background-color: #bcb5ac;
	will-change: width;
	transition: width 250ms cubic-bezier(0.25,0.1,0.25,1);
}

.credit-link{
    position: relative;
	display: inline-block;
	text-decoration: none;	
}

.credit-link:after{
    content: "";
	display: block;
	position: absolute;
    bottom:0.2rem;
	width: 0px;
	height: 1px;
	left: 0;
	background-color: #EFEDE4;
	will-change: width;
	transition: width 250ms cubic-bezier(0.25,0.1,0.25,1);
}

.credit-link:hover:after {
	width: 100%;
}

.contact-links{
    position: relative;
	display: inline-block;
	text-decoration: none;	
    border-bottom: solid 1px #000000;
    color: #000000;
}

.contact-links:after {
    content: "";
	display: block;
	position: absolute;
	width: 0px;
	height: 0.063rem;
	left: 0;
    bottom:0.2rem;
	background-color: #EFEDE4;
	will-change: width;
	transition: width 250ms cubic-bezier(0.25,0.1,0.25,1);
}

.contact-links:hover:after {
	width: 100%;
}

.social-links a {
    margin-right: 1rem;
    display: inline-block;
}

.menu-item a:hover:after {
	width: 100%;
}

/* FRONT PAGE */

.logo-container{
    position:fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index:2;
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
}

.page-logo{
    width:10.875rem;
    height:auto;
    object-fit:contain;
}

#menu-main-menu-1 li{
    opacity:0;
}

/* ABOUT PAGE */

.bottom-logo-container{
    position: fixed;
    bottom: -5.25rem;
    right: -6.25rem;
    z-index: 2;
    width: fit-content;
    display: flex;
    align-items: flex-end;
    justify-content: flex-end;
}

.bottom-logo{
    width:29.375rem;
    height:auto;
    object-fit:contain;
}

.about-page{
    gap:13.125rem;
    display:flex;
    justify-content: center;
    align-items:center;
    padding-left:2.5rem;
    padding-right:2.5rem;
    padding-top:2.5rem;
}

.about-splide{
   width:30.875rem;
}

.about-splide.mobile {
   display: none;
}

.about-text{
    max-width:27.5rem;
}

/* ANIMATION.SVG — 404 only.

   Was shared with the Shop template until the 27 Aug comps took it off that
   page. Left here because 404.php still renders it. See SHOP at the end of
   this file. */

.animation-container{
position: fixed;
bottom: 0;
left: 1.25rem;
z-index: 2;
width: 100%;
display: flex;
align-items: flex-end;
justify-content: flex-start;
}

.animation{
    width:14rem;
}

/* CONTACT PAGE */

.illustration{
    max-width:34.563rem;
}

.contact-page{
    gap:8.5rem;
    display:flex;
    justify-content: center;
    align-items:center;
    padding-left:2.5rem;
    padding-right:2.5rem;
    padding-top:2.5rem;
}

.contact-text{
    max-width:25.75rem;
}

.credit{
    position:fixed;
    bottom:2.5rem;
    left:0;
    width:100%;
    text-align:center;
}


@media only screen and (max-width:1024px){

    /* MENU */

    #menu-main-menu{
        gap: 2.75rem;
    }




    /* ABOUT PAGE */
    
    .about-page{
        gap: 5rem;
        display: flex;
        justify-content: center;
        align-items: center;
        padding-left: 2.5rem;
        padding-right: 2.5rem;
    }

    .about-text, .about-splide{
        width:50%;
    }

    /* CONTACT PAGE */

    .contact-page{
        gap: 5rem;
        display: flex;
        justify-content: center;
        align-items: center;
        padding-left: 2.5rem;
        padding-right: 2.5rem;
    }

    .illustration, .contact-text{
        width:50%;
    }

    .bottom-logo{
        width: 24.375rem;
    }

    .bottom-logo-container {
        bottom: -4.25rem;
        right: -4.25rem;
    }
}

@media (max-width:750px) {

    #menu-main-menu {
        gap: 1.75rem;
    }

    .logo {
        width: 7rem;
    }
}

@media only screen and (max-width:700px){
    body{
        height:fit-content;
        overflow:scroll;
    }

    .show-when-small{
        display:block;
    }
    
    .hide-when-small{
        display:none;
    }

    /* MENU */
    /* .header and .inner-header-right moved to the HEADER BREAKPOINT
       block above — they now switch at 1024px, not 700px. */

    .logo{
        width:8.75rem;
    }

    .content-wrapper{
        margin-left:1rem;
        margin-right:1rem;
    }

    .fullscreen{
        height:fit-content !important;
        min-height:100vh;
    }

    .sub-menu-container{
        position: fixed;
        top: 0;
        bottom: 0;
        left: 0;
        right: 0;
        margin: auto;
        height: 100vh;
        display: none;
        opacity:0;
        align-items: center;
        justify-content: center;
        padding-top:1.5rem;
    }

    .sub-menu-container .menu-item-has-children{
        display:block !important;
    }

    .sub-menu-container .menu-item{
        display:none;
        padding-bottom:0px;
    }

    .sub-menu-container .sub-menu{
        position: static;
        display: block;
        opacity: 1;
        width: fit-content;
        padding-top:.5rem;
    }
    .sub-menu-container .sub-menu .menu-item{
        display:block !important;
        padding-bottom: 0.35rem;
        padding-top:0.35rem;
    }

    .sub-menu-container .sub-menu .menu-item:last-child{
        padding-bottom: 0px !important;
    }

    .menu-item-66{
        padding-bottom:0px !important;
    }

    .sub-menu-container .sub-menu a{
    color: #bcb5ac !important;
    font-size: 0.875rem;
    font-style: normal;
    font-weight: 400;
    line-height: 1.75rem; 
    }

    .sub-menu li{
        white-space: normal;
    }

    .menu-item a:hover:after {
        width: 0%;
    }

    .sub-menu{
        padding-bottom:0px;
    }

    .sub-menu li, .menu-item-shop, .menu-item-75, .menu-item-103, .menu-item-66{
        opacity:0;
        transition: opacity 200ms cubic-bezier(0.25, 0.1, 0.25, 1);
        -o-transition: opacity 200ms cubic-bezier(0.25, 0.1, 0.25, 1);
        -ms-transition: opacity 200ms cubic-bezier(0.25, 0.1, 0.25, 1);
        -moz-transition: opacity 200ms cubic-bezier(0.25, 0.1, 0.25, 1);
        -webkit-transition: opacity 200ms cubic-bezier(0.25, 0.1, 0.25, 1);
        will-change: opacity;
        white-space: nowrap;
    }

    /* SHOP PAGE */

    .animation-container{
        left: 0.563rem;
    }

    .animation{
        width:7rem;
    }

    /* ABOUT PAGE */
    
    .about-page{
        flex-direction:column;
        padding-left:0px;
        padding-right:0px;
        padding-top:11.25rem;
        margin-bottom:6.5rem;
        gap:5rem;
    }

    .about-splide{
        width:100%;
    }
    
    .about-splide.desktop {
       display: none;
    }
    
    .about-splide.mobile {
       display: block;
       margin-bottom: 3rem;
    }

    .about-text{
        max-width:calc(100% - 1rem);
        z-index:2;
    }

    .bottom-logo{
        width:16.794rem;
    }

    .bottom-logo-container{
        bottom:0px;
        right:-3.125rem;
        z-index:1;
    }

    .about-text, .about-splide{
        width:100%;
    }

    /* CONTACT PAGE */

    .contact-page{
        flex-direction:column;
        padding-left:0px;
        padding-right:0px;
        padding-top:11.25rem;
        margin-bottom:5.125rem;
        gap:5rem;
    }

    .contact-text{
        order:1;
    }

    .illustration{
        order:2;
    }

    .credit{
        position:static;
        margin-bottom:3.125rem;
    }

    .illustration, .contact-text{
        width:100%;
    }

    /* 404 PAGE */

    .screen-404{
        gap:2.5rem !important;
    }

    /* SERVICES PAGE */

    .services-navigation{
        bottom: 1.75rem !important;
    }

    .services-navigation-arrow{
        max-height:0.875rem !important;
    }

    .services-navigation.previous{
        padding-left: 1rem !important;
    }

    .services-navigation.previous p{
        left: 2.5rem !important;
    }

    .services-navigation.next{
        padding-right: 1rem !important;
    }

    .services-navigation.next p{
        right: 2.5rem !important;
    }
}

@media only screen and (min-width:1920px){
    .about-splide {
        width: 25.729vw;
    }

    .about-text{
        max-width:22.917vw;
    }

    .illustration{
        max-width:28.802vw;
    }
    
    .contact-text {
        max-width: 21.458vw;
    }

    h1{
        font-size: 1.875vw;
        line-height:2.188vw;
    }
    
    h2{
        font-size: 1.042vw;
        line-height:1.458vw;
    }
    
    h3{
        font-size:0.833vw;
        line-height:1.458vw;
    }
    
    h4{
        font-size:0.625vw;
    }
    
    body{
        font-size:0.729vw;
        line-height:1.146vw;
    }
    
    p{
        font-family:'Orleans Light';
        font-size: 0.729vw;
        line-height:1.146vw;
    }

    .menu-item a{
        font-size: 0.729vw;
    }
}

/* Sitewide from footer.php. In normal flow by default so it lands at the end
   of the document on the pages that scroll — as absolute + bottom:0 it had no
   positioned ancestor and pinned to the first viewport instead. */
.copyright-footer {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 2rem;
    width: 100%;
    padding: 2.5rem;
    text-align: center;
    font-size: 0.9rem;
    color: #bcb5ac;
}

/* These three lock the viewport (body{overflow:hidden}), so anything in flow
   below the fold is clipped. They keep the pin. */
.page-template-page-home .copyright-footer,
.error404 .copyright-footer {
    position: absolute;
    bottom: 0;
    left: 0;
}

.copyright-footer a {
    color: #bcb5ac;
    text-decoration: none;

}

.copyright-footer a:hover {
    text-decoration: underline;
    font-weight: 500;
}

/* 404 inverts the palette. The footer follows the page rather than sitting
   in the light-page grey on a dark ground.

   Shop was in here too until the 27 Aug comps moved it onto the theme's
   default cream. It is a cream page now, so it takes the sitewide #bcb5ac
   like every other cream page. */
.error404 .copyright-footer,
.error404 .copyright-footer a {
    color: inherit;
}

@media(max-width: 700px) {
    .copyright-footer {
        display: none;
    }
}

/* ===============================================================
   PROJECTS GRID — SOW 5. Tasks 2-09, 2-10.
   SHOP GRID — client-confirmed as an exact copy of the above.

   Proportions measured off the desktop comp against its own frame:
   11.1% inset, 5.7% gutter, 6.7% row gap. Kept in vw so the layout
   holds its shape at any width. See TASK_2_PROJECTS.md.

   Shop is grouped onto these rules, not duplicated, so the two
   cannot drift. It has no hover image, hence its absence there.
   =============================================================== */

/* Reopens body { height: 100vh; overflow: hidden }. Done here rather
   than with an inline <style> block inside the Barba container;
   syncBodyClassOnTransition() keeps the body class correct on swap. */
.page-template-page-projects,
.page-template-page-shop {
    height: auto;
    overflow-x: hidden;
    overflow-y: auto;
}

/* On the wrapper so the empty state below inherits the same values. */
.projects-page,
.shop-page {
    --project-grid-inset: 11.1vw;
    --project-grid-gutter: 5.7vw;
    --project-grid-row-gap: 6.7vw;

    /* Floor clears the ~113px fixed header; ceiling stops the gap
       growing past 2560, where the header itself stops. The +2rem
       is the extra clearance under the header, applied at all three
       stops so the shift is the same at every width. */
    --project-grid-top: clamp(10rem, calc(8.5vw + 2rem), 15rem);
    --project-grid-bottom: clamp(3rem, 5vw, 7rem);
}

.project-grid,
.shop-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    column-gap: var(--project-grid-gutter);
    row-gap: var(--project-grid-row-gap);

    padding-block-start: var(--project-grid-top);
    padding-block-end: var(--project-grid-bottom);
    padding-inline: var(--project-grid-inset);

    margin: 0;
    list-style: none;
}

/* The left column's label sits out in the page margin — do not clip. */
.project-grid__item,
.shop-grid__item {
    position: relative;
    min-width: 0;
}

.project-card,
.shop-card {
    display: block;
    position: relative;
    text-decoration: none;
    color: inherit;
}

.project-card:focus-visible,
.shop-card:focus-visible {
    outline: 2px solid currentColor;
    outline-offset: 6px;
}

/* SOW 5: 3:4 in display only, against the full upload. One line to
   change, no re-upload.

   height: auto is load-bearing — wp_get_attachment_image() writes
   width/height attributes, which map to presentational hints for the
   CSS properties and would otherwise beat aspect-ratio. */
.project-card__image,
.shop-card__image {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 3 / 4;
    object-fit: cover;
    object-position: center;
}

/* The hover frame is stacked on the featured image rather than
   swapped into it, so there is no reflow and no flash of empty box
   while a lazy image decodes.

   Absolute, so the card's height keeps coming from the first image.
   Both share the 3:4 rule above, so they land in the same place. */
.project-card__image--hover {
    position: absolute;
    inset: 0;
    height: 100%;

    opacity: 0;
    transition: opacity 300ms cubic-bezier(0.25, 0.1, 0.25, 1);
}

/* Guarded so a touch device cannot stick in a hover state. */
@media (hover: hover) {
    .project-card:hover .project-card__image--hover {
        opacity: 1;
    }
}

/* Parity with hover, or a keyboard user cannot tell where they are. */
.project-card:focus-visible .project-card__image--hover {
    opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
    .project-card__image--hover {
        transition: none;
    }
}

/* The comps showing rotated vertical titles are the hover state.

   opacity, never display: none or visibility: hidden — this span is
   the only accessible name the ten links have, and both of those drop
   it from the accessibility tree. The img carries alt="" so the card
   is not announced twice.

   vertical-rl then rotated 180deg reads bottom to top; rotation is
   about the box centre, so bottom: 0 still bottom-aligns it. */
.project-card__label,
.shop-card__label {
    position: absolute;
    bottom: 0;
    right: calc(100% + 0.75rem);

    writing-mode: vertical-rl;
    transform: rotate(180deg);
    white-space: nowrap;

    font-family: 'Orleans Roman';
    /* Measured off a downsampled artboard — check against the real file.
       Ramps 14px → 22px between roughly 1360px and 2750px viewport.
       The rem part is deliberate: a preferred value of pure vw ignores
       the user's browser font size, which fails WCAG 1.4.4. */
    font-size: clamp(0.875rem, 0.45rem + 0.5vw, 1.375rem);
    line-height: 1;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: #101014;

    opacity: 0;
    transition: opacity 300ms cubic-bezier(0.25, 0.1, 0.25, 1);
}

/* Guarded so a touch device cannot stick in a hover state. */
@media (hover: hover) {
    .project-card:hover .project-card__label,
    .shop-card:hover .shop-card__label {
        opacity: 1;
    }
}

/* Parity with hover, or a keyboard user cannot tell where they are. */
.project-card:focus-visible .project-card__label,
.shop-card:focus-visible .shop-card__label {
    opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
    .project-card__label,
    .shop-card__label {
        transition: none;
    }
}

.project-grid__empty,
.shop-grid__empty {
    padding: var(--project-grid-top) var(--project-grid-inset);
    font-family: 'Orleans Light';
}

/* One column, and the label goes permanent and horizontal — there is
   no hover to reveal it with, which is what the mobile comp shows. */
@media only screen and (max-width: 700px) {
    .projects-page,
    .shop-page {
        --project-grid-inset: 2vw;
        --project-grid-row-gap: 8vw;
        --project-grid-top: 6.5rem;

        /* Clears the fixed Menu trigger at bottom: 1.125rem. */
        --project-grid-bottom: 7rem;
    }

    .project-grid,
    .shop-grid {
        grid-template-columns: 1fr;
    }

    .project-card__label,
    .shop-card__label {
        position: static;
        writing-mode: horizontal-tb;
        transform: none;
        display: block;
        padding-block-start: 1rem;
        opacity: 1;
    }
}

/* ===============================================================
   SINGLE PROJECT — SOW 6. Tasks 2-04 → 2-07.

   Measured off Assets/project individual.jpg (5120px @2x, so a
   2560 frame) and Assets/projects induvidual_mobile.jpg (1206px
   @3x, a 402 frame). Vertical rhythm is in vw, as on the grid, so
   the page holds its proportions at any width.

   Note the content column is asymmetric in the comp — 14.65% in
   from the left, 9.8% from the right, consistently across the
   title, the intro and every block. Reproduced as measured rather
   than tidied, but it is two custom properties, so symmetrising it
   if Sophie says it was unintentional is a one-line change.
   =============================================================== */

/* Reopens body { height: 100vh; overflow: hidden }, the same way the
   grid does. body_class() emits .single-project for free, and
   syncBodyClassOnTransition() carries it across a Barba swap. */
.single-project {
    height: auto;
    overflow-x: hidden;
    overflow-y: auto;
}

.project {
    /* Equal, so the content column sits centred under a centred
       wordmark. Previously 14.65vw / 9.8vw, which pushed the grid
       39px right of centre at a 1600 viewport while the header
       stayed symmetric. The pair is kept as two tokens rather than
       one padding-inline in case the comp ever does want them to
       differ. Their sum is unchanged, so the content width is too. */
    --project-inset-start: 12.225vw;
    --project-inset-end: 12.225vw;

    /* 269px at the 2560 frame. One value for every gap inside
       .project__blocks — between rows, and between the two images of
       a pair — so the grid reads on a single pitch. */
    --project-pair-gutter: 5.25vw;

    /* The shape of every paired portrait. One value, one place, and
       the uploaded file is cropped to it rather than setting it. */
    --project-pair-ratio: 3 / 4;

    --project-bottom: clamp(4rem, 6vw, 9rem);
}

/* --- Hero ------------------------------------------------------

   Full bleed. Cropped to 2.11:1 on desktop; below 700px the crop is
   dropped and the image runs at its own ratio (see the mobile block).

   max-height stops the ratio running away on an ultrawide monitor,
   where 2.11:1 would otherwise be taller than the viewport and the
   title would open below the fold. svh, so the iOS toolbar cannot
   resize the hero mid-scroll.
   -------------------------------------------------------------- */
.project-hero {
    aspect-ratio: 2.11;
    max-height: 100svh;
    overflow: hidden;
}

/* height: 100% is load-bearing — the global img rule sets
   height: auto, which would leave the image at its natural ratio
   inside a box shaped by aspect-ratio. <picture> is an inline box
   by default and needs the same treatment. */
.project-hero__picture,
.project-hero__image {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
}

/* --- Title and intro ------------------------------------------- */

.project__header {
    padding-block-start: 3.8vw;
    padding-inline-start: var(--project-inset-start);
    padding-inline-end: var(--project-inset-end);
}

/* A project with no blocks would otherwise end flush under its own
   intro. */
.project__header:last-child {
    padding-block-end: var(--project-bottom);
}

/* Floor is the size the comp lands on at ~890px, so the title never
   drops below the nav beside it; ceiling is the comp value at 2560,
   where the wordmark stops growing too. Padding is reset because
   the base h1 carries padding-bottom: 3rem. */
.project__title {
    margin: 0;
    padding: 0;
    font-family: 'Orleans Roman';
    font-size: clamp(1.125rem, 2.03vw, 3.25rem);
    font-weight: 400;
    line-height: 1.1;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: #101014;
}

/* Runs the full content width, as the comp does. */
.project__intro {
    margin-block-start: 4.9vw;
}

/* 0.977vw is the comp value (25px at 2560), which lands on exactly
   the theme's 14px at a 1440 laptop and grows from there.

   Leading is the comp's one setting worth overriding: it measures
   ~1.15, which is display typography rather than reading
   typography, against a theme base of 1.71 at 14px. */
.project__intro p {
    font-size: clamp(0.8125rem, 0.977vw, 1.5625rem);
    line-height: 1.45;
    margin-block-end: 0.9em;
}

.project__intro p:last-child {
    margin-block-end: 0;
}

/* --- Blocks ---------------------------------------------------- */

.project__blocks {
    display: flex;
    flex-direction: column;
    gap: var(--project-pair-gutter);

    padding-block-start: 11.8vw;
    padding-block-end: var(--project-bottom);
    padding-inline-start: var(--project-inset-start);
    padding-inline-end: var(--project-inset-end);
}

/* A full width row and the pair that follows it share a group, so
   mobile can slot the landscape between the two portraits with
   `order` — which only sorts siblings. Every other row is a group
   on its own, and the gap matches the one between groups, so on
   desktop the wrapper changes nothing visually.
   See mirahston_project_block_groups() in functions/. */
.project-group {
    display: flex;
    flex-direction: column;
    gap: var(--project-pair-gutter);
}

.project-block {
    margin: 0;
}

/* Natural ratio for a full width block. The width/height attributes
   wp_get_attachment_image() writes reserve the box, so nothing here
   needs an aspect-ratio and CLS stays at 0 whatever is uploaded. A
   pair overrides this below, where the reserved box comes from the
   aspect-ratio instead and CLS stays at 0 the same way. */
.project-block__image {
    display: block;
    width: 100%;
    height: auto;
}

.project-block--pair {
    display: flex;
    gap: var(--project-pair-gutter);
    align-items: flex-start;
}

/* The stylesheet sets the shape of a pair, not the uploaded file.
   Equal widths from flex-basis 0, equal heights because both carry
   the same ratio, so tops and bottoms line up whatever is uploaded
   and pair_layout no longer changes the result. The cost is a crop:
   anything that is not --project-pair-ratio loses its edges. */
.project-pair__item {
    margin: 0;
    min-width: 0;
    flex: 1 1 0;
    aspect-ratio: var(--project-pair-ratio);
}

/* Fill the box the figure just set, and crop rather than squash.
   The crop is centred; art directing it per image would need a
   focal point field of its own. */
.project-block--pair .project-block__image {
    height: 100%;
    object-fit: cover;
    object-position: center;
}

/* One column, and the pair stacks in source order — SOW 9. Values
   from the 402 frame: 6% inset, 209px block gap, 281px between the
   hero and the title. */
@media only screen and (max-width: 700px) {
    /* .content-wrapper carries margin-inline: 1rem below 700px. The
       hero has to reach the viewport edge, and this template sets
       its own insets, so both margins go. */
    .single-project .content-wrapper {
        margin-left: 0;
        margin-right: 0;
    }

    /* No crop on mobile — the hero box takes its shape from the file
       that was uploaded, so a portrait comp needs a portrait upload.
       height: auto on both, or the desktop height: 100% would stretch
       the image inside a box that no longer has a set height. */
    .project-hero {
        aspect-ratio: auto;
        max-height: none;
    }

    .project-hero__picture,
    .project-hero__image {
        height: auto;
    }

    .project {
        --project-inset-start: 6vw;
        --project-inset-end: 6vw;
        --project-pair-gutter: 17.3vw;

        /* Clears the fixed Menu trigger at bottom: 1.125rem. */
        --project-bottom: 7rem;
    }

    .project__header {
        padding-block-start: 22.9vw;
    }

    .project__title {
        font-size: clamp(0.75rem, 2.99vw, 1rem);
    }

    .project__intro {
        margin-block-start: 1.9vw;
    }

    /* The comp measures 11px. Floored at 13 — 11px body copy on a
       phone is a legibility problem, not a style. */
    .project__intro p {
        font-size: clamp(0.8125rem, 2.74vw, 0.875rem);
    }

    .project__blocks {
        padding-block-start: 13.1vw;
    }

    /* display: contents, not flex-direction: column — the two
       portraits have to become children of .project-group so that
       `order` below can move the landscape between them. The
       wrapper's own box is not needed at this width. */
    .project-block--pair {
        display: contents;
    }

    /* Reset the desktop flex shorthand. Left alone, flex-basis 0
       resolves against the column axis once these are items of
       .project-group, and every portrait collapses to zero height. */
    .project-group > .project-block--pair > .project-pair__item {
        flex: 0 0 auto;
    }

    /* Portrait, landscape, portrait — the mobile comp. Visual order
       differs from DOM order here, which is sound for images with
       nothing focusable in them; do not extend this pattern to a
       block that contains a link or a caption that reads in
       sequence. */
    .project-group--interleave > .project-block--full {
        order: 2;
    }

    .project-group--interleave > .project-block--pair > .project-pair__item:first-child {
        order: 1;
    }

    .project-group--interleave > .project-block--pair > .project-pair__item:last-child {
        order: 3;
    }
}

/* ===============================================================
   STUDIO — SOW 4. Tasks 3-01 → 3-04.

   Two columns on the same geometry as the Projects grid. Rows are
   a fixed pitch taken from the image, not from the copy, so every
   image overlaps the one above it by the same amount whatever the
   client writes; the copy hangs below its image and runs on into
   the row beside the next block. See TASK_3_STUDIO.md.

   Layout is in cqw against .studio rather than vw: the page
   scrolls, and a vw pitch would be out by the scrollbar width and
   drift the overlap. Type stays in the theme's clamp() scale.
   =============================================================== */

/* Reopens body { height: 100vh; overflow: hidden }, as the other P4
   templates do. */
.page-template-page-studio {
    height: auto;
    overflow-x: hidden;
    overflow-y: auto;
}

.studio {
    container-type: inline-size;
}

/* No title in the comp, so the one heading on the page is hidden.
   Not display: none — that drops it from the accessibility tree.
   padding is reset because the base h1 carries padding-bottom: 3rem. */
.studio__title {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

.studio__blocks {
    --studio-inset: 11.1cqw;
    --studio-col: 36.05cqw;

    /* The comp's image-to-image rhythm, averaged over the four
       blocks: tops 41.4cqw apart against a 48.07cqw image. */
    --studio-overlap: 5.5cqw;
    --studio-pitch: calc(var(--studio-col) * 4 / 3 - var(--studio-overlap));

    --studio-text-gap: 13.3cqw;

    /* Cap on a full-width break's line length. The content area is
       ~78cqw, which at the body size runs past 150 characters a line —
       unreadable. 45cqw is about 91. Not in the comp. */
    --studio-break-measure: 45cqw;

    display: grid;
    grid-template-columns: repeat(2, var(--studio-col));
    justify-content: space-between;

    /* The pitch lives on the rows, not on the block's height, so a row
       can grow when a block's copy needs more space. */
    grid-auto-rows: minmax(var(--studio-pitch), auto);

    /* Floor clears the ~113px fixed header; ceiling stops the gap
       growing past 2560, where the header itself stops. The +2rem
       is the extra clearance under the header, applied at all three
       stops so the shift is the same at every width. */
    padding-block-start: clamp(10rem, calc(9.8cqw + 2rem), 18rem);
    padding-block-end: clamp(3rem, 5cqw, 7rem);
    padding-inline: var(--studio-inset);
}

.studio-col--left {
    grid-column: 1;
}

.studio-col--right {
    grid-column: 2;
}

/* Each column repeats every other row, so a block owns the two rows
   between its own image and the next image in the same column. Sizing
   it to that span gives the copy a real box to sit in, and copy longer
   than the box grows the span instead of colliding with the next
   image. --studio-row is written per block by the template. */
.studio-block {
    grid-row: var(--studio-row) / span 2;
    min-block-size: calc(var(--studio-pitch) * 2);
    align-self: start;
    display: flex;
    flex-direction: column;
    margin: 0;
}

/* Nothing sits below it in its column: it is the last row, or a full
   width row follows. It reserves no height, so its copy is not left
   hanging below the grid and over the footer. The template decides
   which blocks get this — :last-child cannot, now that a full width
   row is a .studio-block too. */
.studio-block--single {
    grid-row: var(--studio-row);
    min-block-size: 0;
}

/* Full width copy on a row of its own, across both columns. Sized by
   the copy: the template gives its track `auto` rather than the pitch
   floor, so a row of text is not as tall as a row with an image.
   Left-aligned to the first column, measure capped. */
.studio-block--text {
    grid-column: 1 / -1;
    grid-row: var(--studio-row);
    min-block-size: 0;
}

.studio-block--text .studio-block__text {
    flex: 0 0 auto;
    max-inline-size: var(--studio-break-measure);
}

/* SOW 4: 3:4 in display only, against the full upload. height: auto
   is load-bearing — wp_get_attachment_image() writes width/height
   attributes, which would otherwise beat aspect-ratio. */
.studio-block__image {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 3 / 4;
    object-fit: cover;
    object-position: center;
}

/* Takes the space left under the image and centres the copy in it, so
   the gap above the copy is no longer a fixed measure. */
.studio-block__text {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
    margin-block-start: 0;
    padding-block: 4rem;
}

/* A block that reserves no height has no space to centre in, so it
   keeps the measured gap. */
.studio-block--single .studio-block__text {
    margin-block-start: var(--studio-text-gap);
}

/* Shares the top row with the first block, offset to sit level with
   that block's copy rather than its image. */
.studio__intro {
    grid-row: 1;
    align-self: start;
    margin-block-start: 10.9cqw;
}

/* Matches .project__intro — the comp measures ~1.15 leading, which is
   display typography rather than reading typography. */
.studio__intro p,
.studio-block__text p {
    font-size: clamp(0.8125rem, 0.977cqw, 1.5625rem);
    line-height: 1.45;
    margin-block-end: 0.9em;
}

.studio__intro p:last-child,
.studio-block__text p:last-child {
    margin-block-end: 0;
}

.studio__empty {
    grid-row: 1;
    font-family: 'Orleans Light';
}

/* One column, image above text, source order — SOW 9. Values from
   the 402 frame. */
@media only screen and (max-width: 700px) {
    /* Insets below are measured against the viewport, so the 1rem
       .content-wrapper margin has to go. */
    .page-template-page-studio .content-wrapper {
        margin-left: 0;
        margin-right: 0;
    }

    .studio__blocks {
        --studio-inset: 6cqw;
        --studio-text-gap: 24cqw;

        grid-template-columns: 1fr;
        row-gap: 16cqw;

        /* Beats the template's inline grid-template-rows: the pitch and
           the full width exception both stop mattering in one column. */
        grid-template-rows: none !important;
        grid-auto-rows: auto;

        padding-block-start: clamp(6.5rem, 38.6cqw, 10rem);

        /* Clears the fixed Menu trigger at bottom: 1.125rem. */
        padding-block-end: 7rem;
    }

    /* The side control has no effect at this width, and the fixed
       pitch goes with it — nothing overlaps in one column. */
    .studio__intro,
    .studio-block,
    .studio__empty {
        grid-column: 1;
        grid-row: auto;
        block-size: auto;
        min-block-size: 0;
        margin-block-start: 0;
    }

    /* One column, so a full width row already spans everything and the
       cap would only narrow it. */
    .studio-block--text .studio-block__text {
        max-inline-size: none;
        margin-block-start: 0;
    }

    /* No reserved space to centre in at this width, so the copy goes
       back to the measured gap. */
    .studio-block__text {
        flex: 0 0 auto;
        margin-block-start: var(--studio-text-gap);
        padding-block: 2rem;
    }

    /* The comp measures 11px. Floored at 13, as on the project pages. */
    .studio__intro p,
    .studio-block__text p {
        font-size: clamp(0.8125rem, 2.74cqw, 0.875rem);
    }
}

/* ===============================================================
   CONTACT — SOW 7. Tasks 4-01 → 4-03.

   Same column grid and same fixed image pitch as Studio; the
   contact details take the place of the Studio intro. Measured
   against Assets/contact.jpg. See TASK_4_CONTACT.md.

   --contact-overlap is deliberately Studio's shipped 5.5cqw and
   not this comp's measured 7.6cqw, so the two pages run on one
   rhythm. Both figures and the arithmetic behind them are in the
   task doc; changing them is one line per page.
   =============================================================== */

/* Reopens body { height: 100vh; overflow: hidden }. */
.page-template-page-contact {
    height: auto;
    overflow-x: hidden;
    overflow-y: auto;
}

/* cqw, not vw: the page scrolls, so a vw pitch is out by the
   scrollbar width and the overlap drifts down the page. */
.contact {
    container-type: inline-size;
}

.contact__grid {
    --contact-inset: 11.1cqw;
    --contact-col: 36.05cqw;
    --contact-overlap: 5.5cqw;
    --contact-pitch: calc(var(--contact-col) * 4 / 3 - var(--contact-overlap));
    --contact-text-gap: 13.3cqw;

    display: grid;
    grid-template-columns: repeat(2, var(--contact-col));
    justify-content: space-between;

    /* Floor clears the ~113px fixed header; ceiling stops the gap
       growing past 2560, where the header stops. The +2rem is the
       extra clearance under the header, applied at all three stops
       so the shift is the same at every width. Matches Studio. */
    padding-block-start: clamp(10rem, calc(9.8cqw + 2rem), 18rem);
    padding-block-end: clamp(3rem, 5cqw, 7rem);
    padding-inline: var(--contact-inset);
}

.contact-col--left {
    grid-column: 1;
}

.contact-col--right {
    grid-column: 2;
}

/* --- Details --------------------------------------------------- */

/* Shares row 1 with the first block, offset to sit level with that
   block's copy rather than its image. */
.contact__details {
    grid-row: 1;
    align-self: start;
    margin-block-start: 10.9cqw;
}

/* Comp measures 47px at 2560. Padding is reset because the base h1
   carries padding-bottom: 3rem. */
.contact__title {
    margin: 0 0 2.21cqw;
    padding: 0;
    font-family: 'Orleans Roman';
    font-size: clamp(1.5rem, 1.85cqw, 3rem);
    font-weight: 400;
    line-height: 1.1;
    color: #101014;
}

.contact__group + .contact__group {
    margin-block-start: clamp(2.5rem, 3.3cqw, 5.5rem);
}

/* Labels hug their own content and are spaced by the stylesheet, so
   the comp's 48px gutter holds however long the email is. */
.contact__group--pair {
    display: grid;
    grid-template-columns: repeat(2, max-content);
    column-gap: clamp(1.5rem, 1.95cqw, 3.5rem);
}

/* h2 for structure — the page reads Contact > Email / Telephone /
   Studio address / Social. Uppercased in CSS, not in the field, so
   screen readers are not given four initialisms. */
.contact__label {
    margin: 0 0 0.42em;
    font-family: 'Orleans Light';
    font-size: clamp(0.75rem, 0.74cqw, 1.1875rem);
    font-weight: 400;
    line-height: 1.4;
    text-transform: uppercase;
    color: #101014;
}

/* h3 included because the client's field content wraps links in one.
   Without it the UA's 1em heading margin sits inside the inline-block
   link and pushes .contact-links' border-bottom well below the text. */
.contact__value,
.contact__value p,
.contact__value h3 {
    margin: 0;
    font-family: 'Orleans Roman';
    font-size: clamp(0.875rem, 0.91cqw, 1.4375rem);
    line-height: 1.6;
}

/* text-decoration rather than the theme's .contact-links wipe: that
   one paints a #EFEDE4 bar over the border, which no longer matches
   the #F9F8F2 body. This animates the underline itself. */
.contact__value a {
    color: inherit;
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 0.28em;
    transition: text-decoration-color 250ms cubic-bezier(0.25, 0.1, 0.25, 1);
}

.contact__value a:hover,
.contact__value a:focus-visible {
    text-decoration-color: transparent;
}

/* Field content pasted from the old template carries .contact-links,
   which draws a border-bottom AND an ::after bar on top of the
   text-decoration above — three lines. Keep the border as the single
   underline, drop the other two. Hover fades the border rather than
   painting the old #EFEDE4 bar over it; that colour predates the
   #F9F8F2 body. Scoped here so the old template is untouched. */
.contact__value .contact-links {
    text-decoration: none;
    border-bottom-color: currentColor;
    transition: border-bottom-color 250ms cubic-bezier(0.25, 0.1, 0.25, 1);
}

.contact__value .contact-links::after {
    content: none;
}

.contact__value .contact-links:hover,
.contact__value .contact-links:focus-visible {
    border-bottom-color: transparent;
}

/* Flex, not word-spacing: the gap then holds whatever the client
   types, including a two-word link name. Whitespace between the
   links is not rendered as a flex item, so a single space between
   them in the editor is correct — as the field instructions say. */
.contact__social p,
.contact__social h3 {
    display: flex;
    flex-wrap: wrap;
    gap: 0.2em 0.96em;
}

/* --- Blocks ---------------------------------------------------- */

/* Fixed height on the item, not the track, so the last block can
   drop back to auto and its copy stays inside the grid rather than
   hanging over the footer. --contact-row is written per block by
   the template. */
.contact-block {
    grid-row: var(--contact-row);
    block-size: var(--contact-pitch);
    align-self: start;
    margin: 0;
}

.contact-block:last-child {
    block-size: auto;
}

/* 3:4 in display only, against the full upload. height: auto is
   load-bearing — wp_get_attachment_image() writes width/height
   attributes, which would otherwise beat aspect-ratio. */
.contact-block__image {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 3 / 4;
    object-fit: cover;
    object-position: center;
}

.contact-block__text {
    margin-block-start: var(--contact-text-gap);
}

/* Matches .studio-block__text and .project__intro. */
.contact-block__text p {
    font-size: clamp(0.8125rem, 0.977cqw, 1.5625rem);
    line-height: 1.45;
    margin-block-end: 0.9em;
}

.contact-block__text p:last-child {
    margin-block-end: 0;
}

.contact__empty {
    grid-row: 1;
    font-family: 'Orleans Light';
}

/* One column, details first, source order — SOW 9. Values from the
   402 frame. */
@media only screen and (max-width: 700px) {
    /* Insets below are measured against the viewport, so the 1rem
       .content-wrapper margin has to go. */
    .page-template-page-contact .content-wrapper {
        margin-left: 0;
        margin-right: 0;
    }

    .contact__grid {
        --contact-inset: 6cqw;
        --contact-text-gap: 14cqw;

        grid-template-columns: 1fr;

        padding-block-start: clamp(7rem, 45.3cqw, 12rem);

        /* Clears the fixed Menu trigger at bottom: 1.125rem. */
        padding-block-end: 7rem;
    }

    /* The side control has no effect at this width, and the fixed
       pitch goes with it — nothing overlaps in one column. */
    .contact__details,
    .contact-block,
    .contact__empty {
        grid-column: 1;
        grid-row: auto;
        block-size: auto;
        margin-block-start: 0;
    }

    /* Block-to-block is 22cqw but details-to-first-image is 16.7,
       so the rhythm is on the items rather than a row-gap. */
    .contact-block {
        margin-block-start: 22cqw;
    }

    .contact__details + .contact-block {
        margin-block-start: 16.7cqw;
    }

    /* The comp has no title at this width. Not display: none —
       that drops it from the accessibility tree. */
    .contact__title {
        position: absolute;
        width: 1px;
        height: 1px;
        margin: -1px;
        padding: 0;
        border: 0;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }

    .contact__group + .contact__group {
        margin-block-start: 20.5cqw;
    }

    .contact__label {
        margin-block-end: 0.52em;
        font-size: clamp(0.75rem, 3.48cqw, 1rem);
    }

    .contact__value,
    .contact__value p {
        font-size: clamp(0.9375rem, 4.23cqw, 1.125rem);
        line-height: 1.41;
    }

    /* The comp measures 11.5px. Floored at 13, as on the project
       and Studio pages. */
    .contact-block__text p {
        font-size: clamp(0.8125rem, 2.74cqw, 0.875rem);
    }
}

/* ===============================================================
   SINGLE PRODUCT — the PDP.

   Measured off the Figma file, frames 1:2 (1512) and 3:61 (402).
   Vertical rhythm is the Projects grid's, not the comp's — the
   comp draws a 131px header and the built one is ~100px.
   Breakpoint is --bp-header: below 1024 the info column is too
   narrow to set 20px type, and the header collapses there anyway.
   =============================================================== */

/* Reopens body { height: 100vh; overflow: hidden }. */
.single-product {
    height: auto;
    overflow-x: hidden;
    overflow-y: auto;
}

.product {
    --product-inset-start: 11.05vw;
    --product-inset-end: 11.77vw;
    --product-col-gap: 5.75vw;
    --product-media-col: 36.11vw;
    --product-info-col: 35.32vw;

    /* Matches --project-grid-top. */
    --product-top: clamp(10rem, calc(8.5vw + 2rem), 15rem);
    --product-bottom: clamp(3rem, 5vw, 7rem);

    --product-image-gap: 5.75vw;

    /* 14 + 22 + 14: the comp's paragraph gap plus an empty line. The 22
       was the old fixed leading; at the 1512 comp width the fluid copy
       now leads ~21px, so 50 still holds. Left fixed on purpose — it
       drifts a few px wide on a very large screen. */
    --product-block-gap: 50px;

    --product-image-ratio: 546 / 662;

    display: grid;
    grid-template-columns: var(--product-media-col) var(--product-info-col);
    column-gap: var(--product-col-gap);
    align-items: start;

    padding-block-start: var(--product-top);
    padding-block-end: var(--product-bottom);
    padding-inline: var(--product-inset-start) var(--product-inset-end);
}

/* No images, so the info column takes the page. */
.product--no-media {
    grid-template-columns: var(--product-info-col);
}

/* --- Gallery ---------------------------------------------------
   Gated on data-carousel-mounted, not Splide's .is-initialized,
   which destroy() leaves behind. Desktop, JS off and a failed CDN
   all fall through to the plain stack.
   --------------------------------------------------------------- */

.product-gallery {
    position: relative;
    min-width: 0;
}

.product-gallery:not([data-carousel-mounted]) .product-gallery__list {
    display: block;
}

.product-gallery:not([data-carousel-mounted]) .product-gallery__track {
    overflow: visible;
}

.product-gallery:not([data-carousel-mounted]) .product-gallery__item + .product-gallery__item {
    margin-block-start: var(--product-image-gap);
}

.product-gallery:not([data-carousel-mounted]) .product-gallery__counter,
.product-gallery:not([data-carousel-mounted]) .product-gallery__arrows {
    display: none;
}

.product-gallery__item {
    list-style: none;
}

/* Ratio in CSS only, nothing cropped on upload. height: auto is
   load-bearing — the width/height attributes would beat it. */
.product-gallery__image {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: var(--product-image-ratio);
    object-fit: cover;
    object-position: center;
}

/* --- Info column -----------------------------------------------
   Sticky. Nothing in the ancestor chain may clip, or it breaks —
   do not add overflow to .content-wrapper or body.
   --------------------------------------------------------------- */

.product-info {
    position: sticky;
    top: var(--product-top);

    /* Scrolls itself rather than clip Enquire out of view on a
       short laptop. Only engages on unusually long copy. */
    max-height: calc(100dvh - var(--product-top) - 2rem);
    overflow-y: auto;

    min-width: 0;
    color: #101014;
}

/* Order is load-bearing: equal specificity, so a later `margin: 0`
   would cancel every block gap. */
.product-info > * {
    margin: 0;
}

.product-info > * + * {
    margin-block-start: var(--product-block-gap);
}

/* Base h1 carries padding-bottom: 3rem.

   By request 17 Sep: sized off the project title on the grid, then
   nudged up 2px on a second pass. The slope is .project-card__label's
   0.5vw untouched and the floor and cap are its 14/22 plus 2, so this
   runs a constant 2px above the project title at every width rather
   than pulling away from it. Letter-spacing is still not copied —
   the original request was size only.

   line-height is 1.1, not the label's 1: the label never wraps and
   this heading does. A fixed rem leading cannot come with a fluid
   font-size, which is why the old 1.375rem goes. */
.product-info__name {
    padding: 0;
    font-family: 'Orleans Roman';
    font-size: clamp(1rem, 0.575rem + 0.5vw, 1.5rem);
    line-height: 1.1;
    font-weight: 400;
    text-transform: uppercase;
    color: inherit;
}

/* By request 17 Sep: sized off the Studio body copy. vw, not the
   Studio rule's cqw — above 700px the Studio container is the
   viewport, so the only difference is the scrollbar, ~0.15px here,
   and matching it would mean putting a container on this page. */
.product-info__price,
.product-info__dimensions,
.product-info__enquire,
.product-info__description p {
    font-family: 'Orleans Light';
    font-size: clamp(0.8125rem, 0.977vw, 1.5625rem);
    line-height: 1.45;
    font-weight: 400;
}

/* By request 17 Sep, diverging from the comp. Stacked because bare
   'Times' resolves on Apple platforms only. */
.product-info__dimensions {
    font-family: 'Times New Roman', Times, serif;
    text-transform: lowercase;
}

/* Within a block, the 14px paragraph gap alone. */
.product-info__description p {
    margin-block-end: 0.875rem;
}

.product-info__description p:last-child {
    margin-block-end: 0;
}

.product-info__enquire-link {
    color: inherit;
    text-decoration: underline;
    text-underline-position: from-font;
}

.product-info__enquire-link:focus-visible {
    outline: 2px solid currentColor;
    outline-offset: 4px;
}

/* --- Mobile ----------------------------------------------------
   Frame 3:61, 402 wide. Not the desktop layout stacked — the image
   column becomes a swipeable carousel with a counter and arrows.
   --------------------------------------------------------------- */

@media only screen and (max-width: 1023px) {
    /* This template sets its own insets, as single-project does. */
    .single-product .content-wrapper {
        margin-left: 0;
        margin-right: 0;
    }

    .product,
    .product--no-media {
        --product-inset-start: 7.46vw;
        --product-inset-end: 7.46vw;
        --product-image-gap: 2rem;

        /* 14px flat — no empty line between blocks on mobile. */
        --product-block-gap: 0.875rem;

        --product-image-ratio: 343 / 416;

        /* Clears the fixed header; floor matches Projects. */
        --product-top: clamp(6.5rem, 14vw, 9rem);

        /* Clears the fixed Menu trigger at bottom: 1.125rem. */
        --product-bottom: 7rem;

        grid-template-columns: 1fr;
        row-gap: 2.375rem;
    }

    /* Sticky is desktop only. */
    .product-info {
        position: static;
        max-height: none;
        overflow-y: visible;

        /* The comp insets the copy 5px further than the image. */
        padding-inline: 1.25vw;
    }

    /* The clamp above floors at 16px for anything under ~1360px, so
       the fluid part never engages down here. */
    .product-info__name {
        font-size: 1rem;
    }

    /* Studio's own breakpoint is 700, not 1023. Between 701 and 1023 it
       sits on its desktop floor, which is this 13px. The 700-and-under
       rule follows further down. */
    .product-info__price,
    .product-info__dimensions,
    .product-info__enquire,
    .product-info__description p {
        font-size: 0.8125rem;
    }

    .product-info__description p {
        margin-block-end: 0.875rem;
    }
}

/* Studio switches at 700, so its mobile clamp only applies here.
   2.74vw for 2.74cqw for the same reason as the desktop rule. In
   practice this is 13px below ~475px and 14px from there to 700. */
@media only screen and (max-width: 700px) {
    .product-info__price,
    .product-info__dimensions,
    .product-info__enquire,
    .product-info__description p {
        font-size: clamp(0.8125rem, 2.74vw, 0.875rem);
    }
}

/* --- Carousel controls, visible only once Splide has mounted --- */

.product-gallery__counter {
    position: absolute;
    top: 0.3125rem;
    right: 0.5625rem;
    z-index: 1;

    margin: 0;
    font-family: 'Orleans Light';
    font-size: 0.8125rem;
    line-height: 1.125rem;

    /* Black per the comp; will disappear over a dark photograph. */
    color: #101014;
    pointer-events: none;
}

.product-gallery__arrows {
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
}

/* Overrides Splide's own arrow background, radius and transition. */
.product-gallery .product-gallery__arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    pointer-events: auto;

    width: auto;
    height: auto;
    padding: 0.5rem 0.25rem;
    background: none;
    border: 0;
    border-radius: 0;
    opacity: 1;

    font-family: 'Orleans Roman';
    font-size: 1.5rem;
    line-height: 1.125rem;
    color: #101014;
}

.product-gallery .product-gallery__arrow svg {
    display: none;
}

.product-gallery .splide__arrow--prev {
    left: 0;
}

.product-gallery .splide__arrow--next {
    right: 0;
}

.product-gallery .product-gallery__arrow:focus-visible {
    outline: 2px solid currentColor;
    outline-offset: -2px;
}

/* Visually hidden, still announced. */
.screen-reader-text {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}


/* SHOP holding page ("coming soon"), shown by page-shop.php while no products are published. Restored from the 27 Aug comps (commit 5845069): 2560px desktop frame, 402px mobile frame. */
.shop {
    /* Optical centre of the ink, not the line box. dvh so a collapsing address bar does not move it. */
    --shop-title-y: 47dvh;
    display: grid;
    justify-items: center;
    align-content: start;
    min-height: 100dvh;
    padding-inline: 2.5rem;
}

/* Ink centre sits 0.7075em below the top of a 1.1 line box, so the margin is offset by that. Base h1 padding-bottom is zeroed. Face is 38.4px at 402 and 68.7px at 2560. */
.shop__title {
    margin-block-start: calc(var(--shop-title-y) - 0.7075em);
    padding: 0;
    font-size: clamp(2.25rem, 2.048rem + 1.404vw, 4.294rem);
    line-height: 1.1;
    text-align: center;
}

@media only screen and (max-width: 700px) {
    .shop {
        --shop-title-y: 32.9dvh;
        padding-inline: 1.25rem;
    }
}
