/**
 * @file
 * Landing-page slideshow.
 *
 * Composition follows the outgoing Flexslider band: a wide image, a dark
 * gradient over the left half, a large caption inside it, an outlined button
 * beneath, and paging dots at the bottom left. What changed is how it is built
 * — a scroll-snapped flex row rather than absolutely positioned slides, so it
 * works before (and without) JavaScript.
 */

.slider {
  /*
   * DECLARED HERE, NOT ON `.slide`, AND THAT MATTERS.
   *
   * 832px: high enough that a picture at the 1320px container — 552px at the
   * cinema ratio, 724px at the artwork's own — is never capped, so it always
   * reaches the container's edges. It exists only to stop a portrait image
   * producing an absurd band. A `vh` component was tried and removed: on a
   * short laptop screen it clipped the band by a few pixels, which is exactly
   * the letterboxing this is meant to avoid.
   *
   * It used to live on `.slide`. `.slider__controls` is a SIBLING of the
   * viewport, not a descendant of a slide, so it never inherited the value —
   * and the moment the control row's offset calc started referencing this
   * ceiling, `var()` resolved to nothing, the whole `min()` became invalid,
   * and the browser dropped `inset-block-start` entirely. The row then fell to
   * its static position below the picture. Moving the declaration one level up
   * puts it in scope for both. Do not move it back down.
   */
  --slide-max-height: 52rem;

  /*
   * How far the control row sits in from the picture's bottom and right edges.
   * ONE value for BOTH axes — the offset calc uses it for the bottom and the
   * padding uses it for the right — so the corner pocket is square by
   * construction and there is a single number to change.
   */
  --slider-controls-inset: 1.75rem;

  /* The breathing room under the title and button, ON TOP of the arc's own
     clearance. See `.slide__bar`, which explains why the arc is not part of
     this number. */
  --slide-bar-gap: var(--space-2xs);

  position: relative;
  /* Edge to edge from whatever depth the region lands at, the same technique
     the event hero uses. `body` carries `overflow-x: clip`, so the 100vw box
     cannot create a horizontal scrollbar. */
  inline-size: 100vw;
  max-inline-size: 100vw;
  margin-inline: calc(50% - 50vw);

  /*
   * THE SECTION BELOW DOES NOT NEED A FULL --space-section ON TOP OF THE ARC.
   *
   * The first `.section` inside `<main>` opens with `--space-section`, which
   * is 80px at desktop width. Measured on the landing page, that put 152px
   * between the slide's title and the filter chips: 72 under the title, then
   * 80 more. The arc is already doing the work of separating the two — the
   * band visibly ends — so most of that second gap is just empty canvas.
   *
   * Pulling the page up from HERE rather than reducing the section's padding
   * keeps the change with the thing that caused it. `--space-section` is used
   * by every section on every page and must not move; a rule reaching forward
   * to "whichever section happens to follow" would depend on the front page's
   * region markup, which is not the theme's to rely on.
   *
   * Negative margin, and safe: the space it reclaims is the next section's
   * PADDING, so no content moves into the arc. It is also proportional — half
   * of whatever --space-section is at that width, so 40px at desktop and 20px
   * on a phone, rather than a fixed number that would swallow the whole gap
   * on a small screen.
   */
  margin-block-end: calc(var(--space-section) * -0.5);

  /*
   * A SIZE CONTAINER, so the control row can find the picture's foot.
   *
   * `inline-size` and not `size`: this only publishes the WIDTH as `100cqw`,
   * which is all the row's arithmetic needs. `container-type: size` would also
   * apply block containment, and the slider's height has to come from its
   * contents.
   */
  container-type: inline-size;
  background: var(--surface-1);
  border-block-end: 1px solid var(--border-subtle);
}

.slider__viewport {
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  /* The scrollbar is the fallback affordance, not part of the design. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}

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

/**
 * Chrome makes any scroll container keyboard-focusable and draws a UA focus
 * ring round it — a white rectangle across the whole band, which is what the
 * screenshot showed. Keep the focusability (it is how a keyboard user scrolls
 * the slides) and give the ring the theme's look instead of the browser's.
 */
.slider__viewport:focus {
  outline: none;
}

.slider__viewport:focus-visible {
  outline: var(--focus-ring);
  outline-offset: -4px;
}

/**
 * The track, and WHATEVER VIEWS PUT INSIDE IT.
 *
 * `.slide` is only a direct child of the track when the display's format is
 * "Unformatted list", so that
 * templates/views/views-view-unformatted--slideshow.html.twig is the template
 * that runs. Any other row style — Grid, or a contributed slideshow style left
 * over from Blazy — wraps each row in `<div class="views-row">`, and then the
 * flex item is that wrapper, not the slide.
 *
 * That is what collapsed the band to a bare 90px strip of dots and arrows on
 * the live site: the wrapper is a flex item sized from its content, every one
 * of the slide's children is absolutely positioned, so the wrapper measured 0
 * wide, the slide inside it inherited 0, and `aspect-ratio` faithfully turned
 * 0 width into 0 height.
 *
 * So the sizing is applied to the track's children whatever they are, and the
 * slide fills whichever box it lands in. js/slider.js additionally lifts any
 * wrapped slide up to be a direct child, so the two agree.
 */
.slider__track {
  display: flex;
}

.slider__track > * {
  flex: 0 0 100%;
  inline-size: 100%;
  min-inline-size: 0;
}

/* A wrapper Views inserted between the track and the slide. */
.slider__track > *:not(.slide) {
  display: flex;
}

.slider__track > *:not(.slide) > .slide {
  flex: 1 1 100%;
}

/**
 * The FlexSlider style plugin's own markup, named explicitly.
 *
 * The slideshow display's Format is still **FlexSlider**, which wraps the rows
 * in `<div class="flexslider"><ul class="slides"><li>` and then expects the
 * flexslider module's jQuery plugin to lay them out. This theme loads no
 * jQuery, so nothing lays them out — and that is two levels of wrapper, one
 * more than the generic rule above covers.
 *
 * js/slider.js lifts the slides out of any depth of wrapper, so this only
 * matters before the script runs and with JavaScript off. The real fix is to
 * set the display's Format to **Unformatted list** (see PAGE-SETUP.md); the
 * FlexSlider style contributes nothing now that its plugin is gone.
 */
.slider__track .flexslider,
.slider__track .slides {
  display: flex;
  inline-size: 100%;
  min-inline-size: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}

.slider__track .slides > * {
  flex: 0 0 100%;
  inline-size: 100%;
  min-inline-size: 0;
}

/**
 * A slide.
 *
 * THE PICTURE IS THE CONTAINER'S WIDTH
 * ------------------------------------
 * It lines up exactly with the header's content edges — the same
 * `min(--wrapper-max, 100% - gutter * 2)` box the menu, the filter bar and the
 * card grid use — and the band either side of it is a gradient built from two
 * colours read out of that picture.
 *
 * WHAT KEPT GOING WRONG
 * ---------------------
 * 1. The width cap was on the `<img>`, and Views never hands you a bare image:
 *    it wraps it in `.views-field > .field-content`, or `.field > .field__item`.
 *    Those wrappers are full-width blocks, so a capped image sat against their
 *    LEFT edge — the left of the viewport. The cap is on the panel now, and
 *    every wrapper inside it is flattened to fill it.
 * 2. A `max-block-size` low enough to keep the band shallow made `object-fit:
 *    contain` letterbox the picture, so it no longer reached the container's
 *    edges. The ceiling is now high enough that a 16:9 image at the container's
 *    width is never capped; it exists only to stop a portrait image producing
 *    an absurd band.
 * 3. The two colours were sampled on a canvas in the browser, which fails
 *    silently in half a dozen ways. They come from PHP now.
 */
.slide {
  position: relative;
  flex: 0 0 100%;
  inline-size: 100%;
  min-inline-size: 0;
  scroll-snap-align: start;

  /*
   * PICTURE, THEN WORDS. One structure at every width.
   *
   * This was a grid with the caption absolutely positioned over the picture,
   * and a separate phone layout underneath that stacked them. Two layouts for
   * one component is two things to keep in step, and the overlay was the half
   * that kept breaking: a caption over arbitrary press artwork has no
   * guaranteed contrast, so it needed a plate dense enough to hide the picture
   * it sat on, and at phone width it covered most of the slide.
   *
   * In a column there is no contrast problem to engineer around and no second
   * layout to maintain. The picture is whole and uncovered, the bar under it
   * is the theme's own surface, and the arc at the foot of the slider closes
   * the pair.
   *
   * No `max-block-size` and no minimum: the height is the picture plus the
   * words, and both of those are honest. A ceiling here would clip the pair.
   */
  display: flex;
  flex-direction: column;
  overflow: hidden;

  /**
   * The backdrop.
   *
   * Two colours from the picture (`--slide-c1`, `--slide-c2`, set inline per
   * slide by the Twig), laid out as a diagonal wash with a soft radial bloom of
   * each in opposite corners. That is what gives it depth rather than the flat
   * two-stop ramp a plain linear-gradient produces — the same idea as a phone
   * home screen tinting itself from the wallpaper.
   *
   * The fallbacks are the theme's own surfaces, so a slide whose image could
   * not be read is a plain dark band and not a broken one.
   */
  /*
   * THE COLOURS ARE MIXED INTO THE THEME'S INK BEFORE THEY ARE USED.
   *
   * Taking two colours straight from the photograph gave a backdrop that could
   * be any hue at all, and most event photography is warm: the Winter Feast
   * slide produced a milky brown band down both sides, which read as a dirty
   * margin rather than as part of a dark navy site. The wash was competing with
   * the theme instead of extending the picture into it.
   *
   * 42% of the photo's colour against `--me-ink` keeps the idea — the band
   * still breathes the slide's own palette, and two slides in a row never look
   * the same — while guaranteeing it lands dark and close to the surrounding
   * page whatever the picture happens to be. A warm photo now gives a band that
   * is recognisably warm and still unmistakably this site.
   *
   * It also protects the white controls below: the dots, the pause button and
   * the arrows sit on this backdrop, and against a pale wash from a bright
   * photograph they were losing contrast. Against an ink-anchored mix they
   * cannot.
   */
  --slide-tint-1: color-mix(in oklab, var(--slide-c1, var(--surface-2)) 42%, var(--me-ink));
  --slide-tint-2: color-mix(in oklab, var(--slide-c2, var(--surface-1)) 42%, var(--me-ink));

  /*
   * ONE BLOOM, NOT TWO.
   *
   * There was a second radial-gradient anchored at `8% 0%` — the top-left
   * corner. Against the dark ramp it pooled light along the top edge and down
   * the leading edge of the band, which read as a pale line framing two sides
   * of the picture rather than as depth behind it. The 118deg ramp below
   * already gives the wash its direction, so the corner bloom was adding an
   * edge and not much else.
   *
   * The remaining bloom sits at `92% 100%` — the far corner, diagonally
   * opposite the caption plate, where it lifts the darkest part of the ramp
   * without touching any edge the eye follows.
   */
  background:
    radial-gradient(
      120% 140% at 92% 100%,
      color-mix(in oklab, var(--slide-tint-2) 82%, transparent) 0%,
      transparent 64%
    ),
    linear-gradient(
      118deg,
      var(--slide-tint-1) 0%,
      color-mix(in oklab, var(--slide-tint-1) 50%, var(--slide-tint-2)) 50%,
      var(--slide-tint-2) 100%
    );
}



/**
 * The picture's box: the page container, the same measure as everything else.
 *
 * It ran full-bleed for a moment and that was wrong. The slideshow sits at the
 * top of a page whose every other element — the menu, the filter bar, the card
 * grid, the bar of words directly beneath this picture — lines up on
 * `min(--wrapper-max, 100% - gutter * 2)`. A picture that ignores that line is
 * the one thing on the page not agreeing with it, and the disagreement is most
 * obvious exactly where it matters: against the bar immediately below.
 *
 * The slider itself still runs to the screen edges; what lives inside it is
 * the page's width. That is the same arrangement the event hero uses.
 */
.slide__media {
  position: relative;
  z-index: 0;
  inline-size: min(var(--wrapper-max), 100% - var(--gutter) * 2);
  margin-inline: auto;
  max-block-size: var(--slide-max-height);

  /*
   * THE BOX IS THE RIGHT HEIGHT BEFORE THE PICTURE IS IN IT.
   *
   * Without a ratio the band is only its `min-block-size` until the largest
   * image on the site finishes downloading, and then the whole landing page
   * jumps down by several hundred pixels — which is most of what "the slider
   * loads late" actually looks like.
   *
   * This used to be `var(--slide-ratio, auto)`: the artwork's OWN width/height,
   * printed inline per slide by the Twig. That made every slide its own shape,
   * which is the one thing the control row cannot cope with, since the row has
   * to compute the foot of a picture whose height it cannot read. Now the band
   * is a known shape and the pictures fit themselves to it — see `cover` below.
   *
   * `--slide-ratio` is still printed by the Twig and is now unused for sizing.
   * It is left there deliberately: it costs an attribute, and it is the thing
   * to reach for if slides ever need their own shapes again.
   */
  aspect-ratio: var(--slider-media-ratio);

  /*
   * WITHOUT THIS THE RATIO ABOVE IS ONLY A SUGGESTION.
   *
   * `.slide` is a column flex container, so this panel is a flex item, and a
   * flex item's `min-block-size` defaults to `auto` — it refuses to be shorter
   * than its content. The content is a picture whose intrinsic shape is
   * 1.823:1, so at 1320px wide it insists on 724px and the panel grew straight
   * back out of the 552px the cinema ratio asked for. Measured: the box
   * reported 1.823 at every width while the control row, reading the token,
   * had already moved to where a 2.39:1 picture's foot would be — 200px above
   * the actual one.
   *
   * This is the same `min-*: auto` flex default that stopped cards shrinking
   * below their min-content width elsewhere in the theme.
   */
  min-block-size: 0;

  /*
   * THE BACKSTOP. Nothing may leave this box, ever.
   *
   * The rule above should mean nothing tries to, but a formatter change that
   * puts an unexpected element in the chain can break the height cascade
   * again — and the failure mode without this line is the picture painting
   * over the title and the button, which is a broken page rather than a
   * cropped one. With it, the worst case is artwork clipped at the band's
   * edge, which looks intentional.
   */
  overflow: hidden;
}

/*
 * 16:9 BELOW LAPTOP WIDTH. THE SIDES OF THE ARTWORK ARE GIVEN UP, NOT KEPT.
 *
 * 2.39:1 is a shape for a wide screen. Carried down to a phone it becomes a
 * letterbox strip — 149px tall at 390px wide — with the title and the button
 * underneath it taking more height than the picture does. 16:9 at the same
 * width is 200px, and reads as a picture rather than a banner.
 *
 * It costs the sides. Artwork authored at 2.39:1 and fitted into a 16:9 box
 * with `cover` keeps 74.4% of its WIDTH: 12.8% comes off the left and 12.8%
 * off the right. So the composition has to hold together in its middle three
 * quarters horizontally — the same rule as the vertical one, on the other
 * axis. Centred artwork survives; a logo in a corner does not.
 *
 * 64rem is the theme's existing layout breakpoint, the one the nav collapses
 * at, rather than a number invented here. Crossing it steps the band between
 * 401px and 539px, which is only visible while resizing a desktop window.
 *
 * The control row follows automatically: it reads the same
 * `--slider-media-ratio` to find the picture's foot.
 */
@media (max-width: 63.999rem) {
  .slider {
    --slider-media-ratio: 16 / 9;
  }
}

/**
 * Flatten whatever Views wrapped the image in.
 *
 * Naming the wrappers (`.field`, `.field__item`, `.views-field`,
 * `.field-content`, `.blazy`, `picture`) is a list that is wrong the next time
 * a formatter changes. Every element in here is part of the chain down to the
 * one image, so every element in here fills the panel.
 */
.slide__media * {
  display: block;
  inline-size: 100%;
  /*
   * THE HEIGHT HAS TO BE CARRIED DOWN THE CHAIN, NOT JUST THE WIDTH.
   *
   * This rule set `inline-size: 100%` and nothing else, which was enough for
   * as long as the panel took its shape FROM the artwork — the wrappers could
   * be auto-height because the natural height was the right height.
   *
   * The moment the panel became a fixed 2.39:1, that stopped being true. The
   * `<img>` asks for `block-size: 100%`, but 100% of its own parent — and its
   * parent is `.field__item`, with no height of its own, so the percentage had
   * nothing to resolve against and fell back to the image's natural height.
   * The picture rendered 620px tall inside a 552px panel and the extra 67px
   * painted straight over the title and the button underneath it. That is the
   * overflow in the reported screenshot, and it only appears on the real site:
   * a test fixture with the <img> as a direct child of the panel resolves its
   * 100% correctly and shows nothing wrong.
   *
   * Each link in the chain now has a definite height because the one above it
   * does, all the way down from the panel's aspect-ratio to the image, which
   * is what lets `object-fit` do the fitting instead of the box giving way.
   */
  block-size: 100%;
  margin: 0;
}

.slide__media img {
  /*
   * Fills the box its parent has already reserved.
   *
   * With a `--slide-ratio` in play the parent's height is known before the
   * image exists, and `100%` makes the picture exactly fill it — no second
   * layout pass when it decodes. With no ratio the parent's height is `auto`,
   * a percentage height has nothing to resolve against, and this computes to
   * `auto`: the behaviour this line replaced.
   */
  block-size: 100%;
  max-block-size: var(--slide-max-height);
  /*
   * COVER, NOT CONTAIN — THE BAND IS A FIXED SHAPE NOW.
   *
   * `contain` fits the whole picture inside the box. In a 2.39:1 box a 1.823:1
   * picture is limited by height, so it would come out narrower than the band
   * with empty bars down both sides. `cover` fills the band and takes the
   * excess off the top and bottom instead, which is the trade the cinema ratio
   * is asking for.
   *
   * The cost that comes with it: a portrait slide is now cropped hard rather
   * than letterboxed. That was already a content problem in a landscape band;
   * it is a more visible one here.
   */
  object-fit: cover;
  object-position: center;
}

/* No zoom on the artwork. It was keyed to the slide for the same reason the
   plate was, and moving it to the plate would mean hovering a caption nudged a
   picture two feet away — a connection nobody would make. */

/**
 * The scrim.
 *
 * Deliberately lighter than it was, and confined to the left third.
 *
 * Council's slide artwork has its own headline and button drawn into the
 * picture. A heavy 82%-black wash across the left half of it — which is what
 * this used to be — muddied artwork that was already designed to be read. It
 * now does only the job it has to do: keep the caption and the dots legible
 * over whatever happens to be behind them.
 */


/**
 * THE CAPTION PANEL
 * =================
 *
 * CONTRAST (WCAG 1.4.3)
 * ---------------------
 * The text sits on a plate, not on the photograph. Every number below was
 * measured off a rendered slide with a PURE WHITE picture behind it — the
 * hardest backdrop there is — sampling the lightest pixel the plate reaches:
 *
 *   white caption on the plate ............. 10.9:1   (needs 4.5)
 *   eyebrow #ff8fd0 on the plate ............ 5.2:1   (needs 4.5)
 *   white label on the resting chip ........ 10.2:1   (needs 4.5)
 *   white label on the hover fill ........... 4.8:1   (needs 4.5)
 *   chip border against the chip ............ 6.7:1   (needs 3.0, 1.4.11)
 *
 * The eyebrow is #ff8fd0 and not the theme's usual --me-magenta-300: that
 * colour is tuned for the site's dark panels and reaches only 4.3:1 here, which
 * is a fail for text this size. The hover fill is --accent-solid, the same
 * measured fill every button on the site uses, and not raw --me-magenta, which
 * gives white text 4.25:1 — the state the old chip used.
 *
 * TARGET SIZE (WCAG 2.5.8)
 * ------------------------
 * The chip is at least 44px tall, well past the 24px minimum, and it is the
 * only interactive thing in the panel.
 *
 * FOCUS (WCAG 2.4.7, 2.4.11)
 * --------------------------
 * A white ring with a black halo. A single-colour ring cannot be guaranteed
 * against a photograph; a light ring wrapped in a dark one is visible on both,
 * and the pair is never thinner than 2px.
 */

/**
 * The positioning box. Full size, so the panel can be placed against the
 * slide's edges; it passes clicks through, because only the chip inside it is
 * interactive and the rest of the slide is a swipe target.
 */
/**
 * THE CAPTION'S BOX IS THE PICTURE'S BOX.
 *
 * It used to be the whole slide (`inset: 0`) with a padding of
 * `max(gutter, (100vw - 1320px) / 2 + gutter)` — an expression that recreates
 * the page container from the VIEWPORT. That is one width formula for the
 * picture and a different one for the caption over it, and the two disagree by
 * however wide the scrollbar is: `100vw` includes it and the picture's `100%`
 * does not. On a phone that showed as the plate and its accent line running a
 * few pixels past the photograph's right edge — and only its right edge, which
 * is exactly what a scrollbar-width error looks like.
 *
 * The box is now stated the same way the picture is: the same `min(wrapper,
 * 100% - gutter * 2)`, centred by `margin-inline: auto` against `inset-inline:
 * 0`. There is no viewport unit left in it, so the two cannot drift apart.
 *
 * The caption sits at the BOTTOM of that box with a small gap, at the client's
 * request. The gap has to clear the dots and the pause control, which live
 * along the bottom edge — hence the `calc`: 16px of breathing room plus the
 * 40px the controls occupy.
 */


/* ---------------------------------------------------------------------------
 * THE CAPTION PLATE AND EVERYTHING ON IT HAS GONE.
 *
 * The slide used to carry a plate lying over the picture, with a scrim behind
 * it, corner brackets, an inner ring, an accent rail and a scan line. All of
 * it existed to make white text readable on an arbitrary photograph and to
 * make the result look built rather than captioned.
 *
 * The words are under the picture now, on the theme's own surface, so there
 * is no contrast to engineer and nothing for the decoration to frame. The
 * rules went with the markup — see views-view-fields--slideshow.html.twig.
 *
 * What survives is the part that was always about the words rather than the
 * box: the eyebrow and its dash, the caption, and the button.
 * ------------------------------------------------------------------------ */

/* ---------------------------------------------------------------------------
 * THE BAR: words left, action right.
 *
 * It keeps the page container's measure while the picture above runs full
 * width, so the title lines up with the heading of the section below it and
 * with the menu above — the picture is the thing that breaks out, not the
 * words.
 *
 * `flex-wrap` with a generous basis on the text is what makes this one rule
 * rather than two: side by side while there is room for both, and the button
 * dropping under the title when there is not. No breakpoint decides that; the
 * content does.
 *
 * The bottom padding carries the band's arc. The curve is cut out of the
 * slider's own box, so without it the button would sit inside the curve and
 * clip at the edges.
 * ------------------------------------------------------------------------ */
.slide__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-s) var(--space-l);
  inline-size: min(var(--wrapper-max), 100% - var(--gutter) * 2);
  margin-inline: auto;
  /*
   * THE BOTTOM IS THE ARC PLUS ONE SMALL GAP, AND THE ARC IS NOT OPTIONAL.
   *
   * The band's foot is an outward curve, cut with `border-radius: 50%
   * var(--band-arc)` on the bottom corners. That means the box's lower
   * boundary is at its full height in the MIDDLE and curves up by the whole
   * arc depth at the left and right EDGES — which is exactly where the title
   * and the button sit. So `var(--band-arc)` is the clearance that stops them
   * being clipped by the curve; it cannot be reduced without cutting into
   * them. Only the part added on top of it is a gap anyone chose.
   *
   * That part was `--space-m`, making 72px below the text at desktop width.
   * It is now `--slide-bar-gap`, which is the one number to change here:
   *
   *   --space-m   1.5rem  ->  24 + 48 = 72px   (was)
   *   --space-2xs 0.5rem  ->   8 + 48 = 56px   (now)
   *   0                   ->   0 + 48 = 48px   (the floor; descenders touch)
   */
  padding-block: var(--space-m) calc(var(--slide-bar-gap) + var(--band-arc));
}

.slide__text {
  /* A flex item defaults to min-width:auto and will not shrink below its
     longest word, which on a long title pushes the button off the row. */
  min-inline-size: 0;
  flex: 1 1 20rem;
}

.slide__bar .slide__cta {
  flex: none;
}

.slide__eyebrow {
  display: flex;
  align-items: center;
  gap: var(--space-3xs);
  margin: 0;

  /* Not --me-magenta-300: see the contrast note above. */
  color: #ff8fd0;
  font-family: var(--font-mono, ui-monospace, "SFMono-Regular", Menlo, monospace);
  font-size: var(--step--2);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
}

.slide__eyebrow-mark {
  inline-size: 1.25rem;
  block-size: 1px;
  flex: none;
  background: currentColor;
}

.slide__caption {
  margin: 0;
  color: var(--me-white);
  font-size: var(--step-4);
  font-weight: var(--weight-bold);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
  text-wrap: balance;
}

/**
 * THE GAP THE CLIENT ASKED ABOUT.
 *
 * It was 16px: `--space-2xs` from the column's own gap plus another
 * `--space-2xs` of margin on the chip. The chip is the caption's own call to
 * action, so it should read as part of that block rather than as the next thing
 * down the page — the pair is tied together at 8px, while the eyebrow keeps the
 * looser gap above.
 */
.slide__caption + .slide__cta {
  margin-block-start: calc(var(--space-2xs) - var(--space-3xs));
}

/**
 * The chip.
 *
 * `isolation` and the `overflow: hidden` are what let the sweep run underneath
 * the label without escaping the rounded edge.
 */
.slide__cta {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);

  /* 44px minimum, comfortably past the 24px WCAG 2.5.8 target. */
  min-block-size: 2.75rem;
  padding: 0.55rem 1.25rem;

  border: 1px solid color-mix(in srgb, var(--me-white) 70%, transparent);
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--me-black) 35%, transparent);
  color: var(--me-white);
  font-size: var(--step--1);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  text-decoration: none;
  transition:
    background-color var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out);
}

.slide__cta-label,
.slide__cta-arrow {
  position: relative;
  z-index: 1;
}

.slide__cta-arrow {
  display: inline-flex;
  transition: transform var(--duration-fast) var(--ease-out);
}

.slide__cta-arrow svg {
  inline-size: 1.05rem;
  block-size: 1.05rem;
}

/* The band of light that crosses the chip once on hover — the same motion the
   hub cards use, at the same speed. */
/*
 * Reduced motion: the picture stops drifting and the button's sweep stops
 * crossing it. (This rule was previously shared with the caption plate, which
 * no longer exists.)
 */
@media (prefers-reduced-motion: reduce) {
  .slide__media img,
  .slide__cta-sweep {
    transition: none;
  }

  .slide__cta:hover .slide__cta-sweep {
    transform: none;
  }
}

.slide__cta-sweep {
  position: absolute;
  inset-block: 0;
  inset-inline-start: -60%;
  inline-size: 45%;
  z-index: 0;
  pointer-events: none;
  transform: skewX(-18deg);
  background: linear-gradient(
    to right,
    transparent 0%,
    color-mix(in srgb, var(--me-white) 26%, transparent) 50%,
    transparent 100%
  );
}

.slide__cta:hover,
.slide__cta:focus-visible {
  /* --accent-solid, not --me-magenta: the measured fill for white text. */
  background: var(--accent-solid);
  border-color: var(--accent-solid);
  color: var(--me-white);
}

.slide__cta:hover .slide__cta-arrow {
  transform: translateX(0.2rem);
}

.slide__cta:hover .slide__cta-sweep {
  animation: slide-cta-sweep var(--duration-slow, 700ms) var(--ease-out) 1;
}

@keyframes slide-cta-sweep {
  from { transform: translateX(0) skewX(-18deg); }
  to   { transform: translateX(420%) skewX(-18deg); }
}

/**
 * Focus: a white ring inside a black one, so it is visible whatever the
 * photograph behind it is doing.
 */
.slide__cta:focus-visible {
  outline: 3px solid var(--me-white);
  outline-offset: 2px;
  box-shadow: 0 0 0 7px rgb(0 0 0 / 85%);
}

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

  .slide__cta:hover .slide__cta-sweep {
    animation: none;
  }
}

/**
 * Narrow screens.
 *
 * The plate takes the full width and sits at the foot of the picture, where it
 * covers the least of it. The corner brackets stay — they cost nothing — but
 * the panel loses its inset so its edges align with the slide's.
 */
/* ---------------------------------------------------------------------------
 * PHONE: ONE SIZE DOWN, AND THAT IS ALL.
 *
 * Everything structural about the phone layout has gone, because it IS the
 * layout now — picture, then a bar of words. What is left is type: at
 * --step-2 a two-line title plus a 44px button is most of a phone's first
 * screen, and the slide should not be the whole of it.
 * ------------------------------------------------------------------------ */
@media (max-width: 47.999rem) {
  .slide__caption {
    font-size: var(--step-1);
  }
}

/* Deliberately NOT a stretched link: an `::after { inset: 0 }` on the button
   would resolve against `.slide` and cover the whole panel, swallowing the
   swipe gesture and every click on the caption. The button is the target. */

/* ---------------------------------------------------------------------------
 * Controls — added by js/slider.js, so nothing here renders before it runs.
 * ------------------------------------------------------------------------ */
.slider__arrow {
  display: none;
  place-items: center;
  inline-size: 2.5rem;
  block-size: 2.5rem;
  padding: 0;
  border: 1px solid color-mix(in srgb, var(--me-white) 40%, transparent);
  border-radius: 50%;
  /* No backdrop-filter. A 6px blur on a 40px circle over a slideshow that
     changes underneath it is a per-frame re-read for an effect nobody can see
     at that size, and it is one of the surfaces that made the top of the page
     flicker. A denser fill reads identically. See the note on .site-header. */
  background: color-mix(in srgb, var(--me-black) 72%, transparent);
  color: var(--me-white);
  cursor: pointer;
  transition:
    background-color var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out);
}

.slider--ready .slider__arrow {
  display: grid;
}

.slider__arrow:hover {
  background: var(--me-magenta);
  border-color: var(--me-magenta);
}

.slider__arrow svg {
  inline-size: 1.15rem;
  block-size: 1.15rem;
}

.slider__nav {
  display: flex;
  gap: var(--space-3xs);
  /* No `margin-inline-start: auto`. It pushed the arrows to the far right when
     the row filled the width; the row is right-aligned as a group now, and the
     auto margin would tear the arrows away from the dots. */
}

/*
 * On a phone the swipe is the control; two 40px circles over the picture are
 * not worth the space they take.
 *
 * HIDE THE WRAPPER, NOT JUST THE TWO BUTTONS. This used to be
 * `.slider--ready .slider__arrow { display: none }`, which emptied
 * `.slider__nav` but left it in the row as a zero-width flex item — and a
 * zero-width flex item still takes a `gap` before it. So the pause button
 * ended up 44px from the picture's right edge instead of the 28px the inset
 * asks for, while the bottom gap was correct at 28. Measured on a 390px
 * viewport: pause right edge 328.6, picture right edge 372.6, with the empty
 * nav sitting at 344.6 in between.
 *
 * Hiding the container removes the item and the gap with it. The arrows are
 * already `display: none` until `.slider--ready` is set, so nothing here
 * needs that qualifier.
 */
@media (max-width: 47.999rem) {
  .slider__nav {
    display: none;
  }
}

/**
 * The control row — inside the picture, at its bottom right.
 *
 * WHY IT IS POSITIONED THE WAY IT IS
 *
 * The row belongs to the SLIDER (one set of dots for all the slides), while
 * the picture belongs to a SLIDE. So it cannot simply be a child of the thing
 * it has to sit inside — put it in a slide and it would scroll away with that
 * slide. It is positioned against the slider instead, and the distance down
 * from the top has to be worked out.
 *
 * `100cqw` is the slider's own width — hence `container-type: inline-size` on
 * `.slider` — and dividing it by the artwork's aspect ratio gives the picture's
 * height. The row is then lifted back up by its own height plus a margin.
 *
 * ANCHORED BY ITS BOTTOM, NOT BY ITS TOP. THIS IS THE FIX FOR THE PHONE.
 *
 * The offset used to be `pictureHeight - 3.25rem - space-s`: the picture's
 * foot, less a HARD-CODED 3.25rem for the row's own height. That number is
 * only right when the row contains arrows. Below 48rem the arrows are hidden
 * — see the rule above — the row shrinks to the height of the dots and the
 * pause button, and the 3.25rem reservation stopped matching it. The row was
 * then left stranded well short of the picture's foot: on a phone it floated
 * in the middle of the artwork instead of sitting in the corner.
 *
 * It is now placed by its BOTTOM edge. `inset-block-start` is the picture's
 * foot less the inset, and `translateY(-100%)` pulls the row up by its own
 * height — WHATEVER that height turns out to be. Nothing has to know in
 * advance how tall the row is, so hiding the arrows, adding a control or
 * changing a button's size cannot move it again.
 *
 * `--slider-media-ratio` is the one property both this and `.slide__media`
 * read, so the foot this computes is the real foot at every width. The outer
 * `min()` against `--slide-max-height` mirrors the ceiling on the panel: that
 * ceiling does not bite at any current width, but if it ever does the panel
 * stops following the ratio and this row has to stop with it.
 */
.slider__controls {
  position: absolute;
  /*
   * The picture's foot, less the inset — then up by the row's own height.
   *
   * The picture is as wide as the page container, which is
   * `min(--wrapper-max, 100cqw - gutter * 2)` — the same expression the panel
   * itself uses, repeated here because a sibling's used height is not
   * something CSS can ask for. Dividing it by the ratio gives the height.
   *
   * `100cqw` is the slider's width, published by `container-type: inline-size`
   * on `.slider`. If that expression and the one on `.slide__media` ever
   * diverge, the dots stop sitting on the picture's foot — they are a pair.
   */
  inset-block-start: calc(
    min(
      min(var(--wrapper-max), 100cqw - var(--gutter) * 2) / (var(--slider-media-ratio)),
      var(--slide-max-height)
    )
    - var(--slider-controls-inset)
  );
  transform: translateY(-100%);
  inset-inline: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  /* Bottom RIGHT of the picture. */
  justify-content: flex-end;
  gap: var(--space-s);
  inline-size: min(var(--wrapper-max), 100% - var(--gutter) * 2);
  margin-inline: auto;
  /*
   * THE SAME INSET ON BOTH AXES, FROM ONE VALUE.
   *
   * The row ends exactly where the picture does, so this is what holds the
   * controls off its right edge — and it is the same `--slider-controls-inset`
   * the offset above uses for the bottom, so the two cannot drift apart. The
   * control sits in a square corner pocket at every width.
   *
   * It used to be `calc(var(--space-s) + 3.25rem - 2.5rem)`, reverse-engineered
   * to match a bottom gap that was itself an accident of the old arithmetic.
   * Both edges are now deliberate and both are one number.
   *
   * `box-sizing: border-box` from the reset means the padding eats into the
   * row's width rather than widening it, so the row still ends where the
   * picture does and only the content moves inward.
   */
  padding-inline-end: var(--slider-controls-inset);
}

/* Hidden only while js/slider.js has not finished. */
.slider:not(.slider--ready) .slider__controls {
  opacity: 0;
}


.slider__dots {
  display: flex;
  gap: var(--space-3xs);
}

.slider__dot {
  inline-size: 0.75rem;
  block-size: 0.75rem;
  padding: 0;
  border: 1px solid var(--me-white);
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
  transition:
    background-color var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out),
    inline-size var(--duration-base) var(--ease-out);
}

.slider__dot.is-current {
  inline-size: 1.75rem;
  border-radius: var(--radius-pill);
  background: var(--me-magenta);
  border-color: var(--me-magenta);
}

.slider__pause {
  display: grid;
  place-items: center;
  inline-size: 1.85rem;
  block-size: 1.85rem;
  padding: 0;
  border: 1px solid color-mix(in srgb, var(--me-white) 40%, transparent);
  border-radius: 50%;
  background: color-mix(in srgb, var(--me-black) 45%, transparent);
  color: var(--me-white);
  cursor: pointer;
}

.slider__pause svg {
  inline-size: 0.8rem;
  block-size: 0.8rem;
}

.slider__pause:hover {
  border-color: var(--me-magenta);
  color: var(--me-magenta-300);
}

@media (prefers-reduced-motion: reduce) {
  .slider__viewport {
    scroll-behavior: auto;
  }
}
