﻿/* ==========================================================================
   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;
}

/* The colourway flip has moved to css/base.css — the HND project page runs the
   same loop, and the two have to keep the same timing. See .flip there. */

/* --------------------------------------------------------------------------
   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__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;
  }

  /* The 16px used to hold the copy off the role line above it. The copy is the
     first thing in the panel now, so the top margin only belongs between two
     paragraphs — Spadenta is the one panel that has two. */
  .case__idea { line-height: 1.45; }
  .case__idea + .case__idea { margin-top: 12px; }

  /* 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__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__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__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__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;
  }

  /* ---- variant B, dealt in twos ----

     The same wipe carrying posters instead of social frames. Spadenta's three
     stories are 9:16 and fixed at 170px because three of them plus two gaps is
     exactly the width they have; two A-format sheets are neither, so they are
     told to share the row rather than given a number. flex-basis: 0 splits the
     inner width evenly — 267 each inside Stormy's 612 card — and the ratio
     turns that into their height, which keeps them the shape of the poster
     they came from at whatever width the card ends up.

     min-width: 0 is not tidying — without it this does not work at all. A flex
     item's automatic minimum size is the smaller of its specified width and its
     content width, and an image with width: auto has no specified width, so the
     minimum falls back to the file's own 560px. Both sheets then refused to
     shrink and stood at the full 548 one under the other, 530px past the bottom
     of the card. Spadenta never hits this because its shots name a width.

     The transform, the shadow and the stagger all come from .case--sheet
     above; only the size is different. */
  .case--pair .case__shot {
    flex: 1 1 0;
    min-width: 0;
    width: auto;
    height: auto;
    aspect-ratio: 1000 / 1414;
  }

/* --------------------------------------------------------------------------
   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;
}

/* The ladder used to start at 0.26 with the type line and reach the copy at
   0.38. The type and role lines are gone — they repeated the discipline line
   already printed under the card — so the copy takes the step they vacated
   rather than keeping their delay and leaving the panel visibly waiting. */
.project--case .project__frame.is-open .case__idea { transition-delay: 0.26s; }
.project--case .project__frame.is-open .case__idea--second { transition-delay: 0.32s; }
.project--case .project__frame.is-open .case__btn { transition-delay: 0.36s; }

/* A button that follows a second paragraph lands one step later, so the
   sequence stays in reading order. This used to be keyed off .case--sheet,
   which was true of the variant when Spadenta was the only card using it —
   Stormy now wipes the same way with one paragraph, and inherited a button
   that sat waiting 0.16s on nothing. The condition is the paragraph. */
.project--case .project__frame.is-open .case__idea--second ~ .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; }

  /* the nav hands over to the burger — see the note in css/base.css. The chip's
     defaults already centre it in this 64px header and land it on the 18px
     gutter #page uses, so there is nothing to set here. */
  .has-burger .nav { display: none; }
  .has-burger .menu__toggle { display: flex; }
  .has-burger .menu { display: flex; }

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

  /* Two sheets, so they take the width three stories share — a 30% poster on a
     phone is a stamp. Repeated rather than inherited because the desktop pair
     rule sits inside the wide media query: the ratio has to be restated here or
     the sheets keep the 9:16 of the social frames above, and min-width: 0 for
     the same reason it is there — see the note beside it. */
  .case--pair .case__shot {
    flex: 1 1 0;
    min-width: 0;
    width: auto;
    aspect-ratio: 1000 / 1414;
  }

  /* 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; }

  /* On a phone the walk has nowhere to go.

     On the desktop grid the poster takes six of twelve columns and the caption
     holds the opposite margin, so the turntable steps out from under the sheet
     into half a page of empty white. In one column there is no such margin: at
     58% it was nearly as wide as the card, and walking 93% of that carried it
     straight off the left edge of the screen.

     The free space on a phone is under the card, not beside it — and measured,
     there was almost none: 16px between the sheet and the caption, which is a
     gap, not a lane. So the lane is made rather than assumed. 56px under this
     one figure is enough for the walk and small enough that the row does not
     read as a hole when nothing is happening.

     Then it walks down and across into that lane instead of sideways off the
     page, and the distance is arithmetic rather than taste. At 42% of the card
     it is 149 × 100 and rests 8px up from the foot of the frame, so its top
     edge sits 108px above that foot. Anything less than a 108% step leaves that
     much of it still behind the sheet — 92% left 16px of turntable hidden,
     which is the top of the thing you are meant to be looking at. 118% carries
     the whole character out and 10px clear.

     The lane follows from the same number: 118 − 8 puts its feet 110px below
     the card, so 132px is what the row has to give it to stay off the caption.
     Measured, there was almost none to start with — 16px between the sheet and
     the caption, which is a gap, not a lane — so it is made rather than found.

     It still rests fully inside the figure, behind the frame: a walker whose
     feet poke out below the card at rest has already given the trick away.
     overflow: visible on the figure is what lets it leave. */
  .project__figure--walker { margin-bottom: 132px; }

  .walker {
    width: 42%;
    left: 8px;
    bottom: 8px;
  }

  .project__figure--walker.is-open .walker {
    transform: translate(26%, 118%);
  }
}

/* --------------------------------------------------------------------------
   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__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%;
  }
}


