/* =============================================================
   Photo marquee + lightbox
   Loaded on the home page, above The Latest.

   The strip scrolls continuously and stops under the pointer.
   gallery.js clones the run and measures one lap into
   --marquee-lap; the movement itself is the animation below, so
   the compositor runs it rather than a timer on the main thread.

   Before the script runs — or if it never does — the viewport is
   a plain scrollable row, which is why overflow only becomes
   hidden once .is-ready is on.

   Colours come from the --paper/--ink tokens news.css defines on
   .news, so the section follows the theme toggle with the rest of
   the page.
   ============================================================= */

.gallery { position: relative; }

.gallery__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.25rem;
}

.gallery__intro { min-width: 0; }
.gallery__lede {
  margin: 0.35rem 0 0;
  color: var(--ink-2);
  font-size: 0.95rem;
  line-height: 1.5;
}

/* ---------- Viewport and track ---------- */

/* Scrollable until the script takes over, so the photos are reachable with no
   JavaScript. Once the strip is moving there is nothing to scroll: the track is
   several runs long and the animation does the travelling. */
.gallery__viewport {
  overflow-x: auto;
  scrollbar-width: none;
  /* Room for the focus ring, which a clipped overflow would otherwise cut. */
  padding: 4px;
  margin: -4px;
}
.gallery__viewport::-webkit-scrollbar { display: none; }
.gallery.is-ready .gallery__viewport { overflow: hidden; }

.gallery__track {
  display: flex;
  gap: 1rem;
  list-style: none;
  /* Never wrap: the clones have to sit in one line for the lap to be a
     horizontal distance at all. The track stays viewport-width and the runs
     simply overflow it — which is what keeps the percentage basis below
     resolving against the viewport instead of against the content. */
  flex-wrap: nowrap;
}

/* One lap ends on the frame the strip started with, so the loop is seamless
   and there is nothing to fade or snap. */
@keyframes gallery-marquee {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(calc(-1 * var(--marquee-lap)), 0, 0); }
}

.gallery.is-ready .gallery__track {
  animation: gallery-marquee var(--marquee-duration, 60s) linear infinite;
  will-change: transform;
}

/* The stop. :hover covers the pointer, which is what was asked for. The overlay
   pauses it too, so closing does not reveal a different set.

   is-held is the keyboard equivalent, set from the script rather than with
   :focus-within. Closing the lightbox hands focus back to the photo that opened
   it — correct, and it must not count as a visitor reading the strip, or a
   mouse user who opens one photo and closes it leaves the strip stopped for
   good with no way to start it again. */
.gallery.is-ready:hover .gallery__track,
.gallery.is-ready.is-held .gallery__track,
body.has-lightbox .gallery.is-ready .gallery__track { animation-play-state: paused; }

.gallery__item {
  flex: 0 0 clamp(15rem, 78vw, 20rem);
}

@media (min-width: 48rem) {
  /* Three across, less the two 1rem gaps between them. */
  .gallery__item { flex-basis: calc((100% - 2rem) / 3); }
}

.gallery__btn {
  display: block;
  width: 100%;
  padding: 0;
  border: 1px solid var(--edge);
  border-radius: 14px;
  overflow: hidden;
  background: var(--paper);
  box-shadow: var(--shadow-sm);
  cursor: zoom-in;
  transition: transform 0.25s var(--ease-std), box-shadow 0.25s ease;
}
.gallery__btn:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); }

/* height:auto is load-bearing, not tidiness. The width/height attributes on the
   <img> are presentation hints, so the markup's height wins over aspect-ratio
   unless height is auto — without this the slide is as tall as the thumbnail
   file and the row is a column of portraits. */
.gallery__img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  background: var(--paper-2);
}

/* A strip that never stops moving is the clearest case there is for honouring
   this. The animation comes off and the viewport goes back to being scrollable,
   so the photos stay reachable — the clones are harmless, just more of the same
   row to scroll through. .is-ready is named so this outranks the rule that
   turns the animation on, which a media query alone would not. */
@media (prefers-reduced-motion: reduce) {
  .gallery__btn:hover { transform: none; }
  .gallery.is-ready .gallery__track { animation: none; }
  .gallery.is-ready .gallery__viewport { overflow-x: auto; }
}

/* =============================================================
   Lightbox
   ============================================================= */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(1rem, 4vw, 3rem);
  background: rgba(4, 8, 16, 0.92);
  backdrop-filter: blur(4px);
  opacity: 0;
  visibility: hidden;
  /* visibility is discrete, so it is switched rather than interpolated: on
     close it is delayed until the fade has finished, on open it flips at once.
     Giving it a duration instead leaves what it paints mid-transition up to the
     engine, and the caption came and went depending on the timing. */
  transition: opacity 0.25s ease, visibility 0s linear 0.25s;
}
.lightbox.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.25s ease, visibility 0s linear 0s;
}

/* The page behind must not scroll while the overlay is up. */
body.has-lightbox { overflow: hidden; }

.lightbox__figure {
  margin: 0;
  max-width: min(100%, 72rem);
  max-height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
}

.lightbox__img {
  display: block;
  max-width: 100%;
  /* Leaves the caption and counter room at the bottom on short screens. */
  max-height: calc(100vh - 10rem);
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: 10px;
  box-shadow: 0 30px 80px -30px rgba(0, 0, 0, 0.9);
  transform: scale(0.97);
  transition: transform 0.25s var(--ease-std);
}
.lightbox.is-open .lightbox__img { transform: scale(1); }

/* Nothing inside the overlay may transition an inherited property. The
   reduced-motion block in style.css sets transition-duration on `*` with
   !important, and transition-property defaults to `all` — so every element in
   here that declares no transition of its own ends up animating the visibility
   it inherits from .lightbox, and stays hidden while the container is already
   visible. The buttons and the photo declare their own and are unaffected. */
.lightbox__figure,
.lightbox__caption,
.lightbox__text,
.lightbox__count,
.lightbox .icon { transition: none; }

.lightbox__caption {
  margin: 0;
  text-align: center;
  color: rgba(255, 255, 255, 0.75);
  font-size: 0.875rem;
  line-height: 1.5;
}
.lightbox__count { color: rgba(255, 255, 255, 0.5); font-variant-numeric: tabular-nums; }

/* ---------- Lightbox controls ---------- */
.lightbox__btn {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  cursor: pointer;
  transition: background 0.2s ease, transform 0.2s ease;
}
.lightbox__btn:hover { background: rgba(255, 255, 255, 0.2); }
.lightbox__btn .icon { width: 20px; height: 20px; }

.lightbox__close { top: clamp(0.75rem, 3vw, 1.5rem); right: clamp(0.75rem, 3vw, 1.5rem); }
.lightbox__prev  { left: clamp(0.5rem, 2vw, 1.5rem); top: 50%; transform: translateY(-50%); }
.lightbox__next  { right: clamp(0.5rem, 2vw, 1.5rem); top: 50%; transform: translateY(-50%); }
.lightbox__prev:hover, .lightbox__next:hover { transform: translateY(-50%) scale(1.06); }

/* .is-open has to be named here as well. A media query adds no specificity, so
   a bare `.lightbox` rule loses to `.lightbox.is-open` above and the open state
   would keep its transitions — including the one on visibility, which is what
   decides whether the overlay paints at all. */
@media (prefers-reduced-motion: reduce) {
  .lightbox, .lightbox.is-open, .lightbox__img, .lightbox__btn { transition: none; }
  .lightbox__img { transform: none; }
}
