/* ==================================================================
   The run — a horizontal scroll-snap surface, one subject per screen.

   Instead of a grid of cards, the set runs sideways as one continuous
   surface. The opening frame takes the full width alone; every frame
   after it is half the viewport, packed edge to edge with no gutter, so
   two sit side by side and the seam between them is the only edge on
   screen. That contiguity is the whole idea — a gap anywhere in the run
   turns a surface back into a row of cards.

   The editorial page is its only caller — a frame is one look in a
   colourway. It was written to be shared with a run-style shop as well,
   and is kept separate from editorial.css on that basis: what a page puts
   inside a frame is its own business, and everything here is the
   mechanism. The shop has since gone back to its grid, so for now this is
   one page's layout living in two files.

   Layered on top of styles.css.
================================================================== */

:root {
  /* The run is contiguous: no gutter, no page margin. Every frame after
     the opener is half the viewport, so two sit side by side and the seam
     between them is the only edge on screen. The opener takes the full
     width to itself — it is the one image that arrives alone. */
  --run-shot: 50vw;
  --run-lead: 100vw;

  /* One height for every frame in the run, so the tops and bottoms line up
     across the seam. The run reaches the top of the window and passes
     under the bar, and nothing sits under it either — the step controls
     ride the field now rather than taking a row beneath it — so a frame is
     the whole screen until the page is scrolled. svh rather than vh so a
     phone's collapsing chrome cannot crop the foot of the frame, which is
     where the thumbnails and the steps sit. */
  --run-h: 100svh;
  --run-bar: 3.75rem;

  /* How far down a garment shot flat is scaled from the contained size —
     see .run__slide--flat. Held in a variable because the phone rule has
     to scale a flat by the same amount without the carry the desktop rule
     wraps it in, and because the wide flats lower it in place. */
  --run-flat: 0.6667;
}

.run-body {
  /* the strip owns horizontal space; nothing may add a second axis */
  overflow-x: hidden;
}

/* The surfaces the browser draws still belong to the page. Left at their
   defaults they ship a blue that appears nowhere else here. */
.run-body ::selection {
  background: var(--fg);
  color: var(--bg);
}

.run-body {
  caret-color: var(--fg);
}

.run {
  padding-bottom: 4rem;
  /* Up under the bar, so the opening frame reaches the top of the window and
     the bar sits on the artwork rather than on the page above it. The looks
     are cut-outs centred in their frames, so what actually passes beneath
     the bar is the empty head of the frame, not the garment. */
  margin-top: calc(-1 * var(--run-bar));
}

/* --- the bar ------------------------------------------------------
   Over the run the bar is glass with nothing behind it, so the frames
   carry the full height of the screen uninterrupted. Once the run has
   scrolled past, it takes its shelf back and behaves as it does
   everywhere else on the site. The frames are white, so the dark nav
   type holds its contrast the whole way down. */

.run-body .tabs {
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-bottom-color: transparent;
  transition:
    background 320ms var(--ease),
    border-color 320ms var(--ease);
}

/* the filled state is the site's own bar, unchanged — this layout only
   decides when it appears, never what it looks like */
.run-body .tabs.is-filled {
  background: var(--bg-bar);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom-color: var(--border);
}

/* ---------------------------------------------------------------- the stage */

/* Holds the run and everything that has to stay put over it. The run scrolls
   inside this; the stage itself never moves. */
.run__stage {
  position: relative;
}

/* --- thumbnails ---------------------------------------------------
   Down in the corner of the field, small enough to ignore and precise
   enough to aim at. Each is an open box: the border is the whole of the
   control and the frame shows through it, so the strip reads as a set of
   frames over the artwork rather than a bar of chips sitting on it.

   The row sits inside the hud, between the two steps, so it is the hud
   that is placed and the boxes that fall where the row puts them. */

.run__thumbs {
  display: flex;
  align-items: center;
  gap: 0.2rem;
  /* A wrapped second row would carry the next step down with it and break
     the bracket, so a long run scrolls under the arrows instead. */
  min-width: 0;
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
}

.run__thumbs::-webkit-scrollbar {
  display: none;
}

.run__thumb {
  position: relative;
  flex: 0 0 auto;
  width: 1.45rem;
  height: 1.45rem;
  padding: 0;
  border: 1px solid rgba(20, 20, 20, 0.22);
  background: transparent;
  cursor: pointer;
  overflow: hidden;
  opacity: 0.55;
  transition:
    opacity 200ms var(--ease),
    border-color 200ms var(--ease);
}

.run__thumb img,
.run__thumb video {
  width: 100%;
  height: 100%;
  object-fit: contain;
  /* the image inside the box, not the box itself, is what dims */
  display: block;
}

/* a moving thumbnail crops like its slide does */
.run__thumb video {
  object-fit: cover;
}

.run__thumb:hover {
  opacity: 0.9;
  border-color: rgba(20, 20, 20, 0.45);
}

.run__thumb:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
  opacity: 1;
}

.run__thumb[aria-current="true"] {
  opacity: 1;
  border-color: var(--fg);
}

/* ---------------------------------------------------------------- the strip */

.run__strip {
  position: relative;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  /* Contain the horizontal overscroll. Without it a two-finger flick past
     either end of the run hands the gesture to the browser, which reads it
     as a back-navigation and takes the page with it. */
  overscroll-behavior-x: contain;
  outline: none;
}

/* The strip is focusable so the arrow keys have somewhere to land. A ring
   around a full-width scroller would be invisible at both ends and half
   hidden by the nav, so the leading frame wears the focus instead — which
   also names the thing the arrow keys are about to move. */
.run__strip:focus-visible .run__slide.is-lead .run__frame {
  box-shadow:
    inset 0 0 0 1px rgba(20, 20, 20, 0.07),
    0 0 0 2px var(--ring);
}

.run__strip::-webkit-scrollbar {
  display: none;
}

.run__track {
  display: flex;
  align-items: stretch;
  width: max-content;
}

.run__slide {
  flex: 0 0 auto;
  width: var(--run-shot);
  /* start, not center: a half-width frame snapped to the left edge puts
     exactly two full frames on screen and no slivers */
  scroll-snap-align: start;
  position: relative;
}

/* The opener carries a field twice the width of the rest. Nothing animates
   it away — scrolling simply takes the left half off screen, so the field
   retracts to match the others and the frame lands beside the second. */
.run__slide--lead {
  width: var(--run-lead);
  scroll-snap-align: none;
}

/* Two stops inside the opener: one with the field extended and the frame
   alone, one with the field retracted and the second frame alongside. They
   are 1px markers because no element edge falls at the second stop. */
.run__snap {
  position: absolute;
  top: 0;
  width: 1px;
  height: 1px;
  scroll-snap-align: start;
}

.run__frame {
  position: relative;
  margin: 0;
  height: var(--run-h);
  /* Each frame sits on the ground of the shot itself, read off the file at
     load (see readGround in run.js) so the field and the photograph meet
     without a seam. --tint is the step-apart placeholder that holds until
     the file has loaded, and the fallback if a canvas read is ever refused.

     The frame itself takes the left colour flat. Below the bar the image
     box covers the frame edge to edge and carries the real ground (see
     below); what is left to the frame is the strip behind the bar, and on
     the opener the field its image box has been carried off. Both of those
     lie to the left of the subject. */
  --ground-l: var(--tint, #fff);
  --ground-r: var(--ground-l);
  background: var(--ground-l);
  overflow: hidden;
}

/* The image fills the whole frame, the bar included. The bar is glass with
   nothing behind it over the run, so what it sits on is the photograph
   itself rather than a reserved strip above it — no band of white across
   the top of the screen, and a frame is the full height of the window.

   (This used to inset the image by the bar's height to keep the garment
   clear of it. That is what gave the bar its own space.) */
.run__frame img,
.run__frame video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Two stops, not one: a backdrop lit from one side leaves the left and
   right edges of a shot different colours, and the bands it is letterboxed
   into sit against one edge each.

   The split rides the image box rather than the frame, and that is the
   whole point of putting it here. `contain` centres the image in its box,
   so a hard stop at the box's midpoint always falls behind it — and
   because the box is what the opener carries as it scrolls, the two halves
   stay locked to the edges they are matching however far it travels. Keyed
   to the frame instead, the opener's left band ends up painted in the
   right edge's colour once the field retracts. A backdrop that is
   genuinely even samples the same colour twice and reads as flat. */
.run__frame img {
  background: linear-gradient(
    to right,
    var(--ground-l) 0%,
    var(--ground-l) 50%,
    var(--ground-r) 50%,
    var(--ground-r) 100%
  );
}

/* The opener's image is centred in the full field, so as the field's left
   half scrolls away it would drift off with it. Carrying it right by half
   the distance travelled keeps it centred in whatever remains — the image
   settles into place rather than sliding past. Transform only: the field
   itself never relayouts. */
.run__slide--lead .run__frame img {
  transform: translateX(var(--run-pull, 0px));
}

/* A clip is shot in motion, so it fills its frame the way a moving image
   should rather than sitting in a box of white. */
.run__slide--clip .run__frame video {
  object-fit: cover;
}

/* ---------------------------------------------------------------- framing

   Exceptions to `contain`, asked for per look in the catalogue (see the
   note beside CORSET_FRAMING in site.js). They all exist for the same
   reason: a close crop contained in a wide frame floats in the middle of
   the field with its cut edges hanging in mid-air, which reads as a file
   that came out the wrong size rather than as a decision. */

/* Cover. For a crop tight enough that the sides carry nothing — losing a
   little off them costs less than a band of ground either side. */
.run__slide--fill .run__frame img {
  object-fit: cover;
}

/* Cover, but hung from the top rather than centred. For a crop whose cut
   edges are its top and sides: contained, it is held by its height, so the
   top lands on the frame's edge but a band of ground opens either side and
   the side cuts hang in the field. Covering closes the sides; centring it
   would then push the top cut back out of frame, which trades one floating
   edge for another. Anchoring the overflow to the bottom keeps the top and
   both sides on the frame's walls and spends the excess where the crop has
   no cut edge to lose. */
.run__slide--fill-top .run__frame img {
  object-fit: cover;
  object-position: center top;
}

/* Cover, hung a quarter of the way down the overflow rather than from the
   top. For a photograph with a little headroom to spare: the head stays
   whole, but the frame gives up a sliver of sky to show more of the ground
   than a top anchor would. */
.run__slide--fill-high .run__frame img {
  object-fit: cover;
  object-position: center 25%;
}

/* A landscape photograph given the opener's double width, so it is seen
   whole instead of as the middle third of itself. It covers that field —
   at 16:9 a 21:9 frame loses a little off each end, which costs less than
   bands above and below. On a phone every frame is one window wide, so
   there is no double width to take and it is contained instead. */
.run__slide--wide {
  width: var(--run-lead);
}

.run__slide--wide .run__frame img {
  object-fit: cover;
}

@media (max-width: 899px) {
  .run__slide--wide .run__frame img {
    object-fit: contain;
  }
}

/* Contained, but flush right, so the hard cut on that edge lands on the
   frame's edge and the shot reads as running off it. */
.run__slide--bleed-right .run__frame img {
  object-position: right center;
  /* Everything left over is now on one side, so the two-stop ground has
     nothing to match: its seam at the frame's midpoint would fall in open
     field rather than behind the image. The whole band takes the colour
     read off the edge it adjoins. */
  background: var(--ground-l);
}

/* The same move on the other axis: a figure cut off at the hip sits on the
   frame's bottom edge, so the cut reads as the shot continuing past it
   rather than as the file running out. What is left over collects at the
   top as one band across the full width — which is the arrangement the
   two-stop ground already assumes, so unlike the right bleed this one
   leaves it alone. */
.run__slide--bleed-bottom .run__frame img {
  object-position: center bottom;
}

/* The opposite exception to the three above: not a crop that wants more of
   the frame, but a garment shot flat that wants less of it. Contained, a
   flat is scaled to the full height of the window — larger than the figure
   ever appears in the worn frames either side of it, so the piece reads as
   growing when the run reaches it and shrinking when it leaves. Scaling it
   down puts it back in proportion with the body, and the ground it stands
   on carries the space that opens up. The class is named for the kind of
   frame rather than the figure it scales by, so retuning it is one number.

   The pull is carried through rather than dropped: this rule and the lead's
   own translate have the same weight, and a flat is exactly the kind of
   frame a run opens on. Scaling alone here would leave the opener's image
   to slide away with its field. */
.run__slide--flat .run__frame img,
.run__slide--flat-wide .run__frame img {
  transform: translateX(var(--run-pull, 0px)) scale(var(--run-flat));
}

/* One figure does not serve both flats, because the two are not the same
   shape and so do not contain the same way. The tops are portrait, like
   the frame: contained, they fill its height, and two-thirds of that still
   reads as a garment laid out — in the double-width opener, which is where
   a top flat lands, anything less reads as lost in the field. The skirt
   flats are landscape or square, so a tall frame holds them by their width
   and they never reach that height to begin with; scaled by the tops'
   figure they push out past the piece. Half suits them.

   Both figures are set by eye against the worn frames either side, not
   derived from the aspect — the shape is only why they had to be set
   separately. */
.run__slide--flat-wide {
  --run-flat: 0.5;
}

/* No dimming and no scale. Holding the neighbours back is what stops the
   run reading as one continuous surface — every frame is equally present,
   and the only thing that moves is the run itself. .is-lead survives to
   mark the leading frame. */

/* ---------------------------------------------------------------- the hud */

/* The steps sit on the artwork rather than under it, bracketing the
   thumbnails — so the run costs one band along the bottom instead of a
   strip of boxes and a bar of buttons beneath it, and the arrows are
   within reach of the boxes they step through. */
.run__hud {
  position: absolute;
  left: 1.5rem;
  bottom: 1.5rem;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0;
  max-width: calc(100% - 3rem);
}

/* No ring and no fill: over artwork a bordered circle reads as a chip laid
   on the photograph. The glyph alone is the control, and it answers by
   going to full strength. */
.run__step {
  flex: 0 0 auto;
  width: 1.9rem;
  height: 1.9rem;
  display: grid;
  place-items: center;
  border: 0;
  background: transparent;
  color: var(--fg);
  opacity: 0.55;
  cursor: pointer;
  transition:
    opacity 160ms var(--ease),
    color 160ms var(--ease);
}

.run__step svg {
  width: 1.05rem;
  height: 1.05rem;
}

.run__step:hover:not([disabled]) {
  opacity: 1;
}

.run__step[disabled] {
  opacity: 0.2;
  cursor: default;
}

.run__step:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

/* ---------------------------------------------------------------- motion off */

@media (prefers-reduced-motion: reduce) {
  /* Nothing left to reduce inside the run itself — the frames do not move.
     What remains is the travel between them, which arrives without the
     glide, and the bar, which changes without the fade. */
  .run__strip {
    scroll-behavior: auto;
  }

  .run-body .tabs {
    transition: none;
  }
}

/* ---------------------------------------------------------------- small screens */

@media (max-width: 899px) {
  :root {
    /* Two half-width frames on a phone would be thumbnails. One to a screen
       instead, opener included — which leaves the opener no wider than the
       rest, so there is no field to retract and nothing to carry. */
    --run-shot: 100vw;
    --run-lead: 100vw;
  }

  .run__slide--lead {
    scroll-snap-align: start;
  }

  .run__slide--lead .run__frame img {
    transform: none;
  }

  /* Except that a flat opener still wants scaling down — it is the carry
     that has nothing to do here, not the scale. Dropping both left the
     first flat at full height and the second at --run-flat, so the two
     garment frames a run opens on were the one place the treatment was
     visibly not applied. */
  .run__slide--lead.run__slide--flat .run__frame img,
  .run__slide--lead.run__slide--flat-wide .run__frame img {
    transform: scale(var(--run-flat));
  }

  /* the second stop only exists where the opener is double width */
  .run__snap {
    display: none;
  }

  /* Closer in and a touch smaller, so a long run still lands on one line
     between the steps. */
  .run__thumbs {
    gap: 0.15rem;
  }

  .run__thumb {
    width: 1.1rem;
    height: 1.1rem;
    flex: 0 0 auto;
  }

  .run__hud {
    left: 0.9rem;
    bottom: 0.9rem;
    max-width: calc(100% - 1.8rem);
  }

  .run__step {
    width: 1.5rem;
    height: 1.5rem;
    flex: 0 0 auto;
  }
}
