/* ═══════════════════════════════════════════════════════════════════
   CASE STUDIES

   Two states of the same markup.

   With no JS these are plain sections in the flow, and each work row is
   an anchor that jumps to its own — six studies you can read straight
   through. That is the fallback, and it is the reason the content lives
   in the document rather than in a template the script inflates.

   With JS they come out of the flow entirely and become overlays: the
   row opens its study in place, the ground irises open from the row's
   own line, and the name flies up into the title. Nothing navigates.

   `position: fixed` from the first frame is deliberate — the studies
   never contribute height, so adding six of them cannot move a
   ScrollTrigger start or a pin. `visibility` is what hides them, not
   `display`, so the title can still be measured for the flight while the
   study is shut.
   ═══════════════════════════════════════════════════════════════════ */

.study {
  background: var(--bg);
  color: var(--fg);
  padding-block: clamp(4rem, 12vh, 9rem);
}

/* Two states, and they want opposite layouts.

   In the flow, with no script, a study is a section you read straight
   down: title, paragraph, then the visual across the width. As an
   overlay it is a split — the visual takes the left half and the words
   the right — because there the study is the whole screen rather than
   one section of a page, and a full-width visual under the text would
   put the thing worth looking at below the fold every time.

   So the column rules live under `.js .study__inner` and this stays the
   plain stack. */
.study__inner {
  padding-inline: var(--gutter-wide);
}

/* Each study belongs to an employer and wears its ink, so the index here
   is the same colour the row upstairs wipes to on hover. One source for
   both — the tokens in base.css. */
.study[data-co="lyft"] { --accent: var(--wine); }
.study[data-co="ww"]   { --accent: var(--navy); }

/* ── the title block ─────────────────────────────────────────────── */
/* The index sits on the title's own line rather than above it, so the
   two read as one mark — and it holds the accent, which is the panel's
   ink, the same colour the row it flew from wipes to. */
/* The row upstairs is a grid of `3rem` for the index and the rest for the
   name, with a 1.1rem gap. This repeats those two numbers so the title
   starts at the same offset the name does. */
.study__head {
  display: flex;
  align-items: baseline;
  gap: 1.1rem;
}
.study__idx {
  flex: none;
  width: 3rem;
  font-family: var(--f-ui);
  font-size: clamp(0.7rem, 0.9vw, 0.95rem);
  font-weight: 600;
  letter-spacing: 0.16em;
  color: var(--accent);
}

/* Face, weight, case and tracking are held identical to
   `.exp__work-name`; only the size differs. That is what lets the
   flight be a single uniform scale off the width ratio and land on the
   real title without a seam. Change one, change both. */
.study__title {
  font-family: var(--f-display);
  font-weight: 700;
  text-transform: uppercase;
  font-size: clamp(3rem, 10vw, 9rem);
  line-height: 0.94;
  letter-spacing: -0.018em;
  color: var(--fg);
  margin: 0;
}

.study__desc {
  margin-top: clamp(2rem, 5vh, 3.5rem);
  font-family: var(--f-body);
  font-size: clamp(1.05rem, 1.35vw, 1.3rem);
  line-height: 1.6;
  color: var(--fg-2);
  max-width: 46ch;
}
.study__desc + .study__desc { margin-top: 1.4em; }

/* ── the mock ────────────────────────────────────────────────────── */
.study__media {
  margin-top: clamp(2.5rem, 7vh, 5rem);
}

/* One frame, page width.

   Empty, it holds an aspect ratio so the placeholder has a shape. Filled,
   it gives that up and takes the media's own height instead: a splash
   screen is usually taller than any box worth guessing at, and cropping
   it to a ratio chosen here would cut the top off the very thing being
   shown. `height: auto` is what makes the frame fit the image rather
   than the image fit the frame. */
.study__shot {
  position: relative;
  display: grid;
  place-items: center;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  border: 1px dashed var(--rule);
  background: color-mix(in oklab, var(--fg) 4%, transparent);
}
.study__shot:has(img, video) {
  aspect-ratio: auto;
  border-color: transparent;
  background: none;
}
.study__shot :is(img, video) {
  display: block;
  width: 100%;
  height: auto;
}
.study__slot {
  font-family: var(--f-ui);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg-3);
}
.study__shot:has(img, video) .study__slot { display: none; }

.study__cap {
  margin-top: 0.6rem;
  font-family: var(--f-ui);
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg-3);
}

/* ── the way out ─────────────────────────────────────────────────── */
/* Nothing to close when there is no overlay, so it only exists under JS. */
.study__close { display: none; }

@media (max-width: 60rem) {
  /* a page-wide 16:10 frame is a letterbox on a phone */
  .study__shot { aspect-ratio: 4 / 5; }
}

/* ═══════════════ enhanced: the overlay ═══════════════════════════ */
.js .study {
  position: fixed;
  inset: 0;
  z-index: 80;
  overflow-y: auto;
  /* the page behind must not take over once this hits its own end */
  overscroll-behavior: contain;
  visibility: hidden;
}
.js .study.is-open { visibility: visible; }

/* The page under the overlay is held still rather than allowed to drift
   behind it — but *not* with `overflow: hidden` on the root, which is
   what this used to do.

   `position: sticky` needs a scrolling ancestor. Take the root's scroll
   away and the sticky film in the experience section un-sticks and snaps
   back to its static place in the flow: measured, it jumped between
   610px and 1721px upward depending on where the page was, and since the
   study fades in over 0.42s you watched it go before the paper covered
   it. The scrollbar going with it would have shifted the layout sideways
   on any platform that reserves one, too.

   So nothing about overflow changes now. js/study.js swallows the scroll
   events instead, which leaves every box exactly where it was. */

/* The study is the whole screen and carries its own single way out, so
   the page's nav and the sound pill would be a second set of controls
   floating over someone else's page. They step aside for it and come
   back on close. Faded rather than hidden, so nothing reflows under the
   overlay and the focus ring has somewhere to return to. */
.chrome,
.sound {
  transition: opacity 0.4s var(--ease), visibility 0.4s var(--ease);
}
html.is-study-open .chrome,
html.is-study-open .sound {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

/* Anchored to the viewport, not the study's scroll, so it stays reachable
   however far down the visuals run. */
/* An X rather than the word "Close". The study is a full screen with one
   way out of it, and at that size a glyph is read faster than a label —
   the accessible name is on the button, so nothing is lost by dropping
   the visible word. Two rules crossed, drawn rather than set, because no
   glyph in either face has arms of equal weight. */
.js .study__close {
  position: fixed;
  /* Level with the title rather than jammed into the corner above it:
     the body's top inset, plus half the difference between the title's
     line box and this button's own. Derived from the same two variables
     the title is set from, so it cannot fall out of step with them. */
  top: calc(var(--study-lede)
            + (var(--study-title-fs) * var(--study-title-lh) - var(--study-x)) / 2);
  right: clamp(1.75rem, 4vw, 4.25rem);
  z-index: 2;
  display: grid;
  place-items: center;
  width: var(--study-x);
  height: var(--study-x);
  background: none;
  border: 0;
  padding: 0;
  color: var(--fg-2);
  transition: color 0.35s var(--ease), transform 0.45s var(--ease);
}
.js .study__x {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
}
.js .study__x::before,
.js .study__x::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 0;
  width: 100%;
  height: 1.5px;
  background: currentColor;
}
.js .study__x::before { transform: rotate(45deg); }
.js .study__x::after  { transform: rotate(-45deg); }

.js .study__close:hover { transform: rotate(90deg); }
/* On a phone this is the only way out of a study, and it is 24px tall.
   The hit area is grown past the type on all four sides — `::before` is
   the rule, so this is `::after`, and being absolute it costs no layout.
   Nothing else is up in that corner for it to swallow. */
@media (hover: none) {
  .js .study { --study-x: 2.75rem; }
}

.js .study__close:hover,
.js .study__close:focus-visible { color: var(--fg); }

/* ── the overlay's split ──────────────────────────────────────────
   Visual left, words right, each its own full-height column. The left
   runs to the very edge of the screen with no gutter — it is a frame for
   a film, and a margin around it would make it a picture on a wall
   instead. The right keeps the page's own gutter so the type sits on the
   same left edge as everything else on the site.

   Half and half, and that is load-bearing rather than tidy: it makes each
   body column start on the same edge its employer's panel starts on, and
   the rules below then set the words in the panel's own column. Which is
   what makes the title fly straight up. */
.js .study {
  /* the flow version breathes; the overlay is edge to edge, so the media
     column can run to all four corners of the screen */
  padding-block: 0;

  /* Named because the close control lines itself up from them rather than
     from a number someone has to keep in step by hand. */
  --study-lede: clamp(3rem, 8vh, 5.5rem);        /* the body's top inset */
  --study-title-fs: clamp(2.3rem, 4.9vw, 4.6rem);
  --study-title-lh: 0.92;
  --study-x: clamp(2.2rem, 3.4vw, 3.1rem);       /* the X's own box */
}
.js .study__inner {
  padding: 0;
  min-height: 100%;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: stretch;
}

/* A ground of its own, a step up from the paper beside it, so the frame
   reads as a slot for a film even while it is empty. */
.js .study__media {
  margin: 0;
  position: relative;
  background: var(--paper-lift);
  display: grid;
  place-items: center;
  overflow: hidden;
}
/* Fills its column rather than holding a ratio — the column already has
   a shape, and a 16:10 box inside it would just be a smaller box with
   dead ground around it. */
.js .study__media .study__shot {
  aspect-ratio: auto;
  width: 100%;
  height: 100%;
  border: 0;
  background: none;
}
.js .study__media .study__shot :is(img, video) {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Sized to its column, not to the viewport. The flow version can take
   the full measure of the page; here it has half a screen less a gutter,
   and at the page size the longest of the six wrapped to two lines and
   pushed the rule off the fold.

   It is still comfortably bigger than the row it flies up from — about
   1.2x at desktop — which is what keeps the flight an arrival rather
   than a nudge. Shrinking this further starts trading the flight away;
   see the note in js/study.js on `sizeRatio`. */
.js .study__title {
  font-size: var(--study-title-fs);
  line-height: var(--study-title-lh);
}

/* ── WW sits on the other side ────────────────────────────────────
   Upstairs the two employers own opposite halves of the screen — Lyft's
   column on the right, WW's on the left — and a study that opens from a
   row ought to land on the side that row lives on. So WW's whole split
   is mirrored: words left, visual right. Nothing about the layout is
   duplicated for it; the columns swap order and the media grows from its
   other edge.

   `direction` would flip the type with it, so the order is set with
   explicit column placement instead. */
.js .study[data-co="ww"] .study__inner {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}
.js .study[data-co="ww"] .study__media { grid-column: 2; grid-row: 1; }
.js .study[data-co="ww"] .study__body {
  grid-column: 1; grid-row: 1;
  translate: var(--lean) 0;      /* leans the way WW's panel leans */
}

/* The way out stays with the words rather than floating over the film,
   so it mirrors too. */
.js .study[data-co="ww"] .study__close {
  right: auto;
  left: clamp(1.75rem, 4vw, 4.25rem);
}

.js .study__body {
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  /* The work rows' own column, repeated exactly: same measure, same
     centring, same gutter as `.exp__inner`. Anything else and the title
     lands at a different x from the row it flew up from, and the flight
     arrives on a slant — measured before this, Lyft drifted 13px right
     and WW 28px left. Change `.exp__inner` and change this with it. */
  max-width: 46rem;
  margin-inline: auto;
  width: 100%;
  padding-inline: clamp(1.75rem, 5vw, 5rem);
  padding-block: var(--study-lede) clamp(2.5rem, 7vh, 5rem);
  /* The panels lean toward their own film by half the dead band beside
     it, so the study has to lean by exactly the same amount and in the
     same direction or the title lands off its row at any width where the
     lean is not zero. It is zero at 1440; at 1920 it is 77px. */
  translate: calc(-1 * var(--lean)) 0;
}

/* The rule under the title, which is what separates the mark from what
   it is about. Full width of the column, so it reads as a division of
   the page rather than as an underline on the words. */
.js .study__head {
  padding-bottom: clamp(1.1rem, 3vh, 2rem);
  border-bottom: 1px solid var(--rule);
}

/* Its own scroll, so a long study runs inside the right-hand column and
   the visual beside it stays where it is. */
@media (min-width: 60rem) {
  .js .study { overflow: hidden; }
  .js .study__body { overflow-y: auto; overscroll-behavior: contain; }
}

/* One column again where two would leave neither enough room: the visual
   on top at a fixed share of the screen, the words under it. */
@media (max-width: 60rem) {
  /* one column, so there is no band beside anything and nothing to lean */
  .js .study__body { translate: none; }
  .js .study__inner,
  .js .study[data-co="ww"] .study__inner {
    grid-template-columns: 1fr;
    grid-template-rows: 42svh auto;
  }
  /* one column, so the mirror has nothing to mirror — the visual is on
     top and the words under it for both employers */
  .js .study[data-co="ww"] .study__media { grid-column: 1; grid-row: 1; }
  .js .study[data-co="ww"] .study__body  { grid-column: 1; grid-row: 2; translate: none; }
  .js .study[data-co="ww"] .study__close { left: auto; right: clamp(1.75rem, 4vw, 4.25rem); }
  .js .study__body { padding-top: clamp(2rem, 5vh, 3rem); }
}

/* The clone that flies from the row to the title. It is the row's own
   element, so it already carries the display face and the tracking; all
   this adds is the lift out of the page and the licence to scale. */
.study-fly {
  position: fixed;
  z-index: 90;
  margin: 0;
  transform-origin: 0 0;
  pointer-events: none;
  white-space: nowrap;
  will-change: transform;
  /* The row it is cloned from carries `transition: transform 0.6s, color
     0.4s` for its own hover, and the clone inherits it. Left on, the
     browser transitions toward every value the tween writes and the
     flight arrives 0.6s late, eased twice — measured, the transform
     stayed at the identity matrix while GSAP had already written the
     final translate. The tween owns this element outright. */
  transition: none;
}
/* the second copy `.exp__work-name` carries for its hover wipe has no job
   in flight, and would ghost over the real letters at full opacity */
.study-fly::after { content: none !important; }

@media (prefers-reduced-motion: reduce) {
  .js .study__close { transition: none; }
}
