/* ===========================================================================
   Photography portfolio.
   The photographs are the design; the chrome stays out of the way.
   =========================================================================== */

:root {
  --paper: #ffffff;
  --ink: #111213;
  --dim: #75767a;
  --rule: #e8e7e4;

  --pad: clamp(20px, 2.2vw, 40px); /* page padding  */
  /* Grid gutter: floor, how fast it grows with the viewport, ceiling. The
     ceiling has to sit above the floor or clamp() just returns the floor and
     the gutter never moves. app.js reads this back resolved and feeds it to the
     row breaker, so changing it re-fits the rows on its own. */
  --gap: clamp(15px, 1vw, 22px);
  --stage-pad: clamp(15px, 2.2vw, 34px);
  /* Side gutters in the viewer. The chevrons fill these exactly, so a wide
     mat (a panorama) can never slide underneath one and hide it. */
  --lb-gutter: clamp(40px, 5vw, 74px);

  --serif: 'Cormorant', serif;

  --ease: cubic-bezier(0.3, 0.7, 0.25, 1);
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  scrollbar-gutter: stable;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  padding: 0 var(--pad) var(--pad);
  background: var(--paper);
  color: var(--ink);
  font-family: var(--serif);
  font-variant-numeric: oldstyle-nums;
  font-size: 16px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
}

a {
  color: inherit;
  text-decoration: none;
}
a:hover {
  text-decoration: underline;
  text-underline-offset: 0.22em;
  text-decoration-thickness: 0.5px;
}

button {
  font: inherit;
  color: inherit;
  font-variant-numeric: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

:focus-visible {
  outline: 1px solid var(--ink);
  outline-offset: 3px;
}

.skip {
  position: absolute;
  left: -9999px;
}
.skip:focus {
  left: var(--pad);
  top: var(--pad);
  z-index: 100;
  background: var(--paper);
  padding: 0.4rem 0.7rem;
  border: 1px solid var(--rule);
}

/* ─── header ─────────────────────────────────────────────────────────────── */

.top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  padding: calc(var(--pad) * 1.7) 0 calc(var(--pad));
}

.mark {
  display: flex;
  align-items: center;
  font-size: 1.3rem;
  letter-spacing: 0.005em;
  text-transform: lowercase;
  white-space: nowrap;
}
.mark:hover {
  text-decoration: none;
}

/* An image wordmark (src/signature.*) drops into .mark in place of the text. */
.sig {
  display: block;
  width: auto;
  height: 4rem;
}

.nav {
  display: flex;
  align-items: baseline;
  gap: clamp(0.85rem, 1.7vw, 1.6rem);
  font-size: 1.3rem;
  font-weight: 500;
}
.nav-link {
  color: var(--ink);
  transition: color 0.18s ease;
  white-space: nowrap;
}
.nav-link:hover {
  color: var(--ink);
}
/* Underlines whichever page you are on — Highlights at the root, About at
   /about/. The templates carry the aria-current that selects it. */
.nav-link[aria-current='page'] {
  text-decoration: underline;
  text-underline-offset: 0.22em;
  text-decoration-thickness: 0.5px;
}

.ig {
  position: relative;
  top: 2px;
}
.ig:hover {
  text-decoration: none;
}

/* ─── grid ───────────────────────────────────────────────────────────────── */

/* Pre-JS fallback: a plain flex wrap, so the pre-rendered markup already looks
   like a gallery. app.js replaces this with measured justified rows. */
.grid {
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap);
}
.grid > .cell {
  flex: 1 1 clamp(200px, 24vw, 330px);
}
.grid > .cell img {
  width: 100%;
  height: auto;
}

/* After measurement: a column of exactly-fitted rows.

   column-gap on .grid is the value app.js measures and feeds to the row
   breaker, so it must stay equal to the gap on .row. row-gap is free to differ
   and is opened up to separate a caption from the photographs beneath it. */
.grid.laid {
  flex-direction: column;
  flex-wrap: nowrap;
  column-gap: var(--gap);
  row-gap: calc(var(--gap) * 1.2);
}
.grid.laid .row {
  display: flex;
  flex-wrap: nowrap;
  align-items: flex-start;
  gap: var(--gap);
}
/* The figure grows for its caption; only the image box is pinned to the row
   height, which app.js sets inline. */
.grid.laid .cell {
  flex: 0 0 auto;
  min-width: 0;
}
.grid.laid .cell img {
  width: 100%;
  height: 100%;
}

.cell {
  margin: 0;
}

/* First-view reveal. app.js adds .revealing to the grid, and .seen to each cell
   as it scrolls in — and only ever once, so coming back up the page finds every
   frame already in place. Nothing here applies unless that class is set, so the
   pre-rendered page stays visible for a visitor who never runs the script or
   who asked for less motion. */
.grid.revealing .cell {
  opacity: 0;
  transform: translateY(16px);
  transition:
    opacity 0.65s var(--ease),
    transform 0.65s var(--ease);
}
.grid.revealing .cell.seen {
  opacity: 1;
  transform: none;
}

.shot {
  display: block;
  width: 100%;
  height: 100%;
  cursor: zoom-in;
  overflow: hidden; /* holds the hover zoom inside the cell */
  -webkit-tap-highlight-color: transparent;
}
.shot picture {
  display: block;
  width: 100%;
  height: 100%;
}

.shot img {
  display: block;
  /* The cell box is computed from this frame's own aspect ratio, so `cover`
     never actually crops — it only absorbs sub-pixel rounding. */
  object-fit: cover;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  transition:
    transform 0.5s var(--ease),
    filter 0.3s ease;
}
/* Hover/focus: ease in a touch and dim the frame, rather than lightening it. */
.shot:hover img,
.shot:focus-visible img {
  transform: scale(1.045);
  filter: brightness(0.82);
}

/* Place and country under each frame, tight to the photograph above it and
   well clear of the row below. Blank for anything not yet captioned, but it
   still holds its line so the row stays even. Truncated rather than wrapped,
   for the same reason; the full text is in the lightbox. */
.caption {
  color: #343434;
  font-size: 17px;
  font-weight: 650;
  text-align: left;

  margin-top: 0.5rem;
  min-height: 1.25em;
  line-height: 1.25;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ─── footer ─────────────────────────────────────────────────────────────── */

.foot {
  display: flex;
  justify-content: space-between;
  gap: 1.5rem;
  flex-wrap: wrap;
  margin-top: calc(var(--pad));
  padding-top: calc(var(--pad));
  border-top: 1px solid var(--rule);
  color: var(--dim);
  font-size: 0.9rem;
}

/* ─── About page ─────────────────────────────────────────────────────────── */

.about {
  display: grid;
  grid-template-columns: clamp(200px, 32vw, 360px) 1fr;
  gap: clamp(1.5rem, 5vw, 4rem);
  align-items: start;
  max-width: 60rem;
  margin-top: clamp(1rem, 5vw, 3.5rem);
}
@media (max-width: 720px) {
  .about {
    grid-template-columns: 1fr;
    gap: 1.5rem;
    max-width: 32rem;
  }
}

.about-portrait {
  min-width: 0;
}
@media (max-width: 720px) {
  .about-portrait {
    max-width: 18rem;
  }
}

.portrait {
  display: block;
  width: 100%;
  height: auto;
}
.portrait.is-placeholder {
  display: grid;
  place-items: center;
  aspect-ratio: 4 / 5;
  background: repeating-linear-gradient(
    -45deg,
    var(--rule) 0 1px,
    transparent 1px 12px
  );
  border: 1px solid var(--rule);
  color: var(--dim);
  font-style: italic;
}

.about-title {
  margin: 0 0 1rem;
  font-size: clamp(1.6rem, 3.5vw, 2.3rem);
  font-weight: 500;
  line-height: 1.1;
}
.about-text p:not(.about-links) {
  margin: 0 0 1rem;
  max-width: 34rem;
  font-size: 1.15rem;
  line-height: 1.5;
  color: #2b2c2e;
}
.about-links {
  margin: 1.7rem 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: 1.3rem;
  font-size: 1.02rem;
  color: var(--dim);
}
.about-links a:hover {
  color: var(--ink);
}

/* ─── lightbox ───────────────────────────────────────────────────────────── */

.lb {
  position: fixed;
  inset: 0;
  z-index: 70;
}
.lb[hidden] {
  display: none;
}
.lb-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(6, 7, 8, 0.74);
}

/* The page-wide focus ring is ink on white. Inside the viewer the ground is
   near-black, so it has to invert or a keyboard visitor cannot see it — except
   on the mat itself, which is white, where it inverts back. */
.lb :focus-visible {
  outline-color: rgba(255, 255, 255, 0.9);
}
.mat :focus-visible {
  outline-color: var(--ink);
}

.lb-stage,
.lb-ghost {
  position: absolute;
  inset: 0;
  display: grid;
  /* minmax(0, 1fr), not the default auto: an auto track grows to its content's
     min-content width, and a tall portrait frame capped by max-height reports a
     min-content wider than a phone screen. The track would then overflow and
     take the mat with it. */
  grid-template-columns: minmax(0, 1fr);
  place-items: center;
  padding: var(--stage-pad) var(--lb-gutter);
}
.lb-stage {
  z-index: 1;
}
.lb-ghost {
  z-index: 2;
  pointer-events: none;
}

/* The mat: a white print panel with the frame inside and a caption plate under. */
.mat {
  margin: 0;
  min-width: 0;
  background: var(--paper);
  padding: clamp(13px, 1.5vw, 24px);
  box-shadow: 0 26px 80px rgba(0, 0, 0, 0.38);
  max-width: min(1500px, 100%);
  display: flex;
  flex-direction: column;
  gap: clamp(12px, 1.3vw, 20px);
}

.mat-img {
  display: flex;
  justify-content: center;
}
.mat-img picture {
  display: contents;
}
/* The image caps its own height, and its width follows the aspect ratio — so
   the mat shrink-wraps to the photograph, never wider. The cap has to be an
   absolute length for that: a percentage of the box is circular while the box
   is still being measured, and the mat blows out to full width. */
.mat img {
  display: block;
  max-width: 100%;
  max-height: calc(100svh - 350px);
  width: auto;
  height: auto;
  object-fit: contain;
}

/* Vertical frames get a taller cap on anything but a phone — a portrait was
   sitting small in a sea of dark. app.js adds .tall when the frame is taller
   than it is wide. The reserve (~180px) is the stage padding plus the mat's
   own chrome and a two-to-three-line caption. */
@media (min-width: 641px) {
  .mat.tall img {
    max-height: calc(100svh - 350px);
  }
}

/* Baseline, not flex-end: the place name and the camera body are the first
   line of each column, so this sets them on one line however many lines follow
   underneath either side. */
.plate {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.5rem 1.6rem;
}
.plate-l,
.plate-r {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.plate-r {
  text-align: right;
}

.plate .place {
  font-size: 1.4rem;
  line-height: 1.25;
  font-weight: 500;
}
.plate .when {
  font-style: italic;
  color: #4a4b4e;
  font-size: 1.2rem;
}
.plate .body {
  font-size: 1.4rem;
  line-height: 1.25;
  font-weight: 500;
}
/* The lens line is a toggle: click it and the lens model is replaced in place
   by the exposure settings; click again to swap back. The two share this rule
   so the swap is seamless — same size, slant and colour, only the text changes.
   pre-wrap keeps the double spaces deriveSet() puts between the groups. */
.plate .lens,
.plate .set {
  font-style: italic;
  color: #4a4b4e;
  font-size: 1.2rem;
  white-space: pre-wrap;
}

.plate .lensline {
  display: flex;
  align-items: baseline;
  justify-content: flex-end;
  gap: 0.4rem;
  text-align: right;
}
button.lensline {
  align-self: flex-end;
  max-width: 100%;
}
.plate .lensline .set {
  display: none;
}
.plate .lensline.show-set .lens {
  display: none;
}
.plate .lensline.show-set .set {
  display: block;
}

.plate span:empty {
  display: none;
}

.lb-close {
  position: absolute;
  z-index: 4;
  top: clamp(8px, 1.5vh, 18px);
  right: clamp(10px, 1.6vw, 22px);
  font-size: 1.9rem;
  line-height: 1;
  color: rgba(255, 255, 255, 0.62);
  transition: color 0.18s ease;
}
.lb-close:hover {
  color: #fff;
}

.lb-nav {
  position: absolute;
  z-index: 4;
  top: 50%;
  transform: translateY(-50%);
  width: var(--lb-gutter);
  display: grid;
  place-items: center;
  padding: 1.2rem 0;
  color: rgba(255, 255, 255, 0.5);
  transition: color 0.18s ease;
}
.lb-nav:hover {
  color: #fff;
}
.lb-prev {
  left: 0;
}
.lb-next {
  right: 0;
}

body.lb-on {
  overflow: hidden;
}

@media (max-width: 640px) {
  .mat img {
    max-height: calc(100svh - 200px);
  }
  .plate {
    font-size: 0.95rem;
    gap: 1rem;
  }
  .lb-nav {
    display: none;
  } /* swipe instead */
  /* No chevrons here, so the gutters they reserved go back to the photograph. */
  .lb-stage,
  .lb-ghost {
    padding-left: var(--stage-pad);
    padding-right: var(--stage-pad);
  }
}

/* ─── reduced motion ─────────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  /* Keep the hover dim, drop the zoom. */
  .shot:hover img,
  .shot:focus-visible img {
    transform: none;
  }
}
