/* Photos page: the gallery, the film grain and the lightbox.
   Loaded by photos.html only; base.css and styles.css come first. */

.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: var(--space-8);
  padding: 0 0 var(--space-10);
}

.gallery__item {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  padding: 0;
  border: 0;
  background: var(--frame-bg);
  overflow: hidden;
  position: relative;
  cursor: pointer;
  transition: transform var(--dur-press) var(--ease-out);
}

.gallery__img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  /* One duration for the three layers that answer the same hover (the crop,
     the grade and the vignette below), so they read as a single move rather
     than three that finish at different moments. */
  transition: transform .22s var(--ease-out), filter .22s ease;
}

/* The photos carry no ring of their own: the site-wide focus outline is turned
   off here so nothing frames a thumbnail when it is clicked or tabbed to. */
.gallery__item:focus,
.gallery__item:focus-visible { outline: none; }

@media (hover: hover) and (pointer: fine) {
  .gallery__item:hover .gallery__img { transform: scale(1.04); filter: none; }
  .gallery__item:hover::before { opacity: 0; }
}

.photos-page {
  /* colour grade: soft contrast, faded saturation, warm sepia tint */
  --film-grade: contrast(1.06) saturate(0.9) sepia(0.14) brightness(1.04) hue-rotate(-6deg);
  --film-grain: 0.09;
}

.gallery__img {
  filter: var(--film-grade);
}

.gallery__item::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  box-shadow: inset 0 0 26px 3px rgba(25, 18, 10, 0.26);
  transition: opacity .22s ease;
}

.film-grain {
  position: fixed;
  inset: 0;
  z-index: 300;
  pointer-events: none;
  opacity: var(--film-grain, 0.09);
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 140px 140px;
}

@media (prefers-reduced-motion: no-preference) {
  .gallery__item.reveal {
      filter: blur(14px);
    }
  .gallery__item.reveal.is-in {
      filter: blur(0);
      transition: opacity .8s ease, transform .8s ease, filter .8s ease;
    }
}

.gallery__cap {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: var(--space-5) var(--space-3) var(--space-2);
  color: var(--ink-invert);
  font-family: var(--font-mono);
  font-size: var(--mono-2xs);
  font-weight: 500;
  letter-spacing: .2px;
  line-height: var(--leading-snug);
  text-align: right;
  background: linear-gradient(to top, rgba(0, 0, 0, .62), rgba(0, 0, 0, 0));
  opacity: 0;
  transition: opacity .2s ease;
  pointer-events: none;
  z-index: 2;}

.gallery__item:focus-visible .gallery__cap { opacity: 1; }

@media (hover: hover) and (pointer: fine) {
  .gallery__item:hover .gallery__cap { opacity: 1; }
}

/* Nothing to hover on a touch screen: the captions simply stay out. The crop
   and the grade are gated positively above, so there is nothing left to undo
   here. */
@media (hover: none) {
  .gallery__cap { opacity: 1; }
}


.lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  padding: var(--space-16) var(--space-5) var(--space-6);
  background: rgba(241, 242, 246, .96); /* opaque fallback without backdrop-filter */
  opacity: 0;
  visibility: hidden;
  transition: opacity .25s ease, visibility .25s ease;
}

/* Let the gallery show through, blurred, behind the zoomed photo */
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .lightbox {
    background: rgba(241, 242, 246, .75);
    -webkit-backdrop-filter: blur(20px) saturate(115%);
    backdrop-filter: blur(20px) saturate(115%);
  }
}

.lightbox.is-open { opacity: 1; visibility: visible; }

/* The photo itself, blown up and blurred, stands in for the backdrop.
   photos.js only swaps its src; everything else is done here. */
.lightbox__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: blur(34px) saturate(150%) brightness(.62);
  transform: scale(1.25); /* pushes the blurred edges out of frame */
  opacity: .62;
  pointer-events: none;
}

.lightbox__btn { z-index: 2; }

/* The photo grows out of the thumbnail that was clicked rather than fading in
   at the middle of the screen: photos.js hands over that thumbnail's position
   as --lb-origin, so the picture keeps its place in the grid on the way up,
   and gives it back the same way on the way down. */
.lightbox__figure {
  position: relative;
  z-index: 1;
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-end;  max-width: 100%;
  transform-origin: var(--lb-origin, 50% 50%);
  transform: scale(.96);
  opacity: 0;
  transition: transform .28s var(--ease-out), opacity .2s ease;
}

.lightbox.is-open .lightbox__figure {
  transform: none;
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .lightbox__figure { transform: none; transition: opacity .2s ease; }
}

/* Shrink-wrapped around the picture, so the stand-in below can be pinned to
   exactly the box the picture will occupy. Nothing that sizes the picture moves
   out of .lightbox__img: the swipe area and the short-screen rule further down
   keep working off the same element they always did. */
.lightbox__frame {
  position: relative;
  display: block;
  width: fit-content;
  /* Holds the blurred stand-in inside the card. Its own shadow lives here too,
     since overflow clips what is inside an element and not the shadow it casts. */
  overflow: hidden;
  box-shadow: 0 10px 28px rgba(20, 20, 30, .16);
}

.lightbox__img {
  display: block;
  max-width: min(1100px, calc(100vw - 40px));
  max-height: calc(100vh - 118px);  width: auto;
  height: auto;
  object-fit: contain;
  background: var(--card);
  border: 12px solid var(--card);
  touch-action: none;  cursor: grab;
  user-select: none;
  -webkit-user-drag: none;
  /* Above the stand-in, which is positioned and would otherwise paint over it */
  position: relative;
  z-index: 1;
  opacity: 0;
  transition: opacity .3s var(--ease-out);
}

/* photos.js sets this the moment the picture has decoded */
.lightbox__img.is-loaded { opacity: 1; }

/* Inset by the frame's own border, so it fills the picture's box and not the
   card around it. Scaled a touch so the blur has nothing to fade out against. */
.lightbox__proxy {
  position: absolute;
  inset: 12px;
  /* Stated rather than left to the insets: a replaced element with width auto
     falls back on its intrinsic size instead of stretching, so the thumbnail
     would sit at 240px in the corner. */
  width: calc(100% - 24px);
  height: calc(100% - 24px);
  object-fit: cover;
  filter: blur(14px);
  transform: scale(1.05);
}

@media (prefers-reduced-motion: reduce) {
  .lightbox__img { transition: none; }
}

.lightbox__img:active { cursor: grabbing; }

/* The backdrop is the photo itself, so what is behind the caption changes with
   every picture: a drop shadow, rather than a plate, is what keeps it readable
   whatever it lands on. */
.lightbox__cap {
  margin: var(--space-2) 2px 0;
  max-width: 100%;
  color: var(--ink-invert);
  font-family: var(--font-mono);
  font-size: var(--mono-sm);
  letter-spacing: .3px;
  text-align: right;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .55), 0 0 12px rgba(0, 0, 0, .4);
  /* Held back until the picture has settled: the delay matches the figure's
     own transition, so the name arrives once there is something to name. */
  opacity: 0;
  transform: translateY(4px);
  transition: opacity .25s ease .26s, transform .25s var(--ease-out) .26s;
}

.lightbox.is-open .lightbox__cap {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .lightbox__cap { transform: none; transition: opacity .2s ease .1s; }
}

.lightbox__btn {
  position: absolute;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 0;
  border-radius: 50%;
  background: rgba(0, 0, 0, .06);
  color: var(--ink);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), transform var(--dur-press) var(--ease-out);
}

.lightbox__btn:hover { background: rgba(0, 0, 0, .12); }

.lightbox__btn--close { top: 20px; right: 20px; }

@media (prefers-reduced-motion: reduce) {
  .gallery__img,
    .lightbox { transition: none; }
  .gallery__item:hover .gallery__img { transform: none; }
}

@media (max-height: 500px) {
  .lightbox { padding: var(--space-12) var(--space-4) var(--space-4); }
  .lightbox__img { max-width: calc(100vw - 32px); max-height: calc(100vh - 92px); }
  .lightbox__btn--close { top: 12px; right: 12px; }
}

/* Mobile overrides: kept after the base rules above so they win the cascade */
@media (max-width: 560px) {
  .gallery {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-3);
  }
  .gallery__cap { display: none; }
}
