/* ---------- the long page ---------- */

/* The runway the name comes apart over. It holds nothing: the name is fixed
   furniture drawn above it. */
.gallery-hero { height: 100svh; }

.case {
  /* An anchor jump has to clear the lockup along the top. */
  scroll-margin-top: var(--wm-top);
}

/* The screen that names a case: three lines, built like the first screen — what
   the work was, the mark, who it was for. */
.case-open {
  height: 100svh;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* One block of three lines, centred. The same construction serves the opening
   screen and the body, so they sit on the same measure and the same rhythm. */
/* The heading inside a line block is a heading for structure, not for size or
   weight: it is set like every other line around it. Without this it falls back
   to the browser's bold and pulls a whole extra weight of the family down. */
.case-text h2 {
  margin: 0;
  font: inherit;
  letter-spacing: inherit;
}

.case-text {
  margin: 0; padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--line-gap);
  text-align: center;
  font-size: var(--wordmark-size);
  line-height: var(--wordmark-leading);
  letter-spacing: var(--wordmark-track);
  max-width: calc(100vw - 2 * var(--inset));
}

/* The name on the opening screen and the one the lockup flies to the bar are
   the same words in the same place, and only one of them may be painted.

   Fading between them was survivable while both were plain dark type. It is not
   now: the flying one is differenced against what is behind it, and white
   differenced against black type comes back white — the two copies cancel each
   other and leave nothing but the edges the antialiasing rounded off. So where
   the lockup carries the name, the screen's own copy is not drawn at all.

   visibility, not display: the flying copy takes its starting position by
   measuring this one, and a box that is not laid out cannot be measured. */
html[data-case-label="lockup"] .case.is-named .case-open h2 { visibility: hidden; }

/* One screen, as the design has it: the case names itself, then says what it
   was. Anything longer goes here when the layout for a case arrives — and the
   moment it is taller than the screen it stops being a place the page lands and
   becomes something to read through (see engine/settle.js). */
.case-body {
  height: 100svh;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ---------- about ---------- */

/* One screen, centred, in the same type as the rest of the site. The ring and
   the serif this page used to carry came from another project's design and had
   nothing to do with this one. */
.about {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--page);
}

/* Ways to reach me, kept to the chrome's grid and standing off the face. */
.about-links {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(var(--face-bottom) + var(--face-size) + var(--menu-stand) + env(safe-area-inset-bottom, 0px));
  display: flex;
  gap: 28px;
  justify-content: center;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 15px;
  letter-spacing: 0.01em;
}

.about-links a { border-bottom: 1px solid transparent; transition: border-color 200ms var(--ease-out); }
.about-links a:hover { border-bottom-color: currentColor; }

/* ---------- a case's body ---------- */

/* Every block is 16:9 of the page. The design's 810px against 1440px is exactly
   that, so the whole column scales with the window and nothing is pinned to the
   width it was drawn at. */
.case-blocks {
  display: flex;
  flex-direction: column;
  gap: var(--block-gap);
}

.block {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
}

.block-fill {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The artwork sits *on* the field rather than across it. The default is the
   product shot's 1046 of the design's 1440; a block that wants a different
   margin says so and the number lands here. */
.block-media.is-inset .block-fill {
  inset: auto;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: var(--inset-w, 72.64%);
  height: auto;
  object-fit: contain;
  border-radius: var(--radius, 0);
}

/* Sized to the block's height rather than across it, and dissolved into the
   field at either side. The clip is square and the block is not, so filling the
   width would crop most of it away and filling the height leaves an edge down
   both sides — the mask is what makes that edge stop being a line. The element
   takes the clip's own shape (height set, width from the ratio), so the
   gradient is measured across the picture and not across the block. */
/* Two states of one object, crossing on a click. The whole block is the
   control: there is nothing else in it to click, so there is nothing to be
   ambiguous about. The field crosses with the picture, since the object is lit
   in one state and not in the other and the room around it changes too. */
.block-media.is-toggle {
  cursor: pointer;
  transition: background 520ms var(--ease-out);
  -webkit-tap-highlight-color: transparent;
}

.block-media.is-toggle[data-state="night"] { background: var(--tint-night, inherit); }

.block-media.is-toggle .block-fill { transition: opacity 520ms var(--ease-out); }
.block-media.is-toggle .is-night { opacity: 0; }
.block-media.is-toggle[data-state="night"] .is-day { opacity: 0; }
.block-media.is-toggle[data-state="night"] .is-night { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .block-media.is-toggle,
  .block-media.is-toggle .block-fill { transition: none; }
}

.block-media.is-height .block-fill {
  inset: auto;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  height: 100%;
  width: auto;
  object-fit: contain;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 var(--fade, 18%), #000 calc(100% - var(--fade, 18%)), transparent);
  mask-image: linear-gradient(to right, transparent, #000 var(--fade, 18%), #000 calc(100% - var(--fade, 18%)), transparent);
}

/* Words over a picture are their own layer, so the picture stays a picture and
   the words stay words: selectable, and sized to the window rather than baked
   into an export. */
.block-over {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--line-gap);
  padding: 0 11.7%;
  text-align: center;
  /* The block is a fixed share of the page, so words written across it have to
     be one too, or the composition only holds at the width it was drawn at.
     48px against the design's 1440 is 3.33vw; the cap keeps it from growing
     past the design on a very wide screen. */
  font-size: min(var(--block-over-size, 48px), 3.4vw);
  line-height: var(--wordmark-leading);
}

.block-over p { margin: 0; }

.block-text {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* A statement written across a block is set on the measure the design gives it
   — 959 of 1440 — so it breaks where it was drawn to break rather than running
   the whole width of a wide screen. */
.block-text .case-text { max-width: 959px; }

/* ---------- grid blocks ---------- */

.block-grid {
  display: flex;
  gap: var(--block-gap);
  padding: 0 var(--block-gap);
}

.grid-col {
  flex: 1 0 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--block-gap);
}

.cell {
  position: relative;
  flex: 1 0 0;
  min-height: 0;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

.cell-art {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The material sample runs as a band down the middle of its cell, overflowing
   top and bottom — the cell crops it. Percentages, not the design's pixels, so
   it holds its place as the block scales. */
.cell-art.is-strip {
  inset: auto;
  left: 50%;
  top: 12.75%;
  transform: translateX(-50%);
  width: 21.2%;
  height: auto;
  object-fit: contain;
}

/* The converted preview: a rounded card on the cell's middle line. */
.cell-art.is-card {
  inset: auto;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 53.6%;
  height: auto;
  object-fit: contain;
  border-radius: 16px;
}

/* An object laid on the middle of its cell at the share of it the design gives
   it. A percentage, not the design's pixels, so it holds its place as the block
   scales. */
.cell-art.is-plate {
  inset: auto;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: var(--art-w, 50%);
  height: auto;
  object-fit: contain;
}

/* ---------- board blocks ---------- */

/* Things laid on a field, each at the share of it the design gives it. Every
   position and every size in here is a percentage and never a pixel, so the
   arrangement drawn at 1440 is the same arrangement at any width. The type is
   the exception, as it is everywhere else on the site: it keeps its size. */
.block-board {
  display: flex;
  padding: 0 var(--block-gap);
}

.board-field {
  position: relative;
  flex: 1 0 0;
  min-width: 0;
  height: 100%;
  overflow: hidden;
}

.board-item {
  position: absolute;
  left: var(--x, 0);
  top: var(--y, 0);
  width: var(--w, auto);
  height: var(--h, auto);
  overflow: hidden;
  /* engine/drift.js writes --dx and --dy. They are composed here rather than
     written as a transform, because a text item already carries one and two
     authors of the same property is one too many. */
  transform: translate3d(var(--dx, 0px), var(--dy, 0px), 0);
}

.board-item[data-drift],
.board-item[data-fly] { will-change: transform; }

/* A cutout keeps its own shape; a screenshot fills the frame it was drawn
   into. */
.board-art {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.board-art.is-cover { object-fit: cover; }

/* The hairline the design draws round a screenshot of an interface, so a white
   panel on a near-white field still has an edge. Inset, so it survives the
   corner radius and the clip. */
.board-item.is-edged { box-shadow: inset 0 0 0 0.5px rgba(0, 0, 0, 0.09); }

/* A board's words are set like every other line on the site, on the measure the
   design draws around them — and the measure is in the type's own units, so the
   line breaks the design chose survive every window width. */
.board-item.is-text {
  left: 50%;
  transform: translate3d(calc(-50% + var(--dx, 0px)), var(--dy, 0px), 0);
  width: var(--measure, 723px);
  height: auto;
  max-width: calc(100% - 2 * var(--inset));
  overflow: visible;
}

.board-item.is-text .case-text { padding: 0 40px; max-width: none; }

/* Words inside a case lie over a flat field in one block and a photograph in
   the next, so they read their colour off the picture too. See the note in
   styles/chrome.css. */
.cell-label,
.block-over {
  color: #fff;
  -webkit-text-fill-color: #fff;
  mix-blend-mode: difference;
}

/* A cell's label sits on its vertical centre line, over whatever is behind. */
.cell-label {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  margin: 0;
  padding: 10px 30px;
  font-size: var(--wordmark-size);
  line-height: var(--wordmark-leading);
}

.cell-label.is-center { text-align: center; }

/* A cell carrying words instead of a picture sets them like everywhere else. */
.cell .case-text { padding: 0 40px; max-width: 723px; }

/* ---------- sound ---------- */

/* Nothing autoplays with sound, so a clip that has one starts muted and says
   so. Set in the page's own small type — everything else here is words. */
.block-sound {
  position: absolute;
  right: 30px;
  bottom: 30px;
  z-index: 2;
  margin: 0;
  padding: 6px 0;
  border: 0;
  border-bottom: 1px solid currentColor;
  background: none;
  color: #fff;
  mix-blend-mode: difference;
  font: inherit;
  font-size: 15px;
  letter-spacing: 0.01em;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* ---------- controls for a clip worth sitting through ---------- */

/* Most clips here are a few seconds of a thing happening and want no controls
   at all. A reel runs for a minute and has a soundtrack, so it gets a line of
   words and a hairline, in the page's own small type, differenced against the
   picture like every other label on the site. */
.block-player {
  position: absolute;
  right: 30px;
  bottom: 30px;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 20px;
  color: #fff;
  -webkit-text-fill-color: #fff;
  mix-blend-mode: difference;
}

.player-toggle {
  margin: 0;
  padding: 6px 0;
  border: 0;
  border-bottom: 1px solid currentColor;
  background: none;
  color: inherit;
  font: inherit;
  font-size: 15px;
  letter-spacing: 0.01em;
  /* Play and Pause are not the same width, and the hairline under them should
     not change length every time the clip is stopped. */
  min-width: 52px;
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* The dial, drawn by hand: the platform's own slider arrives with an
   appearance that belongs to the platform and not to this page. */
.player-volume {
  -webkit-appearance: none;
  appearance: none;
  width: 96px;
  height: 20px;
  margin: 0;
  background: none;
  cursor: pointer;
}

.player-volume::-webkit-slider-runnable-track {
  height: 1px;
  background: linear-gradient(to right, currentColor var(--filled, 0%), rgba(255, 255, 255, 0.4) var(--filled, 0%));
}

.player-volume::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 9px;
  height: 9px;
  margin-top: -4px;
  border: 0;
  border-radius: 50%;
  background: currentColor;
}

.player-volume::-moz-range-track { height: 1px; background: rgba(255, 255, 255, 0.4); }
.player-volume::-moz-range-progress { height: 1px; background: currentColor; }
.player-volume::-moz-range-thumb { width: 9px; height: 9px; border: 0; border-radius: 50%; background: currentColor; }

.player-volume:focus-visible { outline: 1px solid currentColor; outline-offset: 4px; }

/* ---------- the cloud ---------- */

/* Things that were made and did not become a case. One screen, one cluster,
   floating — engine/cloud.js does the moving. */
.cloud {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 10vh 0;
}

/* Square, because the cluster is round: on a wide screen it is the height that
   runs out first, and on a narrow one the width. The share of the page is the
   reference's — its cluster spans about a third of the window, with each card
   near a twenty-fifth of it. A cluster that filled the screen would stop being
   a cluster and become a layout. */
.cloud-field {
  position: relative;
  width: min(44%, 64svh);
  aspect-ratio: 1;
}

/* Placed by the middle rather than by a corner: what the engine scatters is
   where a thing sits, not where its top-left happens to land. The three pairs
   of offsets are composed here so that nothing has to know about the others —
   the centring, the float (--dx/--dy) and the open state (--ox/--oy). */
.cloud-item {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: var(--w);
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transform:
    translate3d(
      calc(-50% + var(--dx, 0px) + var(--ox, 0px)),
      calc(-50% + var(--dy, 0px) + var(--oy, 0px)),
      0)
    scale(var(--scale, 1));
  will-change: transform;
  transition: opacity 420ms var(--ease-out);
}

/* Only the opening moves on a curve. The float is written every frame and must
   not be eased, or each frame would chase the last one. */
.cloud-item.is-open {
  z-index: 3;
  transition: transform 620ms var(--ease-out), opacity 420ms var(--ease-out);
}

/* While one is open the rest stand back rather than disappear — the cluster is
   still the thing you are in. */
.cloud-field.has-open .cloud-item:not(.is-open) { opacity: 0.12; }

.cloud-art {
  display: block;
  width: 100%;
  height: auto;
  pointer-events: none;
}

.cloud-item:focus-visible { outline: 1px solid currentColor; outline-offset: 6px; }

/* A line under the cloud, if there is ever one to say. */
.cloud-text {
  position: relative;
  padding: 10vh var(--inset) 0;
  display: flex;
  justify-content: center;
}

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

@media (max-width: 760px) {
  /* The cluster takes the width it can get, and the pieces grow with it — at
     375 a third of the page would be a stamp. */
  .cloud-field { width: min(92%, 50svh); }
  .cloud-item { width: calc(var(--w) * 1.5); }
}

/* ---------- a case on a phone ---------- */

/* The design has no frame for this, and the desktop one does not survive the
   trip: two half-width columns inside a 16:9 block leave each cell about as
   wide as a thumbnail, and three lines of type in a 16:9 strip have nowhere to
   go. So the columns stack, cells take a shape that suits a narrow screen, and
   the text blocks size to their content instead of to a ratio. */
@media (max-width: 760px) {
  .block-grid {
    flex-direction: column;
    aspect-ratio: auto;
    padding: 0;
  }

  .cell {
    flex: none;
    aspect-ratio: 4 / 3;
  }

  /* A cell holding an arrangement keeps it, for the same reason a board does,
     and takes a shape with room for it. */
  .cell:has(.board-item) { aspect-ratio: 3 / 4; }

  /* A cell that carries words rather than a picture takes the room the words
     need — a ratio would either crop them or leave a hole. */
  .cell:not(:has(.cell-art)) {
    aspect-ratio: auto;
    padding: 14vh 0;
  }

  .cell-label { padding: 10px 20px; }

  .block-text {
    aspect-ratio: auto;
    padding: 18vh var(--inset);
  }

  /* 16:9 is not enough room for a statement on a phone — at 375 wide the block
     is 211 tall and the words need more than that. A block carrying words gets
     a shape that can hold them. */
  .block-media:has(.block-over) { aspect-ratio: 3 / 4; }

  /* A board stays a board. Taking it apart into a column loses the one thing it
     is — an arrangement — so the phone keeps the arrangement and is given the
     room to hold it: the field turns from 16:9 to 3:4, which is nearly three
     times the height at the same width.

     Positions stay where the design put them, as shares of the field. What
     changes is how a piece is sized: on the desktop its height is a share of
     the field too, and against a field of a different shape that would squash
     it. So on a phone the width is the share and the height comes from the
     piece's own ratio — the arrangement re-proportions and nothing distorts. */
  .block-board { aspect-ratio: var(--phone-ar, 3 / 4); }

  .board-item {
    /* The pieces are drawn larger than their share of the desktop field — the
       design's own figure is a little over half again, measured off the
       garment in the collage. */
    width: calc(var(--w) * var(--phone-scale, 1.58));
    height: auto;
    aspect-ratio: var(--ar, auto);
  }

  .board-item.is-text {
    /* The measure is a desktop number and wider than the phone. */
    width: auto;
    left: var(--inset);
    right: var(--inset);
    transform: translate3d(var(--dx, 0px), var(--dy, 0px), 0);
    max-width: none;
    padding: 0;
  }

  .board-item.is-text .case-text { padding: 0; }

  .block-over {
    padding: 0 var(--inset);
    font-size: 7vw;
  }

  .block-sound { right: var(--inset); bottom: var(--inset); }

  .block-player { right: var(--inset); bottom: var(--inset); gap: 14px; }
  .player-volume { width: 72px; }
}

/* ---------- a block that changes what it holds ---------- */

/* One board, turning over on its own. The design draws four of them stacked;
   repeating the frame four times would only slide the same layout past the
   reader over and over. */
.block-steps {
  position: relative;
  display: flex;
  gap: var(--block-gap);
  padding: 0 var(--block-gap);
  background: #fff;
  /* The whole block takes the next feature. There is nothing else in here to
     click, so nothing to be ambiguous about. */
  cursor: pointer;
}

/* The same move for a keyboard, which a click on a region is not. It shows
   itself when focused rather than staying invisible under the focus ring. */
.steps-next:focus-visible {
  position: absolute;
  width: auto;
  height: auto;
  clip-path: none;
  left: var(--block-gap);
  bottom: var(--block-gap);
  margin: 0;
  padding: 8px 14px;
  border: 1px solid currentColor;
  background: var(--page);
  color: var(--ink);
  font: inherit;
  font-size: 15px;
}

.steps-stage {
  display: contents;
}

.steps-text,
.steps-art {
  position: relative;
  flex: 1 0 0;
  min-width: 0;
}

/* ---------- the words ---------- */

/* Centred in its half, both ways, on the design's own measure. */
.steps-text {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--line-gap);
  padding: 0 40px;
  text-align: center;
  font-size: var(--wordmark-size);
  line-height: var(--wordmark-leading);
}

.steps-lead { margin: 0; max-width: 723px; }

/* All the names are in the document; one is showing. Stacked rather than
   swapped, so nothing has to be rebuilt to change which. */
.steps-names {
  position: relative;
  width: 100%;
  max-width: 723px;
  margin: 0;
  padding: 0;
  list-style: none;
  height: calc(var(--wordmark-size) * var(--wordmark-leading));
}

.steps-name {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 420ms var(--ease-out);
}

/* ---------- the clips ---------- */

/* The right half is a field of its own, filled edge to edge, and it crops what
   overflows it — which is how the design covers it with the last clip. */
.steps-art {
  background: #f3f3f5;
  overflow: hidden;
}

/* Centred at the width the design gave it, as a share of this half — and the
   height follows from the clip's own shape rather than from a second number.

   Two numbers means two chances to disagree, and they did: cropping black rows
   off the configurator changed its shape, the box kept the one the design drew
   around the uncropped file, and cover made up the difference by cutting the
   sides off. A box that is exactly the shape of what it holds cannot crop.

   No corner radius: the rounded-rectangle in the file is Figma's node type,
   not a radius on the frame. */
.step {
  position: absolute;
  left: 50%;
  top: 50%;
  width: var(--w, 100%);
  transform: translate(-50%, -50%);
  opacity: 0;
  transition: opacity 420ms var(--ease-out);
}

.step video,
.step img {
  display: block;
  width: 100%;
  height: auto;
}

.block-steps[data-active="0"] .steps-name[data-step="0"],
.block-steps[data-active="1"] .steps-name[data-step="1"],
.block-steps[data-active="2"] .steps-name[data-step="2"],
.block-steps[data-active="3"] .steps-name[data-step="3"],
.block-steps[data-active="4"] .steps-name[data-step="4"],
.block-steps[data-active="5"] .steps-name[data-step="5"],
.block-steps[data-active="0"] .step[data-step="0"],
.block-steps[data-active="1"] .step[data-step="1"],
.block-steps[data-active="2"] .step[data-step="2"],
.block-steps[data-active="3"] .step[data-step="3"],
.block-steps[data-active="4"] .step[data-step="4"],
.block-steps[data-active="5"] .step[data-step="5"] { opacity: 1; }

/* ---------- on a phone ---------- */

/* Side by side leaves each half about a third of a screen. They stack instead,
   and the block gives up its ratio to fit them. */
@media (max-width: 760px) {
  .block-steps {
    flex-direction: column;
    aspect-ratio: auto;
    padding: 0;
  }

  .steps-text {
    padding: 12vh var(--inset);
  }

  .steps-art {
    flex: none;
    width: 100%;
    aspect-ratio: 4 / 5;
  }
}

/* ---------- the mark, running ---------- */

.block-mark {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Square and sized to whatever holds it, since what goes in it is a square
   canvas. The flat mark sits here until the raymarcher takes the container
   over, and stays if it never can. */
.mark-stage {
  position: relative;
  width: min(64%, 62svh);
  aspect-ratio: 1;
}

/* Half a block is half the room, so the mark takes more of it. */
.cell.is-mark .mark-stage { width: min(78%, 62svh); }

.mark-stage img,
.mark-stage canvas {
  display: block;
  width: 100%;
  height: 100%;
}

/* Once it is live the flat one has done its job. */
.mark-stage[data-live] img { display: none; }
