/* ==========================================================================
   SOTIS — Architecture Studio

   THREE FRAMES, ONE SHEET
   Desktop  1440 · 12 columns · margin 16 · gutter 8  · column 110
   Tablet   1024 · stacked    · its own type ramp (T …)
   Mobile    390 ·  4 columns · margin 12 · gutter 12 · half 177

   1rem = 10 px of whichever frame is in play, and the root size is tied to
   the viewport, so a frame keeps its proportions on any screen instead of
   sitting at a fixed size inside a widening window.

   Every text style in the file carries letter-spacing -6%.
   ========================================================================== */

:root{
  --ink:#0C0D0C;
  --fog:#E9EAE8;
  --white:#FFFFFF;

  --page:3000px;                /* content stops widening here */
  --margin:1.6rem;              /* 16 */
  --gutter:.8rem;               /* 8  */
  --block:12rem;                /* 120 — standard section padding */

  --serif:'Arimo','Helvetica Neue',Arial,sans-serif;
  --sans:'Inter','Helvetica Neue',Arial,sans-serif;
  --track:-0.06em;
  --ease:cubic-bezier(.22,.61,.36,1);
  --ease-out:cubic-bezier(.23,1,.32,1);
  --ease-in-out:cubic-bezier(.77,0,.175,1);
  --hair:rgba(233,234,232,.2);
  --pxr:60%;                    /* how much taller a photo is than its slit */
  --bar-inset:env(safe-area-inset-top,0px);
  --bar-bleed:0px;               /* how far the header panel runs above the screen */
}

html{
  /* the page is ink; without this the scrollbar, the native select and every
     autofilled field are drawn by the browser in its light scheme over it */
  color-scheme:dark;
  /* THE LOADER'S SIDEWAYS JERK. html.pre-lock holds overflow:hidden for the
     length of the loader, and this document is taller than the window — so it
     has a scrollbar, and taking that away hands its width back to the page.
     Everything shifts, then shifts back when the lock comes off, which is the
     shove you see halfway through. The channel is reserved either way now, so
     locking and unlocking the scroll moves nothing at all. */
  scrollbar-gutter:stable;
  -webkit-tap-highlight-color:rgba(233,234,232,.12);
  font-size:clamp(8.3333px,.694444vw,20.8333px);   /* 10px at 1440 */
  -webkit-text-size-adjust:100%;
  scroll-behavior:smooth;
  /* the index is a full-bleed photograph: anything reserved above an anchor
     stops it landing on the card itself, which is the whole point of it */
  scroll-padding-top:var(--bar-inset);
}
@media (max-width:1023px){
  /* an in-app browser pushes the page under its own bar; the panel has to
     start above the screen or a strip of the site shows over it */
  :root{--bar-bleed:14rem;}
}
html.lenis{scroll-behavior:auto;}

/* An in-app browser draws its own bar over the page rather than above it, so
   what shows through that bar is whatever the page happens to have up there —
   a photograph, a heading, the foot of the form. This is the header's own
   ground, held above the top of the window where nothing else can see it. */
.bar-fill{display:none;}
@media (max-width:1023px){
  .bar-fill{display:block;position:fixed;left:0;right:0;
    top:calc(-1 * var(--bar-bleed));height:var(--bar-bleed);
    background:#0C0D0C;z-index:39;pointer-events:none;}
}

*,*::before,*::after{box-sizing:border-box;}
body{
  margin:0;background:var(--ink);color:var(--fog);
  font-family:var(--sans);font-size:1.6rem;line-height:1.36;
  letter-spacing:var(--track);
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
img,svg{display:block;max-width:100%;}
a{color:inherit;text-decoration:none;}
button{font:inherit;color:inherit;background:none;border:0;padding:0;cursor:pointer;letter-spacing:inherit;}
a,button,[role="button"],input,select,textarea,label,summary{touch-action:manipulation;}

/* The first thing in the tab order was a slider bar on the first screen, so
   reaching the text meant walking the whole navigation. */
/* It was hidden by being moved 220% of its own height above its own top,
   which only holds while "its own top" is the top of the window. A fixed
   element is positioned against the nearest ancestor carrying a transform or
   an animation, and <body> has one every time a page arrives on the rise — so
   the whole coordinate frame moves down and the link comes back into view. In
   an in-app browser, which draws its bar over the page rather than above it,
   that is a button sitting on the site. Nothing is moved any more: it is one
   pixel with its content clipped away, which nothing can drag back on. */
.skip{position:fixed;left:var(--margin);top:var(--margin);z-index:140;
  width:1px;height:1px;padding:0;overflow:hidden;clip-path:inset(50%);
  pointer-events:none;white-space:nowrap;background:#0C0D0C;color:#E9EAE8;
  font-family:var(--sans);font-weight:500;font-size:1.3rem;
  letter-spacing:var(--track);text-decoration:none;}
/* The padding has to go with it. 1px of width against 36 of padding is still
   a 36-wide box, and at z-index 140 that is an invisible lid over the corner
   of the wordmark — nothing to see and nothing to click. There is nothing
   left to hit until the key that calls it. */
.skip:focus{width:auto;height:auto;padding:1.1rem 1.8rem;overflow:visible;
  clip-path:none;pointer-events:auto;outline:1px solid rgba(233,234,232,.34);}
#main{outline:0;}

/* The ring was whatever the browser draws by default — an orange hairline on a
   black page. Keyboard only: :focus-visible, so a mouse press leaves no mark. */
:focus-visible{outline:2px solid #E9EAE8;outline-offset:3px;}
.hero--home.is-pale :focus-visible,.section--white :focus-visible,
.section--light :focus-visible{outline-color:#0C0D0C;}
::selection{background:var(--fog);color:var(--ink);}

/* ---------- type ramp ---------------------------------------------------- */
.display{font-family:var(--serif);font-weight:700;font-size:8.8rem;line-height:.92;letter-spacing:var(--track);margin:0;}
.h1     {font-family:var(--serif);font-weight:700;font-size:5.2rem;line-height:.98;letter-spacing:var(--track);margin:0;}
.h2     {font-family:var(--serif);font-weight:700;font-size:4.6rem;line-height:1;   letter-spacing:var(--track);margin:0;}
.h3     {font-family:var(--serif);font-weight:700;font-size:2.6rem;line-height:1.06;letter-spacing:var(--track);margin:0;}
.h4     {font-family:var(--serif);font-weight:700;font-size:1.8rem;line-height:1.12;letter-spacing:var(--track);margin:0;}
.body   {font-size:1.6rem;line-height:1.36;margin:0;}
/* 14, not 13 — this is the style every link and small button on the site is
   set in, from the header through the footer lists to the figures under the
   first screen. .label-xs stays where it is: it is the caption ABOVE a label,
   and if both were 14 the pair would stop reading as a pair. */
.label  {font-family:var(--sans);font-weight:500;font-size:1.4rem;line-height:1.2;letter-spacing:var(--track);margin:0;}
.label-xs{font-family:var(--sans);font-weight:500;font-size:1.1rem;line-height:1.3;letter-spacing:var(--track);margin:0;}
/* These carry the value in a custom property as well, because the reveal's
   end state is opacity:1 at the same weight and sits later in the file — which
   quietly put every dimmed line on the site back to full strength. */
.o35{--o:.35}.o45{--o:.45}.o5{--o:.5}.o55{--o:.55}.o6{--o:.6}
.o7{--o:.7}.o72{--o:.72}.o75{--o:.75}.o8{--o:.8}.o85{--o:.85}
.o35,.o45,.o5,.o55,.o6,.o7,.o72,.o75,.o8,.o85{opacity:var(--o);}

/* ---------- layout ------------------------------------------------------- */
.section{position:relative;padding:var(--block) 0;}
.wrap{width:100%;max-width:var(--page);margin:0 auto;padding:0 var(--margin);}
/* EVERY TRACK IN THIS FILE IS minmax(0,1fr), NEVER A BARE 1fr. A 1fr track
   carries an automatic minimum of its own content, so one word, one long
   address, one unbreakable string wider than its share does not overflow —
   it widens its track and takes that width from the other eleven, and the
   whole row slides off the grid to stay whole. The damage is invisible at
   1440, where everything fits, and shows up a few pixels at a time as the
   window narrows. Rigid tracks put the column back in charge: the grid
   holds and the content is what has to wrap. */
.grid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));column-gap:var(--gutter);}
.c1{grid-column-end:span 1}.c2{grid-column-end:span 2}.c3{grid-column-end:span 3}
.c4{grid-column-end:span 4}.c5{grid-column-end:span 5}.c6{grid-column-end:span 6}
.c7{grid-column-end:span 7}.c8{grid-column-end:span 8}.c9{grid-column-end:span 9}
.c10{grid-column-end:span 10}.c11{grid-column-end:span 11}.c12{grid-column-end:span 12}
.s2{grid-column-start:2}.s4{grid-column-start:4}.s5{grid-column-start:5}
.s7{grid-column-start:7}.s8{grid-column-start:8}.s10{grid-column-start:10}
.s11{grid-column-start:11}.s12{grid-column-start:12}

.rule{height:1px;background:currentColor;opacity:.2;border:0;margin:0;}
.head .rule{margin-bottom:2.3rem;}
/* the mockup splits every head row into two equal halves, second one flushed right */
.head-row{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);column-gap:var(--gutter);}
/* the right-hand half, not an only child that happens to be last as well */
.head-row > :last-child:not(:first-child){text-align:right;}

.section--light{background:var(--fog);color:var(--ink);}
.section--white{background:var(--white);color:var(--ink);}

/* The wall behind everything: one element, fixed to the window, painted once
   and then composited. The whole page is cut out of the same surface, so no
   two blocks can meet on a seam.

   It used to be a background on each textured section with attachment:fixed,
   which looked identical and cost the earth: a fixed background cannot be
   composited, so every section on screen repainted its share of it on every
   frame of every scroll — and in the footer, where the wordmark is a stack of
   additively blended copies of itself, that forced the whole stack to be
   recomposited with it. One fixed element has none of that: it never moves,
   so it never repaints.

   The dark blocks carry no background of their own, so the wall simply shows
   through them. The two light panels do cover it, and they keep a texture of
   their own at the weight the mockup draws — two fixed backgrounds on one
   page rather than nine. */
.wall{position:fixed;inset:0;z-index:0;pointer-events:none;
  background:url(../img/texture-wall.webp) 50% 50%/cover no-repeat;opacity:.10;}
.section--light.section--tex::after,.section--white.section--tex::after{
  content:"";position:absolute;inset:0;
  background:url(../img/texture-wall.webp) 50% 50%/cover no-repeat fixed;
  opacity:.19;pointer-events:none;
}
/* A flat panel a shade off the page is what the mockup draws, but between two
   of them it leaves a hard horizon across the width of the screen. The texture
   is what used to carry that join, so the panels stand down and it comes back. */


@media (max-width:767px){
  .wall{display:none;}
  .section--light.section--tex::after,.section--white.section--tex::after{display:none;}
}
/* The two fade bands are gone. They ran to solid ink inside a block, which is
   how you blend a photograph into the page — but with one continuous wall
   behind everything there is nothing left to blend, and a band of flat ink in
   the middle of that wall is itself the seam. */
.section > *{position:relative;z-index:1;}

/* ---------- progressive layer blur ---------------------------------------
   Figma ramps the blur radius from 0 to R down the element. CSS has no such
   filter, so main.js stacks copies of the content, each blurred one step
   further and masked to a soft band. The masks add up to 1 at every height,
   and plus-lighter makes the stack add rather than composite, which is what
   keeps the glyphs at full opacity instead of banding.
   Parameters come from data-pb="radius rampStart rampEnd".              */
.pb{position:relative;display:block;}
.pb__base{display:block;}
.pb--on > .pb__base{opacity:0;}
.pb__stack{position:absolute;left:0;top:0;width:100%;isolation:isolate;pointer-events:none;}
.pb__l{position:absolute;mix-blend-mode:plus-lighter;
  -webkit-mask-size:100% 100%;mask-size:100% 100%;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;}
.pb__i{display:block;}

/* ---------- page transition ----------------------------------------------
   Moving between pages of the site is not a load: the next page comes up from
   the floor, and it is scrollable the whole way.

   This used to be a cross-document view transition, with the card's photograph
   handed across to the hero as a shared element. Both are gone. The browser
   will not commit the arriving document until it has captured the one you are
   leaving, and measured on these pages that is 0.8 to 1.1 seconds in which
   nothing on screen can move; without it the same move lands in 0.1. The page
   now simply animates itself in, which costs nothing and looks the same.

   The animation fills backwards, not both: a transform left on <body> would
   go on being the containing block for everything fixed on the page.       */
@keyframes sotis-rise{from{transform:translateY(100%);}to{transform:none;}}
@media (prefers-reduced-motion:no-preference){
  /* off the mark in a hurry and a long quiet landing, rather than the even
     climb it reads as on a curve this gentle at the start */
  html.slid body{animation:sotis-rise 1.45s cubic-bezier(.16,1,.3,1) backwards;}
}

/* ---------- the move onto a project ---------------------------------------
   The cover is built in main.js and carries the destination's own hero
   photograph; see the note there. One fixed element moving on transform alone,
   which is the one place on this site a will-change would be honest — and it
   does not need one, because a transform transition on a fixed full-screen box
   is promoted anyway.                                                       */
.xgo{position:fixed;inset:0;z-index:150;overflow:hidden;background:var(--ink);
  transform:translateY(100%);}
.xgo.is-up{transform:none;transition:transform .9s cubic-bezier(.16,1,.3,1);}

/* The navigation is the one thing the cover cannot carry across — the
   photograph under it is identical, so a bar simply appearing on top of it is
   the only seam left. It comes up out of nothing instead. */
@keyframes sotis-lift{from{opacity:0;}to{opacity:1;}}
@media (prefers-reduced-motion:no-preference){
  html.covered .hero--p .nav{animation:sotis-lift .5s var(--ease-out) .05s backwards;}
}
@media (prefers-reduced-motion:reduce){
  .xgo{display:none;}
}

/* ---------- preloader -----------------------------------------------------
   It also buys the first screen its animation: the intersection observer's
   very first callback runs in the same rendering update as the class that
   arms the transition, so anything already on screen at boot would otherwise
   snap straight to its end state. main.js arms the reveals a frame after this
   lifts, by which point the initial state has been painted.                 */
.pre{position:fixed;inset:0;z-index:200;background:none;color:var(--fog);
  display:grid;place-items:center;align-content:center;row-gap:3.2rem;
  transition:opacity .6s var(--ease-out);}
/* The curtain is two slabs rather than one panel, because of how it ends. At
   rest they tile the screen with a pixel of overlap down the middle, so there
   is no seam to catch the light and it reads as one field of ink. At the end
   they leave sideways, and the gap opening between them IS the page — which is
   why the last thing the loader does is grow its middle mark into the line
   they part on. Two translates, both composited, nothing rasterised. */
.pre__half{position:absolute;top:0;bottom:0;width:calc(50% + 1px);
  background:var(--ink);transition:transform .86s var(--ease-out);}
.pre__half--l{left:0;}
.pre__half--r{right:0;}
.pre__mark,.pre__ticks{position:relative;z-index:1;}
.pre__mark{display:block;width:14rem;opacity:.9;
  transition:opacity .34s var(--ease);}
.pre__mark img{display:block;width:100%;}
/* the run of ticks is the ring's own edge, unrolled: it fills left to right
   as the images land, so the loader is made of the site's own marks */
.pre__ticks{display:flex;justify-content:space-between;align-items:stretch;
  width:min(36rem,62vw);height:1.3rem;}
.pre__ticks i{display:block;width:1px;background:var(--fog);opacity:.18;
  transition:opacity .4s var(--ease);}
.pre__ticks i.on{opacity:.95;}
/* the run has done its counting: the mark and the whole run go together, and
   the curtain then parts on an empty field — nothing drawn down the middle */
.pre--close .pre__mark{opacity:0;}
.pre--close .pre__ticks i{opacity:0;}
.pre--open .pre__half--l{transform:translate3d(-100%,0,0);}
.pre--open .pre__half--r{transform:translate3d(100%,0,0);}
/* the fade is still here, for reduced motion and for a page the cover brought
   in, which has no loader to end */
.pre--out{opacity:0;pointer-events:none;}
.pre--gone{display:none;}
/* THE BLACK FRAME BETWEEN THE COVER AND THE PAGE, and it was never a timing
   problem. main.js is the last tag in <body> and carries no defer, so the two
   slabs above are parsed and painted for the whole of the parse — a full
   screen of ink over a page that is already showing the right photograph —
   and only then does script reach the covered branch and hide them. The head
   knows before <body> exists: it reads the flag and sets this class there. So
   an arrival under the cover never paints a curtain it is about to throw
   away. */
html.covered .pre{display:none;}
html.pre-lock,html.pre-lock body{overflow:hidden;}

/* ---------- cursor --------------------------------------------------------
   A ring instead of an arrow, and over a photograph it opens into a soft lens
   — the same blur the headings carry, small enough to be felt not seen.
   Touch and coarse pointers keep the system cursor.                        */
.cur{display:none;}
@media (hover:hover) and (pointer:fine) and (prefers-reduced-motion:no-preference){
  html.cur-on,html.cur-on *{cursor:none;}
  /* above the opened gallery, which is 300 — under it the cursor is hidden by
     the overlay while the real one is still turned off, so there is none */
  .cur{display:block;position:fixed;left:0;top:0;z-index:400;pointer-events:none;
    transform:translate3d(-20rem,-20rem,0);will-change:transform;}
  /* A disc of glass: barely tinted, and carrying the same blur the headings
     come out of, so what is under it is softened rather than hidden. One size
     everywhere — it used to swell to four times this over a photograph, which
     is why it was so large on the first screen.

     Blending cannot give it an edge that reads on everything: the ring sits
     inside a fixed, transformed element and has nothing behind it to invert
     against. So the edge is chosen instead — dark over the light panels, light
     over the ink and the photographs — which main.js decides from whatever the
     pointer is over. */
  .cur__ring{display:block;width:1.4rem;height:1.4rem;margin:-.7rem 0 0 -.7rem;
    border:0;border-radius:50%;opacity:1;
    background:rgba(255,255,255,.14);
    -webkit-backdrop-filter:blur(.42rem) saturate(1.05);
    backdrop-filter:blur(.42rem) saturate(1.05);
    box-shadow:0 0 0 1px rgba(255,255,255,.92),0 0 9px rgba(0,0,0,.22);
    transition:width .35s var(--ease-out),height .35s var(--ease-out),
      margin .35s var(--ease-out),opacity .3s var(--ease),
      background-color .3s var(--ease),box-shadow .3s var(--ease);}
  /* on paper the same disc takes a dark edge instead */
  .cur--onlight .cur__ring{background:rgba(255,255,255,.2);
    box-shadow:0 0 0 1px rgba(12,13,12,.62),0 0 9px rgba(12,13,12,.1);}
  /* on something you can press it opens */
  .cur--tap .cur__ring{width:3rem;height:3rem;margin:-1.5rem 0 0 -1.5rem;}
  .cur--text .cur__ring{width:.2rem;height:2.4rem;margin:-1.2rem 0 0 -.1rem;
    border-radius:0;opacity:.85;background:var(--fog);backdrop-filter:none;
    -webkit-backdrop-filter:none;box-shadow:none;}
  .cur--onlight.cur--text .cur__ring{background:var(--ink);}
}

/* ---------- wordmark ----------------------------------------------------- */
.mark{display:block;width:6.4rem;}
.mark img{width:100%;}

/* ---------- nav ---------------------------------------------------------- */
/* the bar rides along the whole page: it hides on the way down and comes
   straight back on the way up, and once it has left the hero it turns solid
   so the links stay legible over the light sections */
/* An in-app browser lets the page run UNDER its address bar, so a panel that
   stops at top:0 leaves a strip above itself with the site showing through.
   The panel is therefore a layer of its own that starts well above the screen,
   and the hidden bar fades as well as moves — otherwise that same strip stays
   visible in Telegram after the bar has gone. */
.nav{position:fixed;top:0;left:0;right:0;z-index:40;isolation:isolate;
  padding:calc(3rem + var(--bar-inset)) 0 2rem;
  transition:transform .45s var(--ease-out),opacity .3s var(--ease),
             padding .35s var(--ease);}
.nav::before{content:"";position:absolute;z-index:-1;
  inset:calc(-1 * var(--bar-bleed)) 0 0;
  background:rgba(12,13,12,.5);
  -webkit-backdrop-filter:blur(1.6rem) saturate(1.1);
  backdrop-filter:blur(1.6rem) saturate(1.1);
  opacity:0;transition:opacity .35s var(--ease);pointer-events:none;}
/* where the backdrop cannot be filtered, fall back to something solid enough */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .nav::before{background:rgba(12,13,12,.92);}
}
.nav--pin{padding:calc(1.5rem + var(--bar-inset)) 0 1.5rem;color:var(--fog);}
.nav--pin::before{opacity:1;}
.nav--hide{transform:translateY(calc(-100% - var(--bar-bleed)));
  opacity:0;pointer-events:none;}
/* minmax(0,1fr), not 1fr: a 1fr track is allowed to grow past its share to
   fit its content, and one word too wide for its cell will quietly push all
   twelve columns out of line rather than overflow. Rigid tracks keep the row
   on the grid and let the word be the thing that has to give. */
.nav .wrap{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));column-gap:var(--gutter);align-items:start;}
.nav .mark{grid-column:1 / span 3;}
.nav .wrap > a:nth-child(2){grid-column:6 / span 2}
.nav .wrap > a:nth-child(3){grid-column:8 / span 2}
.nav .wrap > a:nth-child(4){grid-column:10 / span 2}
.nav .wrap > a:nth-child(5){grid-column:12 / span 1}
.nav .lang{grid-column:12 / span 1;justify-self:end;text-align:right;}
/* Below 1024 the type ramp has a floor — labels stop shrinking at 13.8px —
   while the columns go on narrowing, and CONTACT outgrows a single one. It
   gets its pair back there and is set against the right margin, which is the
   edge the mockup's own box ends on; at 1024 and above the word fits column
   twelve on its own and stands at its head, exactly as the frame draws it. */
@media (max-width:1023px){
  /* FOUR PAIRS THAT DO NOT TOUCH. Below 1024 the last label needs a pair of
     columns to hold its word, and giving it 11/span 2 while MATERIAL still
     held 10/span 2 claimed column eleven twice. Grid does not complain about
     an overlap - it moves the loser to a second row, which is where CONTACT
     went. Shifted the whole row down one pair instead: 5, 7, 9, 11, with the
     last flushed to the margin the row ends on. */
  .nav .wrap > a:nth-child(2){grid-column:5 / span 2}
  .nav .wrap > a:nth-child(3){grid-column:7 / span 2}
  .nav .wrap > a:nth-child(4){grid-column:9 / span 2}
  .nav .wrap > a:nth-child(5){grid-column:11 / span 2;justify-self:end;}
}
/* READ OFF THE FRAME, NOT GUESSED AT. In the mockup the four labels are text
   boxes two columns wide, left-aligned, standing at columns 6, 8 and 10 — and
   the last one is a single column at 12, so the row ends on the right margin
   with the word itself rather than with an empty half-cell. Centring the word
   in a pair that began one column earlier put every label 35px to the left of
   its line: an even row, on nothing. The box belongs to the grid, the word
   sits at its head, and the rule under it belongs to the word — which is why
   the link is only ever as wide as what it says. */
.nav a.label{position:relative;width:max-content;justify-self:start;
  padding-top:.5rem;opacity:.9;
  transition:opacity .25s var(--ease);white-space:nowrap;}

/* the rule grows out of the centre of the word to both ends at once */
.nav a.label::after,.footer__nav a::after,.footer__social li::after,
.footer__legal a::after{
  content:"";position:absolute;left:0;right:0;bottom:-.16em;height:1px;
  background:currentColor;transform:scaleX(0);transform-origin:50% 50%;}
.footer__nav a,.footer__legal a{position:relative;display:inline-block;}
/* these are set in the markup with no whitespace between them, so as inline
   blocks ARE.NA and JOURNAL simply ran together on one line */
.footer__social li{position:relative;display:block;width:max-content;margin-left:auto;}
@media (hover:hover) and (pointer:fine){
  .nav a.label::after,.footer__nav a::after,.footer__social li::after,
  .footer__legal a::after{transition:transform .45s var(--ease-out);}
  .nav a.label:hover{opacity:1;}
  .nav a.label:hover::after,.nav a.label:focus-visible::after,
  .footer__nav a:hover::after,.footer__nav a:focus-visible::after,
  .footer__legal a:hover::after,.footer__legal a:focus-visible::after,
  .footer__social li:hover::after{transform:scaleX(1);}
}
.nav__menu{display:none;}
/* the phone header is the wordmark, the language and one button; the extra
   weight is because .btn further down sets display on a single class too */
.nav .nav__cta{display:none;font-size:1.6rem;line-height:1.12;}

/* ---------- buttons ------------------------------------------------------ */
/* straight off the v3 component: the icon is centre-aligned with the label, the
   rule sits flush under the 112% line box (Figma draws the stroke inside the
   1px of bottom padding), and the gap and icon are 0.778em and 0.611em of the
   label. The large SEND button is a different instance: 0.304em and 0.696em. */
.btn{--gap:.778em;--ico:.611em;--pb:0rem;
  position:relative;display:inline-flex;align-items:center;gap:var(--gap);
  font-family:var(--serif);font-weight:700;font-size:2.8rem;line-height:1.12;
  letter-spacing:var(--track);padding-bottom:var(--pb);border-bottom:1px solid currentColor;}
.btn__word{display:block;}
.btn .plus{width:var(--ico);height:var(--ico);flex:none;}
/* the arrow waits at the head of the button, in the gap the word leaves behind,
   on the same centre line as the plus it replaces */
.btn__arrow{position:absolute;left:0;top:0;bottom:var(--pb);margin-block:auto;
  width:var(--ico);height:calc(var(--ico) * 0.7222);opacity:0;pointer-events:none;}
.btn .plus svg,.btn__arrow svg{display:block;width:100%;height:100%;}
.btn--lg{--gap:.304em;--ico:.696em;font-size:4.6rem;line-height:1;}

/* The whole gesture runs on one number. --dir is 1 going forward and -1
   coming back, so the mirrored button is the same button read the other way
   round rather than a second set of rules that can drift out of step. */
.btn{--dir:1;}
.btn--back{--dir:-1;flex-direction:row-reverse;}
.btn--back .btn__arrow{left:auto;right:0;}
.btn--back .btn__arrow svg{transform:scaleX(-1);}
@media (hover:hover) and (pointer:fine){
  .btn__word{transition:transform .3s var(--ease-out);}
  .btn .plus{transition:opacity .18s var(--ease-out),transform .3s var(--ease-out);}
  .btn__arrow{transform:translateX(calc(-.45em * var(--dir)));
    transition:opacity .22s var(--ease-out),transform .3s var(--ease-out);}
  .btn:hover .btn__word,.btn:focus-visible .btn__word{
    transform:translateX(calc((var(--ico) + var(--gap)) * var(--dir)));}
  .btn:hover .plus,.btn:focus-visible .plus{opacity:0;transform:translateX(calc(.5em * var(--dir)));}
  .btn:hover .btn__arrow,.btn:focus-visible .btn__arrow{opacity:1;transform:translateX(0);}
}

.arrows{display:flex;gap:1.2rem;}
.arrow{width:4.8rem;height:4.8rem;border-radius:50%;border:1px solid currentColor;
  display:grid;place-items:center;opacity:.8;
  transition:opacity .25s var(--ease),background-color .25s var(--ease),color .25s var(--ease);}
.arrow .arrow__sm{display:block;width:1.3rem;height:.94rem;}
.arrow--prev .arrow__sm{transform:scaleX(-1);}
.arrow:hover{opacity:1;background:var(--fog);color:var(--ink);}
.arrow__lg{display:none;}
.arrow:disabled{opacity:.3;pointer-events:none;}

/* ---------- the rule arrow ------------------------------------------------
   552:444 — the button component with its label taken out, so what is left is
   the icon and the rule the word normally sits on. The glyph and the rule are
   two elements rather than two paths in one drawing, because on hover only
   the glyph travels: it leaves the way the button points and a second copy
   comes in from behind, through a window exactly its own size. The prev
   button is the next button mirrored, which turns the motion round with it —
   out to the left, in from the right — for nothing.

   Every number is a ratio of the width, off the 34.6514 x 40.7378 frame:
   the glyph is .78658 of it tall, the air under it .28235, the rule .10668. */
.arrow--rule{--aw:3.4651rem;
  width:var(--aw);height:calc(var(--aw) * 1.05356 + 1px);
  display:block;border:0;border-radius:0;background:none;opacity:.85;
  transition:opacity .25s var(--ease);}
.arrow--rule:hover{opacity:1;background:none;color:inherit;}
.arrow--rule .arrow__sm{display:none;}
.arrow--rule .arrow__lg{display:block;width:100%;}
.arrow--rule.arrow--prev .arrow__lg{transform:scaleX(-1);}
.arrow__ico{position:relative;display:block;width:100%;
  height:calc(var(--aw) * .78356);overflow:hidden;}
.arrow__ico svg{position:absolute;left:0;top:0;width:100%;height:100%;}
.arrow__ico svg + svg{transform:translateX(-170%);}
/* 1px, which is what every other button on this site puts under its word.
   It was drawn at the arrow's own weight, which made it four of them. */
.arrow__rule{display:block;margin-top:calc(var(--aw) * .27);
  height:1px;background:currentColor;}
@media (hover:hover) and (pointer:fine){
  .arrow__ico svg{transition:transform .4s var(--ease-out);}
  .arrow--rule:hover .arrow__ico svg,
  .arrow--rule:focus-visible .arrow__ico svg{transform:translateX(170%);}
  /* a breath behind the one leaving, so it reads as one arrow replacing
     another rather than two sliding past each other */
  .arrow--rule:hover .arrow__ico svg + svg,
  .arrow--rule:focus-visible .arrow__ico svg + svg{transform:none;transition-delay:.08s;}
}

/* ---------- hero (shared by home and project pages) ----------------------- */
/* --vh is written once by the script in the head. An in-app browser counts
   every viewport unit from its current height, so svh moves with the address
   bar there exactly as vh does — the measured value is the only stable one. */
.hero{height:var(--vh,100svh);min-height:var(--vh,100svh);padding:0;overflow:hidden;}
/* The phone gets its own crop through <picture>, and on a hero with a single
   photograph that wrapper must not become the containing block for the
   absolutely positioned image — hence display:contents.

   The slides on the first screen are the exception, and the exception has to
   be written in, because .hero picture is (0,1,1) and .hero__shot is (0,1,0):
   left alone the wrapper wins, every slide computes display:contents, and a
   box that does not exist cannot be positioned, cannot hold an opacity and
   cannot fade. All three photographs painted on top of each other and the last
   one in the markup was the only one anybody ever saw. A slide is itself
   absolutely positioned over the whole window, so it is the containing block
   the image wants anyway. */
.hero picture:not(.hero__shot){display:contents;}
/* The long drift crops the picture harder, and the first screen is a frame
   someone chose — it travels, but not the whole way.

   A project hero travels less again. Its photograph is 16:9 going into a box
   that is 1.22 wide, and the overhang is what makes that box so tall: at 30%
   only 68% of the width survived, which is why the houses were cropped in and
   sitting low. At 16% it is 78%, and the building reads where it was framed. */
.hero{--pxr:30%;}
.hero--p{--pxr:16%;}
/* three photographs stacked in the first screen, crossfading on their own.
   Each keeps its own picture element so the phone takes the tall crop, and all
   three are movers in the same window, so the parallax carries them together. */
.hero__shots{position:absolute;inset:0;}
.hero__shot{position:absolute;inset:0;display:block;opacity:0;
  transition:opacity 1.2s var(--ease);}
.hero__shot.is-on{opacity:1;}
/* Three bars, the standing one filling over the three seconds it is up. The
   fill is a scaleX on a child rather than a width, so it is composited, and it
   resets with no transition on the way out — the bar that has just handed over
   should be empty at once, not drain back. */
.hero__ticks{position:absolute;left:var(--margin);
  bottom:calc(var(--margin) + 5.4rem);z-index:3;display:flex;gap:.7rem;}
/* the slides are broad daylight now, and a quarter-strength white mark laid
   over a bright sky is not a mark at all — the empty bar is carried up and
   given the same thin shadow the small type gets */
.hero__tick{display:block;width:3rem;height:.15rem;padding:0;border:0;
  border-radius:.1rem;background:rgba(255,255,255,.42);position:relative;
  overflow:hidden;cursor:pointer;-webkit-appearance:none;appearance:none;}
.hero__tick i{position:absolute;inset:0;display:block;background:#fff;
  transform:scaleX(0);transform-origin:left center;}
.hero__tick.is-on i{transform:scaleX(1);transition:transform 3s linear;}
@media (prefers-reduced-motion:reduce){.hero__ticks{display:none;}}
.hero__img{position:absolute;left:0;top:calc(var(--pxr) / -2);width:100%;
  height:calc(100% + var(--pxr));object-fit:cover;object-position:center;z-index:0;}
/* the first screen's photographs are landscape now, so the crop is across
   the width and a vertical bias would do nothing */
.hero--home .hero__img{object-position:center;}
.hero__scrim-t{position:absolute;left:0;right:0;top:-1px;pointer-events:none;z-index:1;}
/* the foot of the screen is drawn over the cut-out house, the way the frames
   stack it — the facts have to read against whatever the building ends on */
.hero__scrim-b{position:absolute;left:0;right:0;pointer-events:none;z-index:4;}
/* The three new frames all state .38 at the head and let it run further down
   instead of stacking it: 634 of 900 on AKATSIA, 456 on BOROVA, and nothing at
   all on LOZA, which the pale slide already turns off. One ramp at .38 over
   70% is AKATSIA's own, and it is a much lighter lid at the top than the short
   steep one it replaces. */
.hero__scrim-t{top:0;height:70.4%;background:linear-gradient(to bottom,rgba(0,0,0,.38),rgba(0,0,0,0));}
/* a percentage height against a viewport height that is rarely a whole
   number leaves a hairline of undimmed photograph along the edge */
.hero__scrim-b{bottom:-1px;height:calc(46.7% + 1px);
  background:linear-gradient(to top,rgba(0,0,0,.512),rgba(0,0,0,0));}
/* Three photographs take turns under the same words, so the ground under the
   type is held a little deeper than a single frame would need. Only a little:
   all three are dusk frames that arrive dark already — 65, 90 and 70 out of
   255, and under 50 in the band the headline sits over. The weight these
   carried was set for the noon photographs they replaced, and laid over these
   it stopped being a scrim and became a lid. Only above the phone: the phone
   has its own, and a class with two names would have quietly overridden them
   from here whatever the order of the media queries. */
@media (min-width:768px){
  /* The frames from the designs are broad daylight: measured in the band the
     headline actually occupies they read 133, 191 and 120 out of 255, and the
     facts row sits over 126, 94 and 166. A single falling ramp cannot hold
     anything at a quarter of the way down without being black at the top, so
     the top one carries a middle stop and keeps about .37 across the headline
     — enough to bring the worst of the three to 120, and nothing like a lid. */
  /* Read off the nodes, not the renders. All three state the same gradient —
     rgba(0,0,0,.385) falling to nothing — and then set the LAYER's opacity per
     slide: AKATSIA .5 over 634 of 900, LOZA 0, BOROVA 1 over 456. So the first
     slide is .385 x .5 = .1925, which is half the lid it was carrying, and the
     third keeps the full weight over a shorter run. */
  .hero--home .hero__scrim-t{height:70.4%;
    background:linear-gradient(to bottom,rgba(0,0,0,.1925),rgba(0,0,0,0));}
  .hero--home.is-deep .hero__scrim-t{height:50.7%;
    background:linear-gradient(to bottom,rgba(0,0,0,.385),rgba(0,0,0,0));}

  /* The headline is wider than the six columns it sits in — the frames give it
     795 against a 700 slot, and PROJECTS alone measures 770 at this size. It
     runs to seven columns, which still stops well short of the lede at 1078. */
  /* The frames hang the headline 47 above the block it belongs to, so its
     last line meets the roofline rather than disappearing behind it. Only the
     headline moves — the lede stays on the block's own top edge, which is
     where the frames put it too. */
  /* The lift is a relative offset, not a margin. As a margin it was a grid
     item's margin, and the row sizes itself off the item's margin box — so
     part of the pull went into the row's own height instead of moving the
     words, and how much depended on which of the two terms in the min() was
     binding. At 1440 it happened to apply in full and at 1920 only a quarter
     of it did, which is why the last line cleared the roof on the bigger
     screen. Offsetting after layout moves the headline and nothing else. */
  /* The frames line the headlines up by the top of their LETTERS, not by the
     top of the text box. Their three text nodes sit at 142, 147 and 153, and
     those three numbers are one common cap line at 160 less the half-leading
     each size happens to carry — which is why stacking the boxes at a shared
     top, as this did, left three different sizes starting at three different
     heights. Written as an em it needs no table: -.11868em is the distance
     from an Arimo line box's top down to the capitals at leading .92, that is
     (.92 - 1.1172)/2 + .90527 - .68799 out of the face's own metrics, and it
     holds whatever size the line is given. The block itself is that cap line,
     five pixels under the row the lede shares with it. */
  /* THREE PROJECTS measures 863 at 105, and seven columns are 818 — the line
     wrapped. The frames give this text box 958, which lands between eight
     columns and nine; nine it is, and the type still stops well short of the
     lede because the box is wider than anything set in it. */
  /* Nine columns is 9x110 + 8x8 = 1054, and 1054 is exactly the width the
     frame gives the longest headline. It stays. */
  .hero--home .hero__head{grid-column:1 / span 9;
    position:relative;top:min(.5rem,.556vh);line-height:.8;}
  /* The leading came down from .92 to the frames' 80%, so the cap-line
     constant moves with it: (.8 - 1.1172)/2 + .90527 - .68799 = .058686 out of
     Arimo's own metrics, where at .92 it was .11868. The three sizes still
     hang their capitals on one line. 3 x .8 x 163 = 391 against the frame's
     390, 2 x .8 x 103 = 165 against 164, 2 x .8 x 112 = 179 against 180. */
  .hero--home .hero__head-l{position:relative;top:-.058686em;}
  .hero--home .hero__head-l:not(:first-child){position:absolute;top:-.058686em;}
  /* The rem here follows the viewport WIDTH, and the screen it has to fit in
     is a height. At 1440 x 900 the frames' 156.855 is 17.44vh and the two agree;
     on a 2560 x 1080 screen the width rule alone asks for 279 against a screen
     that grew by a fifth, and three lines of it eat seven tenths of the
     picture. Taking whichever of the two is smaller reproduces the frames
     exactly on anything near 16:10 and quietly holds the line on the rest. */
  .hero__head-l--xl{font-size:min(16.3rem,18.111vh);}   /* 163            */
  .hero__head-l--l {font-size:min(11.2rem,12.444vh);}   /* 112            */
  .hero__head-l--m {font-size:min(10.3rem,11.444vh);}   /* 103            */
  /* No shadow on the lede. A 14px glow under 13px type is a smear at that
     size — it reads as the text being out of register rather than as lift,
     and it is the first thing you see on a bright slide. The ramp above is
     what holds this corner now. */

  /* ---- a slide that opens on white sky -----------------------------------
     Measured on the photographs themselves, before any scrim: the top of the
     LOZA slide is 235 out of 255 behind the nav and 233 behind the lede. No
     amount of black laid over that keeps white type legible without turning
     the sky into slate — at the weight it would take, the photograph is gone.

     So the top of the screen turns over instead. The band washes toward bone
     rather than ink and the type goes dark, which is the right way round for
     a white ground. Only the top: the same slide reads 106 along the bottom,
     where the facts sit, so that end keeps its dark ramp and white type. The
     two meet in the middle of the picture, where neither is needed.

     The headline keeps its blur stack through this. The stack reconstructs an
     element by adding masked copies under plus-lighter, and with premultiplied
     alpha that sums to the glyph's own colour whatever that colour is — it is
     additive compositing, not a lightening effect. */
  .hero--home.is-pale .hero__scrim-t{background:none;}
  .hero--home.is-pale .hero__lede{opacity:.7;}
  .hero--home.is-pale .hero__side{color:var(--ink);}
  /* the page turning pale and the HEADLINE turning dark are two decisions, not
     one: BOROVA opens on a white sky but its own cut-out is charcoal larch and
     the words run straight into it */
  .hero--home.is-ink-head .hero__head-l{color:#1C1C1C;}
  /* BOROVA's own headline is set in white rather than the site's bone */
  .hero--home.is-deep .hero__head-l{color:#fff;}
  /* The nav turns over only while it is still sitting on the photograph. Once
     it pins it brings its own near-black backing with it, and dark labels on
     that would be the same mistake the other way round.

     The wordmark is an <img> of an SVG with fill="white" written into it, so
     colour cannot reach it; brightness(0) takes any colour to black, which is
     what it has to be against bone. */
  .hero--home.is-pale .nav:not(.nav--pin){color:var(--ink);}
  .hero--home.is-pale .nav:not(.nav--pin) .mark img{filter:brightness(0);}
  /* and 420 of 900 at .55 under the foot — this one is drawn over the cut-out
     house, so the facts read against whatever the building ends on */
  .hero--home .hero__scrim-b{height:46.7%;background:linear-gradient(to top,rgba(0,0,0,.55),rgba(0,0,0,0));}
  /* this one is a portrait going into a landscape window, so the crop is taken
     out of its height — and the house lives low in the frame, where a centred
     cut leaves it hanging off the bottom edge */
  /* ---- holding the house against the words at any size -------------------
     At 1440 x 900 the box is exactly the frames' own 1.231, nothing crops, and
     the roofline measures 506 — the same 506 the Figma frame puts it at. Wider
     than that and the box outruns the photograph, its height is cut, and
     because the picture is then scaled by WIDTH the roof drifts down while the
     headline, capped against the height, stays where it is. That drift is the
     whole complaint: by 1920 the last line had cleared the roof entirely.

     object-position pins exactly one point — the source fraction you name
     lands on that same fraction of the box. Name the roofline and the roofline
     is what holds, at -0.15H + f * 1.3H, the same share of the screen's height
     however wide it gets. Each slide carries its own, read off the alpha edge
     of its own cut-out across the span the headline runs through. */
  .hero__shots .hero__shot:nth-child(1) .hero__img,
  .hero__cuts  .hero__cut:nth-child(1)  .hero__img{object-position:50% 54.8%;}
  .hero__shots .hero__shot:nth-child(2) .hero__img,
  .hero__cuts  .hero__cut:nth-child(2)  .hero__img{object-position:50% 37%;}
  .hero__shots .hero__shot:nth-child(3) .hero__img,
  .hero__cuts  .hero__cut:nth-child(3)  .hero__img{object-position:50% 40%;}

  /* and the words have to be a share of that height too, or they sit at 0.12
     of the screen on one monitor and 0.139 on the next */
  .hero--home .hero__copy{top:min(15.5rem,17.22vh);}
}
/* a project hero now opens on the same dusk frame, so it is lightened with
   the first screen rather than left at the weight the noon photograph needed */
.hero--p .hero__scrim-t{height:40%;
  background:linear-gradient(to bottom,rgba(0,0,0,.30),rgba(0,0,0,.09) 46%,rgba(0,0,0,0));}
.hero--p .hero__scrim-b{height:46.7%;background:linear-gradient(to top,rgba(0,0,0,.46),rgba(0,0,0,0));}
/* The body no longer stacks its children as one. The headline goes under the
   cut-out houses and everything else stays over them, which only works if this
   is not a stacking context of its own — hence z-index:auto, and each piece
   carrying its own level in the hero's context instead. */
.hero__body{position:absolute;inset:0;z-index:auto;}
.hero__cuts{position:absolute;inset:0;z-index:3;pointer-events:none;}
.hero__cut{position:absolute;inset:0;display:block;opacity:0;
  transition:opacity 1.2s var(--ease);}
.hero__cut.is-on{opacity:1;}
.hero__copy{position:absolute;top:15.5rem;left:0;right:0;}   /* 30 nav + 23 + 102 */
/* Each photograph brings its own line. They are three separate elements, built
   once and then only faded, rather than one element rewritten every three
   seconds — the blur stack behind a headline is six composited copies, and
   rebuilding that on a timer is exactly the kind of work that shows up as a
   stutter. The first sits in flow and gives the block its height; the other
   two stand on top of it, so all three must be two lines. */
.hero__head{position:relative;z-index:2;}
/* The headline arrives the way every other block of type on this site does:
   a line at a time, rising out of its own blur, on the same 200ms between
   lines. The words are literally the same .wd spans the reveal uses elsewhere
   — what stood here was a crossfade of the whole block, which was the one
   piece of motion on the page that belonged to nothing else on it.

   The block's own opacity still decides which of the three is showing, with no
   transition on it at all. That matters twice: a reader on reduced motion, who
   gets the words forced visible, still sees one headline rather than three
   stacked on each other; and the three can no longer bleed through one another
   mid-turn, which is what doubled THREE over THREE before. */
/* A column, not a plain block, and only because of margin collapsing. Each
   .hl is let out by .12em and pulled back by the same margin so the clip box
   clears the capitals without moving anything. In normal flow the adjoining
   -.12em margins COLLAPSE to one -.12em instead of summing to -.24em, which
   put every line .12em low — the leading read .92 with line-height set to .8 —
   and the first child's margin collapsed straight out through the parent and
   took the whole headline 19.5px up with it. Flex items never collapse
   margins, so the two cancel exactly as they are meant to: the pitch is .8em
   and the block is 3 x .8 x 163 = 391 against the frame's 390. */
.hero__head-l{display:flex;flex-direction:column;opacity:0;}
.hero__head-l.is-on,.hero__head-l.is-out{opacity:1;}
/* By the line, not by the word. Counted on the page: the word split put 91
   spans under a transition once the blur stack had cloned each of them seven
   times; by the line it is 49. That alone is not the whole of the stutter, but
   it is what buys the blur — which was switched off entirely before, because
   a filter on ninety-one elements costs frames. It now animates on three
   elements per headline: the sharp base layer only. */
/* THE replacement for the blur, and the whole of it.

   Each LINE is a box with overflow hidden, and each WORD inside it is its own
   inline box that starts a line below and rides up into view; going out they
   carry on past the top. The boxes never move, so nothing is ever rasterised —
   one transform per word, nine words at the most, all of it on the compositor.

   The delay is set per word in the markup and runs across the whole headline
   rather than restarting at each break, so the words arrive left to right the
   way they are read. 60ms is small enough that the line still lands as a line
   and large enough to see each word leave the floor.

   The padding is what keeps the caps off the clip: the leading is .8, shorter
   than the glyphs, so the box is let out by .12em either way and pulled back
   by the same margin. */
.hero__head-l .hl{display:block;overflow:hidden;padding:.12em 0;margin:-.12em 0;}
.hero__head-l .hl > i{display:inline-block;font-style:normal;
  transform:translateY(calc(100% + .3em));
  transition:transform .74s var(--ease-out);transition-delay:var(--d,0s);}
/* No filter here, and no will-change. Animating blur on type 146px tall is a
   full-size raster every frame, the stack is seven copies of it, and promoting
   each line to its own layer on top of that is what made the whole page — the
   slide between pages included — come apart.

   The blur still shows, and it costs nothing: the stack's clones ARE the blur,
   so the sharp base simply arrives later than they do. For the first half of
   the move the words are only their blurred copies; the base catches up and
   they come into focus. Opacity and transform all the way down. */
.hero__head-l.is-on .hl > i{transform:none;}
/* leaving is quicker and all at once — the lines have been read already, and
   staggering them out only holds the photograph up */
/* leaving, the words keep going the way they came — up and out of their own
   boxes, and all together: the line has been read, and staggering it out only
   holds the next photograph up */
/* 112% of its own height is not the height of the box it sits in: .hl is
   let out by .12em either way so the ink can overflow the line, and the word
   has to clear that too or the tops of the letters stay on screen — which is
   exactly what was showing. Its own height plus the air, both ends. */
.hero__head-l.is-out .hl > i{transform:translateY(calc(-100% - .3em));
  transition-duration:.34s;transition-delay:0s;}
/* the turn-over rides the same .9s as the crossfade, so the type changes hands
   while the photograph behind it is changing, and reads as one move */
.hero--home .nav,.hero--home .hero__side{
  transition:color 1.2s var(--ease),opacity 1.2s var(--ease);}
/* THE ORDER THE FIRST SCREEN ARRIVES IN, and the reason the loader ends the way
   it does. The curtain parts on the photograph; then the bars and the headline
   move, because they are what it parted to show; then the type beside and under
   them, a beat behind. Before this the deck started on onLanded, which on the
   home page is already true while the curtain is still down — so the headline
   had finished rising and the first bar had begun filling before you could see
   either, and all that was left to watch was everything else.

   main.js adds is-live, and backs it with a timer: nothing in here is allowed
   to be the reason the first screen stays blank. */
.hero--home .hero__side,.hero--home .hero__facts .fact{
  opacity:0;transform:translateY(1.6rem);}
.hero--home.is-live .hero__side,.hero--home.is-live .hero__facts .fact{
  opacity:1;transform:none;
  transition:opacity .9s var(--ease-out),transform .9s var(--ease-out);}
.hero--home.is-live .hero__side{transition-delay:.34s;}
.hero--home.is-live .hero__facts .fact:nth-child(1){transition-delay:.46s;}
.hero--home.is-live .hero__facts .fact:nth-child(2){transition-delay:.52s;}
.hero--home.is-live .hero__facts .fact:nth-child(3){transition-delay:.58s;}
.hero--home.is-live .hero__facts .fact:nth-child(4){transition-delay:.64s;}
@media (prefers-reduced-motion:reduce){
  .hero--home .hero__side,.hero--home .hero__facts .fact{
    opacity:1;transform:none;}
}
/* the lines carry their own motion now, so only the colour crosses over */
.hero--home .hero__head-l{transition:color 1.2s var(--ease);}
.hero--home .mark img{transition:filter 1.2s var(--ease);}
.hero--home .hero__scrim-t{transition:background 1.2s var(--ease);}
.hero__head-l:not(:first-child){position:absolute;left:0;top:0;width:100%;}
.hero__lede{margin-bottom:2.7rem;}
.hero__side{position:relative;z-index:4;}
.hero__facts{position:absolute;left:0;right:0;bottom:var(--margin);z-index:5;}
.fact .label-xs{margin-bottom:.7rem;}

/* project hero: the name sits on the floor of the frame, just above the facts */
/* This was 2, and the bottom scrim is 4 — so the name of the house, its
   place and its four figures were all being painted over by the very ramp
   that is there to let them read. On the phone, where that ramp now reaches
   .86, they were most of the way to invisible. The project hero has no cut-out
   over it, so the foot simply belongs above the scrim, where the first
   screen's own facts row has been all along. */
.phero__foot{position:absolute;left:0;right:0;bottom:var(--margin);z-index:5;}
/* one line now, so it takes the width instead of half of it */
.phero__meta{display:block;margin-bottom:.6rem;}
.phero__title{margin-bottom:5.6rem;}

/* ---------- 02 manifesto -------------------------------------------------- */
.manifesto{min-height:78rem;display:grid;place-items:center;padding:12rem 0;}
.manifesto__in{display:grid;place-items:center;}
.facts-m,.manifesto__rule{display:none;}
.ring{position:relative;width:min(70rem,90vw);aspect-ratio:700/540;display:grid;place-items:center;}
.ring__side{position:absolute;display:flex;}
.ring__side--top,.ring__side--bottom{left:50%;transform:translateX(-50%);width:48.7%;height:1.3rem;justify-content:space-between;}
.ring__side--top{top:0}.ring__side--bottom{bottom:0}
.ring__side--left,.ring__side--right{top:50%;transform:translateY(-50%);height:63.1%;width:1.3rem;flex-direction:column;justify-content:space-between;}
.ring__side--left{left:0}.ring__side--right{right:0}
.ring__side--top i,.ring__side--bottom i{width:1px;height:1.3rem;}
.ring__side--left i,.ring__side--right i{height:1px;width:1.3rem;}
.ring i{display:block;background:var(--fog);opacity:.2;transition:opacity .4s var(--ease);}
.ring i.on{opacity:.95;}
.ring__text{max-width:58.2rem;text-align:center;}

/* ---------- 03 index ------------------------------------------------------ */
/* No band over the index: the three photographs follow the manifesto. The
   card stands 50 past the window at each end, so an anchor that lands on its
   top edge leaves the name 50 under the fold — this drops the landing by the
   same 50 and the first card is framed exactly as the other two are. */
.index{padding:0;scroll-margin-top:-5rem;}
/* A project stands 50 past the window at the head and 50 at the foot, so it
   never sits still in the frame and the three of them run as one movement. The
   name carries the same 50 down its own block, which is what keeps it where it
   has always been on the screen rather than 50 below the fold. */
.card{position:relative;display:block;height:calc(var(--vh,100svh) + 10rem);overflow:hidden;}
.card__img{position:absolute;left:0;top:calc(var(--pxr) / -2);width:100%;
  height:calc(100% + var(--pxr));object-fit:cover;}
.card__scrim{position:absolute;left:0;right:0;bottom:0;height:52%;
  background:linear-gradient(to top,rgba(6,7,6,.86),rgba(6,7,6,0));pointer-events:none;}
/* 26 to the foot of the window, which is 50 more to the foot of the card */
.card__meta{position:absolute;left:0;right:0;bottom:7.6rem;}
/* the counter is gone, so this is one line and not a pair */
.card__row{margin-bottom:.6rem;}

.gal .px[data-group]{cursor:pointer;}

/* ---------- the gallery, opened ---------------------------------------------
   The track is a native horizontal scroller with scroll-snap, and the scroll
   position is the only state there is: a swipe, a sideways wheel and an arrow
   all end by setting scrollLeft, and the index is read back out of it. Nothing
   to keep in sync, and the gesture is the platform's own.
   -------------------------------------------------------------------------- */
.lb{position:fixed;inset:0;z-index:300;
  display:grid;grid-template-rows:auto 1fr auto;
  /* an implicit column takes min-content as its minimum, and one long caption
     would then stretch the whole overlay wider than the screen */
  grid-template-columns:minmax(0,1fr);
  background:rgba(9,10,12,.96);
  -webkit-backdrop-filter:blur(1.4rem);backdrop-filter:blur(1.4rem);
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity .5s var(--ease-out),visibility 0s linear .5s;}
.lb.is-open{opacity:1;visibility:visible;pointer-events:auto;transition-delay:0s;}

.lb__bar,.lb__foot{display:flex;align-items:center;justify-content:space-between;
  gap:2.4rem;min-width:0;
  opacity:0;transform:translateY(.8rem);
  transition:opacity .55s var(--ease-out) .08s,transform .55s var(--ease-out) .08s;}
.lb.is-open .lb__bar,.lb.is-open .lb__foot{opacity:1;transform:none;}
.lb__bar{padding:calc(2.6rem + var(--bar-inset)) var(--margin) 1.8rem;}
.lb__foot{padding:1.8rem var(--margin) calc(2.6rem + env(safe-area-inset-bottom,0px));}
.lb__where{display:flex;align-items:baseline;gap:1.6rem;margin:0;min-width:0;}
.lb__where span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;}
.lb__cap{margin:0;max-width:44rem;min-width:0;}

/* the row the picture lives in takes whatever the other two leave */
.lb__stage{position:relative;min-height:0;}
/* A chain of percentage heights through a 1fr row is the shape that quietly
   fails to resolve in an in-app browser. inset:0 pins the box to the real
   pixel size of its container and leaves nothing to resolve. */
.lb__track{position:absolute;inset:0;
  display:grid;grid-auto-flow:column;grid-auto-columns:100%;
  overflow-x:auto;
  /* a box with aspect-ratio rounds a pixel proud of its frame on a fractional
     scale, and that alone is enough to catch the wheel in the track */
  overflow-y:hidden;
  scroll-snap-type:x mandatory;overscroll-behavior:contain;
  scroll-behavior:smooth;scrollbar-width:none;
  /* no padding, ever: clientWidth counts the scroller's padding while the
     100% column resolves against the content box, so every target misses */
}
.lb__track::-webkit-scrollbar{display:none;}
.lb__slide{position:relative;scroll-snap-align:center;min-width:0;}
/* A room used to be swapped by emptying the track and filling it again, which
   is a cut. The run leaves the way the arrow points and the next one arrives
   from the other side — out quickly, in on the slower curve, the way every
   other thing on this site arrives. */
.lb__stage{transition:opacity .46s var(--ease-out),transform .46s var(--ease-out);}
.lb__stage.is-leaving{opacity:0;
  transform:translateX(calc(var(--dir,1) * -4rem));
  transition:opacity .28s var(--ease),transform .28s var(--ease);}
.lb__stage.is-entering{opacity:0;
  transform:translateX(calc(var(--dir,1) * 4rem));transition:none;}
/* the side margin lives on an inner box, because the track and the slide
   cannot carry one without breaking the arithmetic above */
.lb__shot{position:absolute;inset:0 var(--margin);display:block;}
.lb__shot img{width:100%;height:100%;object-fit:contain;
  opacity:0;transform:scale(.988);
  transition:opacity .6s var(--ease-out),transform .9s var(--ease-out);}
.lb__shot img.is-on{opacity:1;transform:none;}

.lb__nav{display:flex;align-items:flex-end;gap:2.4rem;min-width:0;}
.lb__steps{display:flex;align-items:flex-end;gap:1.8rem;}
/* the overlay is the one place the site spoke a different language — two
   rings and two grey pills. It is the .btn and the rule arrow now, at the
   size this bar can carry. */
/* nothing but the plus, turned a quarter. It turns the rest of the way
   under the cursor, which is the only motion a thing that closes needs. */
.lb__x{display:block;width:1.9rem;height:1.9rem;opacity:.7;transform:rotate(45deg);
  transition:opacity .25s var(--ease),transform .34s var(--ease-out);}
.lb__x .plus,.lb__x svg{display:block;width:100%;height:100%;}
.lb__jump{font-size:1.8rem;max-width:22rem;}
.lb__jump .btn__word{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;}
.lb__jump[hidden]{display:none;}
.lb__steps .arrow--rule{--aw:3rem;}
.lb__btn{display:inline-flex;align-items:center;justify-content:center;
  border:1px solid rgba(233,234,232,.26);color:var(--fog);background:none;
  transition:border-color .35s var(--ease),background-color .35s var(--ease),
    opacity .35s var(--ease),transform .35s var(--ease-out);}
.lb__btn{width:3.6rem;height:3.6rem;border-radius:50%;}
.lb__btn:disabled{opacity:.3;}
@media (hover:hover) and (pointer:fine){
  .lb__btn:not(:disabled):hover{border-color:var(--fog);
    background:rgba(233,234,232,.07);}
  .lb__x:hover{opacity:1;transform:rotate(135deg);}
}
.lb__x:focus-visible{outline:1px solid var(--fog);outline-offset:4px;}
.lb__btn:focus-visible{outline:1px solid var(--fog);outline-offset:2px;}

.gal .px[data-group]{cursor:pointer;}

/* ---------- 04 material --------------------------------------------------- */
.material__grid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));column-gap:var(--gutter);
  align-items:end;margin-top:9.6rem;}
/* Three interiors that trade places: pick one of the small ones and it takes
   the big slot, while the one that was big drops to the end of the row. The
   three frames share an aspect ratio (1.777 against 1.774), so the travel is a
   uniform scale and the crop does not shift while they move. */
.material__ph{display:block;grid-row:1;width:100%;padding:0;border:0;background:none;
  color:inherit;font:inherit;cursor:pointer;-webkit-appearance:none;appearance:none;}
.material__ph[data-slot="0"]{grid-column:1 / span 6;aspect-ratio:700/394;order:0;cursor:default;}
.material__ph[data-slot="1"]{grid-column:7 / span 3;aspect-ratio:346/195;order:1;}
.material__ph[data-slot="2"]{grid-column:10 / span 3;aspect-ratio:346/195;order:2;}
.material__ph .px{height:100%;}
.material__ph .px img{transition:opacity .35s var(--ease);}
@media (hover:hover) and (pointer:fine){
  .material__ph:not([data-slot="0"]):hover .px img{opacity:.78;}
}
.material__caps{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));column-gap:var(--gutter);margin-top:7.8rem;}
.material__caps > *{transition:opacity .28s var(--ease);}
.material__caps.is-turning > *{opacity:0;}
.material__caps > *[data-slot="0"]{grid-column:1 / span 6;order:0;}
.material__caps > *[data-slot="1"]{grid-column:7 / span 3;order:1;}
.material__caps > *[data-slot="2"]{grid-column:10 / span 3;order:2;}
.material__caps .body{max-width:32rem;margin-top:1rem;}

/* ---------- 05 how we work ------------------------------------------------ */
.hww__top{margin-top:5.2rem;}
.rules{margin-top:10rem;display:grid;grid-template-columns:repeat(12,minmax(0,1fr));
  column-gap:var(--gutter);row-gap:6rem;}
.rule-item .label-xs{margin-bottom:.8rem;}
.rule-item .h4{margin-bottom:1.2rem;}
.rule-item .body{max-width:58.2rem;}

/* ---------- 06 team ------------------------------------------------------- */
/* 546:587. The card is the whole 1408, and it reads left to right as one
   sentence: who this is, what she said, and who you get next.

   Columns 1–8 are the half the person occupies — the writing in 1–4 and the
   portrait in 5–8, 468 each, the portrait hanging 14 lower than the text
   because it is 612 to the column's 598. Columns 10–12 hold the one who is
   next: his portrait at exactly two columns, his name under it, and the
   arrows at the foot. The strip of six thumbnails is gone from the frame —
   that portrait is what replaced it, and it is the same control the next
   arrow is, so it is a button too.

   The height is only capped so a short window cannot push the card off the
   bottom of the screen. */
.team__card{position:relative;display:grid;grid-template-columns:repeat(12,minmax(0,1fr));
  column-gap:var(--gutter);margin-top:4rem;height:min(61.2rem,70svh);}
.team__photo{grid-column:5 / span 4;grid-row:1;height:100%;width:100%;}
.team__side{grid-column:1 / span 4;grid-row:1;height:calc(100% - 1.4rem);
  display:flex;flex-direction:column;}
/* Only what actually changes about the person goes out and comes back. The
   whole side column used to be faded, which took the counter and the arrows
   with it — the controls blinking is what made a swap read as the whole block
   moving rather than one person being replaced. */
.team__photo img,.team__name,.team__role,.team__bio,.team__mark,.team__peek{
  transition:opacity .26s var(--ease);}
/* the picture is not faded any more — it is carried out of the frame */
.team__card.is-swap .team__name,
.team__card.is-swap .team__role,
.team__card.is-swap .team__bio,
.team__card.is-swap .team__mark,
.team__card.is-swap .team__peek{opacity:0;}
/* A PORTRAIT DOES NOT FLY ACROSS A PAGE. It travels inside its own frame,
   which is the one move this site already makes everywhere: the headline's
   words leave upward out of their clipped boxes and the next ones arrive from
   below, and every photograph drifts inside the slit that holds it. The frame
   stands still, the picture goes — one out through the top, the next in from
   the bottom, on the same curve and close to the same .74s the words take. */
.team__photo .team__reel{position:absolute;left:0;top:0;width:100%;height:100%;
  transition:transform .72s var(--ease-out);}
/* the mark changes with the person, so it fades on the swap's beat rather
   than on the reveal's long curve, which it also still arrives on */
html.reveals-on .team__card .team__mark{
  transition:opacity .26s var(--ease),transform 1.2s var(--ease-out);}
.team__name{white-space:pre-line;margin:0;font-family:var(--serif);font-weight:700;
  font-size:4.85rem;line-height:.98;letter-spacing:var(--track);}
.team__role{margin:1.86rem 0 0;font-family:var(--sans);font-weight:500;
  font-size:.84rem;line-height:1.3;letter-spacing:var(--track);}
/* The frame leaves 117.9 under the name and stands the counter on the floor.
   Fixed above, automatic below: the rhythm holds and the counter stays at the
   foot whatever height the card resolves to in a shorter window. */
.team__quote{margin-top:11.79rem;}
.team__mark{display:block;width:2.33rem;height:1.79rem;opacity:.75;}
.team__mark svg{display:block;width:100%;height:100%;}
.team__bio{width:30.77rem;max-width:100%;margin:1.31rem 0 0;
  font-size:1.49rem;line-height:1.36;}
.team__count{margin:auto 0 0;font-family:var(--sans);font-weight:500;
  font-size:1.03rem;line-height:1.2;letter-spacing:var(--track);}
.team__count-s{display:none;}
.team__up{grid-column:10 / span 3;grid-row:1;height:calc(100% - 1.4rem);
  display:flex;flex-direction:column;justify-content:space-between;align-items:flex-start;}
.team__peek{display:block;width:22.8rem;max-width:100%;text-align:left;}
.team__peek-photo{display:block;width:100%;aspect-ratio:228/277;overflow:hidden;}
.team__peek-photo img{width:100%;height:100%;object-fit:cover;object-position:50% 20%;}
.team__peek-name{display:block;margin-top:1.58rem;font-family:var(--serif);font-weight:700;
  font-size:1.71rem;line-height:1.06;letter-spacing:var(--track);}
.team__peek-role{display:block;margin-top:.53rem;font-family:var(--sans);font-weight:500;
  font-size:.79rem;line-height:1.24;letter-spacing:var(--track);}
.team__foot{width:100%;margin-top:auto;padding-top:2.3rem;
  display:flex;justify-content:space-between;align-items:flex-end;}
.team__nav{gap:1.8rem;}

/* ---------- 07 practice --------------------------------------------------- */
.practice__body{margin-top:4.3rem;}
.practice__lede{max-width:58.2rem;margin-top:6.4rem;}
.practice__btn{margin-top:6.4rem;justify-self:start;}
.stat{margin-bottom:3.7rem;}
.stat:last-child{margin-bottom:0;}
.stat .label-xs{margin-bottom:.7rem;}

/* ---------- 08 faq -------------------------------------------------------- */
.faq .grid{margin-top:4.3rem;}
.faq__list{grid-column:1 / span 6;}
.faq__item{border-top:1px solid rgba(233,234,232,.16);padding-bottom:3.2rem;}
.faq__item.is-open{padding-bottom:2.4rem;}
.faq__item:last-child{border-bottom:1px solid rgba(233,234,232,.16);}
/* 10 off the right edge, which is the q row's own padding in the frame —
   the sign stops sitting on the margin. The rule under the question belongs to
   the item, not to this button, so it still runs the full measure. */
.faq__q{width:100%;display:flex;justify-content:space-between;align-items:baseline;gap:2.4rem;
  padding:1.9rem 1rem 0 0;text-align:left;}
.faq__q .sign{font-family:var(--serif);font-weight:700;font-size:1.8rem;line-height:1.12;opacity:.6;flex:none;}
.faq__a{overflow:hidden;height:0;transition:height .45s var(--ease);}
.faq__a p{margin:0;padding:1.4rem 0 0;}
/* some answers are worth a picture — it sits under the words and carries the
   same parallax as every other slit on the page */
.faq__shot{display:block;margin-top:2.4rem;aspect-ratio:598/336;}
.faq__body{grid-column:1 / span 6;}
/* the photograph is held for the whole block while the questions run past it */
.faq .faq__still{position:absolute;top:0;right:0;bottom:0;width:50%;z-index:0;}
.faq__still-in{position:sticky;top:0;display:block;height:var(--vh,100svh);overflow:hidden;}
.faq__still-in img{width:100%;height:100%;object-fit:cover;}
.faq__photo{grid-column:8 / span 5;aspect-ratio:598/482;
  width:calc(100% + var(--margin));}

/* ---------- 09 enquiry ---------------------------------------------------- */
.enq__note{text-align:right;}
.enq__contacts{margin:5.6rem 0 0;}
.enq__form{row-gap:5.6rem;margin-top:6.4rem;}
.field label{display:block;margin-bottom:1.4rem;}
.field input,.field textarea,.field select{width:100%;background:none;border:0;
  border-bottom:1px solid rgba(233,234,232,.25);color:var(--fog);
  font-family:var(--sans);font-weight:500;font-size:1.3rem;
  letter-spacing:var(--track);padding:0 0 1.6rem;resize:none;
  transition:border-color .3s var(--ease);}
.field input::placeholder,.field textarea::placeholder{color:rgba(233,234,232,.35);}
.field input:focus,.field textarea:focus,.field select:focus{outline:0;border-color:var(--fog);}
.field input:focus-visible,.field textarea:focus-visible,
.field select:focus-visible{outline:2px solid #E9EAE8;outline-offset:4px;}
/* The native list is the one thing on this page nobody designed, so main.js
   builds a control over it and keeps the real select as the value. */
/* Every .reveal ends on filter:blur(0), and a filter is a stacking context —
   so each field traps its own list behind the fields that come after it. The
   field holding an open list is lifted out of that order. */
.sel{position:relative;}
.field--open{position:relative;z-index:30;}
.sel > select{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;}
.sel__btn{display:flex;align-items:center;justify-content:space-between;gap:1.2rem;
  width:100%;background:none;border:0;border-bottom:1px solid rgba(233,234,232,.25);
  color:rgba(233,234,232,.35);font-family:var(--sans);font-weight:500;font-size:1.3rem;
  letter-spacing:var(--track);padding:0 1rem 1.6rem 0;text-align:left;cursor:pointer;
  transition:border-color .3s var(--ease),color .3s var(--ease);}
.sel--set .sel__btn{color:var(--fog);}
.sel__btn:hover,.sel--open .sel__btn{border-color:var(--fog);}
.sel__btn:focus-visible{outline:0;border-color:var(--fog);}
.sel__chev{flex:none;width:1.2rem;height:.7rem;opacity:.7;
  transition:transform .4s var(--ease-out);}
.sel__chev svg{display:block;width:100%;height:100%;}
.sel--open .sel__chev{transform:rotate(180deg);}
/* Solid, not tinted: at 82% the page behind still read straight through it,
   and white type under a dark wash is exactly what shows. */
.sel__list{position:absolute;left:0;right:0;top:calc(100% + .8rem);z-index:20;
  background:var(--ink);border:1px solid rgba(233,234,232,.16);
  box-shadow:0 2.4rem 4.8rem rgba(0,0,0,.55);
  opacity:0;visibility:hidden;transform:translateY(-.8rem);
  transition:opacity .34s var(--ease-out),transform .34s var(--ease-out),visibility .34s;}
.sel--open .sel__list{opacity:1;visibility:visible;transform:none;}
.sel__opt{display:block;width:100%;background:none;border:0;text-align:left;cursor:pointer;
  font-family:var(--sans);font-weight:500;font-size:1.3rem;letter-spacing:var(--track);
  color:rgba(233,234,232,.6);padding:1.3rem 1.6rem;
  transition:color .22s var(--ease),background-color .22s var(--ease);}
.sel__opt + .sel__opt{border-top:1px solid rgba(233,234,232,.1);}
.sel__opt:hover,.sel__opt:focus-visible{outline:0;color:var(--fog);background:rgba(233,234,232,.06);}
.sel__opt[aria-selected="true"]{color:var(--fog);}

.enq__send{margin-top:0;}

/* ---------- 10 footer ----------------------------------------------------- */
.footer{padding:12rem 0 4.8rem;}
.footer__top{display:none;}
/* The last block on the page is hidden 32 below where it belongs until it
   arrives, and a transform counts towards the scrollable area — so the document
   was 32 taller than its content, and landing took that back under the finger.
   The footer's own padding is deeper than the rise, so clipping costs nothing. */
.footer{overflow:hidden;}
.footer__cols{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));column-gap:var(--gutter);}
.footer__cols ul{list-style:none;margin:0;padding:0;}
/* the mockup sets these lists solid, but the hover rule has to land somewhere
   that is not the next word */
.footer__nav li + li,.footer__social li + li{margin-top:1.2rem;}
.footer__social{text-align:right;}
.footer__rule{margin-top:6.4rem;}
.footer__mark{width:71.02%;margin:3.9rem 0 0;}
/* The ramp of a progressive blur is measured against the box it runs down, so
   a box with no height yet gives a ramp that reaches full blur immediately —
   which is the heavier of the two wordmarks you caught. The shape is declared
   here so it is right before the file lands. */
.footer__mark img{width:100%;height:auto;aspect-ratio:64/23;}
.footer__legal{margin-top:4rem;}
/* The one thing in this style that is not a button, and the one that keeps its
   own size: at 14 the left half measures 190 into a 177 column on a phone and
   wraps, which is exactly the row the mockup is asking for, broken. */
.footer__legal p{font-size:1.2rem;}

/* ==========================================================================
   PROJECT PAGES
   ========================================================================== */

/* ---------- 02 survey ----------------------------------------------------- */
.survey{padding:12rem 0 11.9rem;}
.survey__body{margin-top:4.3rem;}
.survey__spec{grid-column:1 / span 5;}
.survey__notes{grid-column:8 / span 5;}
.survey__notes p{margin:0 0 2.2rem;}   /* a blank line, as in the mockup */
.survey__notes p:last-child{margin-bottom:0;}
.spec-list{border-bottom:1px solid var(--hair);}
.spec-row{border-top:1px solid var(--hair);padding:1.5rem 0 1.7rem;
  display:grid;grid-template-columns:17rem minmax(0,1fr);column-gap:1.4rem;}

/* ---------- 03 gallery ---------------------------------------------------- */
.gal{padding:12rem 0 1.6rem;}
.gal__head{padding-bottom:3.5rem;}
.gal__row{margin-top:.8rem;}
.gal__row .px{aspect-ratio:1408/792;}
.gal__pair{margin-top:.8rem;display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);column-gap:var(--gutter);}
.gal__pair .px{aspect-ratio:700/860;}

/* construction band */
.band{padding:8.8rem var(--margin) 12.4rem;margin-top:.8rem;}
.band--short{padding-bottom:10.2rem;}
.band__head{padding-top:2.3rem;}
.band__fields{padding-top:4.3rem;}
.band__fields .label-xs{margin-bottom:.7rem;}
.band__line{padding-top:3.8rem;}
.band__line p{max-width:93.6rem;}

/* ---------- 04 next site -------------------------------------------------- */
.next{padding:12rem 0;}
.next__body{margin-top:4.3rem;}
/* the mockup reads name → facts → button down the left half, photo on the right */
.next__detail{grid-column:1 / span 6;grid-row:1;}
/* What lines up with the top of the photograph is the cap height of the name,
   not its text box. The shift is optical only — a margin here would collapse
   into the 79 below it and drag the facts and the button up with it. */
.next__detail .display{top:-.075em;}
.next__photo{grid-column:7 / span 6;grid-row:1;}
.next__photo .px{aspect-ratio:700/394;}
.next__meta{margin-top:7.9rem;display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  column-gap:var(--gutter);row-gap:2.4rem;}
.next__meta .label-xs{margin-bottom:.7rem;}
.next .btn{margin-top:9.7rem;}

/* ---------- reveal -------------------------------------------------------- */
.px{position:relative;display:block;overflow:hidden;}
/* The page's own travel is 60% of the slit, which on a gallery plate puts well
   over a third of the photograph outside the frame at any moment. A gallery is
   there to be looked at rather than moved through, so its slits take a far
   shallower one — the drift is still there, the picture is not eaten by it.
   measurePx reads this off each slit, so the arithmetic follows the stylesheet
   rather than having to be told twice. */
.gal .px{--pxr:26%;}
/* a descendant, not a child: the team's portrait sits inside a reel that
   carries it out of the frame when the person changes, and the picture is
   still the thing the parallax moves */
.px img{position:absolute;left:0;top:calc(var(--pxr) / -2);width:100%;
  height:calc(100% + var(--pxr));object-fit:cover;}
/* A slit is shorter than the picture inside it, so some of that picture is
   always outside the frame — at the top of the travel it comes off the foot,
   at the bottom off the head. There is no anchoring round that: the only dial
   is how far the thing moves. These portraits carry 4.6% of air over the hair,
   so the travel is cut to fit inside it and the head is never touched. */
/* a portrait has only so much air over the head — this one travels less */
.team__photo{--pxr:14%;}

/* ---------- reveal -------------------------------------------------------- */
/* The hidden state carries no transition of its own. Declaring both in one
   rule means arriving at the hidden state animates as well — and a block whose
   delay is longer than the wait for the curtain never finishes hiding, which is
   why the hero button was simply always there. main.js arms the transitions on
   <html> the moment the overlay is gone, and the reveals start from there. */
/* No filter anywhere in here any more. A block rises and fades; that is the
   whole reveal. The blur was 18px animated on every .reveal and 16 on every
   word inside the split ones, which on this page is several hundred elements
   each holding a render surface for the length of the transition. */
.reveal{opacity:0;transform:translateY(3.2rem);}
html.reveals-on .reveal{
  transition:opacity 1.2s var(--ease-out),transform 1.2s var(--ease-out);}
.reveal.is-in{opacity:var(--o,1);transform:none;}
/* photographs rise and fade but are never blurred — a sharpening photo reads
   as a half-loaded image, and blurring one this large is expensive */
/* This has the same weight as .reveal.is-in and sits after it, so the shorter
   rise has to stand down once the photograph has landed — otherwise every one
   of them stays parked 24 below where it belongs, for good. */
.gal__row.reveal,.gal__pair.reveal,.material__grid.reveal,
.next__photo.reveal,.faq__photo.reveal,.team__photo.reveal{filter:none;}
.gal__row.reveal:not(.is-in),.gal__pair.reveal:not(.is-in),
.material__grid.reveal:not(.is-in),.next__photo.reveal:not(.is-in),
.faq__photo.reveal:not(.is-in),.team__photo.reveal:not(.is-in){
  transform:translateY(2.4rem);}

/* A block of running text resolves a line at a time instead of all at once:
   main.js wraps every word and hands each one the delay of the line it landed
   on, so the top of the paragraph is already sharp while the foot of it is
   still coming out of the blur. The block itself then must not animate. */
.wd{display:inline-block;}
.rl.reveal{opacity:var(--o,1);transform:none;filter:none;}
.rl .wd{opacity:0;transform:translateY(.5em);}
html.reveals-on .rl .wd{
  transition:opacity 1.2s var(--ease-out),transform 1.2s var(--ease-out);}
.rl.is-in .wd{opacity:1;transform:none;}
/* once it has landed the filters come off, so hundreds of spans stop holding
   render surfaces for nothing */
.rl--done .wd{opacity:1;filter:none;transform:none;}
html.reveals-on .rl--done .wd{transition:none;}
/* where the block already carries the Figma ramp the words only rise and fade —
   the ramp is the blur, and blurring each word inside each stack layer as well
   would mean animating a filter on a hundred spans at once */
.pb.rl .wd{filter:none;}

/* While the manifesto pane is held, its lines are handed out by scroll
   position instead of by one timer started on entry — so the frame closes and
   the text arrives together, which is the whole point of stopping there. */
.rl--scrub .wd{opacity:0;transform:translateY(.5em);
  transition-delay:0ms!important;transition-duration:1s!important;}
.rl--scrub .wd.in{opacity:1;transform:none;}
/* and it leaves the way it came, a line at a time, while the frame swells */
.rl--scrub .wd.out{opacity:0;transform:translateY(-.45em);}

/* ==========================================================================
   TABLET — 768 to 1199. The twelve-column grid stops being readable, so the
   page stacks and the file's own T type ramp takes over (1024 frame).
   ========================================================================== */
/* ---------- 06 team, below the full 1408 ---------------------------------
   Everything about 546:587 that only works at the whole width: below it the
   three columns stack, the one who is next stands down, and the count goes
   back to the arrows' own line. This cannot live in the tablet block under
   it — that one starts at 768, and the phone needs every line of it too. */
@media (max-width:1199px){
  .team__card{height:auto;}
  .team__photo{grid-column:1 / span 12;grid-row:auto;height:auto;width:100%;}
  .team__side{grid-column:1 / span 12;grid-row:auto;height:auto;}
  .team__quote{margin-top:4rem;}
  .team__bio{width:58.2rem;max-width:100%;}
  .team__count{display:none;}
  .team__count-s{display:block;}
  .team__up{grid-column:1 / span 12;grid-row:auto;height:auto;display:block;}
  .team__peek{display:none;}
  .team__foot{align-items:center;}
  .team__nav{gap:1.2rem;}
  /* The ring was the mobile frame's, drawn before the rule arrow existed, and
     it stayed behind when every other control on the site was given the glyph
     and its 1px rule. The arrow is the same arrow at every width now; only the
     size changes, and the press area is let out around it. */
  .team__nav .arrow--rule{--aw:4.2rem;position:relative;}
  .team__nav .arrow--rule::after{content:"";position:absolute;inset:-1.4rem -1rem;}
}

@media (min-width:768px) and (max-width:1199px){
  /* .9766vw is 10px at 1024 and 7.5 at 768, and 7.5px labels are not text.
     The floor lifts the whole ramp a little; the pieces that were smallest get
     a hard pixel floor of their own, so 1024 is still exactly the frame. */
  html{font-size:clamp(8.6px,.9766vw,11.71px);}   /* 10px at 1024 */
  :root{--block:9.6rem;}
  .display{font-size:6.4rem;line-height:.94;}
  .h1{font-size:4rem;line-height:1;}
  .h2{font-size:3.4rem;line-height:1.02;}
  .h3{font-size:2.2rem;line-height:1.08;}
  .h4{font-size:max(1.6rem,14px);line-height:1.14;}
  .body{font-size:max(1.6rem,14px);line-height:1.4;}
  .label{font-size:max(1.4rem,14px);line-height:1.22;}
  .label-xs{font-size:max(1rem,11px);line-height:1.22;}
  .btn{font-size:2.4rem;}
  .btn--lg{font-size:3.4rem;}

  .hero__copy{top:13rem;}
  .hero__copy .c6{grid-column:auto / span 12;}
  .hero__side{grid-column:1 / span 8;margin-top:3.6rem;}
  .hero__facts .c3{grid-column:auto / span 6;margin-bottom:1.4rem;}
  .material__ph[data-slot="0"]{grid-column:1 / span 12;grid-row:1;}
  .material__ph[data-slot="1"],.material__ph[data-slot="2"]{
    grid-column:auto / span 6;grid-row:2;margin-top:.8rem;}
  .material__caps > *{margin-bottom:2.8rem;}
  .material__caps > *[data-slot]{grid-column:auto / span 6;}
  .rules .c6{grid-column:auto / span 12;}
  .team__photo{aspect-ratio:582/598;}
  :root{--pxr:52%;}
  .team__side{margin-top:4rem;}
  .team__role{font-size:max(1rem,11px);line-height:1.22;}
  .team__bio{font-size:max(1.6rem,14px);line-height:1.4;}
  .team__foot{margin-top:4rem;}
  .faq__list{grid-column:1 / span 12;}
  .faq__body{grid-column:1 / span 7;}
  .faq__photo{grid-column:1 / span 12;margin-top:4rem;}
  .practice__lead{grid-column:auto / span 12;}
  .practice__stats{grid-column:1 / span 12;margin-top:5.6rem;}
  .practice__btn{grid-column:auto / span 12;margin-top:6rem;}
  .practice .stat{display:inline-block;width:46%;vertical-align:top;}
  .enq__contacts .c3{grid-column:auto / span 6;margin-bottom:2rem;}
  /* A SPAN OVERRIDE HAS TO RESET THE START, OR THE DESKTOP ONE SURVIVES IT.
     grid-column-end alone left .s5, .s8 and .s12 in force here: the four
     columns began at 1, 5, 8 and 12 while each still claimed six, so they
     overlapped — and the one starting at 8 reached for a thirteenth column
     this row does not have. Grid built the phantom track without complaint,
     and the gutter in front of it took 8px off all twelve, which is the
     drift that showed up everywhere between 768 and 1199. Auto-placed now:
     two to a row, in the order they are written. */
  .footer__cols > *{grid-column:auto / span 6;margin-bottom:2.8rem;}
  .footer__mark{width:100%;}
  .survey__spec,.survey__notes{grid-column:1 / span 12;}
  .survey__notes{margin-top:5.6rem;}
  .next__photo,.next__detail{grid-column:1 / span 12;}
  .next__detail{margin-top:4.8rem;}
  .band__fields > *{grid-column:auto / span 6;margin-bottom:2.8rem;}
  .card{height:calc(var(--vh,100svh) + 10rem);}
}

/* ==========================================================================
   MOBILE — the 390 frame, column for column.
   4 columns, margin 12, gutter 12, halves of 177.
   ========================================================================== */
@media (max-width:767px){
  html{font-size:clamp(8.9px,2.5641vw,11.8px);}   /* 10px at 390 */
  :root{--margin:1.2rem;--gutter:1.2rem;--block:9.2rem;}

  /* M type ramp */
  .display{font-size:4.4rem;line-height:.96;}
  /* two display sizes on the phone: 44 for the hero and the next-site name,
     60 for the name of a project or of a person */
  .card__name,.phero__title,.h1{font-size:6rem;line-height:1.02;}
  .h2{font-size:2.4rem;line-height:1.06;}
  .h3{font-size:1.8rem;line-height:1.12;}
  .h4{font-size:1.6rem;line-height:1.18;}
  /* 15, not 16 — straight off the frame, with its tracking. The form fields
     below keep 16: anything under it and iOS zooms the page on focus. */
  .body{font-size:1.5rem;line-height:1.32;letter-spacing:-.054rem;}
  /* a full-width 18 blur is a real cost on a phone GPU, and it is the thing
     running while you are scrolling past it */
  /* the phone had its own, lighter pair of blurs; they go with the rest */
  .label{font-size:1.4rem;line-height:1.24;}
  .label-xs{font-size:1.2rem;line-height:1.24;}
  .btn{font-size:2.8rem;}
  .btn--lg{font-size:4.6rem;}
  .head .rule{margin-bottom:1.5rem;}

  /* every grid runs full width unless a block opts into halves */
  .grid{grid-template-columns:minmax(0,1fr) minmax(0,1fr);}
  .grid > *{grid-column:auto / span 2;}
  .fact,.stat,.enq__contacts > *,.band__fields > *,.next__meta > *{grid-column:auto / span 1;}

  /* ---- nav: wordmark and a MENU control ---- */
  .nav{padding:2.4rem 0 1.6rem;}
  .nav--pin{padding:1.2rem 0;}
  /* solid rather than blurred: on a phone the backdrop has to be resampled
     every frame the page moves, and in an in-app browser the blur leaves the
     same seam the bleed is there to cover */
  .nav::before{background:#0C0D0C;-webkit-backdrop-filter:none;backdrop-filter:none;}
  .nav .wrap{display:flex;align-items:center;justify-content:space-between;
    gap:1.6rem;}
  /* 72 x 27 in the frame, against the 64 x 23 the desktop header carries —
     the phone is given the larger mark, not the smaller one */
  .nav .mark{width:7.2rem;}
  .nav__menu{display:none;}
  .nav a.label{display:none;}
  /* the language, and to the right of it the one thing the header is for */
  .nav a.lang{display:block;order:2;width:auto;padding-top:0;text-align:right;
    margin-left:auto;margin-right:.8rem;}
  /* the header button is a button, not a headline — the ramp's .btn is 28 */
  .nav .nav__cta{display:inline-flex;order:3;font-size:1.6rem;line-height:1.12;}

  /* ---- 01 hero: copy sits on the floor, facts move to the manifesto ---- */
  /* the first screen is the window, whatever the browser's bar is doing */
  .hero,.hero--p{height:var(--vh,100svh);min-height:var(--vh,100svh);}
  .hero__copy{top:auto;bottom:5.1rem;}
  .hero__side{margin-top:1.2rem;}
  .hero__lede{margin-bottom:5.1rem;max-width:33.7rem;}
  .hero__facts{display:none;}
  /* no cut-outs here — they are cut to the desktop framing, and the phone
     slides are different photographs altogether */
  .hero__cuts{display:none;}
  /* and with nothing to run behind, the words belong above the ramp that is
     there to let them read rather than under it */
  .hero__copy{z-index:5;}
  .hero__ticks{z-index:5;}
  /* The block is as tall as the tallest line, which is three; a two-line
     headline left at the top of it put a whole empty line between itself and
     the lede. Down here the copy hangs off the foot of the screen, so the
     shorter ones sit on the block's bottom edge and the spare line falls above
     them, over the photograph, where there is nothing to push. */
  .hero__head-l:not(:first-child){top:auto;bottom:0;}
  /* above the words, not under them — the foot of the screen is where the
     lede and the button already are. The offset is written by script from the
     measured copy block, because that block is anchored to the bottom and its
     height depends on how the lede wraps. */
  /* measured off the headline that is standing, not off the block, which is
     always as tall as the longest of the three */
  .hero__ticks{left:var(--margin);right:auto;
    bottom:var(--ticks-up,calc(var(--margin) + 22rem));gap:.5rem;
    transition:bottom 1.1s var(--ease);}
  .hero__tick{width:2.3rem;}
  /* No turn-over down here. The phone puts the headline at the foot of the
     screen rather than up on the sky, so the bottom ramp already carries it —
     the band that goes pale on a desktop has only the nav in it here, and
     that sits in its own ramp. The bars are above the headline, inside the
     same dark foot, so they stay chalk on every slide. */
  .hero__scrim-t,.hero--p .hero__scrim-t{height:30.6%;
    background:linear-gradient(to bottom,rgba(0,0,0,.22),rgba(0,0,0,0));}
  /* the phone's own slides open on sky: 226 and 189 behind the mark and MENU,
     where .22 left the white marks all but gone */
  /* Measured on the phone frames before any scrim: the sky behind the
     wordmark is .889 of white on LOZA and .757 on BOROVA, and the band the
     headline sits in is .575 on AKATSIA. White type on that is a contrast of
     about one to one — which is the grey text. The ramps below are what it
     takes to bring the worst of the three to a ratio that can be read, and the
     marks keep a soft shadow for the rest. */
  /* .74 was mine, measured off the old bright-sky slides, and far too much of
     a lid — which is what was being complained about. The frames say .38, and
     the new photographs do not ask for more. */
  .hero--home .hero__scrim-t{height:40%;
    background:linear-gradient(to bottom,rgba(0,0,0,.38),rgba(0,0,0,0));}
  /* The words sit right down on the bottom edge here, over whatever the
     slide happens to put there — 152 under the headline on LOZA and 124
     under the lede on BOROVA. One straight ramp either crushes the foot of
     the picture or lets go before it reaches the headline, so this one holds
     a middle stop and covers the whole stack of type. */
  .hero__scrim-b{height:78%;
    background:linear-gradient(to top,rgba(0,0,0,.9),rgba(0,0,0,.76) 30%,
      rgba(0,0,0,.52) 56%,rgba(0,0,0,.14) 82%,rgba(0,0,0,0));}
  /* the project hero carries the same weight — its name, its place and its
     four figures all sit on the lower half of the photograph */
  /* dialled back off the measurement rather than guessed: at this weight the
     name reads 4.8:1 and the figures 6:1, and roughly a third of the lower
     photograph is still showing instead of a sixth */
  .hero--p .hero__scrim-b{height:66%;
    background:linear-gradient(to top,rgba(0,0,0,.86),rgba(0,0,0,.8) 20%,
      rgba(0,0,0,.68) 42%,rgba(0,0,0,.34) 66%,rgba(0,0,0,0));}
  /* half of white over a lit facade is not a label, it is a watermark */
  .phero__foot .label-xs.o5,.gal .label-xs.o5{--o:.74;}

  /* project hero: counter on its own line, then the location, then the name */
  .phero__foot{bottom:2.6rem;}
  .phero__meta{margin-bottom:.8rem;}
  .phero__title{margin-bottom:3.2rem;}
  .phero__foot .grid{row-gap:1.8rem;}
  .fact .label-xs{margin-bottom:.4rem;}

  /* ---- 02 manifesto ---- */
  .manifesto{min-height:0;display:block;padding:2.3rem 0 9.2rem;}
  .manifesto__in{display:block;}
  /* The pane is held for a screen and a half here exactly as it is on a
     desktop, and on a desktop it holds one thing. The practice facts and the
     rule above them were pushing the ring off the middle of it. */
  .ring{width:33rem;aspect-ratio:1;margin:0 auto;}
  .ring__side--top,.ring__side--bottom{width:51.5%;height:1rem;}
  .ring__side--left,.ring__side--right{height:51.5%;width:1rem;}
  .ring__side--top i,.ring__side--bottom i{height:1rem;}
  .ring__side--left i,.ring__side--right i{width:1rem;}
  .ring__text{max-width:27.4rem;font-size:2.8rem;line-height:1.02;}
  /* the desktop breaks leave a line reading just "CLIENT." at this width, so
     on the phone the sentence is allowed to find its own */
  .ring__text br{display:none;}

  /* ---- 03 index ---- */
  /* a project is a full screen of photograph, one after another */
  .card{height:calc(var(--vh,100svh) + 10rem);margin-top:0;}
  .card__scrim{height:54.5%;}
  .card__meta{bottom:9.3rem;}
  .card__row{margin-bottom:1rem;}

  /* ---- 04 material ---- */
  .material__grid{grid-template-columns:minmax(0,1fr) minmax(0,1fr);column-gap:var(--gutter);
    row-gap:.8rem;margin-top:2.8rem;align-items:start;}
  .material__ph[data-slot="0"]{grid-column:1 / span 2;grid-row:1;aspect-ratio:366/201;}
  .material__ph[data-slot="1"],.material__ph[data-slot="2"]{
    grid-column:auto / span 1;grid-row:2;aspect-ratio:177/98;margin-top:0;}
  /* A phone slot is far taller than any ratio an image model will render, and
     the overhang is what makes it taller still: at 60% a 3:4 frame showed 35%
     of its own width. At 26% it shows 44%, and a 9:16 frame 59%, while the
     drift is still 13% of the picture's height each way. */
  :root{--pxr:26%;}
  .hero{--pxr:16%;}
  .material__caps{display:block;margin-top:3.2rem;}
  .material__caps > *[data-slot]{grid-column:auto;}
  .material__caps > *:not([data-slot="0"]){display:none;}
  .material__caps .body{max-width:none;margin-top:.7rem;}

  /* ---- 05 how we work ---- */
  .hww__top{margin-top:2.4rem;display:block;}
  .hww__intro{margin-top:2.4rem;}
  .rules{display:block;margin-top:4.8rem;}
  .rule-item{border-top:1px solid var(--hair);padding-top:1.5rem;margin-top:3.6rem;}
  .rule-item:first-child{margin-top:0;}
  .rule-item .label-xs{margin-bottom:0;}
  .rule-item .h4{margin:.6rem 0 0;font-size:2rem;line-height:1.12;}
  .rule-item .body{margin-top:1rem;max-width:none;}

  .skip{display:none;}

  /* ---- 06 team ---- */
  .team__card{display:block;height:auto;margin-top:2.8rem;}
  .team__photo{width:100%;height:auto;aspect-ratio:366/420;}
  .team__photo img{position:absolute;}
  .team__side{display:block;padding-top:0;}
  /* the phone frame sets this name at the same 46 the desktop does, not at
     the 60 the rest of .h1 drops to here */
  .team__name{margin-top:3rem;font-size:4.6rem;line-height:1;}
  .team__role{margin-top:1.4rem;font-size:1.2rem;line-height:1.24;}
  /* the mockup's phone block carries neither the biography nor the one who is
     next — the arrows under the rule are how you move through them */
  .team__quote{display:none;}
  .team__foot{margin-top:4rem;padding-top:1.3rem;}
  /* the ring only. The rule arrow carries its own width in --aw, and this
     rule sat later in the file than .arrow--rule does, so without the guard
     it was handing the overlay's arrows a 44px box meant for a circle. */
  .arrow:not(.arrow--rule){width:4.4rem;height:4.4rem;font-size:1.1rem;}
  .arrows,.team__nav{gap:.8rem;}
  .team__nav .arrow--rule{--aw:4rem;}

  /* ---- 07 practice ---- */
  .practice__body{display:flex;flex-direction:column;margin-top:2.4rem;}
  .practice__lead{order:1;}
  .practice__stats{order:2;display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
    column-gap:var(--gutter);row-gap:2rem;margin-top:5.4rem;}
  .practice__btn{order:3;margin-top:4.2rem;align-self:flex-start;}
  .practice .h1{font-size:2.4rem;line-height:1.06;}
  /* the frame's own two gaps: 18 off the statement, 54 down to the figures */
  .practice__lede{margin-top:1.8rem;max-width:none;}
  .stat{margin-bottom:0;}

  /* ---- 08 faq ---- */
  .faq .grid{display:flex;flex-direction:column;margin-top:2.8rem;}
  /* on a phone it is a picture at the head of the block, not a held pane */
  /* on a phone the held pane has nowhere to be held, and as a plain picture
     at the head of the block it only pushes the questions down */
  .faq .faq__still{display:none;}
  .faq__still-in{position:static;height:auto;aspect-ratio:366/201;}
  .faq__photo{order:-1;width:100%;aspect-ratio:366/201;margin-bottom:4.8rem;}
  .faq__item{padding-bottom:2.2rem;}
  .faq__q{padding:1.7rem 1rem 0 0;gap:1.2rem;}
  .faq__q .sign{font-size:1.6rem;line-height:1.18;}
  .faq__a p{padding:1rem 0 0;max-width:none;}

  /* ---- 09 enquiry ---- */
  .enq__head{display:block;}
  .enq__head .h3{font-size:2.4rem;line-height:1.06;}
  .enq__note{text-align:left;margin-top:1.1rem;}
  /* the 391 mockup sets an 11 gap on the block itself, so every band here
     sits 11 further down than its own padding says */
  .enq__contacts{margin:3.9rem 0 0;row-gap:1.7rem;}
  .enq__form{margin-top:3.9rem;row-gap:2.9rem;}
  .field label{font-size:1.6rem;line-height:1.18;margin-bottom:.9rem;}
  .sel__btn,.sel__opt{font-size:1.6rem;line-height:1.32;}
  .sel__btn{padding:0 1rem .7rem 0;}
  .sel__opt{padding:1.1rem 1.2rem;}
  .field input,.field textarea{font-size:1.6rem;line-height:1.32;
    letter-spacing:-.05rem;padding:0 0 .7rem;}
  .field textarea{height:2.1rem;min-height:0;}
  .enq__send{margin-top:2.3rem;}   /* + the 2.9 form row gap = the mockup's 52 */

  /* ---- 10 footer ---- */
  .footer{padding:6.4rem 0 3.8rem;}
  .footer__top{display:block;margin-bottom:1.5rem;}
  .footer__cols{grid-template-columns:minmax(0,1fr) minmax(0,1fr);}
  .footer__cols > *{grid-column:auto / span 1;margin-bottom:0;}
  .footer__name{grid-column:1 / span 2;margin-bottom:3.6rem;order:0;}
  /* the mockup sets these lists on a 214% line — the air between the links
     is the line box, not a margin */
  .footer__nav,.footer__social{line-height:2.14;}
  .footer__nav li + li,.footer__social li + li{margin-top:0;}
  .footer__nav{order:1;}
  .footer__social{order:2;}
  .footer__offices{order:3;grid-column:1 / span 2;margin-top:4.4rem;}
  .footer__rule{margin-top:3.2rem;}
  .footer__mark{width:100%;margin-top:3.1rem;}
  /* One row, held to its two ends, which is what the mockup draws — the two
     overrides that used to stack it are gone, so .head-row's own grid and its
     right-aligned second half apply here as they do everywhere else. */
  .footer__legal{margin-top:3.3rem;}

  /* ---- project pages ---- */
  .survey{padding:6.4rem 0;}
  .survey__body{display:block;margin-top:3.2rem;}
  .survey__notes{margin-top:3.9rem;}
  .spec-row{grid-template-columns:11.2rem minmax(0,1fr);column-gap:.6rem;padding:1.3rem 0 1.6rem;}
  .gal{padding:6.4rem 0 .8rem;}
  .gal__head{padding-bottom:2rem;}
  .gal__row .px{aspect-ratio:366/201;}
  .gal__pair{grid-template-columns:minmax(0,1fr);row-gap:.8rem;}
  .gal__pair .px{aspect-ratio:366/490;}
  .band{padding:4.8rem var(--margin);}
  .band--short{padding-bottom:4.8rem;}
  .band__head{padding-top:1.5rem;}
  .band__fields{padding-top:2.8rem;row-gap:2.3rem;}
  .band__fields .label-xs{margin-bottom:.3rem;}
  .band__line{padding-top:2.9rem;}
  .band__line p{max-width:none;}
  .next{padding:9.2rem 0;}
  .next__body{display:block;margin-top:2.8rem;}
  .next__photo .px{aspect-ratio:366/201;}
  .next__detail{margin-top:2.4rem;}
  .next__meta{margin-top:3.6rem;row-gap:1.8rem;}
  .next .btn{margin-top:2.6rem;}
}

/* ==========================================================================
   The manifesto is the one block that pins: the pane is held for a screen and
   a half while its lines arrive and the ring closes, then it releases.
   ========================================================================== */
@media (prefers-reduced-motion:no-preference){
  .manifesto{display:block;height:calc(var(--vh,100svh) * 1.8);min-height:0;padding:0;}
  /* a phone screen is a third of the height of a desktop one, so the same
     fraction of the track is a third of the scroll — the track gets longer so
     the block lets go at something like the same pace */
  @media (max-width:767px){
    .manifesto{height:calc(var(--vh,100svh) * 2.25);}
  }
  /* the wall is held to the window now, so it neither drifts behind the held
     text nor cuts off at the fold — it simply stays where it is */
  .manifesto__in{position:sticky;top:0;height:var(--vh,100svh);
    display:grid;place-items:center;align-content:center;}
}

/* The held pane is the one panel that is never seen at load: by the time it
   arrives a phone has retracted its address bar, and the height measured when
   the page opened is the short viewport — which left a strip of the next block
   showing under it. This one follows the window as it actually is. */
@supports (height:100dvh){
  @media (max-width:1023px) and (prefers-reduced-motion:no-preference){
    .manifesto__in{height:100dvh;}
  }
}

@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto;}
  .reveal{transition:opacity .3s linear;transform:none;filter:none;}
  .reveal.is-in{opacity:1;}
  .rl .wd{opacity:1;filter:none;transform:none;transition:none;}
  .px img,.hero__img,.card__img{transform:none!important;}
  .btn__word,.btn .plus,.btn__arrow{transition-duration:.01ms!important;}
}

/* ---------- the opened gallery on a phone ---------------------------------- */
@media (max-width:767px){
  .lb__foot{flex-direction:column;align-items:flex-start;gap:1.6rem;}
  .lb__cap{max-width:none;}
  /* the two steps hold the middle whether or not there is a run either side */
  /* the arrows sit on the same floor as the words: every control in here
     ends in a 1px rule, so the bottom edge is the line to align on */
  .lb__nav{width:100%;gap:1.6rem;justify-content:space-between;align-items:flex-end;}
  .lb__steps{gap:1.4rem;align-items:flex-end;}
  /* The names stay. A chevron on its own says there is somewhere to go but
     not where, and where is the whole point of that button — the top bar
     names the run you are in, not the one the button would take you to.
     They fit across 390 once the pills are tightened. */
  .lb__nav{gap:1.2rem;}
  .lb__steps{gap:1.2rem;}
  .lb__jump{font-size:1.6rem;max-width:12rem;flex:0 1 auto;}
  .lb__x{width:1.7rem;height:1.7rem;}
  .lb__steps .arrow--rule{--aw:2.8rem;}
}

/* ==========================================================================
   THE TABLET IS A TOUCH SCREEN THE SIZE OF A DESKTOP - 768 ... 1199

   The drawing is the same twelve columns: at 1024 that is margin 16, gutter 8
   and a column of 75.33, which is exactly what this band already renders. So
   the grid was never the problem. What the band did not have was a finger -
   and the first screen was still wearing the desktop's headline.
   ========================================================================== */
@media (min-width:768px) and (max-width:1199px){

  /* THE HEADLINE WAS SIZED FOR THE DRAWING, NOT FOR THIS SCREEN.
     .hero__head-l--xl is declared up in the min-width:768 block, so the
     tablet inherited 16.3rem - and with the ramp floored at 8.6px that is
     140px across a 726 canvas: the single word CONTRACT ran from margin to
     margin with nothing either side of it. Carried in vw it holds the
     proportion the 1440 frame has (163 of 1440 is 11.3vw) and meets the
     1200 boundary without a step in either direction. */
  .hero__head-l--xl{font-size:min(11.3vw,18.111vh);}
  .hero__head-l--l {font-size:min(7.77vw,12.444vh);}
  .hero__head-l--m {font-size:min(7.14vw,11.444vh);}

  /* THE FORM WAS DRAWN FOR A MOUSE: 29px rows and 11px type. On iOS a field
     under 16px makes the whole page zoom the moment it takes focus, and a
     page that has zoomed is a layout you no longer control. The native select
     stays as it is - it is 1px and transparent, and the control you actually
     press is .sel__btn. */
  .field input,.field textarea,.field select,
  .sel__btn,.sel__val,.sel__opt{font-size:max(1.6rem,16px);}
  .field input{padding:1.2rem 0 1.6rem;min-height:44px;}
  .field textarea{padding:1.2rem 0 1.6rem;min-height:92px;}
  .sel__btn{padding:1.2rem 1rem 1.6rem 0;min-height:44px;}
  .footer__legal p{font-size:max(1.2rem,12px);}

  /* THE PRESS AREA IS LET OUT AROUND THE WORD, AND NOTHING MOVES.
     The same move the team arrows already make: the glyph keeps its size and
     its place on the grid, and the box that catches the finger is bigger than
     the thing you can see. A 21px nav label becomes a 41px target without
     shifting by a pixel. */
  .nav a.label,.footer__nav a,.footer__legal a,
  .footer__social a,.mark,.btn{position:relative;}
  .nav a.label::before,.footer__nav a::before,.footer__legal a::before,
  .footer__social a::before,.mark::before,.btn::before{
    content:"";position:absolute;inset:-1.2rem -.8rem;}
}
