/* ---------------------------------------------------------------------------
   Photography portfolio — single stylesheet for both page types.

   Mobile-first: base rules target small viewports, anything larger is either a
   min-width media query or a fluid mechanism that needs no query at all.
   Monochrome only — black, white and greys, no colour anywhere.
   --------------------------------------------------------------------------- */

/* --- Tokens --------------------------------------------------------------- */

:root {
  /* Dark by default; photographs sit better against a near-black surround and
     it is the more common convention for a gallery. Light mode below is a
     straight inversion for anyone whose OS asks for it. */
  color-scheme: dark light;

  --bg: #0c0c0c;
  --bg-photo: #000;      /* the letterbox surround on the detail page */
  --fg: #f2f2f2;
  --fg-dim: #8f8f8f;
  --rule: #262626;
  --tile-bg: #1a1a1a;    /* shows through while an image is still loading */

  --page-pad: clamp(1rem, 3vw, 2rem);
  --ease: cubic-bezier(0.2, 0, 0, 1);
}

@media (prefers-color-scheme: light) {
  :root {
    --bg: #fbfbfb;
    --bg-photo: #fff;
    --fg: #111;
    --fg-dim: #6b6b6b;
    --rule: #e2e2e2;
    --tile-bg: #ededed;
  }
}

/* --- Reset ---------------------------------------------------------------- */

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  /* The OS default stack, per the spec — no webfont is downloaded. */
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  -webkit-text-size-adjust: 100%;
}

img {
  display: block;
  max-width: 100%;
}

/* <picture> is only a wrapper for format negotiation and should never form a
   box of its own — without this it sits between the layout container and the
   <img> as an inline element, breaking percentage heights on the grid tile and
   centring on the detail page. */
picture {
  display: contents;
}

/* `display: contents` above promotes the picture's children into whatever
   layout the picture itself sat in — and that includes the <source> elements,
   which the UA stylesheet does not hide. They render nothing, but they are
   still boxes: in a grid they claim tracks of their own and push the photograph
   out of the centre. Hiding them leaves the <img> as the only item. */
picture > source {
  display: none;
}

a {
  color: inherit;
}

:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 3px;
}

/* --- Homepage: header ----------------------------------------------------- */

.site-header {
  padding: var(--page-pad);
  padding-bottom: calc(var(--page-pad) * 0.75);
}

.site-title {
  margin: 0;
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

.site-title a {
  text-decoration: none;
}

.collection-name {
  margin: 0.375rem 0 0;
  font-size: 0.8125rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-dim);
}

/* --- Collection navigation ------------------------------------------------ */

/* Links to the other collections, each with an optional small cover thumbnail.
   Same tonal language as the grid: no colour, hover is an opacity dip. */
.collection-nav {
  margin-top: 1rem;
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 1.5rem;
}

.collection-link {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--fg-dim);
  transition: color 200ms var(--ease), opacity 200ms var(--ease);
}

.collection-link img {
  width: 3.25rem;
  height: 4rem; /* the grid's 4:5 crop, in miniature */
  object-fit: cover;
  background: var(--tile-bg);
}

@media (hover: hover) {
  .collection-link:hover {
    color: var(--fg);
  }

  .collection-link:hover img {
    opacity: 0.78;
  }
}

/* --- Homepage: grid ------------------------------------------------------- */

.grid {
  list-style: none;
  margin: 0;
  padding: var(--page-pad);
  padding-top: 0;

  display: grid;
  /* Fluid column count: the browser fits as many columns as the viewport
     allows, with no breakpoints involved. The 220px floor is the one real
     tuning knob — it lands 1 column at 375px, 3 at 768px and 5 at 1440px.
     Worth re-checking against real photographs on an actual phone.
     min(220px, 100%) keeps a single column from overflowing very narrow
     viewports, where 220px + padding would exceed the available width. */
  grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr));
  gap: clamp(0.75rem, 1.6vw, 1.25rem);
}

.tile {
  display: block;
  /* Uniform 4:5 portrait crop for every thumbnail regardless of the source
     photo's own proportions. Deliberate trade-off: a clean, even grid at the
     cost of not showing each photo's real framing until the detail page. */
  aspect-ratio: 4 / 5;
  background: var(--tile-bg);
  overflow: hidden;
}

.tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: opacity 300ms var(--ease), transform 400ms var(--ease);
}

/* Hover/focus feedback has to be tonal rather than chromatic on a monochrome
   site, so it is a small lift in scale plus a dip in opacity. Pointer query
   keeps it off touch devices, where :hover sticks after a tap. */
@media (hover: hover) {
  .tile:hover img {
    opacity: 0.78;
    transform: scale(1.02);
  }
}

.tile:focus-visible {
  outline-offset: 2px;
}

/* --- Photo detail page ---------------------------------------------------- */

/* Two rows: the control bar, then everything left over for the photograph.
   The bar is a real row rather than a fixed overlay, so the space it takes is
   space the photograph was never offered — nothing is ever drawn on top of the
   picture, at any viewport size. */
.photo-page {
  background: var(--bg-photo);
  /* 100vh first as the fallback, then 100dvh where supported. Without the
     dynamic unit the image jumps or gets clipped on mobile as the address bar
     shows and hides; without the static fallback, older browsers get nothing. */
  height: 100vh;
  height: 100dvh;
  display: grid;
  /* minmax(0, …) rather than a bare 1fr: a bare fr track keeps an automatic
     minimum of its content, so the row would grow to fit the image instead of
     the image shrinking to fit the row, and the percentage max-height below
     would have no definite height to resolve against. */
  grid-template-rows: auto minmax(0, 1fr);
  overflow: hidden;
}

.photo-page img {
  /* Letterboxed into the second row: the whole frame is always visible, scaled
     to fit, centred, with the background filling whatever is left over. The
     percentages resolve against the row, which is the viewport minus the bar. */
  grid-row: 2;
  justify-self: center;
  align-self: center;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
}

/* --- Photo detail page: navigation ---------------------------------------- */

/* One row across the top: Index on the left, then the counter and the two
   arrows grouped on the right. Every control sits in the same band, above the
   photograph rather than on it, in the same order as the markup. */
.photo-nav {
  grid-row: 1;
  /* Keep clear of notches and rounded corners. */
  padding: max(0.375rem, env(safe-area-inset-top)) max(0.5rem, env(safe-area-inset-right))
    0.375rem max(0.5rem, env(safe-area-inset-left));
  display: flex;
  align-items: center;
  gap: 0.125rem;
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* The only thing separating the left anchor from the right-hand group. */
.nav-index {
  margin-right: auto;
}

.photo-nav a,
.photo-nav .counter {
  /* 40px touch target rather than the usual 44: these are a secondary route on
     a phone, where the swipe gesture in photo.js is the primary one. */
  min-height: 40px;
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  padding: 0 0.5rem;
  color: var(--fg);
  text-decoration: none;
  transition: opacity 200ms var(--ease);
}

.photo-nav .counter {
  color: var(--fg-dim);
  font-variant-numeric: tabular-nums;
}

@media (hover: hover) {
  .photo-nav a:hover {
    opacity: 0.65;
  }
}

/* --- 404 ------------------------------------------------------------------ */

.notice {
  min-height: 100vh;
  min-height: 100dvh;
  display: grid;
  place-content: center;
  gap: 1.25rem;
  padding: var(--page-pad);
  text-align: center;
}

.notice p {
  margin: 0;
  color: var(--fg-dim);
}

.notice a {
  justify-self: center;
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 0.75rem 1rem;
  border: 1px solid var(--rule);
  text-decoration: none;
}

/* --- View transitions ----------------------------------------------------- */

/* Cross-document transitions. Chromium runs the morph; every other browser
   ignores this block and performs an ordinary instant navigation, which is the
   intended degrade path and needs no extra code. */
@view-transition {
  navigation: auto;
}

/* The photo's own group is what carries the morph — the shared
   view-transition-name is set inline on the <img> in both documents. Note the
   grid thumbnail is cropped (cover, 4:5) while the detail image is uncropped
   (contain), so the browser interpolates between two differently-framed boxes:
   it reads as a resize-and-reveal, with the cropped-off parts of the frame
   coming into view partway through, rather than a clean match-cut. */
::view-transition-group(*) {
  animation-duration: 340ms;
  animation-timing-function: var(--ease);
}

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 220ms;
}

@media (prefers-reduced-motion: reduce) {
  @view-transition {
    navigation: none;
  }

  .tile img {
    transition: none;
  }
}
