/* ==========================================================================
   Hotspot Carousel 1.0 — image carousel with pinned points of interest
   --------------------------------------------------------------------------
   Plain CSS. No framework, no build step, no external request.

   Each slide is a photograph. On top of it sit round pins; opening one
   reveals a card describing what is in the picture at that spot. Below the
   image, a rail of thumbnails doubles as the slide picker.

   The pins are native <details>/<summary>. Before the script runs — or if it
   never runs — every card still opens and closes with the mouse and with the
   keyboard, because that is what <details> already does. What the script
   adds is the projection of each pin onto the photograph: a pin's position
   is data (data-x / data-y), not a class, and a strict Content-Security-
   Policy forbids the inline style that would carry it. So without the
   script the pins stack in a legible column at the foot of the slide
   instead of floating over the image. Nothing is lost, only placed
   differently.

   PROPORTIONS
   Every metric derives from --hsc-font-size, the component's base text size.
   The ratios were read off the published original with getBoundingClientRect
   at five viewport widths (see README.md, "Provenance"); the original itself
   uses no responsive scaling, and neither does this file. The reference unit
   is 16 px, and on that grid the whole drawing falls on round numbers:

     base text          1                (the reference unit, 16 px)
     caption            1.125 em         18 px, at 1.4 line height
     caption band       1.25 em top and sides, 2.5 em bottom
     counter            0.8125 em        13 px, radius 1.25 em
     edge inset         1 em             counter and arrows, every side
     arrow              2.75 em square, icon 1.125 em
     pin                2 em square, 0.125 em ring, icon 0.75 em
     pulse ring         0.375 em outside the pin, to 1.9x
     panel              17.5 em wide, 1.125 em padding, 1 em radius
     panel offset       0.75 em from the pin
     rail inset         1.5 em           and the same for the card radius
     rail card padding  0.5 em
     thumbnail          5 em tall, 1 em radius, 0.5 em apart

   So changing --hsc-font-size rescales the component whole — drawing,
   motion and text together — and nothing else needs to be touched.

   Order of this file:
     1. Face declarations
     2. Design tokens
     3. Shell and viewport
     4. Slide, image, caption
     5. Counter and live status
     6. Arrows and play control
     7. Hotspots: pin, pulse, panel
     8. Thumbnail rail
     9. Slide motion
    10. Without the script
    11. Fallbacks: reduced motion, forced colours, no backdrop-filter

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

/* ==========================================================================
   1. Face declarations
   --------------------------------------------------------------------------
   Inter, the face the original is set in, as one variable file per subset.
   Latin alone covers French and English; latin-ext is fetched only if the
   page actually uses a character that needs it. Weight 100-900 in a single
   file, so the three weights the component asks for cost one request.
   ========================================================================== */

@font-face {
  font-family: "Inter var";
  src: url("fonts/Inter-var-latin.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
    U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
    U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Inter var";
  src: url("fonts/Inter-var-latin-ext.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
    U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
    U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
    U+A720-A7FF;
}

/* ==========================================================================
   2. Design tokens
   --------------------------------------------------------------------------
   Everything that is a colour, a length or a duration lives here, including
   the animation delays — so a timing can be retuned without opening the
   JavaScript. Anything that is neither (a count, a seed, a flag) is a data-*
   attribute instead, and is listed in README.md.

   Lengths are written calc(<ratio> * var(--hsc-font-size)) rather than in
   em: a custom property holding an em value is resolved by whoever reads it,
   so 1.125em read from the 18 px caption would come out at 20 px, not 18.
   Written absolutely, one declaration means one length everywhere.
   Ratios are rounded up — the engine truncates to 1/64 px, and rounding down
   is how a component invents a one-pixel overflow.
   ========================================================================== */

.hotspot-carousel {
  /* --- the module ------------------------------------------------------- */
  --hsc-font-size: 1rem;
  --hsc-font: "Inter var", system-ui, -apple-system, "Segoe UI", Roboto,
    sans-serif;

  /* --- frame ------------------------------------------------------------ */
  --hsc-ratio: 16 / 9;
  --hsc-radius: 0px;
  --hsc-backdrop: #000;
  --hsc-ink: #fff;
  --hsc-inset: calc(1 * var(--hsc-font-size));

  /* --- the veil shared by counter, arrows and pins ---------------------- */
  --hsc-veil: rgb(0 0 0 / 0.48);
  --hsc-veil-strong: rgb(0 0 0 / 0.62);
  --hsc-veil-blur: blur(calc(0.75 * var(--hsc-font-size)));
  --hsc-hairline: rgb(255 255 255 / 0.14);

  /* --- caption ---------------------------------------------------------- */
  --hsc-caption-size: calc(1.125 * var(--hsc-font-size));
  --hsc-caption-weight: 500;
  --hsc-caption-leading: 1.4;
  --hsc-caption-tracking: 0.01em;
  --hsc-caption-pad: calc(1.25 * var(--hsc-font-size));
  --hsc-caption-pad-bottom: calc(2.5 * var(--hsc-font-size));
  --hsc-caption-scrim: rgb(0 0 0 / 0.65);
  --hsc-caption-shadow: 0 calc(0.0625 * var(--hsc-font-size))
    calc(0.25 * var(--hsc-font-size)) rgb(0 0 0 / 0.4);

  /* --- counter ---------------------------------------------------------- */
  --hsc-counter-size: calc(0.8125 * var(--hsc-font-size));
  --hsc-counter-weight: 600;
  --hsc-counter-tracking: 0.02em;
  --hsc-counter-pad-y: calc(0.3125 * var(--hsc-font-size));
  --hsc-counter-pad-x: calc(0.8125 * var(--hsc-font-size));
  --hsc-counter-radius: calc(1.25 * var(--hsc-font-size));
  --hsc-counter-line: rgb(255 255 255 / 0.12);

  /* --- arrows ----------------------------------------------------------- */
  --hsc-arrow-size: calc(2.75 * var(--hsc-font-size));
  --hsc-arrow-icon: calc(1.125 * var(--hsc-font-size));
  --hsc-arrow-stroke: 2.5;

  /* --- pins ------------------------------------------------------------- */
  --hsc-pin-size: calc(2 * var(--hsc-font-size));
  --hsc-pin-icon: calc(0.75 * var(--hsc-font-size));
  --hsc-pin-line: calc(0.125 * var(--hsc-font-size));
  --hsc-pin-ring: calc(0.375 * var(--hsc-font-size));
  --hsc-pin-fill: rgb(0 0 0 / 0.45);
  --hsc-pin-fill-hover: rgb(0 0 0 / 0.68);
  --hsc-pin-fill-open: #fff;
  --hsc-pin-edge: #fff;
  --hsc-pin-ink-open: #111;
  --hsc-pin-shadow: 0 calc(0.125 * var(--hsc-font-size))
    calc(0.5 * var(--hsc-font-size)) rgb(0 0 0 / 0.22);
  --hsc-pulse-to: 1.9;
  --hsc-pulse-from-opacity: 0.7;

  /* --- hotspot panel ---------------------------------------------------- */
  --hsc-panel-width: calc(17.5 * var(--hsc-font-size));
  --hsc-panel-pad: calc(1.125 * var(--hsc-font-size));
  --hsc-panel-radius: calc(1 * var(--hsc-font-size));
  --hsc-panel-gap: calc(0.75 * var(--hsc-font-size));
  --hsc-panel-face: #fff;
  --hsc-panel-ink: #111;
  --hsc-panel-ink-soft: #555;
  --hsc-panel-shadow: 0 calc(0.25 * var(--hsc-font-size))
      calc(0.375 * var(--hsc-font-size))
      calc(-0.0625 * var(--hsc-font-size)) rgb(0 0 0 / 0.08),
    0 calc(1.25 * var(--hsc-font-size)) calc(2.5 * var(--hsc-font-size))
      calc(-0.5 * var(--hsc-font-size)) rgb(0 0 0 / 0.2),
    0 0 0 1px rgb(0 0 0 / 0.06);
  --hsc-panel-title-size: calc(0.9375 * var(--hsc-font-size));
  --hsc-panel-lede-size: calc(1.125 * var(--hsc-font-size));
  --hsc-panel-text-size: calc(0.8125 * var(--hsc-font-size));
  --hsc-panel-action-radius: calc(0.25 * var(--hsc-font-size));
  --hsc-panel-action-pad-y: calc(0.625 * var(--hsc-font-size));
  --hsc-panel-action-pad-x: calc(1 * var(--hsc-font-size));
  --hsc-panel-close-size: calc(1.625 * var(--hsc-font-size));
  --hsc-panel-close-inset: calc(0.625 * var(--hsc-font-size));
  --hsc-panel-close-face: rgb(0 0 0 / 0.07);

  /* --- thumbnail rail --------------------------------------------------- */
  --hsc-rail-inset: calc(1.5 * var(--hsc-font-size));
  --hsc-rail-radius: calc(1.5 * var(--hsc-font-size));
  --hsc-rail-pad: calc(0.5 * var(--hsc-font-size));
  --hsc-rail-face: rgb(0 0 0 / 0.5);
  --hsc-rail-blur: blur(calc(1.25 * var(--hsc-font-size)));
  --hsc-rail-line: rgb(255 255 255 / 0.08);
  --hsc-thumb-height: calc(5 * var(--hsc-font-size));
  --hsc-thumb-ratio: 16 / 9;
  --hsc-thumb-radius: calc(1 * var(--hsc-font-size));
  --hsc-thumb-gap: calc(0.5 * var(--hsc-font-size));
  --hsc-thumb-line: calc(0.125 * var(--hsc-font-size));
  --hsc-thumb-edge: rgb(255 255 255 / 0.92);
  --hsc-thumb-idle-opacity: 0.52;
  --hsc-thumb-idle-scale: 0.94;

  /* --- focus ------------------------------------------------------------ */
  --hsc-focus-width: calc(0.1875 * var(--hsc-font-size));
  --hsc-focus-offset: calc(0.125 * var(--hsc-font-size));
  --hsc-focus-colour: #fff;
  --hsc-focus-colour-light: #111;

  /* --- motion ------------------------------------------------------------
     The slide is a slip, not a fade: the incoming photograph travels one
     full width and settles over the one already there, which never moves.
     On the original that travel decays exponentially with a 133 ms time
     constant (measured across fourteen frames, see _mesures/). A cubic
     bezier cannot hold that shape, so the curve below samples it directly:
     28 points, at most 1.7 px off the measured trajectory over a 1532 px
     travel. Set --hsc-slide-ease to cubic-bezier(0.4, 0, 0.2, 1) and
     --hsc-slide-duration to 520ms for the component author's own default.
     -------------------------------------------------------------------- */
  --hsc-slide-duration: 900ms;
  --hsc-slide-ease: linear(
    0 0%, 0.0863 1.3333%, 0.1651 2.6667%, 0.2428 4.1111%, 0.3133 5.5556%,
    0.3796 7.0556%, 0.4416 8.6111%, 0.4994 10.2222%, 0.5545 11.9444%,
    0.6035 13.6667%, 0.6499 15.5%, 0.692 17.3889%, 0.731 19.3889%,
    0.7661 21.4444%, 0.7981 23.6111%, 0.8271 25.8889%, 0.853 28.2778%,
    0.8746 30.6111%, 0.8943 33.1111%, 0.9116 35.7222%, 0.9269 38.5%,
    0.9405 41.5%, 0.9522 44.6667%, 0.9709 51.7778%, 0.9837 59.8889%,
    0.9922 69.8333%, 0.9974 82.4444%, 1 100%
  );
  --hsc-veil-duration: 180ms;
  --hsc-pin-in-duration: 300ms;
  --hsc-pin-in-delay: 0ms;
  --hsc-pin-in-step: 60ms;
  --hsc-pin-icon-duration: 250ms;
  --hsc-pulse-duration: 2400ms;
  --hsc-panel-duration: 220ms;
  --hsc-thumb-duration: 240ms;
  --hsc-thumb-scale-duration: 220ms;
  --hsc-overshoot: cubic-bezier(0.34, 1.56, 0.64, 1);
  /* How long a slide is held before the carousel moves on by itself. Only
     read when data-hsc-autoplay is set, and read off the probe, never off
     the property text. */
  --hsc-autoplay-interval: 4000ms;

  /* --- the frame itself -------------------------------------------------- */
  box-sizing: border-box;
  position: relative;
  display: block;
  overflow: hidden;
  aspect-ratio: var(--hsc-ratio);
  border-radius: var(--hsc-radius);
  background: var(--hsc-backdrop);
  font-family: var(--hsc-font);
  font-size: var(--hsc-font-size);
  color: var(--hsc-ink);
  -webkit-tap-highlight-color: rgb(0 0 0 / 0.18);
}

/* The component declares its own box model rather than assuming a reset,
   and clears the margins a bare <p>, <h3> or <ul> would otherwise keep. */
.hotspot-carousel *,
.hotspot-carousel *::before,
.hotspot-carousel *::after {
  box-sizing: border-box;
}

/* :where() and not :is(): the reset has to weigh nothing. Written with :is()
   it scores (0,2,1) and beats every single-class rule below it — the counter
   would lose the padding it is drawn with, and nothing would say why. */
.hotspot-carousel :where(p, h2, h3, h4, ul, ol, figure) {
  margin: 0;
  padding: 0;
}

.hotspot-carousel :where(ul) {
  list-style: none;
}

/* ==========================================================================
   3. Shell and viewport
   ========================================================================== */

.hotspot-carousel__viewport {
  position: absolute;
  inset: 0;
  overflow: hidden;
}

/* ==========================================================================
   4. Slide, image, caption
   --------------------------------------------------------------------------
   Once the script has run, every slide is stacked in the same place and the
   stacking order decides what shows: the outgoing slide sits below, holding
   still, while the incoming one slides over it. Slides that take no part in
   the current move are dropped out of the paint and out of the a11y tree
   both, so a screen reader and the tab order see one slide at a time.
   ========================================================================== */

.hotspot-carousel__slide {
  position: relative;
  flex: 0 0 100%;
  min-width: 0;
  height: 100%;
  scroll-snap-align: start;
}

[data-hsc-ready] .hotspot-carousel__slide {
  position: absolute;
  inset: 0;
  z-index: 1;
}

/* Off-stage: painted nowhere, focusable by nobody. `inert` on the element
   itself is what keeps its buttons out of the tab order — hiding a focusable
   thing inside an aria-hidden subtree is the trap this avoids. */
[data-hsc-ready] .hotspot-carousel__slide[hidden] {
  display: none;
}

/* The slide on top of the pile during a move. */
[data-hsc-ready] .hotspot-carousel__slide[data-hsc-state="incoming"] {
  z-index: 3;
}

[data-hsc-ready] .hotspot-carousel__slide[data-hsc-state="current"] {
  z-index: 2;
}

.hotspot-carousel__image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: var(--hsc-fit, cover);
  object-position: center;
  user-select: none;
  -webkit-user-drag: none;
}

.hotspot-carousel__caption-band {
  position: absolute;
  inset: 0 0 auto;
  z-index: 10;
  padding: var(--hsc-caption-pad) var(--hsc-caption-pad)
    var(--hsc-caption-pad-bottom);
  background-image: linear-gradient(
    var(--hsc-caption-scrim),
    transparent
  );
  pointer-events: none;
}

.hotspot-carousel__caption {
  font-size: var(--hsc-caption-size);
  font-weight: var(--hsc-caption-weight);
  line-height: var(--hsc-caption-leading);
  letter-spacing: var(--hsc-caption-tracking);
  color: var(--hsc-ink);
  text-shadow: var(--hsc-caption-shadow);
}

/* ==========================================================================
   5. Counter and live status
   ========================================================================== */

.hotspot-carousel__counter {
  position: absolute;
  top: var(--hsc-inset);
  right: var(--hsc-inset);
  z-index: 20;
  padding: var(--hsc-counter-pad-y) var(--hsc-counter-pad-x);
  border: 1px solid var(--hsc-counter-line);
  border-radius: var(--hsc-counter-radius);
  background: var(--hsc-veil);
  backdrop-filter: var(--hsc-veil-blur);
  -webkit-backdrop-filter: var(--hsc-veil-blur);
  font-size: var(--hsc-counter-size);
  font-weight: var(--hsc-counter-weight);
  line-height: 1.4;
  letter-spacing: var(--hsc-counter-tracking);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  user-select: none;
}

/* --- the probe -----------------------------------------------------------
   getComputedStyle().getPropertyValue('--x') hands back the text of a custom
   property, not its value: `calc(4 * 1000ms)`, never `4000ms`. Durations
   declared in CSS are therefore read off this element instead, whose
   resolved transition-duration and width the engine does compute. It must
   occupy a box to be resolved at all, so it is hidden rather than removed.
   -------------------------------------------------------------------- */
.hotspot-carousel__probe {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  visibility: hidden;
  pointer-events: none;
  transition-property: opacity;
  transition-duration: var(--hsc-autoplay-interval, 4000ms);
  transition-delay: var(--hsc-slide-duration);
}

/* The spoken counterpart. Visually gone, still in the a11y tree — so it
   cannot be display:none, which would take it out of the tree as well. */
.hotspot-carousel__status {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ==========================================================================
   6. Arrows and play control
   ========================================================================== */

.hotspot-carousel__arrow {
  position: absolute;
  top: 50%;
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--hsc-arrow-size);
  height: var(--hsc-arrow-size);
  padding: 0;
  transform: translateY(-50%);
  border: 1px solid var(--hsc-hairline);
  border-radius: 50%;
  background: var(--hsc-veil);
  backdrop-filter: var(--hsc-veil-blur);
  -webkit-backdrop-filter: var(--hsc-veil-blur);
  color: var(--hsc-ink);
  cursor: pointer;
  user-select: none;
  transition: background var(--hsc-veil-duration) ease;
}

.hotspot-carousel__arrow--prev {
  left: var(--hsc-inset);
}

.hotspot-carousel__arrow--next {
  right: var(--hsc-inset);
}

.hotspot-carousel__arrow:hover {
  background: var(--hsc-veil-strong);
}

.hotspot-carousel__arrow:disabled {
  opacity: 0.4;
  cursor: default;
}

.hotspot-carousel__arrow svg {
  display: block;
  width: var(--hsc-arrow-icon);
  height: var(--hsc-arrow-icon);
}

/* The play/pause control only exists while the carousel advances on its
   own — a control that pauses nothing would be noise in the tab order. */
.hotspot-carousel__play {
  position: absolute;
  top: var(--hsc-inset);
  left: var(--hsc-inset);
  z-index: 30;
  display: none;
  align-items: center;
  justify-content: center;
  width: var(--hsc-arrow-size);
  height: var(--hsc-arrow-size);
  padding: 0;
  border: 1px solid var(--hsc-hairline);
  border-radius: 50%;
  background: var(--hsc-veil);
  backdrop-filter: var(--hsc-veil-blur);
  -webkit-backdrop-filter: var(--hsc-veil-blur);
  color: var(--hsc-ink);
  cursor: pointer;
  transition: background var(--hsc-veil-duration) ease;
}

.hotspot-carousel[data-hsc-autoplay] .hotspot-carousel__play {
  display: flex;
}

.hotspot-carousel__play:hover {
  background: var(--hsc-veil-strong);
}

.hotspot-carousel__play svg {
  display: block;
  width: var(--hsc-arrow-icon);
  height: var(--hsc-arrow-icon);
}

.hotspot-carousel__play [data-hsc-when="paused"],
.hotspot-carousel[data-hsc-paused] .hotspot-carousel__play [data-hsc-when="playing"] {
  display: none;
}

.hotspot-carousel[data-hsc-paused] .hotspot-carousel__play [data-hsc-when="paused"] {
  display: block;
}

/* ==========================================================================
   7. Hotspots: pin, pulse, panel
   --------------------------------------------------------------------------
   A pin is a <details>. The <summary> is the round button, the panel is the
   card. The browser already gives the pair a role, keyboard operation and a
   live expanded state; aria-expanded is written into the DOM anyway, since
   a test has no other way to read what the browser knows.
   ========================================================================== */

.hotspot-carousel__hotspot {
  position: absolute;
  z-index: 30;
}

/* Placed by the script from data-x / data-y, through the CSSOM — a strict
   Content-Security-Policy blocks the inline style attribute, but not this. */
[data-hsc-ready] .hotspot-carousel__hotspot {
  left: var(--hsc-x, 50%);
  top: var(--hsc-y, 50%);
  translate: -50% -50%;
}

.hotspot-carousel__pin {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--hsc-pin-size);
  height: var(--hsc-pin-size);
  border: var(--hsc-pin-line) solid var(--hsc-pin-edge);
  border-radius: 50%;
  background: var(--hsc-pin-fill);
  box-shadow: var(--hsc-pin-shadow);
  color: var(--hsc-ink);
  cursor: pointer;
  list-style: none;
  transition: background var(--hsc-veil-duration) ease,
    box-shadow var(--hsc-veil-duration) ease,
    color var(--hsc-veil-duration) ease;
}

/* Safari still draws the disclosure triangle without this. */
.hotspot-carousel__pin::-webkit-details-marker {
  display: none;
}

/* What the pin is called. The drawing is a plus sign and says nothing, so
   the name is carried in text: visually gone, still read aloud, still what
   labels the toggle. Not display:none, which would take it out of the
   accessibility tree along with the layout. */
.hotspot-carousel__label {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.hotspot-carousel__pin:hover {
  background: var(--hsc-pin-fill-hover);
}

.hotspot-carousel__hotspot[open] > .hotspot-carousel__pin {
  background: var(--hsc-pin-fill-open);
  color: var(--hsc-pin-ink-open);
}

.hotspot-carousel__pin svg {
  display: block;
  width: var(--hsc-pin-icon);
  height: var(--hsc-pin-icon);
  transition: rotate var(--hsc-pin-icon-duration) var(--hsc-overshoot);
}

/* The cross turns into an x on opening — the same two strokes, rotated. */
.hotspot-carousel__hotspot[open] > .hotspot-carousel__pin svg {
  rotate: 45deg;
}

/* --- the pulse ----------------------------------------------------------
   Decorative, and nothing but: it carries no state a reader could use, so
   it stays out of the accessibility tree and out of the pointer's way. The
   original's own stylesheet holds the shape — full size reached at 70 %,
   then held — and that is what is reproduced here.
   --------------------------------------------------------------------- */
.hotspot-carousel__pulse {
  position: absolute;
  inset: calc(-1 * var(--hsc-pin-ring));
  border: var(--hsc-pin-line) solid var(--hsc-pin-edge);
  border-radius: 50%;
  opacity: 0;
  pointer-events: none;
  animation: hsc-pulse var(--hsc-pulse-duration) ease-out infinite;
}

.hotspot-carousel[data-hsc-pulse="off"] .hotspot-carousel__pulse {
  display: none;
}

/* Held still while the card is open: the ring would only fight the panel. */
.hotspot-carousel__hotspot[open] .hotspot-carousel__pulse {
  animation-play-state: paused;
  opacity: 0;
}

@keyframes hsc-pulse {
  0% {
    scale: 1;
    opacity: var(--hsc-pulse-from-opacity);
  }
  70%,
  100% {
    scale: var(--hsc-pulse-to);
    opacity: 0;
  }
}

/* Pins arrive one after the other when their slide comes up. The delay is a
   custom property so the cascade can be retimed without touching the JS;
   --hsc-pin-index is set by the script, per pin. */
[data-hsc-ready] .hotspot-carousel__slide[data-hsc-state="current"] .hotspot-carousel__hotspot {
  animation: hsc-pin-in var(--hsc-pin-in-duration) var(--hsc-overshoot)
    calc(var(--hsc-pin-in-delay) + var(--hsc-pin-index, 0) * var(--hsc-pin-in-step))
    backwards;
}

/* Only the from state is described. The landing state is left to the rule
   above, so the entry cannot flatten a translate the placement depends on. */
@keyframes hsc-pin-in {
  from {
    opacity: 0;
    scale: 0.6;
  }
}

/* --- the panel ---------------------------------------------------------- */
/* No max-width here. Once the card is placed, its containing block is the
   pin — thirty-two pixels — and a percentage would resolve against that,
   squeezing the card to nothing and spilling its text. The ceiling that does
   apply is a length in pixels, measured against the frame by the script. */
.hotspot-carousel__panel {
  position: relative;
  width: var(--hsc-panel-width);
  max-width: var(--hsc-panel-max, none);
  padding: var(--hsc-panel-pad);
  border-radius: var(--hsc-panel-radius);
  background: var(--hsc-panel-face);
  box-shadow: var(--hsc-panel-shadow);
  color: var(--hsc-panel-ink);
  text-align: start;
}

/* Centred on its pin and set to one side of it. Which side is a decision
   the script makes by measuring, and hands back as an attribute; how far up
   or down the card has to slide to clear the frame is the one number it
   cannot express as a rule, and that comes through --hsc-panel-shift. */
[data-hsc-ready] .hotspot-carousel__panel {
  position: absolute;
  z-index: 50;
  top: 50%;
  left: calc(100% + var(--hsc-panel-gap));
  translate: 0 calc(-50% + var(--hsc-panel-shift, 0px));
  animation: hsc-panel-in var(--hsc-panel-duration) var(--hsc-overshoot);
}

[data-hsc-ready] .hotspot-carousel__hotspot[data-hsc-side="left"] > .hotspot-carousel__panel {
  left: auto;
  right: calc(100% + var(--hsc-panel-gap));
}

@keyframes hsc-panel-in {
  from {
    opacity: 0;
    scale: 0.88;
  }
}

.hotspot-carousel__panel-title {
  padding-right: calc(var(--hsc-panel-close-size) + var(--hsc-panel-pad) * 0.25);
  font-size: var(--hsc-panel-title-size);
  font-weight: 700;
  line-height: 1.3;
}

.hotspot-carousel__panel-lede {
  margin-top: calc(0.3125 * var(--hsc-font-size));
  font-size: var(--hsc-panel-lede-size);
  font-weight: 700;
  line-height: 1;
}

.hotspot-carousel__panel-text {
  margin-top: calc(0.5 * var(--hsc-font-size));
  font-size: var(--hsc-panel-text-size);
  font-weight: 400;
  line-height: 1.55;
  color: var(--hsc-panel-ink-soft);
}

.hotspot-carousel__panel-action {
  display: block;
  margin-top: calc(0.875 * var(--hsc-font-size));
  padding: var(--hsc-panel-action-pad-y) var(--hsc-panel-action-pad-x);
  border-radius: var(--hsc-panel-action-radius);
  background: var(--hsc-panel-ink);
  color: #fff;
  font-family: inherit;
  font-size: var(--hsc-panel-text-size);
  font-weight: 600;
  line-height: 1.15;
  text-align: center;
  text-decoration: none;
}

.hotspot-carousel__panel-action:hover {
  background: #000;
}

.hotspot-carousel__panel-close {
  position: absolute;
  top: var(--hsc-panel-close-inset);
  right: var(--hsc-panel-close-inset);
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--hsc-panel-close-size);
  height: var(--hsc-panel-close-size);
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--hsc-panel-close-face);
  color: #777;
  font-family: inherit;
  font-size: var(--hsc-panel-text-size);
  line-height: 1;
  cursor: pointer;
}

.hotspot-carousel__panel-close:hover {
  color: var(--hsc-panel-ink);
}

.hotspot-carousel__panel-close svg {
  display: block;
  width: calc(0.625 * var(--hsc-font-size));
  height: calc(0.625 * var(--hsc-font-size));
}

/* ==========================================================================
   8. Thumbnail rail
   --------------------------------------------------------------------------
   The rail is a horizontal scroller by design: seven thumbnails do not fit
   a narrow frame and are not meant to. Its scrollWidth therefore exceeds its
   clientWidth, which is what a scroller is; the overflow test in _mesures/
   skips elements carrying data-hsc-scroller for that reason, and only that.
   ========================================================================== */

.hotspot-carousel__rail-outer {
  position: absolute;
  inset: auto 0 0;
  z-index: 20;
  padding: var(--hsc-rail-inset);
}

.hotspot-carousel[data-hsc-thumbnails="off"] .hotspot-carousel__rail-outer {
  display: none;
}

.hotspot-carousel__rail-card {
  overflow: hidden;
  padding: var(--hsc-rail-pad);
  border: 1px solid var(--hsc-rail-line);
  border-radius: var(--hsc-rail-radius);
  background: var(--hsc-rail-face);
  backdrop-filter: var(--hsc-rail-blur);
  -webkit-backdrop-filter: var(--hsc-rail-blur);
}

.hotspot-carousel__rail {
  display: flex;
  gap: var(--hsc-thumb-gap);
  overflow-x: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
  -webkit-overflow-scrolling: touch;
  scroll-behavior: smooth;
}

.hotspot-carousel__rail::-webkit-scrollbar {
  display: none;
}

.hotspot-carousel__thumb {
  display: block;
  flex-shrink: 0;
  height: var(--hsc-thumb-height);
  aspect-ratio: var(--hsc-thumb-ratio);
  overflow: hidden;
  border: var(--hsc-thumb-line) solid transparent;
  border-radius: var(--hsc-thumb-radius);
  opacity: var(--hsc-thumb-idle-opacity);
  scale: var(--hsc-thumb-idle-scale);
  cursor: pointer;
  transition: opacity var(--hsc-thumb-duration) ease,
    border-color var(--hsc-thumb-duration) ease,
    scale var(--hsc-thumb-scale-duration) ease;
}

.hotspot-carousel__thumb[aria-selected="true"],
.hotspot-carousel__thumb[aria-current="true"] {
  border-color: var(--hsc-thumb-edge);
  opacity: 1;
  scale: 1;
}

.hotspot-carousel__thumb:hover {
  opacity: 1;
}

.hotspot-carousel__thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
}

/* ==========================================================================
   9. Slide motion
   --------------------------------------------------------------------------
   The incoming slide starts one full width away, on the side it is coming
   from, and travels home. The outgoing one holds still underneath: that is
   what makes it a slip rather than a push. Two frames are needed — one to
   place, one to release — and the script handles that with an attribute.
   ========================================================================== */

[data-hsc-ready] .hotspot-carousel__slide[data-hsc-state="incoming"] {
  translate: var(--hsc-travel, 0) 0;
}

[data-hsc-ready] .hotspot-carousel__slide[data-hsc-state="incoming"][data-hsc-run] {
  translate: 0 0;
  transition: translate var(--hsc-slide-duration) var(--hsc-slide-ease);
}

/* ==========================================================================
   10. Without the script
   --------------------------------------------------------------------------
   No data-hsc-ready means no JavaScript ran. The viewport becomes a native
   snap scroller: every slide is laid out side by side, the thumbnails are
   plain anchors that jump to one, and the arrows — which would need to know
   where we are — step aside. The pins leave the photograph and stack at the
   foot of the slide, where they can be read and opened without a position.
   ========================================================================== */

.hotspot-carousel:not([data-hsc-ready]) .hotspot-carousel__viewport {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: none;
}

.hotspot-carousel:not([data-hsc-ready]) .hotspot-carousel__viewport::-webkit-scrollbar {
  display: none;
}

.hotspot-carousel:not([data-hsc-ready]) :is(.hotspot-carousel__arrow, .hotspot-carousel__counter, .hotspot-carousel__play) {
  display: none;
}

.hotspot-carousel:not([data-hsc-ready]) .hotspot-carousel__hotspots {
  position: absolute;
  inset: auto var(--hsc-inset)
    calc(var(--hsc-thumb-height) + var(--hsc-rail-inset) * 2 + var(--hsc-rail-pad) * 2 + var(--hsc-inset))
    var(--hsc-inset);
  display: flex;
  flex-wrap: wrap;
  gap: var(--hsc-thumb-gap);
  align-items: flex-end;
}

.hotspot-carousel:not([data-hsc-ready]) .hotspot-carousel__hotspot {
  position: relative;
}

/* Here the containing block is the pin's row, so a percentage does mean
   something again, and it is what keeps the card inside the frame. */
.hotspot-carousel:not([data-hsc-ready]) .hotspot-carousel__panel {
  position: absolute;
  bottom: calc(var(--hsc-pin-size) + var(--hsc-panel-gap));
  left: 0;
  max-width: 100%;
}

/* ==========================================================================
   11. Fallbacks
   ========================================================================== */

/* --- focus, always visible ---------------------------------------------- */
.hotspot-carousel :is(a, button, summary, [tabindex]):focus-visible {
  outline: var(--hsc-focus-width) solid var(--hsc-focus-colour);
  outline-offset: var(--hsc-focus-offset);
}

.hotspot-carousel__panel :is(a, button):focus-visible {
  outline-color: var(--hsc-focus-colour-light);
}

/* --- reduced motion -----------------------------------------------------
   Delays go to zero along with durations: a delay is movement spread over
   time. What is a picture rather than a movement — the scrim, the veil, the
   dimming of an idle thumbnail — stays exactly as it was.
   --------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .hotspot-carousel {
    --hsc-slide-duration: 1ms;
    --hsc-pin-in-duration: 1ms;
    --hsc-pin-in-delay: 0ms;
    --hsc-pin-in-step: 0ms;
    --hsc-pin-icon-duration: 1ms;
    --hsc-panel-duration: 1ms;
    --hsc-thumb-duration: 1ms;
    --hsc-thumb-scale-duration: 1ms;
  }

  .hotspot-carousel__pulse {
    animation: none;
    opacity: var(--hsc-pulse-from-opacity);
    scale: 1;
  }

  .hotspot-carousel__rail,
  .hotspot-carousel:not([data-hsc-ready]) .hotspot-carousel__viewport {
    scroll-behavior: auto;
  }
}

/* --- forced colours -----------------------------------------------------
   In a forced palette the veils and blurs carry nothing, so the controls
   fall back to a plain outlined shape the system can colour itself.
   --------------------------------------------------------------------- */
@media (forced-colors: active) {
  .hotspot-carousel :is(
    .hotspot-carousel__counter,
    .hotspot-carousel__arrow,
    .hotspot-carousel__play,
    .hotspot-carousel__pin,
    .hotspot-carousel__rail-card,
    .hotspot-carousel__panel
  ) {
    border: 1px solid CanvasText;
    background: Canvas;
    color: CanvasText;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }

  .hotspot-carousel__caption-band {
    background: Canvas;
  }

  .hotspot-carousel__caption,
  .hotspot-carousel__panel :is(p, a, button) {
    color: CanvasText;
    text-shadow: none;
  }

  .hotspot-carousel__panel-action {
    border: 1px solid CanvasText;
  }

  .hotspot-carousel__thumb {
    opacity: 1;
    scale: 1;
  }

  .hotspot-carousel__thumb[aria-selected="true"],
  .hotspot-carousel__thumb[aria-current="true"] {
    border-color: Highlight;
  }

  .hotspot-carousel__pulse {
    animation: none;
    opacity: 0;
  }
}

/* --- no backdrop-filter --------------------------------------------------
   Without the blur the veils must carry the contrast on their own, or white
   text lands on a bright photograph.
   --------------------------------------------------------------------- */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .hotspot-carousel {
    --hsc-veil: rgb(0 0 0 / 0.68);
    --hsc-rail-face: rgb(0 0 0 / 0.72);
  }
}
