/* ═══════════════════════════════════════════════════════════════════
   Arcos Design Firm — one project

   The page a plate on projects.html opens into. It starts with the same
   picture and the same name, at full width, so the arrival reads as the
   plate enlarging rather than as a jump to somewhere unrelated.

   Below the hero is the body of the project, which is written per
   project. The shared parts live here; anything a single project needs
   and no other does belongs in its own block, not in this file.
   ═══════════════════════════════════════════════════════════════════ */

/* ── the hero ──────────────────────────────────────────────────────
   Full-bleed and tall, but never taller than the screen — a hero that
   has to be scrolled past before the project begins is a toll, not an
   opening. Covers come in at whatever shape the render was — some
   square, some landscape — and object-fit crops each to the band. This
   is the one place cropping is right: this is the door, not the work.
   ───────────────────────────────────────────────────────────────── */
.pr__hero{
  position: relative;
  height: clamp(24rem, 82vh, 46rem);
  overflow: hidden;
  background: #101012;
  isolation: isolate;
}

.pr__hero-img{
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 58%;   /* holds the room, drops some ceiling */
  display: block;
}

/* 58% is measured against a square cover. A landscape one has far less
   height to give away — the band takes roughly a third of it rather
   than two thirds — so the same figure lands the window high and fills
   the frame with wall. This drops it far enough to seat the furniture
   in the picture and still leave the title somewhere to sit.

   On the hero, not on the file: the covers that are square want the
   58%, and this is the shape that does not. */
.pr__hero--wide .pr__hero-img{ object-position: center 76% }

/* A project whose cover has not been shot yet. The band keeps its full
   height and its ink ground so the name arrives exactly where it does
   on every other project — the page reads as one still being written
   rather than as one with a broken image in it. Drop a <picture> in and
   remove the modifier; nothing else changes. */
.pr__hero--pending{
  background:
    radial-gradient(120% 90% at 50% 0%, #1b1b1f 0%, #101012 62%);
}

/* The wash follows the type, and the type is at the foot of the frame —
   so this is weighted there, with a little kept at the head for the
   brand lockup. The middle stays as clear as it can, which is where the
   room actually is.

   These are bright renders — pale walls, lit ceilings, marble floors —
   and the bottom stops are heavier than they look like they need to be
   for exactly that reason. */
.pr__hero-scrim{
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to top,
      rgba(12,12,14,.80) 0%,
      rgba(12,12,14,.58) 22%,
      rgba(12,12,14,.30) 46%,
      rgba(12,12,14,.10) 72%,
      rgba(12,12,14,.44) 100%);
}

/* The name keeps its place on the floor of the picture and is centred
   across it. Horizontal only: the height it sits at is what makes the
   hero read as a title card with a room behind it rather than as a
   picture with a caption dropped in the middle, and the room's own
   subject — the seating, the table — is what occupies the centre. */
.pr__hero-over{
  position: absolute;
  inset: auto 0 0 0;
  padding: var(--gut);
  padding-bottom: clamp(2rem, 6vh, 4rem);
  max-width: 108rem;
  margin-inline: auto;
  text-align: center;
  color: var(--on-media);
}

.pr__title{
  font-family: var(--display-hi);
  font-weight: 400;
  font-size:var(--fs-mega);
  line-height: .95;
  letter-spacing: .01em;
  text-transform: uppercase;
  font-variant-numeric: lining-nums;   /* see the same note on .wk__name */
  color: var(--on-media);
  /* Two shadows rather than one: a tight dark edge that keeps the thin
     strokes of the Didone from dissolving where they cross something
     pale, and a wide soft one that lifts the whole word off the room
     behind it. A single blur cannot do both — tight enough to hold a
     hairline serif is too small to separate the mass. */
  text-shadow:
    0 1px 2px rgba(12,12,14,.55),
    0 .04em .55em rgba(12,12,14,.5);
}

/* ── a written passage ────────────────────────────────────────────
   The page alternates: a passage, then the rooms it describes, then the
   next passage. Both share the 108rem container and the same gutter, so
   the left edge of a paragraph and the left edge of the band beneath it
   are the same line all the way down.

   The heading sits above its own copy rather than beside it. Ranged
   into a side column it would have made a two-column layout that the
   image bands then have to break out of, and the bands are the reason
   the page exists.
   ───────────────────────────────────────────────────────────────── */
/* A passage gets the screen to itself. You read it, then you scroll and
   the rooms it describes arrive — rather than reading three lines with
   the next photograph already half in view arguing for your attention.

   min-height, not height: the block still grows past the screen when the
   copy is long or the type is scaled up, instead of clipping. And svh
   rather than vh, because on a phone vh is the tallest the viewport ever
   gets — measured with the address bar retracted — so a vh-tall block
   always starts the page overflowing by the height of that bar. */
/* No min-height and no vertical centring. Both are gone on purpose:
   holding each passage to a full screen meant the copy floated in the
   middle of a tall empty box, and the gap above the opening line was
   the screen doing that rather than any margin. A passage is now only
   as tall as its own words, and the page runs continuously — text,
   pictures, text — instead of stopping for a screen at a time. */
.pr__body{
  max-width: 108rem;
  margin-inline: auto;
  padding: clamp(1.5rem, 3.5vh, 2.5rem) var(--gut);
}

/* The opening passage sits straight under the hero with nothing between
   them but the line of padding every other block gets. */
.pr__body:first-of-type{ padding-top: clamp(1.75rem, 4vh, 2.75rem) }

.pr__h{
  font-family: var(--display-hi);
  font-weight: 400;
  font-size:var(--fs-title);
  line-height: 1.1;
  letter-spacing: -.005em;
  font-variant-numeric: lining-nums;
  color: var(--bone);
  margin-bottom: .55em;
}

/* Run to the same two edges as the bands below, so the left and right
   sides of a passage and of the photographs under it are one line all
   the way down the page. The gutter stays — take it off and the text
   would touch the window while every picture on the page still sits
   inside it, which reads as a mistake rather than as full bleed.

   The size is not a decoration here, it is what makes the width work. A
   measure is counted in characters, not pixels: at the old 1.16rem a
   line running the full container was about a hundred and fifty
   characters, and the eye loses its place on the return sweep somewhere
   around ninety. Growing the type with the column keeps the count where
   it was — the line simply carries bigger letters now. Both sides of
   that trade have to move together; widening the column alone would
   have made this harder to read, not more generous. */
.pr__copy{
  display: grid;
  gap: .7em;
}

/* One size for every paragraph on the page, opening passage included.
   The lead used to be set larger to stand in for the heading it does
   not have; it is now the same as the rest, so the three passages read
   as three equal parts of one piece of writing. */
.pr__copy p{
  font-family: var(--sans);
  font-size:var(--fs-lede);
  font-weight: 300;
  line-height: 1.62;
  color: var(--bone-dim);
  text-wrap: pretty;
}

/* ── a band of rooms ──────────────────────────────────────────────
   Tight gaps, so a band reads as one object with divisions in it
   rather than as loose pictures that happen to be near each other.

   Every source render is square. Two across get a shallow 5:4 crop,
   because at half of 108rem a square is nearly nine hundred pixels tall
   and pushes its own pair off the screen. 5:4 rather than the 4:3 this
   started on: a quarter off a square takes the ceiling with it, and in
   these rooms the ceiling is where most of the work is — the coffers,
   the cove lighting, the chandelier over the foyer. Ten per cent buys
   back the height without cutting into the design.

   Three across keep the square. The cells are small enough by then that
   the full frame fits, and the detail shot in the third slot is
   composed square to begin with.
   ───────────────────────────────────────────────────────────────── */
.pr__band{
  max-width: 108rem;
  margin-inline: auto;
  padding-inline: var(--gut);
  display: grid;
  gap: clamp(.5rem, .9vw, .9rem);
  /* A blank line's worth of air under every band, on top of the padding
     the next block brings with it. The pictures and the writing were
     butting up close enough that a band read as belonging to the
     passage below it rather than to the one it illustrates. */
  margin-bottom: clamp(1.25rem, 2.5vh, 2rem);
}

.pr__band--2{ grid-template-columns: repeat(2, 1fr) }
.pr__band--3{ grid-template-columns: repeat(3, 1fr) }

/* One picture, carrying its own shape.
   ─────────────────────────────────────────────────────────────────
   The paired and tripled bands crop everything to a common ratio,
   which is right for them: a row only closes flat if its cells agree,
   and half a per cent of somebody's ceiling is a fair price for that.
   A band of one has no row to close, so it has no reason to crop at
   all — and the pictures that get this treatment are the ones a
   passage is actually about, where the crop would cost the most. The
   master bedroom here is composed dead square, chandelier at the top
   edge and the foot of the bed at the bottom; taken to 5/4 with the
   others it loses both ends of the thing that makes it symmetrical.

   So --ar comes in on the tag and the figure takes it exactly, the
   same bargain the mosaic below strikes. Width is what gets capped
   rather than height: with the ratio fixed, holding the width to
   --ar × 74vh is the only way to promise the picture will not run
   past the fold, and it degrades to full width on a narrow screen
   without a media query. */
.pr__band--1{ grid-template-columns: 1fr; justify-items: center }
.pr__band--1 .pr__fig{
  aspect-ratio: var(--ar);
  inline-size: min(100%, calc(var(--ar) * 74vh));
}

/* One under the other below the tablet. Three landscape crops side by
   side on a phone are three letterboxes about ninety pixels tall. */
@media (max-width: 48rem){
  .pr__band--2,
  .pr__band--3{ grid-template-columns: 1fr }
}

.pr__fig{
  margin: 0;
  overflow: hidden;
  background: var(--ink);
  aspect-ratio: 5 / 4;
}
.pr__band--3 .pr__fig{ aspect-ratio: 1 / 1 }

/* Back to the full frame once the band is a single stack — the crop
   existed to buy height back, and stacked there is no pair to fit. */
@media (max-width: 48rem){
  .pr__fig,
  .pr__band--3 .pr__fig{ aspect-ratio: 1 / 1 }
}

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

/* ── the mosaic ───────────────────────────────────────────────────
   Everything left over: elevations, garden, kitchen, the build, the
   material sheets and the plans. Twelve columns, and each tile carries
   its own --sp (how many columns) and --ar (what shape) inline, because
   those two numbers are properties of the individual picture rather
   than of the layout — a tall elevation and a wide spec sheet want
   opposite things and no single rule covers both.

   Tiles sharing a row are given the same --ar so the row closes flat.
   Letting every cell keep its natural height instead leaves ragged
   holes under the short ones, which reads as a bug rather than as
   variety; a few per cent of crop is the cheaper price.
   ───────────────────────────────────────────────────────────────── */
.pr__mo{
  max-width: 108rem;
  margin-inline: auto;
  padding-inline: var(--gut);
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: clamp(.5rem, .9vw, .9rem);
  margin-bottom: clamp(1.25rem, 2.5vh, 2rem);
}

.pr__mo-f{
  margin: 0;
  grid-column: span var(--sp);
  aspect-ratio: var(--ar);
  overflow: hidden;
  background: var(--ink);
}

.pr__mo-f img{
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* The plans and the material sheets are documents, not photographs —
   they are read rather than looked at, so they are shown whole. cover
   would crop a floor plan to fit its tile, which is the one thing you
   must never do to a drawing. */
.pr__mo-f--doc{ background: #fff }
.pr__mo-f--doc img{ object-fit: contain }

/* Below the tablet the twelve-column arrangement stops meaning
   anything — a tile spanning three of twelve is ninety pixels wide.
   Everything pairs up, and the documents take the full width so they
   stay legible. */
@media (max-width: 60rem){
  .pr__mo{ grid-template-columns: repeat(2, 1fr) }
  .pr__mo-f{ grid-column: span 1; aspect-ratio: 1 }
  .pr__mo-f--doc{ grid-column: span 2; aspect-ratio: var(--ar) }
}

@media (max-width: 34rem){
  .pr__mo{ grid-template-columns: 1fr }
  .pr__mo-f,
  .pr__mo-f--doc{ grid-column: span 1 }
}

/* A clear break before the closing passage and the mosaic, so the
   bedroom band reads as the end of the walk through the rooms rather
   than as the start of the gallery that follows it. This is the one
   seam on the page where the subject actually changes — finished rooms
   on one side, elevations and drawings on the other — and it is worth
   more air than the seams inside a section. */
.pr__mo-intro{ padding-top: clamp(3rem, 7vh, 5rem) }

/* ── back ─────────────────────────────────────────────────────────── */
.pr__back{
  max-width: 108rem;
  margin-inline: auto;
  padding: 0 var(--gut) clamp(4rem, 12vh, 8rem);
  border-top: 1px solid var(--hair);
  padding-top: clamp(2rem, 5vh, 3rem);
}

.pr__back-l{
  display: inline-flex;
  align-items: center;
  gap: .7em;
  text-decoration: none;
  font-family: var(--sans);
  font-size:var(--fs-fine);
  font-weight: 400;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--bone-dim);
  transition: color .35s var(--ease-out);
}

.pr__back-a{
  width: 1.1em;
  height: 1.1em;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: square;
  transition: transform .4s var(--ease-out);
}

@media (hover: hover){
  .pr__back-l:hover{ color: var(--bronze-hi) }
  .pr__back-l:hover .pr__back-a{ transform: translateX(-.25em) }
}

.pr__back-l:focus-visible{
  outline: 2px solid var(--bronze-hi);
  outline-offset: 4px;
}

/* ── arriving ─────────────────────────────────────────────────────── */
/* js/project.js sets [data-in] on each [data-up] as it is reached.
   .no-js unshuts them, the way every other reveal on this site does. */
[data-up]{
  opacity: 0;
  transform: translateY(26px);
  transition: opacity .85s var(--ease-out), transform .85s var(--ease-out);
}
[data-up][data-in]{
  opacity: 1;
  transform: none;
}
.no-js [data-up]{ opacity: 1; transform: none }

@media (prefers-reduced-motion: reduce){
  .pr__back-l,
  .pr__back-a{ transition: none }
  .pr__back-l:hover .pr__back-a{ transform: none }
  [data-up]{ opacity: 1; transform: none; transition: none }
}

/* ═══════════════════════════════════════════════════════════════════
   Other projects — the tail of every project page

   A reader who reaches the bottom of a project liked something. The
   useful next thing to offer is another one of these, not a return to
   the index they arrived from — so the row comes before the "All
   projects" line, and that line stays on as the quieter catch-all.

   Names are on the cards permanently rather than on hover. The plates
   on the projects page can afford to hide theirs because the picture
   IS the argument there and the page has nothing else to do; here the
   row is a signpost at the end of a long read, and a signpost you have
   to touch before it tells you where it points is not a signpost.
   ═══════════════════════════════════════════════════════════════════ */
.more{
  max-width: 108rem;
  margin-inline: auto;
  padding: clamp(3.5rem, 9vh, 6rem) var(--gut) clamp(2rem, 5vh, 3rem);
  border-top: 1px solid var(--hair);
}

.more__t{
  font-family: var(--display-hi);
  font-weight: 400;
  font-size:var(--fs-title);
  line-height: 1.1;
  letter-spacing: .01em;
  color: var(--bone);
  text-align: center;
  margin-bottom: clamp(1.75rem, 4.5vh, 3rem);
}

.more__row{
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(.75rem, 1.4vw, 1.4rem);
}

/* This is the no-script fallback now: js/coverflow.js takes the same
   <ul> and lays it out as a looping carousel, so the row carries every
   project rather than a chosen three. Left as a plain grid, seven cards
   wrap to three rows with a gap under the last — honest, and the only
   thing a grid can do with a list this long. Anyone running the script
   sees the carousel, where length costs nothing. */
@media (max-width: 56rem){
  .more__row{ grid-template-columns: repeat(2, 1fr) }
}
@media (max-width: 34rem){
  .more__row{ grid-template-columns: 1fr }
}

.more__card{
  position: relative;
  display: block;
  text-decoration: none;
  color: var(--on-media);
  overflow: hidden;
  isolation: isolate;
  background: #101012;
}

.more__media{
  display: block;
  position: relative;
  aspect-ratio: 1 / 1;
  overflow: hidden;
}

.more__media img{
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transform: scale(1.001);      /* seals the edge against the scale below */
  transition: transform 1s var(--ease-out);
}

/* Bottom-weighted and permanent, because the type is permanent. Kept
   off the top two-thirds so the room in the picture is still readable
   as a room. */
.more__scrim{
  position: absolute;
  inset: 0;
  background: linear-gradient(to top,
    rgba(12,12,14,.78) 0%,
    rgba(12,12,14,.42) 26%,
    rgba(12,12,14,.10) 54%,
    rgba(12,12,14,0) 76%);
  transition: opacity .5s var(--ease-out);
}

.more__over{
  position: absolute;
  inset: auto 0 0 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .55em;
  padding: clamp(1rem, 2.4vw, 1.6rem);
  text-align: center;
  pointer-events: none;          /* the <a> underneath takes every click */
}

.more__name{
  font-family: var(--display-hi);
  font-weight: 400;
  font-size:var(--fs-sub);
  line-height: 1.1;
  letter-spacing: .03em;
  text-transform: uppercase;
  text-wrap: balance;
  font-variant-numeric: lining-nums;   /* see the note on .pr__title */
  color: var(--on-media);
  text-shadow: 0 1px 6px rgba(12,12,14,.6);
}

.more__cue{
  display: inline-flex;
  align-items: center;
  gap: .55em;
  font-family: var(--sans);
  font-size:var(--fs-fine);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--on-media-dim);
  text-shadow: 0 1px 6px rgba(12,12,14,.6);
}

.more__cue svg{
  width: 1em;
  height: 1em;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: square;
  transition: transform .45s var(--ease-out);
}

@media (hover: hover){
  .more__card:hover .more__media img,
  .more__card:focus-visible .more__media img{ transform: scale(1.05) }

  .more__card:hover .more__scrim,
  .more__card:focus-visible .more__scrim{ opacity: .88 }

  .more__card:hover .more__cue,
  .more__card:focus-visible .more__cue{ color: var(--on-media) }

  .more__card:hover .more__cue svg,
  .more__card:focus-visible .more__cue svg{ transform: translate(.16em, .16em) }
}

.more__card:focus-visible{
  outline: 2px solid var(--bronze-hi);
  outline-offset: 4px;
}

/* Kind and place, on paper under the card — the same line the plates on
   the projects page carry, so a project reads the same wherever it is
   shown. */
.more__meta{
  margin-top: .7rem;
  font-family: var(--sans);
  font-size:var(--fs-micro);
  letter-spacing: .15em;
  text-transform: uppercase;
  color: var(--bone-faint);
  text-align: center;
}

@media (prefers-reduced-motion: reduce){
  .more__media img,
  .more__scrim,
  .more__cue,
  .more__cue svg{ transition: none }
  @media (hover: hover){
    .more__card:hover .more__media img{ transform: scale(1.001) }
    .more__card:hover .more__cue svg{ transform: none }
  }
}

/* ═══════════════════════════════════════════════════════════════════
   Opening a picture

   Every photograph on this page is shown at the size the layout can
   spare, which for a floor plan or a material sheet is not the size it
   needs to be read at. So each one opens.

   Two parts: the invitation, which is CSS, and the frame it opens into,
   which js/lightbox.js builds. The invitation has to exist even if the
   script never loads — but a plus sign promising something that then
   does nothing is worse than no plus sign, so the script adds
   .has-lb to the root and everything below is scoped to it.
   ═══════════════════════════════════════════════════════════════════ */

.has-lb .pr__fig,
.has-lb .pr__mo-f{
  position: relative;
  cursor: zoom-in;
}

/* The wash. Just enough to say the picture has become a control, not so
   much that you stop seeing the picture — this is a hover state, and
   the thing being hovered is the thing you want to look at. */
.has-lb .pr__fig::before,
.has-lb .pr__mo-f::before{
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: rgba(12,12,14,.28);
  opacity: 0;
  transition: opacity .35s var(--ease-out);
  pointer-events: none;
}

/* A plus, drawn as two gradients rather than as a glyph or an SVG: a
   font may not have loaded and a hairline cross is four values of CSS.
   The bar is 1.5px because a 1px white line over a photograph
   disappears into whatever is behind it at some zoom levels. */
.has-lb .pr__fig::after,
.has-lb .pr__mo-f::after{
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 2;
  width: clamp(38px, 4vw, 58px);
  height: clamp(38px, 4vw, 58px);
  background:
    linear-gradient(#fff, #fff) center / 1.5px 100% no-repeat,
    linear-gradient(#fff, #fff) center / 100% 1.5px no-repeat;
  opacity: 0;
  transform: translate(-50%, -50%) scale(.82);
  transition: opacity .35s var(--ease-out), transform .45s var(--ease-out);
  pointer-events: none;
  filter: drop-shadow(0 1px 6px rgba(12,12,14,.55));
}

@media (hover: hover){
  .has-lb .pr__fig:hover::before,
  .has-lb .pr__fig:focus-visible::before,
  .has-lb .pr__mo-f:hover::before,
  .has-lb .pr__mo-f:focus-visible::before{ opacity: 1 }

  .has-lb .pr__fig:hover::after,
  .has-lb .pr__fig:focus-visible::after,
  .has-lb .pr__mo-f:hover::after,
  .has-lb .pr__mo-f:focus-visible::after{
    opacity: 1;
    transform: translate(-50%, -50%) scale(1);
  }
}

/* No pointer to hover with. The plus is shown at rest instead, quietly:
   on a touch screen it is the only thing saying these open at all. */
@media (hover: none){
  .has-lb .pr__fig::after,
  .has-lb .pr__mo-f::after{ opacity: .8; transform: translate(-50%,-50%) scale(.9) }
}

.has-lb .pr__fig:focus-visible,
.has-lb .pr__mo-f:focus-visible{
  outline: 2px solid var(--bronze-hi);
  outline-offset: 3px;
}

/* ── the frame ────────────────────────────────────────────────────── */
.lb{
  position: fixed;
  inset: 0;
  z-index: 500;              /* over the nav at 100, under the loader */
  display: grid;
  place-items: center;
  opacity: 0;
  visibility: hidden;
  transition: opacity .3s var(--ease-out), visibility 0s linear .3s;
}
.lb.is-open{
  opacity: 1;
  visibility: visible;
  transition: opacity .3s var(--ease-out), visibility 0s;
}

/* Near-black rather than the page's brown: for as long as this is up,
   the picture is the only thing on screen and everything else should
   get out of its way. */
.lb__scrim{
  position: absolute;
  inset: 0;
  background: rgba(8,8,10,.94);
  cursor: zoom-out;
}

.lb__fig{
  position: relative;
  z-index: 1;
  margin: 0;
  max-width: min(92vw, 1600px);
  max-height: 86svh;
  display: grid;
  place-items: center;
  transform: scale(.97);
  transition: transform .35s var(--ease-out);
  pointer-events: none;      /* clicks fall through to the scrim */
}
.lb.is-open .lb__fig{ transform: none }

.lb__img{
  max-width: 100%;
  max-height: 86svh;
  width: auto;
  height: auto;
  object-fit: contain;       /* never crop in here — this is the full view */
  display: block;
  box-shadow: 0 30px 90px -30px rgba(0,0,0,.8);

  /* The figure around it stays transparent to clicks so the margin
     closes the frame; the picture itself takes them back, because it is
     the zoom control. Scaling from the centre is what lets the script
     hold a point under the cursor by translating in screen pixels. */
  pointer-events: auto;
  transform-origin: center center;
  cursor: zoom-in;

  /* The browser's own pan and zoom would fight the script's. */
  touch-action: none;
  user-select: none;
  -webkit-user-drag: none;
}

/* Magnified, the picture is larger than its frame, so it has to be
   allowed out of it — and the pointer changes from "this opens" to
   "this moves". */
.lb.is-zoomed .lb__fig{ overflow: visible }
.lb.is-zoomed .lb__img{ cursor: grab }
.lb.is-zoomed .lb__img:active{ cursor: grabbing }

/* ── the controls ─────────────────────────────────────────────────── */
.lb__btn{
  position: absolute;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  color: #F4F1EC;
  background: rgba(244,241,236,.09);
  cursor: pointer;
  transition: background .3s var(--ease-out), transform .3s var(--ease-out),
              opacity .3s var(--ease-out);
}
.lb__btn svg{
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: square;
}
@media (hover: hover){
  .lb__btn:hover{ background: rgba(244,241,236,.2) }
}
.lb__btn:focus-visible{
  outline: 2px solid #F4F1EC;
  outline-offset: 3px;
}

.lb__close{ top: clamp(1rem, 3vh, 2rem); right: clamp(1rem, 3vw, 2rem) }

/* Beside the cross, on the same line. Both glyphs live in the button and
   CSS picks one — a button that rewrites its own contents loses the
   transition, and on a touch screen this is the only thing that says the
   picture magnifies at all. */
.lb__zoom{
  top: clamp(1rem, 3vh, 2rem);
  right: calc(clamp(1rem, 3vw, 2rem) + 56px);
}
/* Both halves of each pair are written with the button in the selector,
   so they out-weigh the `.lb__zoom span` rule above them — a bare
   `.lb__zo` loses to it on specificity and both glyphs stack up. */
.lb__zoom span{ display: grid; place-items: center }
.lb__zoom .lb__zo,
.lb.is-zoomed .lb__zoom .lb__zi{ display: none }
.lb.is-zoomed .lb__zoom .lb__zo{ display: grid }
.lb__prev{ left: clamp(.5rem, 2vw, 2rem);  top: 50%; margin-top: -24px }
.lb__next{ right: clamp(.5rem, 2vw, 2rem); top: 50%; margin-top: -24px }

@media (hover: hover){
  .lb__prev:hover{ transform: translateX(-3px) }
  .lb__next:hover{ transform: translateX(3px) }
}

/* One picture, no arrows. Same argument as the missing carousel arrows
   on the "other projects" row: a control that cannot move anything is
   worse than no control. */
.lb.is-single .lb__prev,
.lb.is-single .lb__next{ display: none }

/* Where you are in the set. Small, because it is a reassurance rather
   than a caption — the picture is the content. */
.lb__count{
  position: absolute;
  z-index: 2;
  left: 50%;
  transform: translateX(-50%);
  bottom: clamp(1rem, 3vh, 2rem);
  font-family: var(--sans);
  font-size:var(--fs-fine);
  letter-spacing: .18em;
  color: rgba(244,241,236,.62);
  font-variant-numeric: tabular-nums lining-nums;
}
.lb.is-single .lb__count{ display: none }

/* The page behind must not scroll while this is up. Set by the script
   on <html>, with the scrollbar's width replaced as padding so the
   layout does not jump sideways at the moment of opening. */
.is-lb-open{ overflow: hidden }

@media (prefers-reduced-motion: reduce){
  .lb,
  .lb__fig,
  .lb__btn,
  .has-lb .pr__fig::before,
  .has-lb .pr__fig::after,
  .has-lb .pr__mo-f::before,
  .has-lb .pr__mo-f::after{ transition: none }
  .lb__fig{ transform: none }
}

/* The one line a project page carries before it has been written. Set
   at body size in the same column the passages use, so the page has a
   measured empty state instead of a gap between the plate and the
   footer. */
.pr__soon{
  max-width: 108rem;
  margin: clamp(3rem, 9vh, 5.5rem) auto clamp(3.5rem, 10vh, 6.5rem);
  padding-inline: var(--gut);
  font-family: var(--sans);
  font-size:var(--fs-body);
  line-height: 1.75;
  color: var(--bone-dim);
  max-inline-size: 34rem;
}

/* Same idea on the closing row: a card whose project has no cover yet
   still shows its name on the ink plate the card already carries. */
.more__media--pending{
  background:
    radial-gradient(120% 90% at 50% 0%, #1b1b1f 0%, #101012 62%);
}
.more__media--pending::after{
  content: "Cover to come";
  position: absolute;
  inset-inline: 0;
  top: 1.15rem;
  font-family: var(--sans);
  font-size:var(--fs-micro);
  letter-spacing: .2em;
  text-transform: uppercase;
  text-align: center;
  color: rgba(245,240,232,.34);
}

/* ── the other projects, on a coverflow ───────────────────────────
   Every rule below is scoped to .has-cf, which js/coverflow.js adds to
   the section on startup. Without the script the row stays the plain
   three-column grid above and none of this applies — the carousel is an
   enhancement, not the only way the section works.

   The geometry is the reference's: rotate 40deg and 100px of depth at
   one slot from centre, both applied by the script as inline transforms
   because they are a function of each card's live distance from the
   middle. What is here is everything that is not that — the stage the
   cards are placed on, the light falling off them, and the controls.
   ───────────────────────────────────────────────────────────────── */
.has-cf .more__vp{
  position: relative;
  /* Deep enough for 100px of push-back to read as distance rather than
     as the cards simply getting smaller. Too short and the rotation
     shears; too long and the far cards vanish to a point. */
  perspective: 1200px;
  overflow: hidden;
  /* The row is absolutely positioned inside, so the viewport carries the
     height: one card, plus room for the rotation to swing through. */
  padding-block: clamp(1rem, 2.5vh, 2rem);
}

.has-cf .more__row{
  display: block;
  position: relative;
  transform-style: preserve-3d;
  touch-action: pan-y;          /* the page still scrolls through the row */
  cursor: grab;
  /* The measured slot the script reads comes from this. */
  column-gap: clamp(.75rem, 1.4vw, 1.4rem);
  /* One card at its full square, plus the line of meta under it. */
  height: min(56vw, 31rem);
}
.has-cf.is-grabbing .more__row{ cursor: grabbing }
.has-cf .more__row:focus-visible{
  outline: 2px solid var(--bronze-hi);
  outline-offset: 6px;
}

.has-cf .more__cell{
  position: absolute;
  top: 0;
  left: 50%;
  /* Half the card, whatever the card turned out to be. Written as a
     division of the same expression rather than as its own min(): with
     two negatives min() returns the more negative, so -19vw won every
     time the viewport was wide enough for 21rem to cap the width, and
     the whole carousel sat left of centre by the difference. */
  /* As large as the frame takes. Bigger than this and the two turned
     cards run past the viewport's hidden edge and get sliced off square,
     which reads as a clipping bug rather than as depth. */
  width: min(44vw, 28rem);
  margin-left: calc(min(44vw, 28rem) / -2);
  transform-style: preserve-3d;
  /* No transition: the script eases every frame itself on rAF so a drag
     can be caught and retargeted mid-flight. A transition here would
     fight it and lag the finger. */
  will-change: transform;
}

/* No slide shadow. The reference dims the turned cards and this file
   did too, but these are photographs of finished houses and each one is
   a door you can click straight through — dimmed, they read as disabled
   rather than as further away. Every cover sits at full intensity and
   the turn and the depth carry the hierarchy on their own.

   The name and the meta stay up on every card for the same reason: a
   card you can open is a card you have to be able to identify without
   bringing it to the middle first. */

/* ── the arrows ───────────────────────────────────────────────────
   Over the turned-away cards to either side, which is the one band of
   the frame the centre card never reaches. Round, quiet, and only as
   loud as they need to be to be found. */
.has-cf .more__arrow{
  position: absolute;
  top: 50%;
  z-index: 200;
  width: 44px; height: 44px;    /* the 44px floor, not a coincidence */
  margin-top: -22px;
  display: grid;
  place-items: center;
  border: 1px solid var(--hair);
  border-radius: 50%;
  background: rgba(253,251,247,.86);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  color: var(--bone);
  cursor: pointer;
  padding: 0;
  transition: background 180ms ease, border-color 180ms ease,
              transform 180ms var(--ease-out);
}
.has-cf .more__arrow svg{ width: 16px; height: 16px; display: block }
.has-cf .more__arrow--prev{ left: clamp(.4rem, 1.5vw, 1.5rem) }
.has-cf .more__arrow--next{ right: clamp(.4rem, 1.5vw, 1.5rem) }

@media (hover: hover) and (pointer: fine){
  .has-cf .more__arrow:hover{
    background: #FDFBF7;
    border-color: rgba(74,59,41,.38);
  }
  .has-cf .more__arrow--prev:hover{ transform: translateX(-3px) }
  .has-cf .more__arrow--next:hover{ transform: translateX(3px) }
}
.has-cf .more__arrow:active{ transform: scale(.94) }
.has-cf .more__arrow:focus-visible{
  outline: 2px solid var(--bronze-hi);
  outline-offset: 3px;
}

/* ── the dots ─────────────────────────────────────────────────────── */
.has-cf .more__dots{
  display: flex;
  justify-content: center;
  gap: .5rem;
  margin-top: clamp(1rem, 2.5vh, 1.6rem);
}
.has-cf .more__dot{
  width: 7px; height: 7px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(74,59,41,.24);
  cursor: pointer;
  transition: background 200ms ease, transform 200ms var(--ease-out);
  /* The dot is 7px; the thing you press is 44. */
  position: relative;
}
.has-cf .more__dot::after{
  content: "";
  position: absolute;
  inset: -18px;
}
.has-cf .more__dot.is-on{
  background: var(--bronze-hi);
  transform: scale(1.35);
}
.has-cf .more__dot:focus-visible{
  outline: 2px solid var(--bronze-hi);
  outline-offset: 6px;
}

/* Narrow screens: one card at a time, the neighbours only just showing. */
@media (max-width: 48rem){
  .has-cf .more__row{ height: min(92vw, 27rem) }
  .has-cf .more__cell{
    width: min(76vw, 22rem);
    margin-left: calc(min(76vw, 22rem) / -2);
  }
}

/* Reduced motion: the script already snaps instead of easing. Take the
   turn and the depth out of the look too, so what is left is a plain
   row that steps one card at a time. */
@media (prefers-reduced-motion: reduce){
  .has-cf .more__vp{ perspective: none }
  .has-cf .more__media::before{ background: rgba(8,8,10,0) }
  .has-cf .more__arrow{ transition: none }
}
