/* ═══════════════════════════════════════════════════════════════════════════
   PROJECT PAGE — the stagecrew.studio/work/… layout.

   Measured off the reference at 1440px: the info column is a third of the
   padded page and sticks 40px below the bar while the image column scrolls
   past it. Images sit in the remaining two thirds, either full width or in
   flush half-width pairs.
   ═══════════════════════════════════════════════════════════════════════════ */

.proj{
  margin-top: var(--bar);
  padding: 40px var(--gut) 0;
  display: flex;
  align-items: flex-start;
}

/* A page can carry more than one project — the Apple event page holds the event
   identity and the iPhone 17 angle. Only the first clears the bar; each later
   one opens a new sticky run, so its info column takes over as its own images
   come up. */
.proj + .proj{
  margin-top: 0;
  padding-top: 14svh;
}

/* ─── Info column ─────────────────────────────────────────────────────────── */

.proj__info{
  width: 33.3333%;
  flex: 0 0 33.3333%;
  position: sticky;
  top: calc(var(--bar) + 40px);   /* == .proj's padding-top, so it starts level
                                     with the first image and never jumps */
  padding: 0 0 14svh;
}

.info__title{
  margin: 0 0 22px;
  font-size: calc(var(--fs) + 4px);   /* 17px — the project's name is the one
                                         thing on these pages set above 13 */
  font-weight: 500;
  letter-spacing: -.01em;             /* 17px wants a hair less than 13px does */
}

/* ─── A compilation's head ────────────────────────────────────────────────────
   Where one page carries several projects down a single image column, the name
   holds the left of the info column and the run you are looking at sits hard
   right on the same line, the way the landing page pairs a title with its
   services. It aligns to the right edge of the prose measure, not the column's
   own edge, or it would sit flush against the images. */

.info__head{
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--gut);
  padding-right: 24%;
}
.info__head .info__title{ margin-bottom: 0; }

/* 13px and quiet: a label for the images, not a second title competing with
   the project's name beside it. */
.info__run{
  text-align: right;
  color: var(--ink-quiet);
  text-wrap: nowrap;
  transition: opacity .16s var(--ease-out-quint),
              transform .16s var(--ease-out-quint);
}
.info__run.is-swapping{ opacity: 0; transform: translateY(3px); }

.info__line{
  margin: 22px 0 0;
  padding-right: 24%;
  font-size: calc(var(--fs) + 1px);   /* the description reads a step up */
  text-wrap: pretty;
}

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

/* ─── Project info, as a disclosure ───────────────────────────────────────────
   The description is folded away behind "Project Info" so the column opens
   short and the reader chooses whether to read it. Everything below — the +
   list, the meta pair, the credit — stays put.

   A native <details>: the browser gives the toggling, the keyboard and the
   semantics, and it still opens if the JS on the page never runs. */

.info__disclose{ margin: 0 0 22px; }

.info__disclose > summary{
  display: inline-flex;
  align-items: center;
  gap: 9px;
  cursor: pointer;
  font-size: calc(var(--fs) + 2px);  /* reads as a title, a step under the
                                        project name's 17px */
  font-weight: 500;
  color: var(--ink);
  list-style: none;                  /* kill the default triangle */
  transition: color .18s var(--ease-out-quart);
}
.info__disclose > summary::-webkit-details-marker{ display: none; }
.info__disclose > summary::marker{ content: ""; }
.info__disclose > summary:hover{ color: var(--ink-quiet); }

.info__caret{
  display: inline-flex;
  width: 17px;
  height: 17px;
  transition: transform .28s var(--ease-out-quart);
}
.info__caret svg{
  width: 100%; height: 100%;
  fill: none; stroke: currentColor;
  stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round;
}
.info__disclose[open] .info__caret{ transform: rotate(180deg); }

/* The prose measure is narrower than the column — that inset is what keeps the
   text off the images rather than a gutter between the columns. */
.info__body{
  margin-top: 14px;
  padding-right: 24%;
  font-size: calc(var(--fs) + 1px);   /* the description reads a step up */
  text-wrap: pretty;
}
.info__disclose[open] .info__body{
  animation: disclose-in .3s var(--ease-out-quart) both;
}

@keyframes disclose-in{ from { opacity: 0; transform: translateY(-4px) } }

@media (prefers-reduced-motion: reduce){
  .info__caret{ transition: none; }
  .info__disclose[open] .info__body{ animation: none; }
}
.info__body p{ margin: 0 0 1.2em; }
.info__body p:last-child{ margin-bottom: 0; }
.info__lead{ font-weight: 500; }

/* A question set, one per line, kept as its own block */
.info__asks{
  margin: 1.6em 0 0;
  padding: 0;
  list-style: none;
}
.info__asks li{ margin-bottom: .3em; }

/* The + list — one discipline per line, as the reference has it */
.info__tasks{
  margin: 2.2em 0 0;
  padding: 0;
  list-style: none;
}

/* A labelled pair: field name, then value */
.info__meta{
  margin: 2.2em 0 0;
  display: grid;
  grid-template-columns: 1fr 2fr;
  gap: 0 var(--gut);
  padding-right: 24%;
}
.info__meta dt{ color: var(--ink-quiet); }
.info__meta dd{ margin: 0; }

/* The Apple page's meta carries a Link row. Site-wide `a` is undecorated, so
   the rule has to come back here or the value reads as plain text. */
.info__meta a{
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: from-font;
}
.info__meta a:hover{ color: var(--ink-quiet); }

.info__credit{
  margin: 2.2em 0 0;
  padding-right: 24%;
  color: var(--ink-quiet);
}

/* ─── Image column ────────────────────────────────────────────────────────── */

.proj__media{
  width: 66.6667%;
  flex: 0 0 66.6667%;
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
  gap: var(--gap-media);
}

:root{
  --gap-media: 3px;               /* between every image in the column */
  --caro-ground: #121212;         /* every carousel shot sits on a dark surface */
}

/* figure carries a default 1em/40px margin — without this reset nothing pairs
   and the whole column sits 40px in from the grid */
.shot{
  margin: 0;
  width: 100%;
}
.shot--half { width: calc(50% - var(--gap-media) / 2); }
.shot--third{ width: calc((100% - var(--gap-media) * 2) / 3); }

/* The other half of a third — for a row that pairs one tall portrait shot with
   one wide landscape one. Halving those two would leave a rag three times the
   short image's height; 1/3 + 2/3 lands them within a hundred-odd pixels. */
.shot--twothirds{ width: calc((100% - var(--gap-media) * 2) / 3 * 2 + var(--gap-media)); }

/* Four across, for a run of same-ratio images that reads as a contact sheet —
   the Thresholds book spreads. */
.shot--quarter{ width: calc((100% - var(--gap-media) * 3) / 4); }

/* Off the grid, for the rare row no halving or thirding can settle — the Apple
   page pairs a 2:3 portrait with a 16:9 film, which even at thirds rags by 119
   and closes only by letterboxing the film inside a taller box. Each member
   takes its own share of the row instead, solved from the two ratios so both
   come out the same height, nothing cropped and nothing letterboxed:

       share = (1 / ratio) / Σ(1 / ratio)

   Reach for this only where the grid genuinely cannot hold the row. Halves,
   thirds and quarters are the column's structure; this is the exception.

   `--split-n` is how many gaps the row spends — one for a pair, which is why it
   defaults to 1 and every existing pair keeps its width. The TEDxRISD page runs
   a three-member split (a tote, and two portrait slides), so it sets 2. */
.shot--split{
  width: calc((100% - var(--gap-media) * var(--split-n, 1)) * var(--share));
}

/* A wider gap after one shot, where a hero wants air before the row beneath it
   rather than the column's usual hairline. The 3px the column already spends is
   part of the 70, so the margin only makes up the difference. */
.shot--break{ margin-bottom: calc(70px - var(--gap-media)); }

/* A chapter break, where the run of work changes subject rather than a single
   figure needing air after it — the Apple page steps out of the event identity
   and into the Nyx logo guidelines. Full width, so it forces a row break in the
   wrap and then holds the gap on its own. */
.proj__break{
  width: 100%;
  height: 50px;
}

/* A row is as tall as its tallest member, so the shorter image of a pair used to
   leave a gap under it — a hole in the column, since the figure stretched but
   the image did not. The frame's job is now only to RESERVE height: each image's
   own ratio, as an inline padding-bottom, which is what sets the row's height
   and stops anything shifting as it loads. The image is then laid against the
   figure instead of the frame, and a figure in a row stretches to the row, so
   the short one fills the space under it. `cover` takes the difference off its
   sides rather than letterboxing it. */
.shot{
  position: relative;
  overflow: hidden;
  background: var(--media-ground);
}
.shot__frame{
  position: relative;
  height: 0;
  overflow: hidden;
  background: var(--media-ground);
}
/* Static, so the absolutely positioned image below resolves against .shot. Only
   inside a figure — the next-project card reuses .shot__frame on its own and
   still needs the frame itself to be the containing block. */
.shot > .shot__frame{ position: static; }
.shot__frame img,
.shot__frame video,
.shot__frame iframe{
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
}
/* A film is self-hosted and plays with no controls of its own, so it sits in the
   frame exactly as a still would — see the Mac40 film on the Apple page. An
   iframe is the fallback for anything that has to stay on a third party. */
.shot__frame iframe{ border: 0; }

/* A lockup, not a photograph — the Apple event headlines ship as transparent
   PNGs of blue-gradient type, which is why the old page laid every one of them
   on black with padding. Same here: the frame is the black, the padding on the
   image is the air, and the ratio is set loose enough that `contain` never
   crops the mark. */
.shot--lockup,
.shot--lockup .shot__frame{ background: #000; }
.shot--lockup .shot__frame img{
  object-fit: contain;
  padding: 6%;
}

/* ─── Group note ──────────────────────────────────────────────────────────────
   The Thuma page runs its media in labelled groups — the subject on the left,
   how it was made on the right. The note spans the image column above the group
   it names and splits on the same halves the images under it use, so the two
   labels sit over their own columns. The space above it is what separates one
   group from the next; the images themselves stay a pixel apart. */

.note{
  width: 100%;
  display: flex;
  gap: var(--gap-media);
  padding: 5svh 0 10px;
  color: var(--ink-quiet);
}

/* The first note follows the hero directly — it opens the run rather than
   breaking it, so it takes the images' own spacing. */
.note--first{ padding-top: 12px; }

.note__what,
.note__how{
  width: calc(50% - var(--gap-media) / 2);
  text-wrap: pretty;
}

/* One of these labels is a sentence long. Left to fill its half it would run
   right up against the label in the next column and the two would read as one
   line — this inset is what keeps them apart, the same trick the info column
   plays on its prose. The right label still starts on the halfway line, over
   the column it names. */
.note__what{ padding-right: 12%; }

@media (max-width: 639px){
  .note{ display: block; padding-top: 4svh; }
  .note__what,
  .note__how{ width: auto; display: block; }
  .note__how{ margin-top: .25em; }
}

/* ─── Run marker ──────────────────────────────────────────────────────────────
   A compilation names its runs in the info column instead, so here the marker
   draws nothing. It still does two jobs: it breaks the flex line, which is what
   keeps one run's images from wrapping into the next run's row, and its height
   is the space between them. The name it carries is read by
   js/compilation.js, and its sr-only heading is what a screen reader gets. */

.run{
  width: 100%;
  height: 5svh;
}
/* The first opens the column rather than breaking it, so it takes no space. */
.run--first{ height: 0; }

/* :not, so the narrow override cannot outrank the first run's reset above —
   both are one class deep, and this rule comes later in the file. */
@media (max-width: 639px){
  .run:not(.run--first){ height: 4svh; }
}

/* ─── Carousel ────────────────────────────────────────────────────────────────
   Matches the sliders the old project pages used. Every slide renders in one
   fixed frame so they are all the same size and width, sized to the group's
   median aspect ratio with object-fit:contain — these are photographs of open
   books, and letterboxing a few percent beats cropping a page edge. The ground
   is near-black because every one of these shots sits on a dark surface. */

/* Reserves its height the same way a .shot__frame does, and stays static for
   the same reason — the slides lay against the figure, so a carousel that is the
   shorter member of a row fills the row instead of leaving a hole under it. The
   ground has to move up to the figure with them, which is why the token is
   declared on :root rather than here. */
.caro{
  position: static;
  height: 0;                      /* padding-bottom is set inline, per group */
}
.shot:has(> .caro){ background: var(--caro-ground); }

.caro__slide{
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity .5s var(--ease-out-quart);
}
.caro__slide.is-on{ opacity: 1; }

.caro__slide img{
  width: 100%;
  height: 100%;
  display: block;
  object-fit: contain;
}

/* Before the script runs — and whenever it cannot — the first slide is already
   visible, so the carousel is never a blank box.

   Scoped to a carousel with nothing live yet, which it has to be: unscoped, this
   rule ties with `.caro__slide.is-on` on specificity and comes after it, so
   nothing could ever turn the first slide back off. It stayed at full opacity
   under every later slide — invisible while the shots were opaque, and plainly
   wrong the moment one of them had a transparent background to see through. */
[data-caro]:not(:has(> .caro__slide.is-on)) > .caro__slide:first-child{ opacity: 1; }

/* Manual next. Sits inside the frame on the right; these shots are all on a
   dark ground, so a light outline reads on every one of them. */
.caro__next{
  appearance: none;
  -webkit-appearance: none;
  padding: 0;
  background: none;
  font: inherit;
  cursor: pointer;

  position: absolute;
  top: 50%;
  right: 14px;
  margin-top: -15px;
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  border: 1px solid rgb(255 255 255 / .55);
  border-radius: 50%;
  color: rgb(255 255 255 / .8);
  opacity: .65;
  transition: opacity .2s var(--ease-out-quart),
              border-color .2s var(--ease-out-quart);
}
.caro__next svg{
  width: 13px; height: 13px;
  fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.caro__next:hover,
.caro__next:focus-visible{
  opacity: 1;
  border-color: rgb(255 255 255 / .9);
}

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

/* ─── Next project ────────────────────────────────────────────────────────────
   The card keeps its place: name and thumbnail, bottom-right, at the end of the
   page. What comes out of the flow is the words — "Next Project" pins to the
   bottom-left corner above 1024 so the way on is reachable from anywhere in the
   page, not only from the end of it.

   The corner is the one part of the layout reliably free: the text column is
   sticky to the top and the images are in the right two thirds. It carries the
   page's ground so it stays legible over whatever it crosses. And when you do
   reach the end of the page it lands exactly where the label used to sit, so
   the pair reads as it always did — words left, card right.

   Below 1024 nothing is sticky and a fixed corner would cover the images, so
   the words go back into the row beside the card. */

.next{
  padding: 14svh var(--gut) var(--gut);
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: var(--gut);
}

.next__pin{
  color: var(--ink-quiet);
  font-weight: 500;
  white-space: nowrap;
}
.next__pin:hover,
.next__pin:focus-visible{ color: var(--ink); }

.next__link{
  display: block;
  width: 33.3333%;
}

/* The corner pin has no arrow — the words carry it. This is for a card that
   sets one inside its name, as the TEDxRISD rebuild does. */
.next__arrow{
  display: inline-block;
  margin-left: .35ch;
  transition: transform .2s var(--ease-out-quart);
}
.next__link:hover .next__arrow,
.next__link:focus-visible .next__arrow{ transform: translate(1px, -1px); }

/* The frame here is a <span>, because it sits inside the link next to the
   project's name — and an inline box takes neither the height:0 nor the
   padding-bottom the ratio is built from, so without this the thumbnail
   collapses to nothing and only the name shows. */
.next__link .shot__frame{
  display: block;
  margin-top: var(--gut);
}
.next__link img{
  transition: transform .8s var(--ease-out-quart);
}
.next__link:hover img,
.next__link:focus-visible img{ transform: scale(1.014); }

@media (min-width: 1024px){
  /* the words leave the row; the card holds the right edge on its own */
  .next{ justify-content: flex-end; }

  .next__pin{
    position: fixed;
    left: var(--gut);
    bottom: var(--gut);
    z-index: var(--z-header);
    background: var(--ground);
  }
}

/* A short window — a landscape tablet, a half-height desktop one — leaves the
   info column running all the way to the bottom, and the pin would sit on its
   last lines. Same call the landing page makes about its bio at this height:
   give the corner up and let the words ride back into the row. */
@media (min-width: 1024px) and (max-height: 560px){
  .next{ justify-content: space-between; }
  .next__pin{ position: static; background: none; }
}

/* ─── Narrow ──────────────────────────────────────────────────────────────── */

@media (max-width: 1023px){
  .proj{ display: block; padding-top: 24px; }

  .proj__info{
    width: auto;
    position: static;
    padding: 0 0 12svh;
  }
  .info__body,
  .info__meta,
  .info__credit,
  .info__head,
  .info__line{ padding-right: 0; }

  /* Single column here, so the info column no longer sticks — a title that
     tracks the scroll would just scroll away with it, and the images below
     would carry no names at all. So the live title goes and each run shows the
     heading it was already carrying for a screen reader, in place, which is
     where the label sat before the column took the job. */
  .info__run{ display: none; }

  .run{
    height: auto;
    padding: 5svh 0 10px;
  }
  .run--first{ padding-top: 12px; }
  .run h2{
    position: static;
    width: auto; height: auto;
    margin: 0; padding: 0;
    overflow: visible;
    clip: auto;
    white-space: normal;
    font-size: var(--fs);
    font-weight: 400;
    color: var(--ink-quiet);
  }

  .proj__media{ width: auto; }

  .next{ display: block; padding-top: 12svh; }
  .next__link{ width: 100%; margin-top: 6svh; }
}

/* A pair on a phone is ~177px a side, a third ~118px — too small to read.
   Below 640 every shot takes the full width; from 640 up the pairs and the
   3-up grid both still hold. */
@media (max-width: 639px){
  .shot--half,
  .shot--third,
  .shot--twothirds,
  .shot--quarter,
  .shot--split{ width: 100%; }
}
