/* ------------------------------------------------------------
   Project page
   Two columns: the photograph on the left, the menu and details on the
   right. The page never scrolls — the frame holds one position and you
   click its halves to move through the series.
   Loaded after style.css and scoped to body.project throughout.
------------------------------------------------------------ */

:root{
  /* How the photograph is sized, in two parts.

     ratio — the share of the column's height that the photograph takes. The
     remainder becomes white space above and below, so the margin stays
     proportional at any window height instead of being what happens to be
     left over.

     max — a ceiling in CSS pixels, set to the height of the source files so a
     photograph is never displayed larger than it actually is. */
  --plate-height-ratio:0.94;
  --plate-max-height:1000px;

  --side-width:clamp(230px, 24vw, 300px);
  --page-pad-y:clamp(26px, 4vh, 46px);
  --page-pad-x:clamp(20px, 3vw, 44px);
}

body.project{
  height:100dvh;
  overflow:hidden;
  /* style.css keeps 76px clear for the fixed top bar; there isn't one here. */
  padding-top:0;
}

/* style.css fixes a header to the top of every page. A project page has its
   menu in the right-hand column instead. */
body.project .header{ display:none; }

/* style.css also gives every <main> a max-width, auto side margins and a big
   top padding meant to clear that header. The stage is a <main>, so all of it
   has to go — and the auto margins especially: an auto margin on the cross
   axis switches off `stretch`, which silently collapses the column layout. */
body.project main{
  max-width:none;
  margin:0;
  padding:0;
}

.page{
  height:100dvh;
  display:flex;
  /* Menu first in the source so it is read first, but painted on the right. */
  flex-direction:row-reverse;
  gap:var(--page-pad-x);
  padding:var(--page-pad-y) var(--page-pad-x);
  overflow:hidden;
}

/* ----------------------------------------------------------------- side --- */
.side{
  flex:0 0 var(--side-width);
  width:var(--side-width);
  min-width:0;
  display:flex;
  flex-direction:column;
  justify-content:space-between;
}

.side-top{
  min-height:0;
  overflow-y:auto;
  padding-bottom:18px;
  scrollbar-width:none;
}
.side-top::-webkit-scrollbar{ display:none; }

/* The mark carries the identity here, the way the top bar carries it on every
   other page. The name sits under it as a caption rather than as a heading —
   at 52px the glyph is doing the work a 26px name used to do. */
/* Two lines, both starting at the column's left edge, where the menu below
   starts: the mark on the first, the tagline under it on the second. */
.ident{ display:block; }

.ident-mark{
  --mark:clamp(42px, 3.4vw, 56px);
  display:block;
  width:var(--mark);
  margin-bottom:10px;
  color:var(--ink);
  transition:color .2s ease;
}
.ident-mark:hover{ color:var(--accent); opacity:1; }

/* One anchor around the whole line, so the target is the line and not just the
   last two words. The spans inside keep their own size, weight and colour. */
.ident-line{
  display:inline;
  color:inherit;
}
.ident-line:hover{ opacity:1; }
.ident-line:hover .ident-name,
.ident-line:hover .ident-role{ color:var(--accent); }

.ident-name{
  display:inline;
  font-size:17px;
  font-weight:500;
  letter-spacing:.02em;
  line-height:1.35;
  color:var(--ink);
}
.ident-role{
  display:inline;
  margin-left:.2em;
  font-size:13px;
  font-weight:var(--w-body);
  letter-spacing:.05em;
  line-height:1.4;
  color:var(--muted);
}
.ident-name,
.ident-role{ transition:color .2s ease; }

.side-nav{
  display:flex;
  flex-direction:column;
  margin-top:18px;
  font-size:15px;
  letter-spacing:.03em;
  line-height:1.75;
}
.side-nav a{ color:var(--ink); transition:color .2s ease; }
.side-nav a:hover,
.side-nav a[aria-current="page"]{ color:var(--accent); }

/* A project named in the menu that has no page yet. --hair is a border tone:
   at #eaeaea on white it sits around 1.2:1 and reads as nothing at all. */
.side-nav .pending{ color:var(--faint); cursor:default; }

/* First item of the menu block, so this is what holds it clear of the identity
   above — and it sits close to the list, which it belongs to.

   `.counter-space` is the same box with nothing in it, written on to a page that
   has no count. Without it the menu on the front page and on About would start
   higher than on a series page and the column would jump as you moved between
   them. It is not a spacer for its own sake: it is the count's line, held open. */
.counter,
.counter-space{
  margin-top:clamp(36px, 5.5vh, 58px);
  font-size:14px;
  line-height:1.3;
  /* The line is held open whether or not there is anything in it. That covers
     three cases with one rule: a page that never has a count, and a project
     whose photographs have not been uploaded yet — where project.js empties the
     element rather than writing "1 / 1" against an empty stage. Without it the
     menu on a new project sits 37px higher than everywhere else. */
  min-height:1.3em;
}
.counter{
  display:flex;
  align-items:center;
  color:var(--muted);
  letter-spacing:.08em;
  font-variant-numeric:tabular-nums;
}

/* One hairline rule per photograph, the current one in the accent. The row is
   sized so it occupies the same 1.3em line the old "3/7" did, which is what
   keeps every menu on the site starting at the same y. The rules shrink rather
   than wrap if a series ever grows long enough to fill the column — a second
   line here would push the menu down and break that alignment. */
.counter .rules{
  display:flex;
  align-items:center;
  gap:6px;
  min-height:1.3em;
}

.counter .rule{
  /* The width is set on width, not flex-basis: an empty element contributes its
     content size to the row's intrinsic width, so a bare `flex:0 1 16px` left
     every rule shrunk to its 4px minimum. */
  width:16px;
  flex:0 1 auto;
  min-width:4px;
  height:2px;
  background:var(--faint);
  transition:background .35s ease;
}

.counter .rule.on{ background:var(--accent); }

/* The words are for screen readers only in the wide viewer and the pager.
   In the short/stack mode there are no dots to track, so the line goes back
   to plain text and the span is shown. */
.counter .counter-text{
  position:absolute;
  width:1px;
  height:1px;
  margin:-1px;
  padding:0;
  border:0;
  overflow:hidden;
  clip:rect(0 0 0 0);
  clip-path:inset(50%);
  white-space:nowrap;
}

.counter-stacked .rules{ display:none; }

.counter-stacked .counter-text{
  position:static;
  width:auto;
  height:auto;
  margin:0;
  overflow:visible;
  clip:auto;
  clip-path:none;
  white-space:normal;
}

.side-contact{
  display:flex;
  flex-direction:column;
  margin-top:clamp(56px, 9vh, 88px);
  font-size:14px;
  line-height:1.75;
  letter-spacing:.03em;
}
/* The email is plain text now, so the colour has to live on the block rather
   than on the anchor, or it would come out black beside the Instagram link. */
.side-contact{ color:var(--muted); }
.side-contact a{ color:var(--muted); transition:color .2s ease; }
.side-contact a:hover{ color:var(--accent); }

.side-foot{
  flex:0 0 auto;
  display:flex;
  flex-direction:column;
  font-size:12.5px;
  line-height:1.55;
  color:var(--muted);
}

/* ---------------------------------------------------------------- stage --- */
.stage{
  position:relative;
  flex:1 1 auto;
  min-width:0;
  display:flex;
  align-items:center;
  justify-content:center;
  touch-action:pan-y;
  user-select:none;
}

/* One window for the whole series. project.js sizes it from the widest
   photograph and then leaves it alone, so it no longer animates: there is
   nothing left for it to animate to. overflow:hidden is what makes the slide
   a slide — it is the edge the photographs pass behind. The pager and the
   stack set this back to visible, because there the frame is a track taller
   than itself and hiding its overflow would leave one photograph on screen. */
.frame{
  position:relative;
  overflow:hidden;
}

/* display:block is load-bearing. Every plate is a <picture>, and style.css
   sets `picture{ display:contents }` so the wrapper stays out of the layout of
   the grids. A display:contents box has no box, so `position`, `inset` and
   `opacity` below were all being thrown away here: the seven photographs were
   stacking down the page in normal flow, one under the other, with the frame's
   overflow hiding the rest. `.plate` outranks the `picture` type selector, so
   naming display here puts the box back. */
.plate{
  display:block;
  position:absolute;
  inset:0;
  opacity:0;
  /* No transition here. The plates used to crossfade, which meant the outgoing
     photograph was being re-fitted inside a box that was changing shape
     underneath it — it appeared to squash before it had finished leaving.
     They slide now, and project.js writes the transform transition inline on
     the two plates that are actually moving. */
  pointer-events:none;
}
.plate.on{ opacity:1; }
.plate img{ width:100%; height:100%; object-fit:contain; display:block; }

/* ----------------------------------------------------------------- prose ---
   A page that wears the project chrome but holds words instead of photographs.
   The text sits where a photograph would and scrolls inside the stage rather
   than making the page scroll, so the column beside it stays put.
--------------------------------------------------------------------------- */
/* The words are given the box a photograph would have had — the same 94% of
   the stage height, the same square the meadow and 1:1 plates resolve to, on
   the same centre line — so the About page reads as one more plate in the
   series rather than a column pushed against the left edge. The text is held
   in the middle of that box; the paragraphs keep their own readable measure
   inside it, so widening the box does not widen the line. */
.prose-frame{
  position:relative;
  display:flex;
  flex-direction:column;
  justify-content:center;
  height:calc(100% * var(--plate-height-ratio));
  max-height:var(--plate-max-height);
  aspect-ratio:1 / 1;
  max-width:100%;
  min-height:0;
}

.prose{
  width:100%;
  max-width:58ch;
  max-height:100%;
  overflow-y:auto;
  scrollbar-width:none;
  color:var(--muted);
}
.prose::-webkit-scrollbar{ display:none; }

.prose h1{
  margin:0 0 22px;
  font-size:17px;
  font-weight:500;
  letter-spacing:.02em;
  color:var(--ink);
}

.prose p{
  margin:0 0 14px;
  font-size:14px;
  line-height:1.75;
  letter-spacing:.02em;
}
.prose p:last-child{ margin-bottom:0; }
.prose a{ color:var(--muted); transition:color .2s ease; }
.prose a:hover{ color:var(--accent); opacity:1; }

/* ---------------------------------------------------------------- arrows ---
   A chevron above and below the photo space, as the reference site has: an 8px
   square with two of its borders drawn, rotated. project.js builds them, so no
   project page has to carry them in its markup and a page without JavaScript
   never shows a control that cannot work. Hidden everywhere but the pager —
   without this rule a browser's default button styling shows through as a grey
   bar beside the photograph on the wide layout.
--------------------------------------------------------------------------- */
.nav-arrow{
  display:none;            /* the pager turns them on */
  width:100%;
  height:22px;
  align-items:center;
  justify-content:flex-start;   /* at the left edge, as the reference's are */
  flex:0 0 auto;
  appearance:none;
  background:none;
  border:0;
  padding:0;
  cursor:pointer;
  transition:opacity .25s ease;
}
.nav-arrow span{
  display:block;
  width:8px;
  height:8px;
  border-left:1px solid var(--ink);
  border-top:1px solid var(--ink);
}
.nav-arrow.up span{ transform:translateY(3px) rotate(45deg); }
.nav-arrow.down span{ transform:translateY(-3px) rotate(225deg); }

/* Bounded rather than looping in the pager, which is what an arrow that can be
   spent implies — and what the reference does. */
.nav-arrow.disabled{ opacity:.3; pointer-events:none; }

/* Without JavaScript the first photograph is simply visible. */
.no-js .frame{ width:auto; height:auto; max-height:86vh; }
.no-js .plate{ position:static; opacity:1; }
.no-js .plate:not(.on){ display:none; }
.no-js .plate img{ max-height:86vh; width:auto; }
.no-js .zone, .no-js .counter, .no-js .nav-arrow{ display:none; }

/* The picture itself is the control — the zones sit inside the frame, so a
   click only counts when it lands on the photograph. */
.zone{
  position:absolute;
  top:0; bottom:0;
  width:50%;
  appearance:none;
  background:none;
  border:0;
  padding:0;
  z-index:2;
}
.zone.prev{ left:0;  cursor:w-resize; }
.zone.next{ right:0; cursor:e-resize; }
.zone:focus-visible{ outline:1px solid var(--ink); outline-offset:-5px; }

/* When the coral pointer is running it already shows the direction, so the
   native resize arrow has to go — otherwise you get two of them. This needs
   more specificity than `.zone.prev`, because project.css loads after
   style.css and would otherwise win on source order alone. The resize
   cursors above stay as the fallback for when cursor.js doesn't run. */
.has-custom-cursor .zone.prev,
.has-custom-cursor .zone.next{ cursor:none; }

/* --------------------------------------------------------------- narrow ---
   One column, locked to the window. The page does not scroll here: the menu
   sits above and below at a much smaller size, and the stage takes everything
   left over — that leftover is the dedicated space the photographs page
   through, one at a time, on a vertical swipe.

   The sizes below are not arbitrary. Measured on the previous layout, the menu
   was eating about 490px before a photograph got any room at all, which left
   280px on a 390x844 phone and 130px on an SE. At the sizes here it comes to
   roughly 290px, leaving about 510px and 335px respectively.
--------------------------------------------------------------------------- */
@media (max-width:820px){
  .page{
    flex-direction:column;
    gap:0;
    padding:clamp(16px, 2.4vh, 22px) clamp(16px, 4vw, 22px);
  }

  /* The menu's two wrappers fall out of the layout so their children become
     items of .page and can be ordered around the stage. */
  .side, .side-top{ display:contents; }

  /* The order on a phone, which is not the order of the markup: a top row, the
     menu under it, the photograph filling what is left between the two arrows,
     and the contact lines below. The stage is the only thing that flexes, so it
     absorbs all the slack and everything after it is held against the bottom
     without being positioned. */
  .ident{ order:1; flex:0 0 auto; }
  .counter,
  .counter-space{ order:2; }
  .side-nav{ order:3; flex:0 0 auto; }
  .nav-arrow.up{ order:4; margin:14px 0 6px; }
  .stage{ order:5; flex:1 1 auto; min-height:0; }
  .nav-arrow.down{ order:6; margin-top:6px; }
  .side-contact{ order:7; flex:0 0 auto; margin-top:16px; }
  .side-foot{ order:8; flex:0 0 auto; }

  .nav-arrow{ display:flex; }

  /* The mark in the top right corner with the tagline squared up under it —
     the mirror of the wide layout, which starts both at the left. */
  .ident{ text-align:right; width:100%; }
  .ident-mark{ margin-left:auto; margin-bottom:4px; }

  /* The menu alone is set against the right edge. The arrows, the contact lines
     and the credit stay at the left, so the page is anchored on both sides
     rather than everything sliding to one. */
  .counter,
  .counter-space,
  .side-nav{ text-align:right; }
  .side-contact,
  .side-foot{ text-align:left; }

  /* Everything drops to roughly the reference site's scale. */
  .ident-mark{ --mark:24px; margin-bottom:0; }
  .ident-name{ font-size:15px; letter-spacing:.04em; }
  .ident-role{ font-size:12px; letter-spacing:.06em; }

  /* Hidden rather than removed: it is still written, and the short-viewport
     fallback below brings it back, where a scroll has no other way of saying
     how many photographs there are. */
  .counter,
  .counter-space{ display:none; }

  .side-nav{ margin-top:16px; font-size:12.5px; line-height:1.55; }
  .side-contact{ margin-top:14px; font-size:12px; line-height:1.6; }
  .side-foot{ margin-top:12px; font-size:11px; }

  /* ---- the pager ----
     The stage becomes the window and the frame becomes the track: the plates
     are stacked in a column, each the full height of the stage, and the track
     is moved by whole screens. project.js writes --i and nothing else. */
  .stage{
    position:relative;
    overflow:hidden;
    touch-action:none;   /* or an upward swipe fights iOS overscroll */
  }

  .frame{
    display:flex;
    flex-direction:column;
    width:100%;
    height:100%;
    overflow:visible;   /* the track is taller than itself; the stage clips */
    transform:translateY(calc(var(--i, 0) * -100%));
    transition:transform .42s cubic-bezier(.22,.68,.28,1);
  }

  .plate{
    position:static;
    flex:0 0 100%;
    width:100%;
    height:100%;
    opacity:1;
    transition:none;
  }
  .plate img{ width:100%; height:100%; object-fit:contain; }

  /* The tap halves live inside the frame, and a transformed element is the
     containing block for everything inside it — so they would slide away with
     the track. project.js reads the tap off the stage instead, which does not
     move. */
  .zone{ display:none; }

  /* Words, not a pager: nothing to clip and nothing to page through, so the
     stage gives up its window behaviour and the page scrolls normally. */
  body.about{ height:auto; overflow:visible; }
  body.about .page{ height:auto; }
  body.about .stage{
    display:block;
    overflow:visible;
    min-height:0;
    touch-action:auto;
    user-select:auto;
    margin:24px 0 0;
  }
  /* No photograph to match on a phone: the box gives up its square and the
     words simply follow the menu. */
  .prose-frame{
    display:block;
    height:auto;
    max-height:none;
    aspect-ratio:auto;
  }
  body.about .prose{ max-height:none; overflow:visible; }
  /* Nothing between the words and the contact lines here — the down arrow that
     usually separates them does not exist on this page. */
  body.about .side-contact{ margin-top:34px; }
}

/* ---------------------------------------------------- narrow and short ---
   A phone on its side, or any window too short to give the photographs a
   usable box: the pager would leave about 60px for the picture. Below this the
   page becomes an ordinary scroll — every photograph in the flow, one under
   the other, the browser doing the paging — and project.js stands down
   entirely. The reference site does the same thing under 380px of height.
------------------------------------------------------------------------- */
@media (max-width:820px) and (max-height:560px){
  body.project{ height:auto; overflow:visible; }
  .page{ height:auto; }

  .stage{
    display:block;
    overflow:visible;
    min-height:0;
    touch-action:auto;
    user-select:auto;
  }

  .frame{
    display:block;
    width:auto;
    height:auto;
    overflow:visible;
    transform:none;
    transition:none;
  }

  .plate{
    flex:none;
    height:auto;
    pointer-events:auto;
    margin-bottom:clamp(18px, 5vw, 34px);
  }
  .plate:last-child{ margin-bottom:0; }
  .plate img{ height:auto; }

  .counter{ display:block; }

  /* Nothing to step through — the scroll is the navigation. */
  .nav-arrow{ display:none; }

}

@media (prefers-reduced-motion:reduce){
  .frame, .plate{ transition:none; }
}
