/* ==========================================================================
   Scenes. Ink and Porcelain alternate as the environment; Aubergine and
   Patina appear as accents and bleeds, never as full grounds.
   ========================================================================== */

/* <picture> is an inline wrapper with auto height. Without this an img's
   height: 100% has nothing to resolve against and object-fit never engages. */
.hero__frame picture,
.unfold__side picture {
  display: block;
  width: 100%;
  height: 100%;
}

/* Height-driven, so width follows the portrait's own ratio. The archival
   plate opens from its own horizontal axis, like the artworks. */
.artist__portrait picture {
  display: block;
  height: 100%;
  width: auto;
  clip-path: inset(var(--portrait-mask, 0%) 0 var(--portrait-mask, 0%) 0);
}

/* --------------------------------------------------------------------------
   1. Hero
   Full viewport. The type sits low and left, off the artwork's mirror axis,
   so image and typography occupy different halves of the frame.
   -------------------------------------------------------------------------- */

.hero {
  position: relative;
  min-height: 100svh;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-content: end;
  overflow: hidden;
  isolation: isolate;
}

.hero__media {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  background: rgb(149 155 169);
}

/* Overscanned by 7% top and bottom so the image can drift on scroll without
   ever exposing an edge. The scrim stays fixed to the viewport. */
.hero__frame {
  position: absolute;
  top: -7%;
  left: 0;
  right: 0;
  height: 114%;
  transform-origin: 62% 45%;
}

.hero__frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Push the work's bright centre right of the type. */
  object-position: 64% center;
}

/* Functional scrim. The crop is a midtone and Porcelain type will not hold
   4.5:1 against it unaided. Weighted to the lower left, where the type sits. */
.hero__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(
      to top,
      rgb(16 17 20 / 0.94) 0%,
      rgb(16 17 20 / 0.86) 30%,
      rgb(16 17 20 / 0.55) 48%,
      rgb(16 17 20 / 0.12) 68%,
      rgb(16 17 20 / 0) 82%
    ),
    linear-gradient(to right, rgb(16 17 20 / 0.45) 0%, rgb(16 17 20 / 0.1) 44%, rgb(16 17 20 / 0) 70%);
}

.hero__type {
  position: relative;
  padding: 0 var(--page-x) clamp(3.5rem, 11vh, 8rem);
  max-width: min(100%, 62rem);
  display: grid;
  justify-items: start;
  gap: clamp(0.75rem, 2vh, 1.5rem);
}

.hero__logo {
  width: clamp(34px, 3.4vw, 48px);
  color: var(--porcelain);
  margin-bottom: clamp(0.5rem, 2vh, 1.25rem);
}

.hero__logo svg {
  width: 100%;
  height: auto;
}

.hero__title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--fs-display);
  line-height: var(--lh-display);
  letter-spacing: var(--tr-display);
  color: var(--porcelain);
  margin: 0;
}

/* Each line masks its own reveal, so nothing bleeds outside the wipe. */
.hero__title .line {
  display: block;
  overflow: hidden;
  padding-bottom: 0.06em;
}

.hero__title .line__inner {
  display: block;
}

/* Takes the title-2 step rather than an em fraction of the line above it.
   At 0.44em it computed to 53px against title-2's 52px: a 1.02 ratio, which
   is two sizes impersonating one step. */
.hero__title-sub {
  font-style: italic;
  font-size: var(--fs-title-2);
  line-height: var(--lh-title-2);
  letter-spacing: var(--tr-title-2);
  color: rgb(240 236 229 / 0.86);
  padding-left: 0.06em;
}

/* Sentence case, not caps. This is a full sentence, and the shared design
   law reserves caps for short labels and headings; the 0.14em cap-tracking
   also over-opens two lowercase words the way it never would a single
   short label. Moved to the meta role, which is what a supporting line
   under a headline actually is. */
.hero__tagline {
  font-size: var(--fs-meta);
  font-weight: var(--fw-label);
  line-height: var(--lh-meta);
  letter-spacing: var(--tr-caption);
  color: var(--mirror-silver);
  margin-top: clamp(0.75rem, 2vh, 1.5rem);
  /* Wide enough to hold on one line at desktop; wraps naturally below. */
  max-width: 34rem;
}

.hero__cue {
  position: absolute;
  right: var(--page-x);
  bottom: clamp(3.5rem, 11vh, 8rem);
  width: var(--hairline);
  height: clamp(52px, 9vh, 88px);
  background: rgb(240 236 229 / 0.2);
  overflow: hidden;
}

/* Three passes, then rest. A perpetual loop would be the one piece of
   constant motion on an otherwise still page. */
.hero__cue::after {
  content: "";
  display: block;
  width: 100%;
  height: 38%;
  background: var(--porcelain);
  animation: cue-travel 3.4s var(--ease-quart) 3;
  animation-fill-mode: forwards;
}

@keyframes cue-travel {
  0% { transform: translateY(-100%); }
  62%, 100% { transform: translateY(265%); }
}

/* --------------------------------------------------------------------------
   2. The artist
   A full screen. The portrait is shown whole: no aggressive crop, no frame,
   no container. A Patina field bleeds off the left edge behind it.
   -------------------------------------------------------------------------- */

/* Sized so the whole screen is exactly one viewport and the portrait stands
   on the floor. The section's own padding is deliberately small; --space-2xl
   here would add 460px and push the photo below the fold. */
.artist {
  position: relative;
  min-height: 100svh;
  display: grid;
  grid-template-columns: minmax(0, 40fr) minmax(0, 60fr);
  align-items: end;
  gap: clamp(2rem, 5vw, 5rem);
  padding: clamp(3.5rem, 8vh, 6rem) var(--page-x) 0;
  overflow: hidden;
}

.artist__portrait {
  position: relative;
  z-index: 1;
  margin: 0;
  justify-self: start;
  align-self: end;
  width: fit-content;
  /* Height-driven, so the portrait always fits the screen it owns. */
  height: min(74svh, 44rem);
}

.artist__portrait img {
  height: 100%;
  width: auto;
  max-width: none;
}

/* Atmospheric accent, not a ground. A Patina field standing behind the
   portrait and reaching out past its right edge, toward the type. */
.artist__field {
  position: absolute;
  left: 26%;
  right: -22%;
  top: 22%;
  bottom: 0;
  background: var(--patina-blue);
  z-index: -1;
}

/* Stretched to the full screen and split top-to-bottom, so the column has two
   anchors instead of floating in the upper third. */
.artist__text {
  position: relative;
  z-index: 1;
  align-self: stretch;
  display: grid;
  align-content: space-between;
  max-width: 46rem;
  padding-block: clamp(4rem, 14vh, 9rem) clamp(3rem, 9vh, 6rem);
}

/* Demoted from title-1 to title-3. The hero already spent the wordmark's
   monumental moment on this exact name; repeating it at the same scale one
   screen later read as a second headline with nothing new to say. It stays
   present as an identifying line, and the lead statement below takes over
   the job of carrying this screen. */
.artist__name {
  font-family: var(--font-display);
  font-size: var(--fs-title-3);
  line-height: var(--lh-title-3);
  letter-spacing: var(--tr-title-3);
  margin: 0;
}

.artist__lead {
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--fs-title-2);
  line-height: var(--lh-title-2);
  letter-spacing: var(--tr-title-2);
  color: var(--patina-blue);
  margin-top: clamp(0.5rem, 1.5vw, 1rem);
  max-width: 18ch;
}

/* Metadata. On Porcelain this is Ink at reduced opacity, never Mirror Silver,
   which measures 1.7:1 here. */
.meta {
  margin: 0;
  max-width: 34rem;
}

.meta__row {
  display: grid;
  grid-template-columns: minmax(6rem, 10rem) 1fr;
  gap: var(--space-sm);
  padding-block: var(--space-2xs);
  border-top: var(--hairline) solid var(--rule);
}

.meta dt {
  font-size: var(--fs-label);
  font-weight: var(--fw-label);
  line-height: var(--lh-label);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--ink-secondary);
}

/* The dossier facts are the one place a curator actually reads, so they take
   the body size, not the caption size. PRODUCT.md puts the floor at 17px. */
.meta dd {
  margin: 0;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  letter-spacing: var(--tr-body);
}

/* --------------------------------------------------------------------------
   3. Reflection becomes image
   The left half of one work, and its own mirror sliding in to complete it.
   The halves are exact: measured difference against the real artwork is
   3.5 of 255, i.e. compression noise.
   -------------------------------------------------------------------------- */

.unfold {
  position: relative;
  height: 280svh;
}

.unfold__sticky {
  position: sticky;
  top: 0;
  height: 100svh;
  display: grid;
  grid-template-rows: 1fr auto;
  align-items: center;
  justify-items: center;
  gap: clamp(1.5rem, 4vh, 3rem);
  padding: clamp(2rem, 6vh, 4rem) var(--page-x);
}

.unfold__frame {
  position: relative;
  aspect-ratio: 1 / 1;
  height: min(70svh, 44rem);
  max-width: 100%;
  margin: 0;
  overflow: hidden;
  /* Transparent, so the room shows through where the image is not yet whole.
     The ground wash is visible inside the work's own incompleteness. */
  background: transparent;
  align-self: center;
}

.unfold__side {
  position: absolute;
  top: 0;
  width: 50%;
  height: 100%;
  overflow: hidden;
  will-change: transform;
}

.unfold__side img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.unfold__side--left {
  left: 0;
  background: rgb(143 155 169);
}

.unfold__side--right {
  left: 50%;
  transform: translate3d(var(--unfold-x, 0%), 0, 0);
}

.unfold__side--right img {
  transform: scaleX(-1);
}

.unfold__frame::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: var(--hairline);
  background: var(--porcelain);
  opacity: var(--unfold-seam, 0);
}

/* The thesis of the whole page, so it takes the rank below the bookends
   rather than sitting level with the section headings. */
.unfold__statement {
  font-family: var(--font-display);
  font-size: var(--fs-title-1);
  line-height: var(--lh-title-1);
  letter-spacing: var(--tr-title-1);
  text-align: center;
  opacity: var(--unfold-text, 1);
  align-self: end;
}

/* --------------------------------------------------------------------------
   4. Selected works
   No two plates share a size, a position, or a caption placement.
   -------------------------------------------------------------------------- */

/* Arrival after the unfold: the biggest breath on the page. */
.works {
  padding-block: var(--space-3xl) 0;
}

.works__head {
  max-width: var(--page-max);
  margin-inline: auto;
  padding-inline: var(--page-x);
  margin-bottom: var(--space-lg);
  display: grid;
  gap: var(--space-sm);
}

.works__title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--fs-title-2);
  line-height: var(--lh-title-2);
  letter-spacing: var(--tr-title-2);
  margin: 0;
}

/* Real prose, so it reads at the body size. It was set at label size (13px),
   which put the only running sentence on the page below the readable floor. */
.works__note {
  color: var(--ink-secondary);
  max-width: 52ch;
  font-size: var(--fs-body);
  line-height: var(--lh-body-dark);
  letter-spacing: var(--tr-body-dark);
}

.plate {
  position: relative;
  margin: 0;
}

.plate__media {
  position: relative;
  overflow: hidden;
  background: var(--plate-tone, #1a1c20);
}

.plate__media img {
  width: 100%;
  height: auto;
  display: block;
}

/* Parallax moves the media box, never the image inside it. The image fills
   its box exactly, so translating the image would expose the box behind it
   and crop the artwork; translating the box crops nothing. */
.js [data-mask] .plate__media {
  transform: translate3d(0, var(--drift, 0px), 0);
}

/* The plate is uncovered from its horizontal centreline outward, never faded. */
.js [data-mask] .plate__media img {
  clip-path: inset(var(--mask-y, 0%) 0 var(--mask-y, 0%) 0);
  transform: scale(var(--mask-scale, 1));
  transform-origin: center;
}

.js [data-mask] .plate__caption {
  transform: translate3d(0, var(--caption-drift, 0px), 0);
}

/* Headings open from their vertical centreline. The vertical insets are
   negative so ascenders, descenders and italic overshoot are never clipped. */
.js [data-clip] {
  clip-path: inset(-0.25em var(--clip-x, 0%) -0.3em var(--clip-x, 0%));
}

.plate__caption {
  margin-top: var(--space-sm);
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding-inline: var(--page-x);
}

.plate__num {
  font-size: var(--fs-label);
  font-weight: var(--fw-label);
  line-height: var(--lh-label);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--ink-secondary);
  font-variant-numeric: tabular-nums lining-nums;
}

/* Explicit leading: without it the title inherited the ground's body
   line-height, so the same element led at 1.6 on light and 1.7 on dark. */
.plate__title {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 400;
  font-size: var(--fs-title-3);
  line-height: var(--lh-title-3);
  letter-spacing: var(--tr-title-3);
}

.plate__meta {
  font-size: var(--fs-meta);
  line-height: var(--lh-meta);
  color: var(--ink-secondary);
  letter-spacing: 0.01em;
}

/* Plate I: full bleed */
.plate--wide .plate__media {
  width: 100%;
  --plate-tone: rgb(150 149 146);
}

.plate--wide .plate__caption {
  max-width: var(--page-max);
  margin-inline: auto;
  width: 100%;
}

/* Plate II: pushed right, caption sticky in the void */
.plate--offset {
  max-width: var(--page-max);
  margin: var(--space-3xl) auto 0;
  padding-inline: var(--page-x);
  display: grid;
  grid-template-columns: 1fr minmax(0, 44%);
  gap: var(--space-lg);
  align-items: start;
}

.plate--offset .plate__media {
  grid-column: 2;
  --plate-tone: rgb(134 141 155);
}

/* Held at the right edge of the void rather than stranded at the far margin,
   so the caption and the plate read as one object across the gutter. */
.plate--offset .plate__caption--sticky {
  grid-row: 1;
  grid-column: 1;
  position: sticky;
  top: 44vh;
  margin-top: 0;
  padding-inline: 0;
  justify-self: end;
  align-items: flex-end;
  text-align: right;
  max-width: 24rem;
}

/* Plate III: the largest object, capped so the symmetry can be seen whole */
.plate--major {
  margin-top: var(--space-2xl);
  display: grid;
  justify-items: center;
}

.plate--major .plate__media {
  width: min(88%, 74svh);
  --plate-tone: rgb(143 155 168);
}

.plate--major .plate__caption--right {
  width: min(88%, 74svh);
  align-items: flex-end;
  text-align: right;
  padding-inline: 0;
}

/* Plate IV: the single warm work, on the light ground */
/* Ground change, so the gap above it is the largest in the sequence. */
.works__warm {
  margin-top: var(--space-3xl);
  padding-block: var(--space-2xl);
}

.plate--warm {
  max-width: var(--page-max);
  margin-inline: auto;
  padding-inline: var(--page-x);
  display: grid;
  grid-template-columns: minmax(0, 54%) 1fr;
  gap: var(--space-lg);
  align-items: end;
}

.plate--warm .plate__media {
  --plate-tone: rgb(144 137 128);
}

.plate--warm .plate__caption {
  padding-inline: 0;
  padding-bottom: var(--space-2xs);
}

/* --------------------------------------------------------------------------
   5. Archive
   Back matter of a catalogue. Not linked: nothing here exists yet.
   -------------------------------------------------------------------------- */

.archive {
  padding: var(--space-3xl) var(--page-x) var(--space-2xl);
}

.archive__title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--fs-title-2);
  line-height: var(--lh-title-2);
  letter-spacing: var(--tr-title-2);
  max-width: var(--page-max);
  margin: 0 auto var(--space-xl);
}

.archive__list {
  list-style: none;
  padding: 0;
  margin: 0 auto;
  max-width: var(--page-max);
}

.archive__row {
  display: grid;
  grid-template-columns: 4rem 1fr auto;
  align-items: baseline;
  gap: var(--space-sm);
  padding-block: clamp(1.25rem, 3vw, 2.25rem);
  border-top: var(--hairline) solid var(--rule);
}

.archive__row:last-child {
  border-bottom: var(--hairline) solid var(--rule);
}

/* Numerals, not capitals: 0.14em cap-tracking on digits reads as a mistake. */
.archive__num {
  font-size: var(--fs-label);
  font-weight: var(--fw-label);
  line-height: var(--lh-label);
  letter-spacing: var(--tr-num);
  color: var(--ink-secondary);
  font-variant-numeric: tabular-nums lining-nums;
}

.archive__name {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--fs-title-3);
  line-height: var(--lh-title-3);
  letter-spacing: var(--tr-title-3);
}

.archive__state {
  font-size: var(--fs-label);
  font-weight: var(--fw-label);
  line-height: var(--lh-label);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--ink-secondary);
}

/* --------------------------------------------------------------------------
   6. Invitation
   The only action on the page.
   -------------------------------------------------------------------------- */

/* Bookend to the hero: same rank, same monumental scale, opposite ground.
   Left-aligned rather than a centred stack, with the action pushed to the far
   edge of a hairline so the closing screen holds tension instead of symmetry. */
.invite {
  min-height: 84svh;
  display: grid;
  align-content: center;
  justify-items: start;
  gap: var(--space-lg);
  padding: var(--space-3xl) var(--page-x) var(--space-2xl);
  max-width: var(--page-max);
  margin-inline: auto;
  width: 100%;
}

.invite__title {
  font-family: var(--font-display);
  font-size: var(--fs-display);
  line-height: var(--lh-display);
  letter-spacing: var(--tr-display);
  margin: 0;
}

.invite__foot {
  width: 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-md) var(--space-lg);
  padding-top: var(--space-md);
  border-top: var(--hairline) solid var(--rule);
}

/* Same fix as .hero__tagline: sentence case, meta role, not a caps label. */
.invite__audience {
  font-size: var(--fs-meta);
  font-weight: var(--fw-label);
  line-height: var(--lh-meta);
  letter-spacing: var(--tr-caption);
  color: var(--ink-secondary);
}

.invite__link {
  display: inline-block;
  padding: var(--space-xs) var(--space-lg);
  border: var(--hairline) solid var(--gallery-ink);
  color: var(--gallery-ink);
  font-size: var(--fs-label);
  font-weight: var(--fw-label);
  line-height: var(--lh-label);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  text-decoration: none;
  transition:
    background-color var(--dur-med) var(--ease-expo),
    color var(--dur-med) var(--ease-expo);
}

.invite__link:hover,
.invite__link:focus-visible {
  background: var(--gallery-ink);
  color: var(--porcelain);
}

.colophon {
  padding: var(--space-lg) var(--page-x);
  border-top: var(--hairline) solid var(--rule);
}

.colophon__inner {
  max-width: var(--page-max);
  margin-inline: auto;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-sm);
  font-size: var(--fs-meta);
  line-height: var(--lh-meta);
  letter-spacing: 0.02em;
  color: var(--ink-secondary);
}

/* --------------------------------------------------------------------------
   Responsive tiers.

   Three width tiers, not two. A single mobile/desktop split left tablet
   portrait (roughly 641-1023px) inheriting the phone's stacked composition
   stretched across a much wider canvas: the artist portrait capped at 22rem
   sat in a sea of empty Porcelain, and archive rows wasted their spare width
   wrapping a label that has room to sit beside the name. Tablet gets its own
   recalibrated two-column artist screen instead of the phone's single column.

   A fourth, height-driven tier follows the three width tiers: short viewports
   (landscape phones, a short desktop window) break the full-viewport-spread
   sections in a way no width breakpoint catches, since the constraint is
   vertical room, not horizontal. Measured, not assumed: at 844x390 the artist
   section's content ran 614px past the viewport, the unfold statement fell
   entirely off-screen behind an oversized frame, and the invitation's only
   button was cut in half. That tier is content-driven, keyed to the actual
   failure, and applies at any width, including a short desktop window, since
   the same "not enough vertical room" problem is the same problem there.
   -------------------------------------------------------------------------- */

/* Tablet + phone shared: what's true at every width below desktop. The
   asymmetric void-column composition on Plate II only has room to breathe
   at 1024px+; below that it collapses to the same full-width treatment as
   the other three plates rather than squeezing a diminished version of it.

   This block also carries the tablet-recalibrated artist screen (originally
   a separate `(min-width: 641px) and (max-width: 1023px)` range, merged here;
   see the .98px note below for why the pairing was the wrong shape anyway).

   The threshold is 1023.98px, not 1023px. Verified on this machine at a
   1023px-wide, 900-tall viewport with devicePixelRatio 1.25: the browser's
   actual layout width resolves a fraction of a pixel above 1023, so a plain
   `max-width: 1023px` silently stops matching, the desktop rules below don't
   apply either since the viewport is still under 1024px, and the section
   renders with neither tier's styling. `.98px` is the standard fix for this
   (Bootstrap uses the same convention on every one of its breakpoints) and
   it's cheap enough to apply on principle, not only where it was caught. */
@media (max-width: 1023.98px) {
  .artist {
    grid-template-columns: minmax(0, 42fr) minmax(0, 58fr);
    gap: clamp(1.5rem, 4vw, 3rem);
    padding-block: clamp(2.5rem, 6vh, 4.5rem) clamp(2rem, 5vh, 3rem);
  }

  /* Height-driven, like desktop, but the figure's width isn't tied to its
     grid track (fit-content sizing can overflow a narrow track), so the cap
     is computed against the tightest case this rule has to cover: a 641px
     viewport's 42fr column is ~231px wide. 22rem keeps the portrait's
     natural width safely under that; the track only grows from there as
     the viewport widens toward 1024px. The phone block below replaces this
     entirely with its own width-driven sizing, so this value only governs
     the 641-1023px range in practice. */
  .artist__portrait {
    height: min(48svh, 22rem);
  }

  /* The portrait is well under half the height it is on desktop, so the text
     column can no longer stretch across the section with space-between:
     that left the headline stranded at the container's top edge, a gap
     above the portrait, and the meta rows landing low enough to overlap
     the Patina field. A plain block, anchored to the same floor as the
     portrait, keeps the pairing coherent at this height. */
  .artist__text {
    align-self: end;
    display: block;
    max-width: 32rem;
    padding-block: 0;
  }

  .meta {
    margin-top: clamp(1.5rem, 5vh, 2.5rem);
  }

  /* The field's rightward reach (right: -22%) is sized for desktop's wider
     column gap and portrait; at this narrower portrait and tighter gap that
     same percentage overran into the text column. Pulled back so it still
     clears the portrait's own edge (a positive value would leave it
     invisible, hidden entirely behind the portrait) but stops well short
     of the text. */
  .artist__field {
    right: -15%;
  }

  .plate--offset,
  .plate--warm {
    display: block;
  }

  .plate--offset .plate__media,
  .plate--warm .plate__media {
    width: 100%;
  }

  .plate--offset .plate__caption--sticky {
    position: static;
    margin-top: var(--space-sm);
  }

  .plate--major .plate__media,
  .plate--major .plate__caption--right {
    width: 100%;
  }

  .plate--major .plate__caption--right {
    align-items: flex-start;
    text-align: left;
    padding-inline: var(--page-x);
  }

  .plate--offset .plate__caption,
  .plate--warm .plate__caption {
    padding-inline: var(--page-x);
  }

  .plate--offset,
  .plate--warm {
    padding-inline: 0;
  }

  .unfold {
    height: 240svh;
  }

  /* Same width-driven sizing as phone, with a slightly taller cap: tablet has
     the width to let the frame grow a little without losing the square. */
  .unfold__frame {
    height: auto;
    width: min(92vw, 30rem);
  }
}

/* Phone only (up to 640px). The artist screen restacks so the portrait
   leads, the Patina field moves behind the type instead of the portrait,
   and archive rows compress to two lines. This is where the canvas is
   genuinely too narrow for two columns to hold real content. */
@media (max-width: 640.98px) {
  .artist {
    grid-template-columns: 1fr;
    align-content: center;
    gap: clamp(2rem, 7vw, 3.5rem);
    padding-block: var(--space-xl);
  }

  .artist__portrait {
    width: min(78%, 22rem);
    height: auto;
    justify-self: start;
    order: -1;
  }

  .artist__portrait img {
    width: 100%;
    height: auto;
  }

  .artist__field {
    left: -14%;
    right: 34%;
    top: 30%;
    bottom: -6%;
  }

  .artist__text {
    align-self: start;
    display: block;
    padding-block: 0;
  }

  .artist__lead {
    max-width: none;
  }

  .meta {
    margin-top: var(--space-lg);
  }

  .archive__row {
    grid-template-columns: 3rem 1fr;
    row-gap: 0.25rem;
  }

  .archive__state {
    grid-column: 2;
  }

  .hero__cue {
    display: none;
  }
}

/* Short viewport, any width. Landscape phones and a short desktop window
   share the same constraint: not enough vertical room for a full-viewport
   spread. Measured at 844x390 before writing this: the artist section ran
   614px past the fold with the name, lead and meta entirely below it; the
   unfold frame at 432px square left the statement with nowhere to go; the
   invitation's only button was cut in half by the fold. This tier answers
   that directly rather than letting "full screen" quietly become "however
   tall the content happens to be." It is layered after every width tier so
   it wins regardless of which one also matched. .98px for the same reason
   every other threshold in this file uses it: a fractional viewport height
   from a non-integer devicePixelRatio can land just above a round number
   and silently miss it. */
@media (max-height: 500.98px) {
  .hero {
    min-height: 100vh;
  }

  .artist {
    min-height: 0;
    grid-template-columns: minmax(0, 40fr) minmax(0, 60fr);
    align-items: center;
    gap: clamp(1.25rem, 4vw, 2.5rem);
    padding-block: clamp(1.5rem, 6vh, 2.5rem);
  }

  .artist__portrait {
    width: auto;
    height: min(70vh, 14rem);
    justify-self: start;
    order: 0;
  }

  .artist__portrait img {
    width: auto;
    height: 100%;
  }

  .artist__field {
    left: 26%;
    right: -22%;
    top: 22%;
    bottom: 0;
  }

  .artist__text {
    align-self: center;
    display: block;
    padding-block: 0;
  }

  .artist__lead {
    max-width: none;
  }

  .meta {
    margin-top: clamp(0.75rem, 3vh, 1.25rem);
  }

  .unfold__sticky {
    padding-block: clamp(0.75rem, 4vh, 1.5rem);
    gap: clamp(0.5rem, 2vh, 1rem);
  }

  .unfold__frame {
    height: min(58vh, 16rem);
    width: min(58vh, 16rem);
  }

  .unfold__statement {
    font-size: var(--fs-title-3);
    line-height: var(--lh-title-3);
    letter-spacing: var(--tr-title-3);
  }

  .invite {
    min-height: 0;
    padding-block: clamp(1.5rem, 8vh, 3rem) clamp(1.25rem, 6vh, 2rem);
    gap: var(--space-md);
  }

  .invite__title {
    font-size: var(--fs-title-1);
    line-height: var(--lh-title-1);
    letter-spacing: var(--tr-title-1);
  }
}

/* ==========================================================================
   Display face treatment. Applied last so it governs every display element
   in one place instead of drifting across ten rules.

   Bodoni Moda is a Didone. Its hairlines are structurally near-zero and at
   weight 400 they render below a pixel, so the letterforms lose their spine
   and the type stops reading as present. Weight thickens the stems; pinning
   the optical size low borrows the text-size master, whose hairlines are
   drawn sturdier. Both levers together, chosen by rendering the ladder.
   ========================================================================== */

.hero__title,
.artist__name,
.unfold__statement,
.works__title,
.archive__title,
.archive__name,
.invite__title {
  font-weight: var(--fw-display);
  font-optical-sizing: none;
  font-variation-settings: "opsz" var(--display-opsz);
}

.hero__title-sub,
.artist__lead,
.plate__title {
  font-weight: var(--fw-display-italic);
  font-optical-sizing: none;
  font-variation-settings: "opsz" var(--display-opsz);
}

/* Optical alignment. Large display caps carry side bearing, so a heading set
   flush to the page edge reads as indented against the rules and images
   beneath it. Pull it back by the bearing, not by a guess at a round number. */
.hero__title,
.invite__title {
  margin-left: -0.04em;
}
