/* ==========================================================================
   Greta Volungevičiūtė — project case study
   Design canvas: 1440 wide, free height. Requires css/base.css.

   Shared by every project page, not written for Crabshakk alone. Nothing here
   invents a type size, a colour or a spacing unit — every value is one already
   in use on work.html or about.html, so a case study reads as another room in
   the same building rather than as a second website.

   The page is four things in order: a hero, the project described in one
   block, the printed work in one row, and the photographs three to a row.
   There are no section headings — the pictures are already grouped by what
   they are, and a word over each group only said what was visible.
   ========================================================================== */

/* Same white ground as the work index, re-pointed the same way, so the shared
   header, nav, footer and hover rules invert for free. */
:root {
  --bg: #ffffff;
  --bg-lift: #f1f0ec;                    /* image box before it loads      */
  --ink: #0d0d0d;
  --ink-dim: rgba(13, 13, 13, 0.55);
  --ink-faint: rgba(13, 13, 13, 0.34);
  --hairline: rgba(13, 13, 13, 0.14);

  --pad-x: 96px;               /* page gutter inside the 1440 canvas       */
  --col: 82px;                 /* 12 columns + 11 × 24px gutters = 1248px  */
  --gut: 24px;

  /* Two spacing steps, not five. --sec is the silence between one idea and
     the next; --row is the smaller pause inside one. 168 is the work index's
     own row gap, and 200 is the only value on the page that is larger than
     anything there — a case study earns more air than a contact sheet. */
  --sec: 200px;
  --row: 168px;

  /* the parallax travel from the work index, unchanged: an image drifts from
     +16px to -16px inside its frame across a full pass through the viewport */
  --travel: 32px;
}

/* dark text on white wants the opposite smoothing from the hero */
/* clip, not hidden — see the note on #shell below */
body {
  -webkit-font-smoothing: auto;
  -moz-osx-font-smoothing: auto;
  overflow-x: clip;
  overflow-y: auto;
}

/* --------------------------------------------------------------------------
   1440 canvas that scales down but keeps scrolling — identical to work.css
   -------------------------------------------------------------------------- */

/* clip rather than the hidden the other pages use, and this is not cosmetic.
   overflow: hidden makes an element a scroll container, and a scroll container
   becomes the scrollport that any sticky descendant is measured against — so
   the reel's sticky frame had no scroll range of its own and simply travelled
   up the page with the section. overflow: clip cuts the same overflow off
   without creating a scroll container, which leaves the window as the
   scrollport where the reel expects it. */
#shell {
  position: relative;
  width: 100%;
  overflow-x: clip;
}

#page {
  position: relative;
  width: var(--design-w);
  transform-origin: 0 0;
  padding: 0 var(--pad-x) 0;
}

/* --------------------------------------------------------------------------
   The frame — one component, every picture on the page

   Lifted wholesale from .project__frame + .project__img on the work index,
   because the parallax has to behave identically or the two pages would drift
   apart in the one way a visitor actually notices: how things move.

   The image is taller than its frame by the full --travel and starts half a
   travel high, so it can slide the whole range without ever exposing an edge.
   transform is left alone for js/project.js to drive.

   --ar is always the source photograph's own ratio, never a shape chosen for
   the layout. Every Crabshakk original is portrait, so every frame on this
   page is portrait: a vertical picture squeezed into a horizontal box is not a
   crop, it is a different photograph.
   -------------------------------------------------------------------------- */

.f {
  position: relative;
  display: block;
  margin: 0;
  overflow: hidden;
  aspect-ratio: var(--ar, 3 / 4);
  background: var(--bg-lift);
}

.f > img,
.f > video {
  position: absolute;
  left: 0;
  top: calc(var(--travel) / -2);
  width: 100%;
  height: calc(100% + var(--travel));
  object-fit: cover;
  transform: translate3d(0, var(--shift, 0px), 0);
  will-change: transform;
}

/* --------------------------------------------------------------------------
   The project, in one block

   .bio__body p from about.css, set bold and run across the full 1248 as a
   single piece — the whole description, before any picture, rather than
   fragments threaded between photographs. One paragraph, one measure, the
   full width of the page.
   -------------------------------------------------------------------------- */

.pr-say { padding-top: 168px; }

.pr-copy {
  margin: 0;
  font-size: 17px;
  font-weight: 600;
  line-height: 1.62;
  color: var(--ink);
}

/* a project whose copy arrived as more than one paragraph keeps the break.
   22px is .bio__body p's own gap on the About page. */
.pr-copy + .pr-copy { margin-top: 22px; }

/* .project__sub from work.css */
.pr-label {
  margin: 0;
  font-family: var(--font-nav);
  font-size: 16px;
  font-weight: 400;
  letter-spacing: 0.04em;
  line-height: 1.4;
  text-transform: uppercase;
  color: var(--ink-dim);
}

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

   900px, which is what "full screen" means everywhere else here: it is the
   home page's canvas height exactly, so the hero fills a 1080p window the same
   way index.html does rather than guessing at a viewport it cannot measure.
   -------------------------------------------------------------------------- */

.pr-hero {
  position: relative;
  height: 900px;
  display: grid;
  grid-template-columns: repeat(12, var(--col));
  column-gap: var(--gut);
  align-content: end;
  padding-bottom: 96px;
}

.pr-hero__meta {
  grid-column: 1 / span 6;
  align-self: end;
  /* clears the descender of the frame beside it so the two read as level */
  padding-bottom: 4px;
}

/* .intro__title's 56px, but tracked like .project__title because this one is
   set in caps and Unbounded's caps close up badly at -0.01em */
.pr-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 56px;
  font-weight: 600;
  line-height: 1.06;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.pr-year {
  margin: 26px 0 0;
  font-family: var(--font-nav);
  font-size: 16px;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

.pr-cats {
  margin: 34px 0 0;
  padding: 0;
  list-style: none;
}

.pr-cats li {
  font-family: var(--font-nav);
  font-size: 16px;
  letter-spacing: 0.04em;
  line-height: 1.75;
  text-transform: uppercase;
  color: var(--ink-dim);
}

/* Five columns, not six, because the height has to come from the photograph.
   At six the frame is 612 wide, and 612 at the source's own 3:4 is 816 tall —
   which leaves 84px of the 900 block for the header that sits over it, and the
   nav would land on the picture. At five it is 506 × 675, and the 129px above
   clears the 88px header outright. The frame's height is never a number; it is
   the width times the ratio. */
.pr-hero__frame {
  grid-column: 8 / span 5;
  align-self: end;
}

/* Six columns, for a hero that is squarer than 3:4 and so does not run as tall
   for the width. At 612 a 1:1 frame is 612 high, which still leaves 192 of the
   900 block clear above it — more than the 88 the header needs. */
.pr-hero__frame--wide { grid-column: 7 / span 6; }

/* And the opposite case. Below the 88px header and above the 96px foot there
   are 716px of the block free; a negative taller than 3:4 spends more than
   that on five columns — Palace's 1 : 1.51 comes to 763 at 506 wide, and the
   nav would have landed on the photograph.

   Sizing it down costs nothing: the picture is smaller, never cropped, and
   justify-self keeps it against the right margin once its width is no longer
   the column's.

   The width is computed rather than the height capped. max-height plus
   width: auto looks like the tidy answer and collapses the frame to nothing —
   the figure's only child is absolutely positioned, so its fit-content width
   is zero and the ratio has no basis to work from. Given a definite width the
   height falls out of aspect-ratio the way it does everywhere else, and calc
   takes "1012 / 1526" straight out of --ar. */
.pr-hero__frame--tall {
  justify-self: end;
  width: calc(716px * var(--ar));
}

/* The slow zoom. It is a separate transform from the parallax --shift so the
   two can be driven independently; js/project.js only ever writes the two
   custom properties and never the transform itself. 1.08 down to 1.00 across
   one screen of scrolling is about half a pixel a frame — you notice it the
   way you notice a room getting lighter, which is the whole point. */
.pr-hero__frame > img {
  transform: translate3d(0, var(--shift, 0px), 0) scale(var(--zoom, 1.08));
  transform-origin: 50% 50%;
}

.pr-scroll {
  position: absolute;
  left: 0;
  bottom: 30px;
  display: flex;
  align-items: center;
  gap: 12px;
  font-family: var(--font-nav);
  font-size: 16px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
  transition: color 0.35s ease;
}

.pr-scroll:hover { color: var(--ink); }

/* a hairline that grows and retreats, rather than an arrow that bounces */
.pr-scroll__line {
  display: block;
  width: 64px;
  height: 1px;
  background: currentColor;
  transform-origin: 0 50%;
  animation: pr-scroll-run 2.6s cubic-bezier(0.16, 1, 0.3, 1) infinite;
}

@keyframes pr-scroll-run {
  0%        { transform: scaleX(0.15); }
  55%, 100% { transform: scaleX(1); }
}

/* --------------------------------------------------------------------------
   Timeline — a rail down the right-hand edge

   2023 at the top, 2026 at the bottom, and a dot carrying the page's scroll
   progress between them. Vertical and fixed, so it is on screen for the whole
   of a page this long rather than scrolling away in the first few seconds.

   It lives OUTSIDE #page, directly in the body. Everything inside the canvas
   is scaled by fit(), and a fixed element inside a transformed ancestor is
   positioned against that ancestor rather than the window — so the one thing
   on the page that must sit against the real viewport is the one thing that is
   not in the canvas. That also means its 16px is a real 16px at every window
   width, which is what you want from a marginal mark.

   Centred vertically, which is also what keeps it clear of the nav.
   -------------------------------------------------------------------------- */

.pr-rail {
  position: fixed;
  z-index: 50;
  right: 34px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  pointer-events: none;
}

/* vertical-rl turns the line, and the 180° flip sets it reading bottom-to-top,
   which is the way a spine reads and the way the eye expects a rail to run */
.pr-rail__year {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  font-family: var(--font-nav);
  font-size: 16px;
  letter-spacing: 0.14em;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

.pr-rail__track {
  position: relative;
  width: 1px;
  height: 30vh;
  min-height: 160px;
  max-height: 300px;
  background: var(--hairline);
}

/* --p is 0 → 1, written by js/project.js. The calc keeps the dot inside the
   track at both ends instead of hanging half of it off. */
.pr-rail__dot {
  position: absolute;
  left: -2px;
  top: calc(var(--p, 0) * (100% - 5px));
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--ink);
}

/* --------------------------------------------------------------------------
   Rows

   Every group of pictures on the page is one of these: a flex row whose items
   share a height and take their width from the picture's own proportions, so
   the row finishes flush with both margins and nothing is cropped to a shape
   it was not shot in.

   flex-grow does the arithmetic. With flex-basis: 0 each item takes
   ratio / (sum of ratios) of the row, and because .f also carries
   aspect-ratio, its height is width / ratio — which comes out identical for
   every item in the row. That is a justified row, and it costs one number:
   calc(var(--ar)) turns "3 / 4" into 0.75 and "500 / 708" into 0.706, so a
   frame's shape and its share of the row can never drift apart.
   -------------------------------------------------------------------------- */

.pr-rows {
  display: flex;
  flex-direction: column;
  gap: var(--row);
  padding-top: var(--sec);
}

.pr-row {
  display: flex;
  gap: var(--gut);
}

.pr-row > .f {
  flex: calc(var(--ar)) 1 0;
}

/* A print, shown whole.

   The parallax overhang comes off, and this is why posters were being trimmed
   before. .f > img is deliberately 32px taller than its frame so it can drift
   without ever showing an edge, and object-fit: cover pays for that height by
   trimming the sides. On a 900px photograph 32px is nothing. On a 300px poster
   it is a ninth of the height, and cover was taking about 5% off each side —
   straight through the type on LATE NIGHT OYSTERS and through the right-hand
   rule on the Huntleys & Palmers sheet.

   contain rather than cover for the same reason .project__img--whole uses it
   on work.css: at the exact ratio the two are identical, and contain is the
   one that cannot crop if a rounding error ever puts them a pixel apart.

   Losing the drift is not a side effect either. A print is flat artwork, not a
   window — there is nothing behind it for it to move against. */
.f--whole > img {
  top: 0;
  height: 100%;
  object-fit: contain;
  transform: none;
  will-change: auto;
}

/* A row given a height instead of taking one.

   Crabshakk's five prints use it: at 300 they measure 1048 of the 1248, and
   space-between turns the remaining 200 into four 50px gutters — more air than
   the 24px grid gutter, which is what a row of prints wants. A row of four or
   fewer does not need it, because justified they come out large anyway. */
.pr-row--set { justify-content: space-between; }

.pr-row--set > .f {
  flex: 0 0 auto;
  height: 300px;
  width: auto;
}

.pr-prints { padding-top: var(--sec); }

/* A row that cannot fill: the last one, with two photographs instead of three.
   Left to grow they would stretch to a row 864 tall against their neighbours'
   550, so here the height is fixed and the width follows the ratio. What is
   left over is left over — a page built on whitespace can end on some. */
.pr-row--part > .f {
  flex: 0 0 auto;
  height: 540px;
  width: auto;
}

/* --------------------------------------------------------------------------
   The reel — a photoshoot scrubbed by the scroll

   For a project with a whole roll of film behind it rather than four or five
   finished pieces. The frames do not sit in rows; one frame holds still in the
   middle of the window and the photographs change inside it as you scroll, so
   thirty-seven negatives take one screen of space instead of twelve.

   The mechanism is a tall section with a child one design-screen high that is
   held in place while the section passes. Its height comes from --frames, so
   adding or removing photographs needs no arithmetic here: the scroll distance
   follows the count.

   The holding is done from js/project.js, not by position: sticky, and that is
   not a preference. Sticky inside the scaled canvas pins in canvas
   coordinates: the box lands at canvas-y = scrollTop, which is visually
   scrollTop × scale, so it drifts up the window by scrollTop × (1 − scale).
   At a 1440 window that is 83px across the whole reel and looks like a slow
   rise; at 1000 it is 1 680 and the frame leaves the screen entirely. A
   translate computed in canvas units cancels exactly, at every width.
   -------------------------------------------------------------------------- */

.pf-reel {
  position: relative;
  margin-top: var(--sec);
  /* one screen to hold the frame, plus a fixed run of scroll per photograph */
  height: calc(900px + var(--frames, 30) * 150px);
}

.pf-reel__sticky {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 900px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 30px;
  will-change: transform;
}

/* A fixed stage with the photographs contained inside it, not filling it. The
   roll is a mix of portrait and landscape, and this is the one place on the
   site where frames of different shapes have to occupy the same box: contain
   keeps every negative whole and lets the shape itself change as the reel
   runs, which is what a contact sheet does anyway.

   1000 × 620 because at 620 high a 2:3 portrait is 413 wide and a 3:2
   landscape is 930 — both inside the box, both the same height. */
.pf-reel__stage {
  position: relative;
  width: 1000px;
  height: 620px;
}

.pf-shot {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  opacity: 0;
  transition: opacity 0.45s ease;
}

.pf-shot.is-on { opacity: 1; }

/* .project__index's setting — a count, not a control */
.pf-reel__count {
  margin: 0;
  font-family: var(--font-nav);
  font-size: 16px;
  letter-spacing: 0.14em;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------
   Next project

   The page ends by pointing at the next one. The card is the work index's own
   language — a frame, an index number, a title, a discipline line — at the
   size the end of a page can afford.
   -------------------------------------------------------------------------- */

.pr-next {
  display: block;
  margin-top: var(--sec);
  padding-top: 56px;
  border-top: 1px solid var(--hairline);
}

.pr-next__mark {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
  margin: 0 0 40px;
  font-family: var(--font-nav);
  font-size: 16px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.pr-next__card {
  display: grid;
  grid-template-columns: repeat(12, var(--col));
  column-gap: var(--gut);
  align-items: end;
}

.pr-next__frame {
  grid-column: 1 / span 5;
  /* the only frame on the page that answers the pointer, because it is the
     only one that is a link */
  transition: opacity 0.5s ease;
}

.pr-next__words {
  grid-column: 7 / span 6;
  padding-bottom: 6px;
}

/* .cta__link's 46px, minus the underline — the whole card is the target */
.pr-next__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 46px;
  font-weight: 600;
  line-height: 1.06;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.pr-next__sub { margin-top: 16px; }

.pr-next__go {
  display: inline-block;
  margin-top: 34px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--hairline);
  font-family: var(--font-nav);
  font-size: 16px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-dim);
  transition: border-color 0.4s ease, color 0.4s ease;
}

.pr-next__arrow {
  display: inline-block;
  margin-left: 10px;
  transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}

@media (hover: hover) {
  .pr-next:hover .pr-next__frame { opacity: 0.86; }
  .pr-next:hover .pr-next__go { border-bottom-color: var(--ink); color: var(--ink); }
  .pr-next:hover .pr-next__arrow { transform: translateX(10px); }
}

/* --------------------------------------------------------------------------
   Foot

   The two-line credit stays — it is on every page — but it is the credit, not
   a footer. The thing that used to be the footer's job on a case study is the
   card above it.
   -------------------------------------------------------------------------- */

.site-foot {
  margin-top: 120px;
  padding: 34px 0 40px;
  border-top: 0;
}

/* --------------------------------------------------------------------------
   Phones

   Below the breakpoint the canvas is abandoned and everything becomes one
   column, exactly as the work index does.
   -------------------------------------------------------------------------- */

@media (max-width: 900px) {

  #shell {
    height: auto !important;
    overflow: visible;
  }

  #page {
    width: 100%;
    transform: none !important;
    padding: 0 18px;
  }

  :root {
    --sec: 84px;
    --row: 56px;
  }

  .header {
    position: static;
    width: 100%;
    height: 64px;
    padding: 0;
  }

  .brand { font-size: 12px; }
  .nav { gap: 12px; }
  .nav a { font-size: 12px; }

  /* the hero stops being a two-column arrangement and becomes a title over a
     picture — 900px of canvas height means nothing here */
  .pr-hero {
    display: block;
    height: auto;
    padding: 24px 0 0;
  }

  .pr-title { font-size: 32px; letter-spacing: 0.03em; }
  .pr-year { margin-top: 16px; }
  .pr-cats { margin-top: 20px; }
  .pr-cats li { font-size: 14px; line-height: 1.6; }

  .pr-hero__frame { margin-top: 28px; }

  /* it would be pointing at something already on the screen */
  .pr-scroll { display: none; }

  /* A fixed rail costs a phone a third of its height and gives back a date
     range that is already in the hero. It goes. */
  .pr-rail { display: none; }

  .pr-say { padding-top: var(--sec); }

  .pr-copy { font-size: 15px; }
  .pr-label { font-size: 13px; }

  /* Rows stay rows. Three frames across a 354px column is about 110 each —
     small, but still a picture, and stacking them would turn twenty-two
     portraits into a column half a kilometre long. The posters keep their
     single line for the same reason: five across is what the row is about. */
  .pr-row { gap: 6px; }

  .pr-row--part > .f,
  .pr-row--set > .f {
    height: auto;
    flex: calc(var(--ar)) 1 0;
  }

  /* five prints at a fixed 300 would need 1048px; here they share what there
     is, and space-between has nothing left to distribute */
  .pr-row--set { justify-content: flex-start; }

  /* The reel in real units rather than canvas ones — 900px of sticky box is
     taller than most phones, and 150px a photograph would make a section the
     length of a street. */
  .pf-reel { height: calc(70vh + var(--frames, 30) * 90px); }

  .pf-reel__sticky { height: 70vh; gap: 16px; }

  .pf-reel__stage {
    width: 100%;
    height: 56vh;
  }

  .pf-reel__count { font-size: 13px; }

  .pr-next { margin-top: var(--sec); padding-top: 34px; }
  .pr-next__mark { display: block; margin-bottom: 22px; font-size: 13px; }

  .pr-next__card { display: block; }
  .pr-next__words { margin-top: 20px; padding-bottom: 0; }
  .pr-next__title { font-size: 26px; }
  .pr-next__go { margin-top: 22px; font-size: 13px; }

  .site-foot { margin-top: 64px; padding: 26px 0 32px; }
}

/* --------------------------------------------------------------------------
   Scroll reveal — the is-in class is set by js/project.js

   The same 28px and the same 0.9s as work.css and about.css. The delays are
   the only addition: a row of three arriving together looks like a page load,
   and arriving one after another looks like a page.
   -------------------------------------------------------------------------- */

.reveal {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity 0.9s ease, transform 0.9s cubic-bezier(0.16, 1, 0.3, 1);
}

.reveal.is-in {
  opacity: 1;
  transform: none;
}

.reveal--d1 { transition-delay: 0.1s; }
.reveal--d2 { transition-delay: 0.2s; }
.reveal--d3 { transition-delay: 0.3s; }
.reveal--d4 { transition-delay: 0.4s; }

@media (prefers-reduced-motion: reduce) {
  .reveal,
  .pr-next__frame,
  .pr-next__go,
  .pr-next__arrow,
  .pr-scroll { transition: none; }

  .reveal {
    opacity: 1;
    transform: none;
  }

  .reveal--d1,
  .reveal--d2,
  .reveal--d3,
  .reveal--d4 { transition-delay: 0s; }

  /* js/project.js skips the parallax and the zoom too, so the images sit flush */
  .f > img,
  .f > video {
    top: 0;
    height: 100%;
  }

  .pr-hero__frame > img { transform: none; }

  .pr-scroll__line { animation: none; transform: scaleX(1); }

  /* The reel stops being a reel and becomes a column. A gallery you can only
     see by scrolling through an effect is one that someone who has turned
     effects off cannot see at all — so here the whole roll is simply printed
     down the page. js/project.js fetches every frame in this mode. */
  .pf-reel { height: auto; }

  .pf-reel__sticky {
    position: static;
    height: auto;
    display: block;
    transform: none !important;
  }

  .pf-reel__stage {
    position: static;
    display: flex;
    flex-direction: column;
    gap: var(--row);
    width: 100%;
    height: auto;
  }

  .pf-shot {
    position: static;
    width: 100%;
    height: auto;
    opacity: 1;
    transition: none;
  }

  .pf-reel__count { display: none; }
}
