/* =========================================================================
   Enlargeable images — the "view larger" affordance and the lightbox.

   Opt-in per image with `"enlarge": true` in the content, because most
   figures hold nothing more at full size and offering to enlarge one that
   doesn't is a promise the page cannot keep.

   Markup: media() in scripts/study.mjs. Behaviour: assets/js/enlarge.js.
   Shipped only to pages that carry one — see usesEnlarge().
   ========================================================================= */

/* The wrapper is an <a> to the image file, so the block is a real link with a
   real destination before any script runs. */
.study-media__zoom {
  display: block;
  border-radius: var(--tile-radius);
  /* The native fallback, and it is what a reader gets if enlarge.js never
     runs. Only once the script is up does the pill replace it — see below. */
  cursor: zoom-in;
  transition: box-shadow .28s cubic-bezier(.16, 1, .3, 1);
}

/* Images lost their resting shadow, which is what frees this one to mean
   something: the lift IS the hover state now, rather than a decoration the
   figure wore all along.

   It sits on the link, whose radius matches the image's, so the shadow hugs
   the artwork's real rounded edge rather than a square box behind it. */
.study-media__zoom:hover { box-shadow: var(--shadow-media); }

/* Except on a `--ground` figure. There the figure is the visible surface — it
   carries the colour, the radius and `overflow: hidden` — and the link is a
   full-width box behind a narrower drawing. A shadow on the link would be
   clipped away by the figure and, where it showed, would trace an edge the
   reader cannot see. The figure lifts instead; its own shadow is not clipped
   by its own overflow. */
.study-media--ground .study-media__zoom:hover { box-shadow: none; }
/* The transition is scoped to figures that actually carry the affordance, so
   a plain `--ground` diagram keeps no transition it will never run. */
.study-media--ground:has(.study-media__zoom) {
  transition: box-shadow .28s cubic-bezier(.16, 1, .3, 1);
}
.study-media--ground:has(.study-media__zoom:hover) { box-shadow: var(--shadow-media); }
.study-media__zoom:focus-visible {
  outline: 2px solid var(--ink); outline-offset: var(--space-3xs);
}

/* Inside a carousel there is no lift, and it is not a stylistic choice.
   `.car__track` is a scroll container, and a scroller clips on BOTH axes: set
   `overflow-x: auto` and `overflow-y` computes to auto with it. --shadow-media
   runs to a 50px blur at a 20px offset, so it needs roughly 60px of clearance
   on every side, and what it gets instead is a cut square at the slide's edge.

   The track cannot spare that room. Padding it moves every scroll-snap point
   and breaks the `--car-slide` width the whole strip is measured from. So the
   affordance is carried by the pill and the zoom cursor alone, which are the
   two parts a reader is looking at anyway. */
.car__slide .study-media__zoom:hover { box-shadow: none; }
.car__slide .study-media__zoom { transition: none; }

/* `cursor: none` is applied ONLY once the script has initialised and only
   where there is a real pointer to replace. Putting it in the base rule would
   leave a reader with no cursor at all whenever the JS failed to load, and a
   touch device has nothing to hide in the first place. */
@media (hover: hover) and (pointer: fine) {
  .js-enlarge .study-media__zoom { cursor: none; }
}

/* ------------------------------- the pill -------------------------------
   Follows the pointer. `position: fixed` and translated on the compositor —
   it moves on every pointermove, so it must never touch layout. */
/* TWO elements, and the split is the whole trick. The outer one carries the
   pointer position — its `transform` is rewritten every frame — so it must
   have no transition at all, or the pill would swim after the cursor instead
   of tracking it. The inner one carries the appearance and everything that
   animates. */
.enlarge-pill {
  position: fixed; top: 0; left: 0; z-index: 60;
  pointer-events: none;
  will-change: transform;
  /* Stated, not merely left at the 0s default — this element must never
     interpolate its position, whatever is added around it later. */
  transition: none;
}
.enlarge-pill__in {
  display: block;
  padding: var(--pad-pill);
  border-radius: var(--radius-pill);
  background: var(--ink); color: var(--card);
  font-size: var(--type-pill-size);
  font-weight: var(--type-pill-weight);
  letter-spacing: var(--type-pill-track);
  text-transform: uppercase; white-space: nowrap;
  /* Grows into place from the pointer's own corner rather than blinking on. */
  opacity: 0; transform: scale(.8);
  transform-origin: 0 0;
  transition: opacity .16s ease, transform .24s cubic-bezier(.16, 1, .3, 1);
}
.enlarge-pill.is-on .enlarge-pill__in { opacity: 1; transform: scale(1); }

/* ----------------------------- the lightbox -----------------------------
   A dialog over the page. The backdrop is the page's own dark, not black, so
   the overlay reads as the site dimming rather than as a separate surface. */
.lb {
  position: fixed; inset: 0; z-index: 70;
  display: grid; place-items: center;
  padding: var(--space-fluid-s);
  background: rgb(23 22 28 / .93);
  opacity: 0;
  transition: opacity .28s ease;
}
.lb.is-open { opacity: 1; }

/* The image rises INTO the backdrop rather than arriving with it, and leaves
   the same way. The two are on one timing curve but not one duration — the
   backdrop is the room going dark, the image is the thing being handed to
   you, and they should not land together. */
.lb img {
  max-width: 100%; max-height: 100%;
  width: auto; height: auto; display: block;
  border-radius: var(--radius-s);
  opacity: 0; transform: scale(.965) translateY(var(--space-xs));
  transition: opacity .24s ease, transform .34s cubic-bezier(.16, 1, .3, 1);
}
.lb.is-open img { opacity: 1; transform: none; }
.lb__close, .lb__cap, .lb__nav {
  opacity: 0; transition: opacity .2s ease .06s, box-shadow .2s ease;
}
.lb.is-open .lb__close, .lb.is-open .lb__cap, .lb.is-open .lb__nav { opacity: 1; }

/* Enlarging a carousel slide keeps the strip, so the overlay carries the same
   two arrows the strip has. They sit against the viewport edges rather than
   the image, because the image changes shape between slides and controls that
   move when the picture changes are controls a reader has to re-find. */
.lb__nav {
  position: absolute; top: 50%; translate: 0 -50%;
  width: 44px; height: 44px; padding: 0; border: 0;
  border-radius: var(--radius-round);
  display: grid; place-items: center;
  background: var(--card); color: var(--ink);
  box-shadow: var(--shadow-onmedia);
  cursor: pointer;
}
.lb__nav--prev { left: var(--space-m); }
.lb__nav--next { right: var(--space-m); }
.lb__nav:hover { box-shadow: var(--shadow-onmedia-hi); }
.lb__nav:focus-visible { outline: 2px solid var(--card); outline-offset: var(--space-3xs); }
.lb__nav[hidden] { display: none; }
.lb__close {
  position: absolute; top: var(--space-m); right: var(--space-m);
  width: 44px; height: 44px; padding: 0; border: 0;
  border-radius: var(--radius-round);
  display: grid; place-items: center;
  background: var(--card); color: var(--ink);
  box-shadow: var(--shadow-onmedia);
  cursor: pointer;
}
.lb__close:hover { box-shadow: var(--shadow-onmedia-hi); }
.lb__close:focus-visible { outline: 2px solid var(--card); outline-offset: var(--space-3xs); }

/* The caption repeats the figure's own, so the enlarged view still says what
   it is rather than becoming an unlabelled picture. */
.lb__cap {
  position: absolute; left: 0; right: 0; bottom: var(--space-m);
  margin-inline: auto; width: fit-content; max-width: 80%;
  padding: var(--space-2xs) var(--space-s);
  border-radius: var(--radius-pill);
  background: rgb(23 22 28 / .8); color: var(--card);
  font-size: var(--type-caption-size); line-height: var(--type-caption-line);
  text-align: center;
}

/* The pill is a cursor. Reduced motion does not mean a still cursor — it
   means nothing animates on its own — so only the fades are dropped. */
@media (prefers-reduced-motion: reduce) {
  .enlarge-pill__in, .lb, .lb img, .lb__close, .lb__cap, .lb__nav,
  .study-media__zoom, .study-media--ground:has(.study-media__zoom) { transition: none; }
  /* Without a transition the from-state would simply stick, so the states
     that animate in have to be neutralised rather than merely un-eased. */
  .lb img { opacity: 1; transform: none; }
}
