/* ==============================================================================
   THE PHOTO VIEWER (the <dialog> of blocks/gallery.html, ADR-057)

   A project's photos one at a time over the whole window, opened by
   static/js/photo-viewer.js with showModal(): a bar with the counter and
   the buttons, the stage with the photo whole and an arrow at each side,
   and the strip of thumbnails under it.

   Dark in both modes and in every palette: a photo is looked at on
   black, so the colours here are the viewer's own and not the page's.
   The type, the sizes and the focus ring are still the site's tokens.
   ============================================================================== */

.viewer {
  /* The black the photos sit on, a step off pure black so the edge of a
     night photo can still be told from the stage. */
  --viewer-paper: #0b0b0b;
  /* The words and the icons: white at 92 %, 17:1 on the paper. */
  --viewer-ink: rgb(255 255 255 / 0.92);
  /* The disc behind an arrow, so the arrow reads over a light photo. */
  --viewer-veil: rgb(0 0 0 / 0.45);
  /* The site's ring is a dark blue in the light mode, lost on black:
     here it is the ink's white in both. */
  --focus-ring: #fff;
  /* The WCAG 2.5.5 target for every button of the viewer. */
  --viewer-target: 44px;
  /* The height of a thumbnail; its width follows the photo's shape. */
  --viewer-thumb: 56px;

  /* The dialog is the window: the user agent's centred box, its border,
     its padding and its size limits all go. */
  position: fixed;
  inset: 0;
  width: 100vw;
  max-width: none;
  height: 100vh;
  height: 100dvh;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background-color: var(--viewer-paper);
  color: var(--viewer-ink);
  font-family: var(--font-text);
  /* An overscroll inside the viewer must not pull the page under it. */
  overscroll-behavior: contain;
}

/* Everything in the dialog sits in this frame, which is also what goes
   full screen (photo-viewer.js): it paints its own paper, since on its
   own in full screen the dialog's is not behind it. */
.viewer__frame {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  width: 100%;
  height: 100%;
  background-color: var(--viewer-paper);
}

/* The window is already covered; the backdrop only matters for the
   instant before the first paint. */
.viewer::backdrop {
  background-color: var(--viewer-paper);
}

/* While the viewer is open the page under it stays put: a modal dialog
   makes it inert, but a wheel or a finger would still scroll it. */
html:has(.viewer[open]) {
  overflow: hidden;
}

/* --------------------------------------------------------------------------
   The bar: the counter on the left, the buttons on the right
   -------------------------------------------------------------------------- */

.viewer__bar {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-2);
}

.viewer__counter {
  margin: 0 auto 0 var(--space-2);
  font-size: var(--fs-14);
  letter-spacing: var(--tracking-05);
  /* Figures of one width, so "9 / 10" to "10 / 10" moves nothing. */
  font-variant-numeric: tabular-nums;
}

.viewer__button {
  display: grid;
  place-items: center;
  width: var(--viewer-target);
  height: var(--viewer-target);
  padding: 0;
  border: 0;
  border-radius: 50%;
  background-color: transparent;
  color: inherit;
  cursor: pointer;
  transition: background-color var(--transition-button);
}

.viewer__button:hover {
  background-color: rgb(255 255 255 / 0.14);
}

/* A face is a span with its icon and its hidden words; the hidden
   attribute has to win over the display set here. */
.viewer__face:not([hidden]) {
  display: grid;
  place-items: center;
}

/* --------------------------------------------------------------------------
   The stage: the photo whole, never cropped, and the two arrows
   -------------------------------------------------------------------------- */

.viewer__stage {
  position: relative;
  min-height: 0;
  /* A photo zoomed in is larger than the stage: what does not fit is
     cut here, not drawn over the bar and the thumbnails. */
  overflow: hidden;
  /* Sideways the finger is the viewer's (a swipe changes the photo);
     up, down and the pinch stay the browser's. */
  touch-action: pan-y pinch-zoom;
}

/* Zoomed in, the finger moves the photo in every direction and the
   browser takes none of it (photo-viewer.js sets the attribute). */
.viewer__stage[data-zoomed] {
  touch-action: none;
}

.viewer__slides {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}

.viewer__slide {
  position: absolute;
  inset: 0;
  animation: viewer-fade var(--transition-fade);
}

.viewer__slide[hidden] {
  display: none;
}

/* The picture gives its box to the image, so the image's is the
   slide's. */
.viewer__slide picture {
  display: contents;
}

/* The image takes the whole stage and object-fit draws the photo whole
   inside it, centred, with the paper showing on the two sides it does
   not reach. The limits of a grid cell would have made the box the
   photo's own, but a percentage max-height did not hold a square photo
   in Chromium, and this does everywhere. photo-viewer.js works out
   where the photo ends to tell a click on it from one beside it. */
.viewer__slide img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  /* lazy-fade.js keeps a waiting photo transparent; here the stage is
     black already and the slide fades as a whole. */
  opacity: 1;
  /* The zoom: photo-viewer.js writes the three properties on the image
     and takes them away at 1, so a photo nobody zoomed has no transform.
     The pan comes first so it is in pixels of the window, not of the
     enlarged photo. */
  transform: translate(var(--viewer-pan-x, 0), var(--viewer-pan-y, 0)) scale(var(--viewer-zoom, 1));
  /* A mouse drags the zoomed photo, not a ghost of the image file, and
     the drag selects nothing. */
  -webkit-user-drag: none;
  user-select: none;
}

.viewer__stage[data-zoomed] .viewer__slide img {
  cursor: grab;
}

.viewer__stage[data-dragging] .viewer__slide img {
  cursor: grabbing;
}

/* Zooming out with the photo already whole, zooming in at the limit. */
.viewer__button:disabled {
  opacity: 0.35;
  cursor: default;
}

.viewer__button:disabled:hover {
  background-color: transparent;
}

/* A photo coming in; main.css stops it for whoever asked for less
   motion. */
@keyframes viewer-fade {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

.viewer__arrow {
  position: absolute;
  top: 50%;
  translate: 0 -50%;
  background-color: var(--viewer-veil);
}

.viewer__arrow:hover {
  background-color: rgb(0 0 0 / 0.7);
}

.viewer__arrow--previous {
  left: var(--space-2);
}

.viewer__arrow--next {
  right: var(--space-2);
}

/* --------------------------------------------------------------------------
   The thumbnails: one row that scrolls sideways, the current one framed
   -------------------------------------------------------------------------- */

.viewer__thumbs {
  /* offsetLeft of a thumbnail is measured from here (photo-viewer.js). */
  position: relative;
  display: flex;
  gap: var(--space-1);
  margin: 0;
  padding: var(--space-2);
  overflow-x: auto;
  list-style: none;
  scrollbar-width: thin;
  scrollbar-color: rgb(255 255 255 / 0.3) transparent;
}

/* auto margins centre a row shorter than the window and, unlike
   justify-content: center, leave a longer one scrollable to its start. */
.viewer__thumbs li:first-child {
  margin-inline-start: auto;
}

.viewer__thumbs li:last-child {
  margin-inline-end: auto;
}

.viewer__thumb {
  display: block;
  padding: 0;
  border: 0;
  background-color: transparent;
  cursor: pointer;
  opacity: 0.5;
  transition: opacity var(--transition-button);
}

.viewer__thumb img {
  display: block;
  width: auto;
  height: var(--viewer-thumb);
}

.viewer__thumb:hover,
.viewer__thumb:focus-visible,
.viewer__thumb[aria-current="true"] {
  opacity: 1;
}

/* The current one, told apart by more than its brightness. */
.viewer__thumb[aria-current="true"] {
  outline: 2px solid var(--viewer-ink);
  outline-offset: 2px;
}

/* On a phone, upright or lying down, the strip would take a fifth of
   the window from the photo: it goes, and the arrows, the finger and the
   counter are enough. */
@media (max-width: 36em), (max-height: 30em) {
  .viewer__thumbs {
    display: none;
  }

  .viewer__frame {
    grid-template-rows: auto minmax(0, 1fr);
  }
}
