﻿/* ==========================================================================
   Greta Volungevičiūtė — Work index
   Design canvas: 1440 wide, free height. Requires css/base.css.

   Screenshot_63 arranges its images loosely down the page; this keeps that
   left/right rhythm but pins every edge to a 12-column grid, so the
   alternation reads as a decision rather than as scatter. Captions follow
   Screenshot_64: the index number sits at the top of the empty column, the
   title block at the bottom of it, level with the foot of the facing image.
   ========================================================================== */

/* The work index runs on a white ground. Every token from base.css is
   re-pointed here rather than overriding individual rules, so the shared
   header, nav and hover rule invert along with the page 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;
  --row-gap: 168px;

  /* total parallax travel: the image drifts from +16px to -16px inside its
     frame across a full pass through the viewport. js/work.js reads this. */
  --travel: 32px;
}

/* dark text on white wants the opposite smoothing from the hero */
body {
  -webkit-font-smoothing: auto;
  -moz-osx-font-smoothing: auto;
}

body {
  /* this page scrolls, unlike the hero */
  overflow-x: hidden;
  overflow-y: auto;
}

/* --------------------------------------------------------------------------
   1440 canvas that scales down but keeps scrolling
   -------------------------------------------------------------------------- */

/* #shell is given an explicit height in js/work.js — the scaled height of
   #page. A CSS transform does not affect layout, so without that the document
   would stay as tall as the unscaled 1440 layout and scroll past its own end. */
#shell {
  position: relative;
  width: 100%;
  overflow: hidden;
}

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

/* --------------------------------------------------------------------------
   Page head
   -------------------------------------------------------------------------- */

.work-head {
  padding-top: 204px;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 48px;
}

.work-head__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 24px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.work-head__note {
  margin: 0;
  max-width: 400px;
  font-family: var(--font-nav);
  font-size: 16px;
  font-weight: 400;
  letter-spacing: 0.04em;
  line-height: 1.45;
  text-transform: uppercase;
  color: var(--ink-dim);
  text-align: right;
}

.work-head__rule {
  margin-top: 30px;
  height: 1px;
  background: var(--hairline);
}

/* --------------------------------------------------------------------------
   The grid
   -------------------------------------------------------------------------- */

.work-grid {
  display: flex;
  flex-direction: column;
  gap: var(--row-gap);
  padding-top: var(--row-gap);
}

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

/* Square rows: the image takes half the grid on one side, the number and the
   caption hold the opposite margin. The text hugs the outer edge, so it reads
   as the page edge answering the image rather than as a second column. */
.project--left  .project__figure { grid-column: 1 / span 6; }
.project--left  .project__side   { grid-column: 8 / span 5; text-align: right; }

.project--right .project__figure { grid-column: 7 / span 6; }
.project--right .project__side   { grid-column: 1 / span 5; text-align: left; }


/* Both belong to the same row. Without this the caption on a --right row asks
   for columns 1-5 after the figure has already taken 7-12, which auto-placement
   can only satisfy by starting a second row — so the column never stretched and
   the caption just fell underneath the image instead of sitting beside it. */
.project--left > *,
.project--right > * { grid-row: 1; }

.project__figure {
  position: relative;
  margin: 0;
  background: var(--bg-lift);
}

/* The frame holds the shape and does the clipping. Square rows fall back to
   1 : 1; the wide rows pass their own ratio in as --ar, because their height
   can no longer come from the image (it is positioned out of flow below). */
.project__frame {
  position: relative;
  display: block;
  overflow: hidden;
  aspect-ratio: var(--ar, 1);
  transition: opacity 0.5s ease;
}

/* A row that is shorter than its columns would allow.

   Only Spadenta needs it. Its poster is shown whole at 5 : 8, and a 5 : 8 card
   filling the six columns is 612 × 979 — taller than the ~945px a browser
   leaves on a 1080p screen, so the one row on this page whose whole point is an
   uncropped poster was the one row you could not see whole. Capping the figure
   at the poster's own 500px width brings the row to 500 × 800, which fits with
   145px to spare and renders the source at 1 : 1 instead of blowing it up 22%.

   The cap goes on the figure, not the frame: the figure carries the --bg-lift
   background, and a frame narrower than its figure would show it as a band. */
.project--compact .project__figure { max-width: 500px; }

/* plain cards just dim a touch; the case-study card has its own answer. Under a
   pointer only — on touch :hover latches after a tap and the card would stay
   dimmed until you tapped something else. */
@media (hover: hover) {
  .project:not(.project--case) .project__figure:hover .project__frame { opacity: 0.88; }
}

/* Only present on the case-study card. inset:0 means the image inside keeps
   exactly the geometry it had when the frame was its offset parent, so the
   parallax maths is untouched. */
.project__media {
  position: absolute;
  inset: 0;
  display: block;
  transform-origin: 50% 50%;
  transition: transform 0.85s cubic-bezier(0.16, 1, 0.3, 1);
}

/* The image is taller than its frame by the full parallax 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/work.js to drive — nothing else on
   this page animates it, or the parallax would inherit that easing and lag
   a second behind the scroll. */
.project__img {
  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;
}

/* Shown whole rather than filling the card. Spadenta's cover is a poster, and
   the card is cut to its exact ratio, so contain and cover come out the same —
   contain is the one that cannot crop it if the two ever drift apart by a
   rounding error.

   Parallax comes off with it. The trick above depends on the image being taller
   than its frame, which is precisely what must not happen here. */
.project__img--whole {
  top: 0;
  height: 100%;
  object-fit: contain;
  transform: none;
  will-change: auto;
}

/* --------------------------------------------------------------------------
   Colourway flip

   One poster in four colourways, cut between on a loop. Hard cuts, not fades:
   the source animation switches, and a cross-fade would put two colours on
   screen at once and turn the line work to mud.

   object-fit is contain and the frame carries the posters' own ratio, so the
   sheet is never cropped. These sit outside the parallax for the same reason —
   the parallax works by making the picture taller than its frame.
   -------------------------------------------------------------------------- */

.flip {
  position: absolute;
  inset: 0;
  display: block;
}

.flip__frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  opacity: 0;
  animation: flip-cycle 1.6s linear infinite;
}

.flip__frame:nth-child(1) { animation-delay: 0s; }
.flip__frame:nth-child(2) { animation-delay: 0.4s; }
.flip__frame:nth-child(3) { animation-delay: 0.8s; }
.flip__frame:nth-child(4) { animation-delay: 1.2s; }

@keyframes flip-cycle {
  0%, 24.99%  { opacity: 1; }
  25%, 100%   { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .flip__frame { animation: none; }
  .flip__frame:nth-child(1) { opacity: 1; }
}

/* --------------------------------------------------------------------------
   The walking turntable

   The character is lifted straight out of the poster artwork. It sits behind
   the print — the frame is z-index 1, this is 0 — so at rest it is completely
   hidden, and on hover it walks out from under the left edge. Because it is
   behind the print, the part still tucked underneath is simply covered; there
   is no cut-out edge to hide.
   -------------------------------------------------------------------------- */

.project__figure--walker { overflow: visible; }
.project__figure--walker .project__frame { z-index: 1; }

.walker {
  position: absolute;
  z-index: 0;
  left: 10px;
  bottom: 84px;
  width: 420px;
  display: block;
  pointer-events: none;
  /* fallback for browsers without linear(): a plain glide, no footsteps */
  transition: transform 1.05s cubic-bezier(0.16, 1, 0.3, 1);
  /* three strides with a pause on each landing — a cubic-bezier cannot hold
     a value, which is exactly what a footstep needs */
  transition-timing-function:
    linear(0, 0.30 16%, 0.30 30%, 0.64 46%, 0.64 60%, 1 88%, 1);
}

.walker__art {
  display: block;
  width: 100%;
  height: auto;
}

/* The walk itself, and the bob that goes with it, are triggered further down
   with the rest of the case-study state. The bob is a one-shot animation rather
   than a transition because it has to rise and fall three times inside a single
   state change; it ends on zero, so leaving mid-walk has nothing to snap back
   from. */

@keyframes walker-step {
  0%          { transform: translateY(0) rotate(0deg); }
  8%          { transform: translateY(-7px) rotate(-3deg); }
  16%, 30%    { transform: translateY(0) rotate(0deg); }
  38%         { transform: translateY(-7px) rotate(3deg); }
  46%, 60%    { transform: translateY(0) rotate(0deg); }
  70%         { transform: translateY(-7px) rotate(-3deg); }
  88%, 100%   { transform: translateY(0) rotate(0deg); }
}

@media (prefers-reduced-motion: reduce) {
  .walker { transition: none; }
  .walker__art { animation: none !important; }
}

/* --------------------------------------------------------------------------
   Case studies

   Everything here is one state change on the frame. The parts move on the same
   expo-out curve the rest of the site uses, staggered by 70ms so they arrive as
   a sequence rather than a pop. Leaving is deliberately not staggered and
   roughly half the duration — a slow exit reads as lag.

   These are the components; the trigger is further down and is not gated on
   having a pointer, because on a phone the panels open on scroll instead.
   -------------------------------------------------------------------------- */

  /* ---- shared shell ---- */

  .case {
    position: absolute;
    inset: 0;
    z-index: 3;
    display: flex;
    flex-direction: column;
    pointer-events: none;
  }

  /* content has to clear whichever background the variant brings in */
  .case__shots,
  .case__body {
    position: relative;
    z-index: 2;
  }

  .case__shot {
    object-fit: cover;
    opacity: 0;
    transition:
      opacity 0.7s ease 0s,
      transform 0.7s cubic-bezier(0.16, 1, 0.3, 1) 0s;
  }

  .case__body { display: block; }

  .case__type,
  .case__role,
  .case__idea,
  .case__btn {
    display: block;
    font-family: var(--font-nav);
    font-size: 18px;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    opacity: 0;
    transform: translateY(16px);
    transition:
      opacity 0.65s ease 0s,
      transform 0.65s cubic-bezier(0.16, 1, 0.3, 1) 0s;
  }

  .case__role { margin-top: 3px; }

  .case__idea {
    margin-top: 16px;
    line-height: 1.45;
  }

  /* White by default, because almost every panel sits on a dark field — the
     four dark variants, the band, and the black panel under the lifted poster.
     A #0d0d0d button on any of those read as a hole punched in the card rather
     than as a control. Spadenta is the one light panel and inverts it back.

     It is also the only link inside the panel, and .case is pointer-events:
     none, so it has to take them back — but only once the panel is showing, or
     a closed card would carry an invisible click target. */
  .case__btn {
    width: fit-content;
    margin-top: 20px;
    padding: 10px 20px 9px;
    background: #fff;
    letter-spacing: 0.06em;
    line-height: 1;
    color: #0d0d0d;
    pointer-events: none;
  }

  .project__frame.is-open .case__btn { pointer-events: auto; }

  /* ---- variant A: the photograph recedes and a dark layer settles on it ---- */

  /* the padding is what pays for the frame size: three prints and two gaps have
     to fit the card's inner width, so every pixel taken off the sides goes
     straight into the pictures */
  .case--dark {
    justify-content: space-between;
    padding: 46px 30px 40px;
    color: #fff;
  }

  /* sits inside .project__media, so it recedes with the photograph instead of
     floating over the mat the photograph pulls back from */
  .case__scrim {
    position: absolute;
    inset: 0;
    z-index: 2;
    opacity: 0;
    background:
      linear-gradient(180deg,
        rgba(13, 13, 13, 0.22) 0%,
        rgba(13, 13, 13, 0.48) 40%,
        rgba(13, 13, 13, 0.93) 100%);
    transition: opacity 0.55s ease 0s;
  }

  .case--dark .case__shots {
    display: flex;
    justify-content: center;
    gap: 16px;
  }

  /* 3 × 170 + 2 × 16 = 542 inside a 545 inner width — as large as three prints
     go on this card without dropping the gaps */
  .case--dark .case__shot {
    width: 170px;
    height: 233px;
    transform: translateY(24px);
    box-shadow: 0 26px 50px -26px rgba(0, 0, 0, 0.75);
  }

  /* ---- variant A on a tall card ----

     The 866-tall poster row left a hole between the prints and the text, so the
     column is centred rather than pushed to both ends. The prints stay at their
     normal size and keep clear of each other: all three are printed on the same
     grey stock, so overlapping them read as one smudged sheet rather than as
     three posters. A hairline does the separating instead of size. */

  .case--tall { justify-content: center; gap: 58px; }

  .case--tall .case__shots { gap: 16px; }

  .case--tall .case__shot {
    outline: 1px solid rgba(255, 255, 255, 0.22);
    outline-offset: -1px;
    box-shadow: 0 22px 44px -20px rgba(0, 0, 0, 0.9);
  }

  /* variant A with nothing to show but the words: space-between would push the
     single block to the top of the card, where it reads as a heading rather
     than as a caption */
  .case--words { justify-content: flex-end; }

  .case--dark .case__type,
  .case--dark .case__role { color: rgba(255, 255, 255, 0.55); }

  .case--dark .case__idea {
    max-width: 30em;
    color: #fff;
  }

  /* ---- variant C: the full-width band opens into a spread ----

     1440 across and only 810 tall, so a centred stack would strand everything
     in the middle of a very long line. The words take the left gutter, the
     frames arrive from the right, and the photograph is pushed left underneath
     them — the two opposing movements are what make the band read as opening
     rather than as being covered over. Frames keep their own orientation: the
     tall one runs the full height, the two landscapes stack beside it. */

  .case--band {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    /* lines up with the page gutter even though the band ignores it */
    padding: 0 96px;
    color: #fff;
  }

  /* Nearly solid: the frames have to sit on something, not float on a picture.
     The three stops are tunable per card because how much cover the words need
     depends entirely on the photograph underneath. Katrick is a dark render and
     the defaults swallow it. Rangers is a bright newspaper collage — headlines,
     body columns, cut-out players — and at 0.86 on the far stop it was still
     perfectly readable underneath, so the panel was one busy collage laid over
     another. It runs at 0.99/0.97/0.94, which leaves the page as a texture
     rather than as something competing for the eye. */
  .case__scrim--band {
    background:
      linear-gradient(90deg,
        rgba(13, 13, 13, var(--scrim-near, 0.94)) 0%,
        rgba(13, 13, 13, var(--scrim-mid, 0.88)) 46%,
        rgba(13, 13, 13, var(--scrim-far, 0.78)) 100%);
  }

  .case--band .case__body { max-width: 470px; }

  .case--band .case__type,
  .case--band .case__role { color: rgba(255, 255, 255, 0.55); }

  .case--band .case__idea {
    max-width: 28em;
    color: #fff;
  }

  /* The cells carry the source proportions almost exactly — 340 × 484 is 0.702
     against the tall image's 0.698, and 340 × 232 is 1.466 against the two
     landscapes' 1.463 — so nothing is cropped worth speaking of. Growing them
     also closes the hole this variant used to have: the words ran out at 532
     and the frames began at 724, and 190px of empty scrim in the middle of the
     card is what made the row read as unfinished. */
  .case--band .case__shots {
    display: grid;
    grid-template-columns: 340px 340px;
    grid-template-rows: 232px 232px;
    gap: 20px;
  }

  /* A hairline, because half of what goes in these frames is a dark photograph
     sitting on a near-black scrim. Without an edge the first Katrick frame did
     not read as a frame at all — it melted into the background and left the
     composition looking like two pictures and a gap. */
  .case--band .case__shot {
    width: 100%;
    height: 100%;
    outline: 1px solid rgba(255, 255, 255, 0.18);
    outline-offset: -1px;
    /* travels in from the right, against the photograph */
    transform: translateX(56px);
    box-shadow: 0 30px 60px -28px rgba(0, 0, 0, 0.9);
  }

  .case--band .case__shot:first-child { grid-row: 1 / span 2; }

  /* one spread rather than a group: the grid would force a landscape page into
     a portrait cell, so the frame simply takes the image's own proportions */
  .case--band.case--solo .case__shots {
    display: block;
    width: 736px;
  }

  .case--band.case--solo .case__shot {
    width: 736px;
    height: 523px;
  }

  /* ---- variant D: the poster lifts and a contact sheet is underneath ----

     The other three variants all put something on top of the photograph. This
     one takes the photograph away instead: it slides up out of the frame and
     the four prints were already there, tiled edge to edge. Nothing about the
     stacking is decorative — .case--reveal has to paint *below* .project__media,
     which is why it comes first in the markup and carries the lower z-index. */

  .project--reveal .project__media { z-index: 2; }

  .case--reveal {
    z-index: 1;
    display: block;
    padding: 0;
  }

  /* Two equal rows, hairline gaps, filling the card exactly. The cells come out
     at the same 0.708 as the poster itself, so neither print is cropped: the
     two photographs take the top row, the words take the bottom one. */
  .case--reveal .case__shots {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: 1fr 1fr;
    gap: 2px;
    width: 100%;
    height: 100%;
  }

  .case--reveal .case__shot {
    width: 100%;
    height: 100%;
    /* no movement: they are revealed, not delivered. They only need to catch
       up in tone as the poster clears them. */
    transform: none;
    transition: opacity 0.45s ease 0s;
  }

  /* the two of them sit low in the frame */
  .case--reveal .case__shot:nth-child(2) { object-position: 50% 62%; }

  /* A plain black field the full width of the card. Its own height is fixed by
     the grid, so the words hold both ends of it rather than pooling in the
     middle and leaving the rest looking unfinished. */
  .case__panel {
    grid-column: 1 / -1;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    padding: 38px 40px 36px;
    background: #0d0d0d;
    color: #fff;
  }

  .case__panel-head,
  .case__panel-foot { display: block; }

  .case__panel .case__type,
  .case__panel .case__role { color: rgba(255, 255, 255, 0.55); }

  .case__panel .case__idea {
    max-width: 34em;
    color: #fff;
  }


  /* ---- variant B: a clean sheet wipes across the photograph ---- */

  /* the padding and gaps here are tight on purpose: the three frames are as
     large as they can be and still leave the copy room inside the 612 square.
     This card carries two paragraphs and no button, which is what buys the
     frames their extra height — the copy is the tightest of any panel. */
  /* Centred, not top-aligned. The card is 5:7 because the cover poster is shown
     whole, which is taller than the panel needs — top-aligning left 200px of
     bare white under the button. */
  .case--sheet {
    justify-content: center;
    gap: 14px;
    padding: 34px 32px 34px;
    color: var(--ink);
  }

  /* scaleX on a flat fill shows no distortion, and it keeps the wipe on the
     compositor. The content above it fades in once the sheet has passed. */
  .case__sheet {
    position: absolute;
    inset: 0;
    z-index: 1;
    background: var(--bg);
    transform: scaleX(0);
    transform-origin: 0 50%;
    transition: transform 0.75s cubic-bezier(0.16, 1, 0.3, 1) 0s;
  }

  /* the three assets are all 9:16 social frames, so they line up as a strip
     of stories rather than as a centred row of prints */
  .case--sheet .case__shots {
    display: flex;
    justify-content: flex-start;
    gap: 14px;
  }

  .case--sheet .case__shot {
    width: 170px;
    height: 302px;
    /* enters the way the sheet is travelling */
    transform: translateX(-18px);
    box-shadow: 0 20px 44px -28px rgba(13, 13, 13, 0.6);
  }

  /* The strip has to be refitted when the card is. Inside a 500px card with
     32px of padding a side there are 436px to fill, and three frames with two
     14px gaps means 136 each — 170 would have run 102px past the edge. The
     height follows the frames' own 280 : 498 so nothing is squashed. */
  .project--compact .case--sheet .case__shot {
    width: 136px;
    height: 242px;
  }

  .case--sheet .case__type,
  .case--sheet .case__role { color: rgba(13, 13, 13, 0.5); }

  .case--sheet .case__idea {
    margin-top: 13px;
    max-width: 32em;
    color: var(--ink);
  }

  /* inverted: this is the only panel that wipes to white, so the default white
     button would vanish into it */
  .case--sheet .case__btn {
    margin-top: 16px;
    background: #0d0d0d;
    color: #fff;
  }

/* --------------------------------------------------------------------------
   The indicator

   One small button per card, ⊕ to open and ✕ to close, on every device. It
   used to appear only under @media (hover: none), which left the desktop with
   nothing to press at all. It is also the only thing on the card that can be
   pressed — the card itself is no longer a link, which is what lets the real
   "View project" link live inside the panel.

   On a pointer it is a second way in rather than the only one: hover still
   opens the card, and the button is what closes it again without moving the
   mouse away. It doubles as the affordance — a plain photograph gives no sign
   that hovering does anything.

   The mark is two bars crossed at right angles. Rotating the button 45° turns
   the plus into a cross, so open and close are one continuous movement rather
   than two icons swapped over.
   -------------------------------------------------------------------------- */

.case__toggle {
  position: absolute;
  z-index: 6;
  right: 14px;
  bottom: 14px;
  width: 38px;
  height: 38px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: #0d0d0d;
  /* a black disc on the dark scrim needs an edge of its own */
  outline: 1px solid rgba(255, 255, 255, 0.32);
  outline-offset: -1px;
  cursor: pointer;
  display: block;
  transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}

.case__toggle-bar {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 13px;
  height: 1px;
  background: #fff;
  transform: translate(-50%, -50%);
}

.case__toggle-bar:nth-child(2) { transform: translate(-50%, -50%) rotate(90deg); }

.project__frame.is-open .case__toggle { transform: rotate(45deg); }

/* --------------------------------------------------------------------------
   The one state change

   One class, one code path, both devices. Every way in — the pointer, the
   indicator, the keyboard — goes through js/work.js and ends up setting
   .is-open on the frame.

   This used to read :is(:hover, :focus-within, .is-open), and on a phone that
   was broken: a tap latches :hover on whatever was tapped, so pressing ✕ took
   .is-open away and the latched :hover held the panel open anyway. There is no
   way to spell "hover, but not the sticky kind" in a selector, so hover stopped
   being a selector at all.
   -------------------------------------------------------------------------- */

.project--case .project__frame.is-open .project__media {
  transform: var(--media-hover, scale(0.94));
}

.project--case .project__frame.is-open .case__sheet {
  transform: scaleX(1);
}

.project--case .project__frame.is-open .case__scrim {
  opacity: 1;
}

.project--case .project__frame.is-open .case__shot {
  opacity: 1;
  transform: none;
}

.project--case .project__frame.is-open .case__shot:nth-child(1) { transition-delay: 0.10s; }
.project--case .project__frame.is-open .case__shot:nth-child(2) { transition-delay: 0.17s; }
.project--case .project__frame.is-open .case__shot:nth-child(3) { transition-delay: 0.24s; }

/* the contact sheet is uncovered in one move, not dealt out card by card —
   the poster sliding off it is the sequence */
.project--case .project__frame.is-open .case--reveal .case__shot {
  transition-delay: 0.06s;
}

.project--case .project__frame.is-open .case__type,
.project--case .project__frame.is-open .case__role,
.project--case .project__frame.is-open .case__idea,
.project--case .project__frame.is-open .case__btn {
  opacity: 1;
  transform: none;
}

.project--case .project__frame.is-open .case__type { transition-delay: 0.26s; }
.project--case .project__frame.is-open .case__role { transition-delay: 0.32s; }
.project--case .project__frame.is-open .case__idea { transition-delay: 0.38s; }
.project--case .project__frame.is-open .case__idea--second { transition-delay: 0.44s; }
.project--case .project__frame.is-open .case__btn { transition-delay: 0.46s; }
/* Spadenta's button follows a second paragraph, so it lands one step later */
.project--case .project__frame.is-open .case--sheet .case__btn { transition-delay: 0.52s; }

/* The walking turntable answers to the same triggers, but keyed off the figure:
   .walker is written before the frame in the markup so it paints behind it,
   which puts it out of reach of the frame's own selectors. js/work.js sets
   .is-open on the figure as well as on the frame. Distance is a percentage of
   its own width, so it still clears the poster at any size. */
.project__figure--walker.is-open .walker {
  transform: translateX(-93%);
}

.project__figure--walker.is-open .walker__art {
  animation: walker-step 1.05s linear both;
}

/* --------------------------------------------------------------------------
   Full-bleed rows — wide images run past the page gutter, edge to edge, with
   the caption on a single line underneath
   -------------------------------------------------------------------------- */

.project--bleed { row-gap: 26px; }

.project--bleed .project__figure {
  grid-column: 1 / -1;
  /* cancel the page gutter so the image reaches both edges of the canvas */
  margin-left: calc(var(--pad-x) * -1);
  margin-right: calc(var(--pad-x) * -1);
  overflow: hidden;
}

.project__bleed-caption {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(12, var(--col));
  column-gap: var(--gut);
  align-items: baseline;
}

.project__bleed-caption .project__index { grid-column: 1 / span 1; }
.project__bleed-caption .project__title { grid-column: 3 / span 5; }

.project__bleed-caption .project__sub {
  grid-column: 8 / span 5;
  margin: 0;
  text-align: right;
}

/* --------------------------------------------------------------------------
   Number + caption column
   -------------------------------------------------------------------------- */

/* space-between is what pins the number to the top of the column and the
   caption to the bottom, level with the foot of the image beside it */
.project__side {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 2px 0 4px;
}

/* On the tall poster row the caption would end up stranded most of a metre
   below its own image, so number and title stay together at the top instead. */
.project--top-caption .project__side {
  justify-content: flex-start;
  gap: 20px;
}

.project__index {
  font-family: var(--font-nav);
  font-size: 16px;
  font-weight: 400;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

.project__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 24px;
  font-weight: 600;
  letter-spacing: 0.04em;
  line-height: 1.3;
  text-transform: uppercase;
  color: var(--ink);
  transition: color 0.35s ease;
}

.project__sub {
  margin: 12px 0 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);
}

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

/* the footer itself is in base.css — the same two lines on all four pages.
   Only the space above it belongs to this page. */
.site-foot { margin-top: 180px; }

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

   The 1440 canvas is abandoned here rather than scaled: a 3.7× reduction would
   put the 24px titles at 6px. js/work.js drops the transform below the same
   breakpoint and the page lays out fluidly instead — one column, images the
   full width, captions underneath.

   The case studies keep every part of their choreography. Only the measurements
   change, and where a panel was built around the width of a 612px card it is
   rebuilt around the width of a phone.
   -------------------------------------------------------------------------- */

@media (max-width: 900px) {

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

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

  /* back into the flow: there is no fixed canvas to hang off any more */
  .header {
    position: relative;
    width: 100%;
    height: 64px;
    padding: 0;
  }

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

  .work-head {
    display: block;
    padding-top: 44px;
  }

  .work-head__note {
    max-width: none;
    margin-top: 14px;
    text-align: left;
  }

  .work-head__rule { margin-top: 22px; }

  .work-grid {
    gap: 84px;
    padding-top: 84px;
  }

  /* one column: the grid columns and the left/right alternation both go */
  .project {
    display: block;
  }

  .project--left .project__side,
  .project--right .project__side {
    text-align: left;
    margin-top: 16px;
    padding: 0;
  }

  .project--top-caption .project__side { gap: 0; }

  .project__index { display: block; margin-bottom: 10px; }

  .project--bleed .project__figure {
    margin-left: -18px;
    margin-right: -18px;
  }

  .project__bleed-caption {
    display: block;
    margin-top: 16px;
  }

  .project__bleed-caption .project__sub {
    margin-top: 4px;
    text-align: left;
  }

  .site-foot { margin-top: 90px; }

  /* ---- case studies, rebuilt to phone width ---- */

  /* 18px uppercase in a 300px column would run to seven lines inside a card
     only 340 tall. The card captions keep their sizes; these do not — 13px is
     as far up as the longest panel goes before the copy stops fitting. */
  .case__type,
  .case__role,
  .case__idea,
  .case__btn { font-size: 13px; }

  .case__idea { margin-top: 10px; line-height: 1.35; }

  .case__btn {
    margin-top: 12px;
    padding: 7px 12px 6px;
  }

  .case--dark { padding: 22px 16px 18px; }

  .case--dark .case__shots { gap: 8px; }

  .case--dark .case__shot {
    width: 30%;
    height: auto;
    aspect-ratio: 170 / 233;
  }

  .case--tall { gap: 20px; }

  .case--sheet {
    gap: 14px;
    padding: 20px 16px 18px;
  }

  .case--sheet .case__shot {
    width: 30%;
    height: auto;
    aspect-ratio: 9 / 16;
  }

  /* Both halves of .project--compact come off here.

     Its cap answers a desktop problem — a 979px row inside a ~945px window —
     and this layout is one column that scrolls, so the card should be as wide
     as every other card, not 500px marooned in an 864px column at tablet
     widths. The shot rule has to be undone by hand: .project--compact
     .case--sheet .case__shot is three classes and would otherwise outrank the
     two-class responsive rule directly above. */
  .project--compact .project__figure { max-width: none; }

  .project--compact .case--sheet .case__shot {
    width: 30%;
    height: auto;
  }

  /* The band is words only on a phone.

     It cannot be a spread here — but stacking the words over the frames did not
     work either, and not by a little. A bleed row takes its height from its own
     wide image, which at 500px across leaves the card 281 tall for Katrick and
     356 for Rangers. Measured, the stack wanted 65 and 102 more than that, and
     .project__frame clips, so both panels lost their first and last lines out of
     sight, top and bottom. There is no size the frames could shrink to that
     buys those lines back.

     So the photograph under the scrim is the picture, and the panel is the
     story. The frames come back at 900px, where the row is tall enough. */

  /* --media-hover is written in pixels and tuned to a 1424-wide row: 46px of
     travel under a scale(1.08) that gains 57px a side, so the photograph always
     covers its own frame. At 500 the same scale gains only 20px, the shift ran
     the photograph clean off its right edge, and a 26px white gutter opened up
     under the last word of every long line. That is the "text outside the
     block" — the block moved, not the text. Scale only here, no travel. */
  .project--bleed .project__frame.is-open .project__media {
    transform: scale(1.04);
  }

  /* One even field rather than the desktop's three-stop wash, which put its
     lightest end directly under the copy at this size. */
  .case__scrim--band { background: rgba(13, 13, 13, 0.94); }

  /* stretch, not the centre alignment inherited from the desktop rule: that
     sized the text to its own content and floated it in the middle of the card
     with uneven air either side, which is the opposite of a clean rectangle */
  .case--band {
    flex-direction: column;
    align-items: stretch;
    justify-content: center;
    gap: 14px;
    padding: 26px 20px;
  }

  .case--band .case__body,
  .case--band .case__idea { max-width: none; }

  /* both selectors, because the solo spread declares display: block at three
     classes deep and would out-specify a single .case--band rule here */
  .case--band .case__shots,
  .case--band.case--solo .case__shots { display: none; }

  .case__panel { padding: 18px 16px 16px; }

  /* proportional so it still clears the poster on any width */
  .walker {
    width: 58%;
    left: 4px;
    bottom: 44px;
  }
}

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

.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;
}

@media (prefers-reduced-motion: reduce) {
  .project__frame,
  .project__media,
  .case__scrim,
  .case__sheet,
  .case__shot,
  .case__type,
  .case__role,
  .case__idea,
  .case__btn,
  .reveal { transition: none; }

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

  /* js/work.js skips the parallax too, so the image can sit flush */
  .project__img {
    top: 0;
    height: 100%;
  }
}


