/* ==========================================================================
   Fan carousel 1.0 — a fanned card deck, driven by native radio inputs
   --------------------------------------------------------------------------
   Plain CSS. No framework, no build step, no external request.

   The deck lays its cards out on an arc: each card is pushed sideways,
   dropped a little, tilted, and scaled down in proportion to how far it sits
   from the selected one. All of that is arithmetic on a single number — the
   signed distance between a card's index and the selected index — so the
   whole layout is expressed in CSS, without a line of script.

   The selected index comes from a group of native radio inputs. `:has()`
   reads which one is checked and publishes it as --arc-active; every card
   then computes its own place from it. That is why the component works with
   JavaScript switched off, and why the keyboard already does the right
   thing: arrow keys move between radios of the same group, natively.

   The script is an enhancement and nothing more. It adds pointer dragging,
   which has no CSS equivalent. Remove it and nothing breaks.

   PROPORTIONS
   Every metric derives from --arc-font-size, so that one variable rescales
   the component whole — the arc and the shadows included, not just the text.

   The module is 16 px. The component never prints text at that size, but its
   whole grid is built on it: the five type sizes and all forty-odd measures
   of the reference are exact multiples of 16, which is what lets the ratios
   below be finite. Measured on the published original (see README.md,
   "Provenance"), reference / ratio:

     card                 400 x 500 px    25 x 31.25   (ratio 1.25)
     fan spread           180 px          11.25        (step between cards)
     vertical step         60 px           3.75        (drop per rank)
     tilt                   8 deg                      (per rank)
     scale                 1.1 selected, 0.85 - 0.05 per rank, floor 0.4
     card radius           40 px           2.5
     block padding         80 px           5
     intro width         1000 px          62.5
     intro to deck        240 px          15          (bottom margin)
     intro shift           24 px           1.5        (pushed down)
     deck shift          -166 px         -10.375      (pulled up)
     eyebrow               14 px           0.875
     headline              48 px           3
     caption padding       24 px           1.5
     card title            24 px           1.5
     card subtitle         14 px           0.875
     tag                   11 px           0.6875     (8 / 16 padding)
     dot                    8 px           0.5        (24 when selected)

   Order of this file:
     1. Face declarations
     2. Design tokens
     3. Shell, intro
     4. Stage and the arc arithmetic
     5. Card: frame, image, caption, tag
     6. Hit area
     7. Dots
     8. Dragging (classes the script sets)
     9. Probe (how the script reads CSS numbers)
    10. Variants
    11. Narrow containers
    12. Fallbacks: reduced motion, forced colours, no backdrop-filter

   Naming follows BEM: .fan-carousel, .fan-carousel__part, .fan-carousel--variant.
   ========================================================================== */

/* ==========================================================================
   1. Face declarations
   --------------------------------------------------------------------------
   Inter, three weights, latin subset only — which covers French too: the
   subset carries U+0000-00FF plus Œ, œ and €.
   ========================================================================== */

@font-face {
  font-family: "Fan carousel Inter";
  src: url("fonts/Inter-500-latin.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Fan carousel Inter";
  src: url("fonts/Inter-600-latin.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Fan carousel Inter";
  src: url("fonts/Inter-700-latin.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* ==========================================================================
   2. Design tokens
   --------------------------------------------------------------------------
   Everything that is a colour, a measure or a duration lives here — animation
   delays included, so they can be retuned without opening the script.

   Measures are written `calc(<ratio> * var(--arc-font-size))`, never in `em`.
   A custom property holding an `em` value is resolved by whoever reads it: a
   ratio meant against 16 px would be five times too large when read from the
   48 px headline. Absolute values do not have that problem.
   ========================================================================== */

.fan-carousel {
  /* The component declares its own box model and assumes no reset. */
  box-sizing: border-box;

  /* --- the one variable that rescales everything ----------------------- */
  --arc-font-size: 16px;

  /* --- the arc --------------------------------------------------------- */
  --arc-card-width: calc(25 * var(--arc-font-size));
  --arc-card-height: calc(31.25 * var(--arc-font-size));

  /* The original ratio. It only comes into play where the width is
     constrained by the container (§11): the height then follows the width, as
     on the reference. A custom property is resolved where it is declared, so a
     height derived here would be frozen at the width of here — hence the
     redeclaration in each breakpoint rather than one single formula. */
  --arc-card-ratio: 1.25;
  --arc-spread: calc(11.25 * var(--arc-font-size));
  --arc-step: calc(3.75 * var(--arc-font-size));
  --arc-tilt: 8deg;
  --arc-scale-selected: 1.1;
  --arc-scale-idle: 0.85;
  --arc-scale-decay: 0.05;
  --arc-scale-floor: 0.4;
  --arc-hover-lift: calc(-0.625 * var(--arc-font-size)); /* -10 px */
  --arc-hover-grow: 0.05;

  /* How many cards the deck holds. Only feeds the stacking order, so an
     approximate value still stacks correctly — but keeping it exact makes
     the z-index of each card readable. The script refreshes it. */
  --arc-count: 6;

  /* --- shell ----------------------------------------------------------- */
  --arc-padding-block: calc(5 * var(--arc-font-size));
  --arc-intro-width: calc(62.5 * var(--arc-font-size));
  --arc-intro-padding: calc(1.25 * var(--arc-font-size));
  --arc-intro-gap: calc(15 * var(--arc-font-size));
  --arc-intro-shift: calc(1.5 * var(--arc-font-size));
  --arc-stage-shift: calc(-10.375 * var(--arc-font-size));
  --arc-stage-ratio: 1.2; /* stage height, as a share of a card */

  /* --- type ------------------------------------------------------------ */
  --arc-family: "Fan carousel Inter", ui-sans-serif, system-ui, -apple-system,
    "Segoe UI", roboto, "Helvetica Neue", arial, sans-serif;
  --arc-eyebrow-size: calc(0.875 * var(--arc-font-size));
  --arc-eyebrow-gap: calc(1 * var(--arc-font-size));
  --arc-eyebrow-tracking: 0.1em;
  --arc-headline-size: calc(3 * var(--arc-font-size));
  --arc-headline-tracking: -0.03em;
  --arc-title-size: calc(1.5 * var(--arc-font-size));
  --arc-title-tracking: -0.02em;
  --arc-subtitle-size: calc(0.875 * var(--arc-font-size));
  --arc-subtitle-tracking: -0.01em;
  --arc-subtitle-gap: calc(0.25 * var(--arc-font-size));
  --arc-tag-size: calc(0.6875 * var(--arc-font-size));
  --arc-tag-tracking: 0.05em;

  /* --- card chrome ----------------------------------------------------- */
  --arc-radius: calc(2.5 * var(--arc-font-size));
  --arc-caption-padding: calc(1.5 * var(--arc-font-size));
  --arc-caption-gap: calc(0.75 * var(--arc-font-size));
  --arc-tag-padding-block: calc(0.5 * var(--arc-font-size));
  --arc-tag-padding-inline: calc(1 * var(--arc-font-size));
  --arc-tag-blur: 12px;
  --arc-tag-saturation: 180%;

  /* Shadow geometry. The four numbers are read off the reference: an idle
     card sits on 10/30, the selected one on 20/40, and hovering adds 5/10
     to whichever it is. Composed rather than branched — see §5. */
  --arc-shadow-y: calc(0.625 * var(--arc-font-size));
  --arc-shadow-y-selected: calc(0.625 * var(--arc-font-size));
  --arc-shadow-y-hover: calc(0.3125 * var(--arc-font-size));
  --arc-shadow-blur: calc(1.875 * var(--arc-font-size));
  --arc-shadow-blur-selected: calc(0.625 * var(--arc-font-size));
  --arc-shadow-blur-hover: calc(0.625 * var(--arc-font-size));

  /* --- dots ------------------------------------------------------------ */
  --arc-dot-height: calc(0.5 * var(--arc-font-size));
  --arc-dot-width: calc(0.5 * var(--arc-font-size));
  --arc-dot-width-selected: calc(1.5 * var(--arc-font-size));
  --arc-dot-gap: calc(0.5 * var(--arc-font-size));
  --arc-dot-radius: calc(0.25 * var(--arc-font-size));
  --arc-dots-offset: calc(2.5 * var(--arc-font-size));
  --arc-dot-hit: calc(1.375 * var(--arc-font-size)); /* invisible target */

  /* --- colours --------------------------------------------------------- */
  --arc-bg: rgb(0 0 0);
  --arc-card-bg: rgb(26 26 26);
  --arc-shadow-color: rgb(0 0 0 / 0.5);
  --arc-eyebrow-color: rgb(227 231 143);
  --arc-headline-color: rgb(255 255 255);
  --arc-title-color: rgb(255 255 255);
  --arc-subtitle-color: rgb(255 255 255 / 0.7);
  --arc-veil: rgb(0 0 0 / 0.4);
  --arc-tag-color: rgb(255 255 255);
  --arc-tag-bg: rgb(255 255 255 / 0.15);
  --arc-tag-border: rgb(255 255 255 / 0.18);
  --arc-tag-shadow: rgb(0 0 0 / 0.15);
  --arc-dot-color: 255 255 255;
  --arc-dot-idle-alpha: 0.4;
  --arc-focus-color: rgb(227 231 143);
  --arc-focus-width: 2px;
  --arc-focus-offset: 4px;

  /* --- motion ----------------------------------------------------------
     The reference animates its cards with a Motion spring (mass 1,
     stiffness 260, damping 20), which CSS cannot express as a cubic-bezier:
     the curve overshoots by 8.3 %. `linear()` can, by sampling the spring's
     step response. 52 points over 0.65 s land within 0.06 % of rest — see
     _mesures/ressorts.txt for the derivation.

     The image zoom under the pointer uses a stiffer spring (300 / 25), and
     the elastic snap-back after a drag a damped one (300 / 30). Same method. */
  --arc-move-duration: 0.65s;
  --arc-move-delay: 0s;
  --arc-move-easing: linear(
    0, 0.0187, 0.0683, 0.1404, 0.2273, 0.3227, 0.4213, 0.5187, 0.6119, 0.6983,
    0.7765, 0.8453, 0.9045, 0.954, 0.9942, 1.0257, 1.0493, 1.066, 1.0766,
    1.0821, 1.0834, 1.0815, 1.077, 1.0708, 1.0635, 1.0554, 1.0472, 1.0391,
    1.0314, 1.0243, 1.0179, 1.0122, 1.0074, 1.0034, 1.0001, 0.9976, 0.9957,
    0.9944, 0.9935, 0.9931, 0.993, 0.9932, 0.9936, 0.9942, 0.9948, 0.9955,
    0.9962, 0.9968, 0.9975, 0.998, 0.9986, 0.999, 1
  );
  --arc-zoom-duration: 0.65s;
  --arc-zoom-easing: linear(
    0, 0.0345, 0.12, 0.2338, 0.3592, 0.4841, 0.6008, 0.7043, 0.7923, 0.8644,
    0.9212, 0.9642, 0.9951, 1.016, 1.0289, 1.0356, 1.0378, 1.0368, 1.0337,
    1.0295, 1.0248, 1.0201, 1.0156, 1.0116, 1.0082, 1.0054, 1.0032, 1.0015,
    1.0003, 0.9995, 0.999, 0.9987, 0.9986, 0.9986, 0.9987, 0.9989, 0.999,
    0.9992, 0.9994, 0.9995, 1
  );
  --arc-return-duration: 0.3s;
  --arc-return-easing: linear(
    0, 0.0207, 0.073, 0.1451, 0.2279, 0.3149, 0.4014, 0.4841, 0.561, 0.631,
    0.6935, 0.7483, 0.7959, 0.8365, 0.8707, 0.8993, 0.9229, 0.9421, 0.9575,
    0.9698, 0.9794, 0.9868, 0.9924, 0.9966, 1
  );
  --arc-dot-duration: 0.3s;
  --arc-dot-easing: ease;

  /* --- dragging (read by the script through the probe, §9) -------------- */
  --arc-drag-threshold: calc(3.125 * var(--arc-font-size)); /* 50 px */
  --arc-drag-elastic: 0.2;

  /* ------------------------------------------------------------------- */

  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  inline-size: 100%;
  block-size: 100%;

  /* The block padding is a margin on the two children in flow, not padding
     here. An element is never its own query container, so anything that has
     to answer to the breakpoints of §11 must live one level down — and the
     padding does. Nothing moves: there is no margin collapsing inside a flex
     container, and an absolutely positioned dot bar is laid out against the
     padding box either way. */

  /* The arc is wider than the frame by design: cards leave on both sides.
     Clipping here stops the overflow from reaching the page — and, because
     the stage clips too (§4), it stops it from reaching this element as
     well, which is what `scrollWidth - clientWidth` measures. */
  overflow: clip;

  /* Cards are stacked with z-index; isolating keeps that private. */
  isolation: isolate;

  background-color: var(--arc-bg);
  font-family: var(--arc-family);

  /* The deck is read as one region, and announced as a carousel. */
  container-type: inline-size;
  container-name: fan-carousel;
}

.fan-carousel *,
.fan-carousel *::before,
.fan-carousel *::after {
  box-sizing: border-box;
}

/* ==========================================================================
   3. Shell, intro
   -------------------------------------------------------------------------- */

.fan-carousel__intro {
  inline-size: 100%;
  max-inline-size: var(--arc-intro-width);
  margin-block-start: var(--arc-padding-block);
  margin-block-end: var(--arc-intro-gap);
  padding-inline: var(--arc-intro-padding);
  text-align: center;

  /* The reference nudges the wording down and pulls the deck up, rather than
     centring the pair. Reproduced: it is what puts the headline above the
     fan without the two ever colliding. */
  transform: translateY(var(--arc-intro-shift));
}

/* No reset is assumed: a <p> keeps its default bottom margin and an <h2>
   keeps its own. Left alone, that is 16 px of unexplained height. */
.fan-carousel__eyebrow,
.fan-carousel__headline,
.fan-carousel__title,
.fan-carousel__subtitle {
  margin: 0;
}

.fan-carousel__eyebrow {
  margin-block-end: var(--arc-eyebrow-gap);
  color: var(--arc-eyebrow-color);
  font-size: var(--arc-eyebrow-size);
  font-weight: 600;
  line-height: 1em;
  letter-spacing: var(--arc-eyebrow-tracking);
  text-transform: uppercase;
}

.fan-carousel__headline {
  color: var(--arc-headline-color);
  font-size: var(--arc-headline-size);
  font-weight: 700;
  line-height: 1.1em;
  letter-spacing: var(--arc-headline-tracking);
  text-wrap: balance;
}

/* ==========================================================================
   4. Stage and the arc arithmetic
   --------------------------------------------------------------------------
   --arc-active is the index of the checked radio, published by :has(). Each
   card knows its own index through :nth-child, and derives the rest:

     d     signed distance to the selection   (negative on the left)
     abs   how far, either way                max(d, -d) — abs() is younger
                                              than the browsers we support
     flag  1 on the selected card, 0 elsewhere
           abs is a whole number, so max(0, 1 - abs) is exactly that

   `flag` is what replaces a branch. Anything that differs between the
   selected card and the others is written

     flag * <selected value> + (1 - flag) * <other value>

   which a single declaration can express, and which therefore needs neither
   a class on the selected card nor a script to put it there.
   ========================================================================== */

.fan-carousel {
  --arc-active: 3; /* 4th card, the middle of a six-card deck */
}

/* One rule per rank. Twelve is a deliberate ceiling: past that the deck is
   unreadable anyway, and every extra rule costs a selector on every card. */
.fan-carousel:has(.fan-carousel__radio:nth-of-type(1):checked) { --arc-active: 0; }
.fan-carousel:has(.fan-carousel__radio:nth-of-type(2):checked) { --arc-active: 1; }
.fan-carousel:has(.fan-carousel__radio:nth-of-type(3):checked) { --arc-active: 2; }
.fan-carousel:has(.fan-carousel__radio:nth-of-type(4):checked) { --arc-active: 3; }
.fan-carousel:has(.fan-carousel__radio:nth-of-type(5):checked) { --arc-active: 4; }
.fan-carousel:has(.fan-carousel__radio:nth-of-type(6):checked) { --arc-active: 5; }
.fan-carousel:has(.fan-carousel__radio:nth-of-type(7):checked) { --arc-active: 6; }
.fan-carousel:has(.fan-carousel__radio:nth-of-type(8):checked) { --arc-active: 7; }
.fan-carousel:has(.fan-carousel__radio:nth-of-type(9):checked) { --arc-active: 8; }
.fan-carousel:has(.fan-carousel__radio:nth-of-type(10):checked) { --arc-active: 9; }
.fan-carousel:has(.fan-carousel__radio:nth-of-type(11):checked) { --arc-active: 10; }
.fan-carousel:has(.fan-carousel__radio:nth-of-type(12):checked) { --arc-active: 11; }

.fan-carousel__stage {
  position: relative;
  display: flex;
  flex: 0 1 auto;
  align-items: center;
  justify-content: center;
  inline-size: 100%;
  block-size: 100%;
  min-block-size: calc(var(--arc-card-height) * var(--arc-stage-ratio));
  margin-block-end: var(--arc-padding-block);

  /* Where the sideways overflow stops. Without it the cards that leave the
     frame would still count in the component's own scrollWidth — 31 px at
     810 and 131 px at 390 on the reference, hidden by overflow:hidden but
     real, and enough to give a page a horizontal scrollbar.

     Clipped on one axis only, which is what `clip` allows and `hidden` does
     not: the deck is taller than its stage and has to spill downwards, where
     the component's own frame catches it. Hence the two longhands — the
     `overflow` shorthand would clip both ways and cut 96 px off the bottom
     of the cards. */
  overflow-x: clip;
  overflow-y: visible;

  transform: translateY(var(--arc-stage-shift));
}

.fan-carousel__card {
  --arc-i: 0;
  --arc-d: calc(var(--arc-i) - var(--arc-active));
  --arc-abs: max(var(--arc-d), calc(var(--arc-d) * -1));
  --arc-flag: max(0, calc(1 - var(--arc-abs)));
  --arc-hover: 0; /* set to 1 by :hover, §5 */
  --arc-drag: 0px; /* set by the script while dragging, §8 */

  --arc-scale: calc(
    var(--arc-flag) * var(--arc-scale-selected) +
      (1 - var(--arc-flag)) *
        max(
          var(--arc-scale-floor),
          var(--arc-scale-idle) - var(--arc-abs) * var(--arc-scale-decay)
        ) *
        (1 + var(--arc-hover) * var(--arc-hover-grow))
  );

  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  inline-size: var(--arc-card-width);
  block-size: var(--arc-card-height);
  margin-inline-start: calc(var(--arc-card-width) / -2);
  margin-block-start: calc(var(--arc-card-height) / -2);

  /* Stacking: nearest to the selection on top. */
  z-index: calc(var(--arc-count) - var(--arc-abs));

  /* Same composition order as the reference: translate, then rotate, then
     scale. Any other order changes where the corners land. */
  transform: translateX(calc(var(--arc-d) * var(--arc-spread) + var(--arc-drag)))
    translateY(
      calc(
        var(--arc-abs) * var(--arc-step) + (1 - var(--arc-flag)) *
          var(--arc-hover) * var(--arc-hover-lift)
      )
    )
    rotate(calc(var(--arc-d) * var(--arc-tilt))) scale(var(--arc-scale));

  transition: transform var(--arc-move-duration) var(--arc-move-easing)
    var(--arc-move-delay);

  /* Vertical scrolling must still work when a finger starts on a card. */
  touch-action: pan-y;
}

/* One rule per position. The cards are the only children of the stage. */
.fan-carousel__card:nth-child(1) { --arc-i: 0; }
.fan-carousel__card:nth-child(2) { --arc-i: 1; }
.fan-carousel__card:nth-child(3) { --arc-i: 2; }
.fan-carousel__card:nth-child(4) { --arc-i: 3; }
.fan-carousel__card:nth-child(5) { --arc-i: 4; }
.fan-carousel__card:nth-child(6) { --arc-i: 5; }
.fan-carousel__card:nth-child(7) { --arc-i: 6; }
.fan-carousel__card:nth-child(8) { --arc-i: 7; }
.fan-carousel__card:nth-child(9) { --arc-i: 8; }
.fan-carousel__card:nth-child(10) { --arc-i: 9; }
.fan-carousel__card:nth-child(11) { --arc-i: 10; }
.fan-carousel__card:nth-child(12) { --arc-i: 11; }

/* ==========================================================================
   5. Card: frame, image, caption, tag
   -------------------------------------------------------------------------- */

/* Hovering is a pointer affordance, so it is asked of the pointer — not
   guessed from the width, which is what the reference does and which costs
   the effect to anyone on a narrow window with a mouse. */
@media (hover: hover) {
  .fan-carousel__card:hover {
    --arc-hover: 1;
  }
}

.fan-carousel__frame {
  position: relative;
  inline-size: 100%;
  block-size: 100%;
  overflow: hidden;
  border-radius: var(--arc-radius);
  background-color: var(--arc-card-bg);

  /* Belt and braces, as in the reference: a rounded clip on top of the
     overflow, because a scaled child can otherwise paint over the corner. */
  clip-path: inset(0 round var(--arc-radius));

  /* Composed instead of branched — see §4. Idle 10/30, selected 20/40,
     hovering adds 5/10 to either. */
  box-shadow: 0
    calc(
      var(--arc-shadow-y) + var(--arc-flag) * var(--arc-shadow-y-selected) +
        var(--arc-hover) * var(--arc-shadow-y-hover)
    )
    calc(
      var(--arc-shadow-blur) + var(--arc-flag) * var(--arc-shadow-blur-selected) +
        var(--arc-hover) * var(--arc-shadow-blur-hover)
    )
    var(--arc-shadow-color);

  transition: box-shadow var(--arc-move-duration) var(--arc-move-easing)
    var(--arc-move-delay);
  isolation: isolate;
}

.fan-carousel__image {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  border-radius: var(--arc-radius);
  object-fit: cover;
  transform: scale(calc(1 + var(--arc-hover) * var(--arc-hover-grow)));
  transition: transform var(--arc-zoom-duration) var(--arc-zoom-easing)
    var(--arc-move-delay);
}

.fan-carousel__caption {
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  padding: var(--arc-caption-padding);

  /* Reads over any photograph, without dimming the whole card. */
  background-image: linear-gradient(180deg, var(--arc-veil) 0%, transparent 100%);
}

.fan-carousel__text {
  flex: 1;
  margin-inline-end: var(--arc-caption-gap);
  min-inline-size: 0; /* a grid or flex child needs this to be allowed to shrink */
}

.fan-carousel__title {
  color: var(--arc-title-color);
  font-size: var(--arc-title-size);
  font-weight: 700;
  line-height: 1.2em;
  letter-spacing: var(--arc-title-tracking);
}

.fan-carousel__subtitle {
  margin-block-start: var(--arc-subtitle-gap);
  color: var(--arc-subtitle-color);
  font-size: var(--arc-subtitle-size);
  font-weight: 500;
  line-height: 1.3em;
  letter-spacing: var(--arc-subtitle-tracking);
}

.fan-carousel__tag {
  flex: none;
  padding: var(--arc-tag-padding-block) var(--arc-tag-padding-inline);
  border: 1px solid var(--arc-tag-border);
  border-radius: 999px;
  background-color: var(--arc-tag-bg);
  box-shadow: 0 calc(0.25 * var(--arc-font-size))
    calc(0.75 * var(--arc-font-size)) var(--arc-tag-shadow);
  backdrop-filter: blur(var(--arc-tag-blur)) saturate(var(--arc-tag-saturation));
  color: var(--arc-tag-color);
  font-size: var(--arc-tag-size);
  font-weight: 600;
  line-height: 1em;
  letter-spacing: var(--arc-tag-tracking);
  white-space: nowrap;
}

/* ==========================================================================
   6. Hit area
   --------------------------------------------------------------------------
   Clicking a card brings it to the front. That is a <label> covering the
   card, so the click reaches the radio and the arc updates — with the script
   or without it.

   The selected card must not carry one: it would swallow text selection and
   any link placed in the caption. Rather than a class the script would have
   to maintain, the label is simply given a size of zero when its card is
   selected, using the same flag as everything else.
   ========================================================================== */

.fan-carousel__hit {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  inline-size: calc((1 - var(--arc-flag)) * 100%);
  block-size: calc((1 - var(--arc-flag)) * 100%);
  z-index: 1;
  cursor: pointer;
}

/* ==========================================================================
   7. Dots
   --------------------------------------------------------------------------
   Native radios, one per card, kept in the accessibility tree and reachable
   by keyboard: arrow keys walk a radio group without a line of script, which
   is exactly the interaction the reference reimplemented on `window` — and
   there it fired even while the visitor was typing in a form elsewhere on
   the page.

   The input is not hidden with `display: none`: that would take it out of
   the focus order and the browser would refuse to focus it. It is reduced to
   one transparent pixel instead, and the focus ring is drawn on the label
   next to it.
   ========================================================================== */

.fan-carousel__dots {
  position: absolute;
  inset-block-end: var(--arc-dots-offset);
  inset-inline-start: 50%;
  display: flex;
  align-items: center;
  gap: var(--arc-dot-gap);
  z-index: 2;
  transform: translateX(-50%);
}

.fan-carousel__radio {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  margin: 0;
  padding: 0;
  border: 0;
  opacity: 0;
  pointer-events: none;
}

.fan-carousel__dot {
  --arc-i: 0;
  --arc-d: calc(var(--arc-i) - var(--arc-active));
  --arc-abs: max(var(--arc-d), calc(var(--arc-d) * -1));
  --arc-flag: max(0, calc(1 - var(--arc-abs)));

  position: relative;
  display: block;
  flex: none;
  inline-size: calc(
    var(--arc-flag) * var(--arc-dot-width-selected) +
      (1 - var(--arc-flag)) * var(--arc-dot-width)
  );
  block-size: var(--arc-dot-height);

  /* The reference leaves the browser's default button padding in place. With
     border-box that padding becomes a floor, and an 8 px dot renders 12 px
     wide. Reset here; --arc-dot-width brings the original back. */
  padding: 0;
  border: 0;
  border-radius: var(--arc-dot-radius);
  background-color: rgb(
    var(--arc-dot-color) /
      calc(
        var(--arc-flag) + (1 - var(--arc-flag)) * var(--arc-dot-idle-alpha)
      )
  );
  cursor: pointer;
  transition: inline-size var(--arc-dot-duration) var(--arc-dot-easing),
    background-color var(--arc-dot-duration) var(--arc-dot-easing);
}

/* An 8 px target is below anyone's finger. The dot keeps its drawn size and
   grows an invisible one, which is what the pointer actually hits. */
.fan-carousel__dot::before {
  content: "";
  position: absolute;
  inset-block: calc((var(--arc-dot-height) - var(--arc-dot-hit)) / 2);
  inset-inline: calc(var(--arc-dot-gap) / -2);
}

.fan-carousel__radio:focus-visible + .fan-carousel__dot {
  outline: var(--arc-focus-width) solid var(--arc-focus-color);
  outline-offset: var(--arc-focus-offset);
}

.fan-carousel__dot:nth-of-type(1) { --arc-i: 0; }
.fan-carousel__dot:nth-of-type(2) { --arc-i: 1; }
.fan-carousel__dot:nth-of-type(3) { --arc-i: 2; }
.fan-carousel__dot:nth-of-type(4) { --arc-i: 3; }
.fan-carousel__dot:nth-of-type(5) { --arc-i: 4; }
.fan-carousel__dot:nth-of-type(6) { --arc-i: 5; }
.fan-carousel__dot:nth-of-type(7) { --arc-i: 6; }
.fan-carousel__dot:nth-of-type(8) { --arc-i: 7; }
.fan-carousel__dot:nth-of-type(9) { --arc-i: 8; }
.fan-carousel__dot:nth-of-type(10) { --arc-i: 9; }
.fan-carousel__dot:nth-of-type(11) { --arc-i: 10; }
.fan-carousel__dot:nth-of-type(12) { --arc-i: 11; }

/* Visible to a screen reader, invisible on screen. */
.fan-carousel__sr {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ==========================================================================
   8. Dragging
   --------------------------------------------------------------------------
   Two classes, both set by the script. Everything they change is declared
   here, so the motion can be retuned without opening the JavaScript.
   ========================================================================== */

/* While a finger or a pointer is down: the card follows, with no easing in
   the way — a transition here would make it lag behind the pointer. */
.fan-carousel.is-dragging .fan-carousel__card {
  transition: none;
}

/* The card that was let go, on its way back. */
.fan-carousel__card.is-returning {
  transition: transform var(--arc-return-duration) var(--arc-return-easing);
}

/* A drag that ends on a card must not also count as a click on it. */
.fan-carousel.is-dragging .fan-carousel__hit {
  pointer-events: none;
}

/* ==========================================================================
   9. Probe
   --------------------------------------------------------------------------
   getComputedStyle().getPropertyValue('--x') does not resolve anything: it
   hands back `calc(3.125 * 16px)`, not `50px`. Resolved values only exist on
   real properties, so the two numbers the script needs are carried by an
   element that has a box but is never painted.

   `visibility: hidden` and not `display: none` — a box that is not generated
   has no resolved width either.
   ========================================================================== */

.fan-carousel__probe {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  inline-size: var(--arc-drag-threshold);

  /* The elasticity is a bare number; it travels as a length and the script
     divides by 100 on the way out. */
  block-size: calc(var(--arc-drag-elastic) * 100px);
  visibility: hidden;
  pointer-events: none;
}

/* ==========================================================================
   10. Variants
   --------------------------------------------------------------------------
   The reference is drawn large, the way a marketplace screenshot is: a
   400 px card and a 48 px headline need most of a laptop screen. Reproduced
   as measured above — and offered here at a size that fits a section of an
   ordinary page, by moving the one variable everything derives from.
   ========================================================================== */

.fan-carousel--compact {
  --arc-font-size: 10px;
}

/* ==========================================================================
   11. Narrow containers
   --------------------------------------------------------------------------
   The reference measures its own element, not the window, and switches at
   640 and 1024. Container queries say exactly that, and keep saying it when
   the deck is dropped in a column narrower than the page.

   Only variables are reassigned here: every rule above reads them, so
   nothing else has to know these breakpoints exist.

   They are set on the children rather than on .fan-carousel, because an element
   is never its own query container. Every consumer either is one of those
   children or sits below one, so the values reach all of them by
   inheritance — and a declaration on a child always beats an inherited one,
   whatever the specificity.

   `data-arc-fixed` switches them off. A deck that has to keep one drawing at
   every size — inside a preview frame scaled to its container, or in a
   screenshot taken at an arbitrary width — sets it and gets the wide layout
   whatever the measured width says.
   ========================================================================== */

@container fan-carousel (max-width: 639.98px) {
  .fan-carousel:not([data-arc-fixed]) > * {
    --arc-padding-block: calc(2.5 * var(--arc-font-size));
    --arc-intro-width: 90%;
    --arc-intro-gap: calc(5.625 * var(--arc-font-size));
    --arc-intro-shift: calc(6.25 * var(--arc-font-size));
    --arc-eyebrow-size: calc(0.7 * var(--arc-font-size));
    --arc-eyebrow-gap: calc(0.75 * var(--arc-font-size));
    --arc-headline-size: calc(2.1 * var(--arc-font-size));

    --arc-card-width: min(calc(20 * var(--arc-font-size)), 78cqi);
    --arc-card-height: calc(var(--arc-card-width) * var(--arc-card-ratio));
    --arc-spread: min(calc(6.48 * var(--arc-font-size)), calc(0.52 * var(--arc-card-width)));
    --arc-step: calc(2 * var(--arc-font-size));
    --arc-tilt: 3.6deg;
    --arc-scale-selected: 1;
    --arc-scale-idle: 0.78;
    --arc-stage-shift: calc(-2.8125 * var(--arc-font-size));
    --arc-stage-ratio: 1.35;
    --arc-radius: calc(1.75 * var(--arc-font-size));

    --arc-caption-padding: calc(1 * var(--arc-font-size));
    --arc-title-size: calc(1.05 * var(--arc-font-size));
    --arc-subtitle-size: calc(0.6125 * var(--arc-font-size));
    --arc-tag-size: calc(0.48125 * var(--arc-font-size));
    --arc-tag-padding-block: calc(0.375 * var(--arc-font-size));
    --arc-tag-padding-inline: calc(0.75 * var(--arc-font-size));

    --arc-dots-offset: calc(1.25 * var(--arc-font-size));
    --arc-drag-threshold: calc(1.875 * var(--arc-font-size)); /* 30 px */
    --arc-drag-elastic: 0.3;
  }
}

@container fan-carousel (min-width: 640px) and (max-width: 1023.98px) {
  .fan-carousel:not([data-arc-fixed]) > * {
    --arc-padding-block: calc(3.75 * var(--arc-font-size));
    --arc-intro-width: 80%;
    --arc-intro-gap: calc(10 * var(--arc-font-size));
    --arc-intro-shift: calc(5 * var(--arc-font-size));
    --arc-eyebrow-size: calc(0.7875 * var(--arc-font-size));
    --arc-headline-size: calc(2.55 * var(--arc-font-size));

    --arc-card-width: min(calc(22 * var(--arc-font-size)), 46cqi);
    --arc-card-height: calc(var(--arc-card-width) * var(--arc-card-ratio));
    --arc-spread: calc(8.775 * var(--arc-font-size));
    --arc-step: calc(2.875 * var(--arc-font-size));
    --arc-tilt: 5.6deg;
    --arc-scale-selected: 1.05;
    --arc-scale-idle: 0.82;
    --arc-stage-shift: calc(-6.25 * var(--arc-font-size));
    --arc-stage-ratio: 1.25;
    --arc-radius: calc(2.125 * var(--arc-font-size));

    --arc-caption-padding: calc(1.25 * var(--arc-font-size));
    --arc-title-size: calc(1.275 * var(--arc-font-size));
    --arc-subtitle-size: calc(0.74375 * var(--arc-font-size));
    --arc-tag-size: calc(0.584375 * var(--arc-font-size));
  }
}

/* ==========================================================================
   12. Fallbacks
   ========================================================================== */

/* A delay is movement spread over time, so delays go to zero as well. What
   is a picture and not a movement — the arc, the tilt, the shadows — stays. */
@media (prefers-reduced-motion: reduce) {
  .fan-carousel {
    --arc-move-duration: 0.001s;
    --arc-move-delay: 0s;
    --arc-zoom-duration: 0.001s;
    --arc-return-duration: 0.001s;
    --arc-dot-duration: 0.001s;
    --arc-hover-grow: 0;
    --arc-hover-lift: 0px;
  }
}

/* In forced colours the photographs survive but every wash of colour is
   dropped, so anything that was distinguished by a tint needs an edge. */
@media (forced-colors: active) {
  .fan-carousel__frame {
    border: 1px solid CanvasText;
  }

  .fan-carousel__caption {
    background-image: none;
    background-color: Canvas;
  }

  .fan-carousel__tag {
    border-color: CanvasText;
    background-color: Canvas;
    backdrop-filter: none;
  }

  .fan-carousel__dot {
    border: 1px solid ButtonText;
    background-color: Canvas;
    forced-color-adjust: none;
  }

  .fan-carousel:has(.fan-carousel__radio:nth-of-type(1):checked) .fan-carousel__dot:nth-of-type(1),
  .fan-carousel:has(.fan-carousel__radio:nth-of-type(2):checked) .fan-carousel__dot:nth-of-type(2),
  .fan-carousel:has(.fan-carousel__radio:nth-of-type(3):checked) .fan-carousel__dot:nth-of-type(3),
  .fan-carousel:has(.fan-carousel__radio:nth-of-type(4):checked) .fan-carousel__dot:nth-of-type(4),
  .fan-carousel:has(.fan-carousel__radio:nth-of-type(5):checked) .fan-carousel__dot:nth-of-type(5),
  .fan-carousel:has(.fan-carousel__radio:nth-of-type(6):checked) .fan-carousel__dot:nth-of-type(6),
  .fan-carousel:has(.fan-carousel__radio:nth-of-type(7):checked) .fan-carousel__dot:nth-of-type(7),
  .fan-carousel:has(.fan-carousel__radio:nth-of-type(8):checked) .fan-carousel__dot:nth-of-type(8),
  .fan-carousel:has(.fan-carousel__radio:nth-of-type(9):checked) .fan-carousel__dot:nth-of-type(9),
  .fan-carousel:has(.fan-carousel__radio:nth-of-type(10):checked) .fan-carousel__dot:nth-of-type(10),
  .fan-carousel:has(.fan-carousel__radio:nth-of-type(11):checked) .fan-carousel__dot:nth-of-type(11),
  .fan-carousel:has(.fan-carousel__radio:nth-of-type(12):checked) .fan-carousel__dot:nth-of-type(12) {
    background-color: Highlight;
  }

  .fan-carousel__radio:focus-visible + .fan-carousel__dot {
    outline-color: Highlight;
  }
}

/* Without backdrop-filter the tag would be a pale film over a photograph.
   Given a solid ground instead, it reads the same way. */
@supports not (backdrop-filter: blur(1px)) {
  .fan-carousel__tag {
    background-color: rgb(60 60 60 / 0.85);
  }
}
