/* ============================================================
   DESIGN TOKENS
   Light theme, matching your existing brand. Change a value here
   and it propagates everywhere it's used.
   ============================================================ */
:root {
  --bg: #ffffff;
  --surface: #f2f2f0;      /* placeholder grey behind an image while it loads */
  --border: #e6e6e3;
  --text: #111111;
  --text-dim: #8c8c8c;     /* inactive nav, captions, secondary info */

  /* ---- Typography ----
     Each stack names the Adobe Fonts family FIRST, then a free fallback. */
  --font-display: 'sofia-pro-variable', system-ui, sans-serif;
  --font-script: 'adobe-handwriting-ernie', cursive;
  --font-ui: 'Inter', system-ui, sans-serif;

  /* ---- Header spacing ----
     Tune these two to sit the wordmark where you want it vertically.
     Top is larger than bottom, which pushes it slightly below true centre —
     optical centring. Text sitting at exact geometric centre reads as
     slightly high, so designers nudge it down a touch. */
  --header-pad-top: 1.6rem;
  --header-pad-bottom: 1.1rem;

  --max-width: 1680px;
  --gutter: 8px;           /* space between photos — tight, like your site */
  --radius: 3px;
  --pad: clamp(var(--gutter), 3vw, 2.5rem);

  /* Lightbox backdrop. Raise the alpha toward 1 for a more solid white;
     lower it to let more of the gallery show through behind the blur.
     0.985 is nearly opaque with just a hint of the page beneath. */
  --lb-backdrop: rgba(250, 250, 249, 0.9);
}


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

body {
  margin: 0;
  font-family: var(--font-ui);
  font-weight: 300;
  background: var(--bg);
  color: var(--text);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

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


/* ============================================================
   HEADER
   ============================================================ */
.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem var(--pad);
  padding: var(--header-pad-top) var(--pad) var(--header-pad-bottom);
  background: rgba(255, 255, 255, 0.88);
  backdrop-filter: blur(10px);
  /* Translucent white plus blur means photos soften as they scroll under the
     header rather than vanishing behind a solid bar. */
}

.wordmark {
  text-decoration: none;
  color: var(--text);
  line-height: 1.1;
  margin-left: var(--wordmark-shift);
  /* Nudged in from the header padding. Because it's a token you can tune it
     without hunting through the file. */
}

.wordmark__name {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(1.15rem, 2.3vw, 1.55rem);
  letter-spacing: 0.05em;
  text-transform: uppercase;

  /* NOTE: no font-weight here on purpose.
     Sofia Pro Variable exposes weight as a continuous AXIS, so weight is set
     through font-variation-settings instead. Declaring both would conflict,
     and font-variation-settings wins — leading to the confusing situation
     where changing font-weight does nothing at all. */
  font-variation-settings: "slnt" 0, "wght" 400;

  /* A variable font's axes are numbers, so the browser can interpolate
     between them — which means this genuinely animates rather than snapping.
     With a traditional font family you'd only have discrete cuts (regular,
     italic, bold) and no in-between to transition through. This is the main
     practical reason variable fonts exist. */
  transition: font-variation-settings 0.45s cubic-bezier(0.2, 0.7, 0.3, 1);
}

/* Slant and thicken on hover. Also on keyboard focus, so it isn't
   mouse-only. */
.wordmark:hover .wordmark__name,
.wordmark:focus-visible .wordmark__name {
  font-variation-settings: "slnt" 10, "wght" 461.64383;
}

.wordmark__tagline {
  display: block;
  font-family: var(--font-script);
  font-weight: 400;
  font-style: normal;
  font-size: clamp(0.8rem, 1.6vw, 1.2rem);
  color: var(--text);
  margin-top: 0.2rem;
}


/* ============================================================
   FILTER BUTTONS
   ============================================================ */
.filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.35rem;
}

.filter-btn {
  font-family: inherit;
  /* Buttons don't inherit fonts by default — they use the OS control font.
     Without this line these would render in a different typeface. */
  font-size: 0.78rem;
  font-weight: 400;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-dim);
  background: none;
  border: 0;
  padding: 0.35rem 0;
  cursor: pointer;
  position: relative;
  transition: color 0.25s ease;
}

.filter-btn:hover { color: var(--text); }

/* The underline is a pseudo-element scaled from 0 to 1, not a border.
   Animating transform is cheap because it doesn't force the browser to
   recalculate layout — animating width or border-width would. */
.filter-btn::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background: currentColor;
  /* currentColor = whatever this element's `color` is right now, so the
     underline tracks the text colour automatically. */
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.3s cubic-bezier(0.2, 0.7, 0.3, 1);
}

.filter-btn:hover::after,
.filter-btn.is-active::after { transform: scaleX(1); }

.filter-btn.is-active {
  color: var(--text);
  font-weight: 500;
}

/* The About link is an <a>, not a button, so it needs the filter-btn look
   applied separately. Sharing the class would be tempting, but a link and a
   button behave differently — the link navigates, the buttons filter in
   place — and keeping them distinct in the markup keeps that honest. */
.nav-link {
  font-size: 0.78rem;
  font-weight: 400;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-dim);
  text-decoration: none;
  padding: 0.35rem 0;
  position: relative;
  transition: color 0.25s ease;
}

.nav-link::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.3s cubic-bezier(0.2, 0.7, 0.3, 1);
}

.nav-link:hover { color: var(--text); }
.nav-link:hover::after,
.nav-link.is-active::after { transform: scaleX(1); }
.nav-link.is-active { color: var(--text); font-weight: 500; }

.nav-link:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 4px;
}

.filter-btn:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 4px;
  /* :focus-visible shows a ring only for keyboard navigation, not mouse
     clicks. Deleting focus rings entirely is what makes sites unusable
     without a mouse. */
}


/* ============================================================
   GALLERY — FIXED COUNT, JUSTIFIED WIDTHS
   ============================================================
   The gallery is a vertical stack of rows. JavaScript decides which photos
   go in which row (3 per row on desktop); CSS decides how wide each photo
   is within its row.

   THE KEY RULE is on .photo:

       flex: var(--aspect) 1 0;

   Read as flex-grow / flex-shrink / flex-basis. A basis of 0 means every
   photo starts at zero width, so ALL the row's width is "free space" — and
   flexbox hands out free space in proportion to flex-grow. Since flex-grow
   is each photo's aspect ratio, final widths come out proportional to aspect
   ratios.

   Widths proportional to aspect ratio is exactly the condition for equal
   heights. A 3:2 landscape gets twice the width of a 3:4 portrait, and both
   land at the same height. The row's height emerges from the arithmetic
   rather than being declared — which is why a row of three portraits comes
   out tall and a row of three panoramas comes out short.
   ============================================================ */
.gallery {
  display: flex;
  flex-direction: column;     /* rows stack vertically */
  gap: var(--gutter);
  max-width: var(--max-width);
  margin: 0 auto;
  padding: var(--gutter) var(--pad) 4rem;
}

.gallery__row {
  display: flex;              /* photos sit side by side within a row */
  gap: var(--gutter);
}

/* Invisible spacer that keeps a partial last row at a sensible scale.
   JavaScript sets --grow to the width the missing photos would have taken. */
.gallery__filler {
  flex: var(--grow, 1) 1 0;
}

.photo {
  flex: var(--aspect) 1 0;
  aspect-ratio: var(--aspect);

  min-width: 0;
  /* Flex items refuse to shrink below their content's intrinsic size unless
     told otherwise. Without this, a wide image can force the row past the
     container edge and cause horizontal scrolling. */

  position: relative;
  overflow: hidden;
  /* Clips the image as it scales up on hover, so the photo grows inside a
     fixed frame instead of pushing the layout around. */

  padding: 0;
  border: 0;
  background: var(--surface);
  cursor: pointer;
  display: block;

  /* Entrance animation start state. JS adds .is-visible to trigger it. */
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.7s ease, transform 0.7s cubic-bezier(0.2, 0.7, 0.3, 1);
}

.photo.is-visible {
  opacity: 1;
  transform: none;
}

.photo__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* The container's aspect-ratio already matches the image's, so `cover`
     isn't meaningfully cropping — it just guarantees a perfect fill despite
     sub-pixel rounding. */
  transition: transform 0.7s cubic-bezier(0.2, 0.7, 0.3, 1);
}

.photo:hover .photo__img,
.photo:focus-visible .photo__img {
  transform: scale(1.055);
}

/* Caption overlay: a gradient fading up from the bottom edge carrying the
   title and date. Hidden until hover. */
.photo__overlay {
  position: absolute;
  inset: 0;                   /* shorthand for top/right/bottom/left: 0 */
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: clamp(0.6rem, 1.6vw, 1.1rem);
  background: linear-gradient(
    to top,
    rgba(0, 0, 0, 0.62) 0%,
    rgba(0, 0, 0, 0.22) 38%,
    rgba(0, 0, 0, 0) 68%
  );
  opacity: 0;
  transition: opacity 0.45s ease;
  text-align: left;
}

.photo:hover .photo__overlay,
.photo:focus-visible .photo__overlay { opacity: 1; }

.photo__title {
  color: #fff;
  font-size: clamp(0.78rem, 1.4vw, 0.95rem);
  font-weight: 400;
  line-height: 1.3;
  /* A soft shadow keeps text legible over a bright patch of sky. */
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.5);
}

.photo__meta {
  color: rgba(255, 255, 255, 0.78);
  font-size: clamp(0.65rem, 1.1vw, 0.75rem);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin-top: 0.2rem;
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.5);
}

.photo:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 3px;
}


/* ============================================================
   STATUS / EMPTY STATE
   ============================================================ */
.gallery-status {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 3rem var(--pad) 6rem;
  color: var(--text-dim);
  font-size: 0.85rem;
  letter-spacing: 0.04em;
  text-align: center;
}

.gallery-status[hidden] { display: none; }
/* Browsers style [hidden] as display:none by default, but ANY `display` rule
   on the element overrides it. Declaring it explicitly avoids that whole
   class of confusing bug. */


/* ============================================================
   FOOTER
   ============================================================ */
.site-footer {
  border-top: 1px solid var(--border);
  padding: 2rem var(--pad);
  color: var(--text-dim);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-align: center;
}

.site-footer p { margin: 0; }


/* ============================================================
   REDUCED MOTION
   ============================================================
   Some people experience nausea, dizziness, or migraine from on-screen
   motion, and their operating system exposes that preference. Honouring it
   costs a few lines and is the difference between your site being pleasant
   or unusable for them. Photos still appear — they just don't move.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  .photo {
    opacity: 1;
    transform: none;
  }
}


/* ============================================================
   LIGHTBOX
   ============================================================
   Layout on desktop is a three-column grid:

       [rail]  [photo stage]  [info panel]
        56px       1fr            320px

   On mobile it becomes a vertical stack with the rail as a bottom bar.
   Using grid here (rather than absolute positioning) means the photo stage
   automatically gets whatever space the rail and panel don't use, at any
   screen size.
   ============================================================ */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  /* Above the sticky header (z-index 20) so nothing shows through. */

  display: grid;
  grid-template-columns: 56px minmax(0, 1fr) 320px;
  /* minmax(0, 1fr) instead of plain 1fr. A bare 1fr has an implicit minimum
     of "auto" — meaning the track refuses to shrink below its content's
     natural size. For a 2000px-tall photo that means the track stays
     2000px tall and the image overflows. minmax(0, ...) sets the floor to
     zero so the track can actually shrink to the viewport. */
  grid-template-rows: minmax(0, 1fr);
  /* A DEFINITE height for the row. This is the crux of the portrait bug:
     percentage and max-height values need a definite parent height to
     resolve against. When the row height was auto (content-sized), the
     image's max-height: 100% was circular — it would have depended on the
     very thing it was determining — so browsers ignore it and render at
     natural size. Hence portraits overflowing and being clipped at the
     bottom. */
  align-items: stretch;

  /* Near-white and translucent, echoing your Adobe lightbox — softer than a
     hard black overlay, and it keeps the page faintly visible behind. */
  background: var(--lb-backdrop);
  backdrop-filter: blur(22px) saturate(1.1);

  opacity: 0;
  transition: opacity 0.3s ease;
}

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

.lightbox[hidden] { display: none; }
/* [hidden] is display:none by default, but the `display: grid` above would
   override it. Restating it explicitly is what keeps the element genuinely
   hidden. Easy bug to spend an hour on. */


/* ---------- Control rail ---------- */
.lb-rail {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  padding: 1.15rem 0;
  border-right: 1px solid var(--border);
}

.lb-btn,
.lb-close,
.lb-arrow {
  display: grid;
  place-items: center;
  background: none;
  border: 0;
  padding: 0;
  color: var(--text-dim);
  cursor: pointer;
  transition: color 0.2s ease, background-color 0.2s ease;
}

.lb-btn {
  width: 38px;
  height: 38px;
  border-radius: 50%;
}

.lb-btn:hover,
.lb-close:hover,
.lb-arrow:hover { color: var(--text); }

.lb-btn:hover { background: rgba(0, 0, 0, 0.05); }

/* All the icons are stroked outlines, so one rule styles every SVG. Using
   currentColor means they inherit the button's text colour and change on
   hover for free — no separate icon states to maintain. */
.lb-btn svg,
.lb-close svg,
.lb-arrow svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.lb-arrow svg { width: 26px; height: 26px; }

/* Active toggle states */
.lb-btn[aria-pressed="true"] {
  color: var(--text);
  background: rgba(0, 0, 0, 0.07);
}

/* Play/pause icon swap, driven by a class on the lightbox root */
.lb-icon-pause { display: none; }
.lightbox.is-playing .lb-icon-play { display: none; }
.lightbox.is-playing .lb-icon-pause { display: block; }


/* ---------- Close button ---------- */
.lb-close {
  position: absolute;
  top: 1.15rem;
  right: 1.15rem;
  width: 40px;
  height: 40px;
  z-index: 2;
}

.lb-close svg { width: 22px; height: 22px; }


/* ---------- Stage ---------- */
.lb-stage {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-rows: minmax(0, 1fr);
  align-items: center;
  /* center, not stretch. The arrows have a fixed 46px height, so stretch
     can't resize them — it just parks them at the top of the row, which is
     the bug you spotted. The figure opts into stretching on its own below
     via align-self. */
  gap: 0.5rem;
  padding: 1.75rem 1rem;
  min-height: 0;
  overflow: hidden;
}

.lb-arrow {
  width: 46px;
  height: 46px;
  border-radius: 50%;
}

.lb-arrow:hover { background: rgba(0, 0, 0, 0.05); }

.lb-figure {
  margin: 0;
  display: block;
  align-self: stretch;
  /* Overrides the parent's align-items: center for this one child, so the
     figure fills the full row height while the arrows stay centred. */
  width: 100%;
  height: 100%;
  min-width: 0;
  min-height: 0;
}

.lb-figure img {
  /* THE FIX for portraits being cropped.
     The previous approach — width/height auto with max-width/max-height at
     100% — depends on an unbroken chain of definite heights all the way up
     the tree. The figure was a grid container with an auto-sized row, so the
     image's max-height: 100% resolved against a track whose height depended
     on the image itself. Circular, so browsers ignore it and render at
     natural size: a 2000px-tall portrait overflows and gets clipped.

     Instead: make the <img> box exactly fill its parent, and let object-fit
     scale the picture INSIDE that box. No percentage-height chain to break.
     'contain' preserves the aspect ratio and letterboxes the leftover space,
     which is exactly right for a viewer — the gallery crops, the viewer
     never does. */
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center;
  opacity: 1;
  transition: opacity 0.25s ease;
}

/* Fade the outgoing photo while the next one downloads, so switching feels
   like a cross-dissolve rather than a flicker. */
.lb-figure.is-loading img { opacity: 0.25; }


/* ---------- Info panel ---------- */
.lb-info {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 1.15rem 1.5rem;
  border-left: 1px solid var(--border);
  overflow-y: auto;
  /* Long notes scroll within the panel rather than stretching the overlay. */
}

.lb-counter {
  font-size: 0.68rem;
  letter-spacing: 0.14em;
  color: var(--text-dim);
  margin: 0 0 1.5rem;
  font-variant-numeric: tabular-nums;
  /* Tabular figures are all the same width, so "9 / 42" and "10 / 42" don't
     make the text shift sideways as you page through. */
}

.lb-title {
  font-size: 1.05rem;
  font-weight: 400;
  line-height: 1.35;
  margin: 0 0 0.5rem;
}

.lb-location {
  font-size: 0.8rem;
  color: var(--text-dim);
  margin: 0;
}

.lb-date {
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin: 0.35rem 0 0;
}

.lb-notes {
  font-size: 0.85rem;
  line-height: 1.65;
  margin: 1.15rem 0 0;
  max-width: 34ch;
  /* "ch" is the width of a zero in the current font, so this is roughly 34
     characters per line. Comfortable reading sits between 45 and 75; a
     narrow sidebar wants to be tighter than that. */
}

.lb-notes[hidden],
.lb-location[hidden],
.lb-date[hidden],
.lb-title[hidden] { display: none; }


/* ---------- Camera details ---------- */
.lb-details {
  margin-top: 1.75rem;
  padding-top: 1.15rem;
  border-top: 1px solid var(--border);
}

.lb-details[hidden] { display: none; }

.lb-details__list {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.4rem 1rem;
  margin: 0;
  font-size: 0.75rem;
}

/* A two-column grid with dt/dd alternating gives a clean aligned table
   without any table markup. */
.lb-details__list dt {
  color: var(--text-dim);
  letter-spacing: 0.04em;
}

.lb-details__list dd {
  margin: 0;
  font-variant-numeric: tabular-nums;
}

.lb-details__empty {
  font-size: 0.75rem;
  color: var(--text-dim);
  margin: 0;
}


/* ---------- Mobile ---------- */
@media (max-width: 860px) {
  .lightbox {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr) auto auto;

    height: 100dvh;
    /* dvh = dynamic viewport height. On iOS Safari, `inset: 0` resolves
       against the LARGEST viewport, so the bottom of a fixed element can sit
       underneath the browser toolbar. dvh tracks the actually-visible height,
       which is what keeps the control rail reachable. */
  }

  /* EXPLICIT row assignment. Auto-placement follows source order, and the
     markup has the rail first (correct for the desktop three-column layout).
     In one column that put the rail in row 1 — the tall track — and pushed
     the photo into an auto-sized row, which is what broke both the portrait
     overflow and the landscape centring.

     Assigning rows by hand decouples visual order from source order, so each
     layout gets the arrangement it needs from the same HTML. */
  .lb-stage { grid-row: 1; }
  .lb-info  { grid-row: 2; }
  .lb-rail  { grid-row: 3; }

  .lb-stage {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr);
    gap: 0;
    padding: 3.25rem 0.75rem 0.75rem;
    /* Collapse to a single track. The arrows are absolutely positioned on
       mobile, so they don't need columns of their own — and removing the
       extra tracks removes every possible source of horizontal asymmetry
       rather than trying to balance it. */
  }

  .lb-arrow {
    /* Float the arrows over the photo edges — on a narrow screen, giving
       them their own grid columns would steal width the photo needs. */
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    background: rgba(255, 255, 255, 0.75);
    z-index: 2;
  }

  .lb-arrow--prev { left: 0.35rem; }
  .lb-arrow--next { right: 0.35rem; }

  .lb-info {
    border-left: 0;
    border-top: 1px solid var(--border);
    padding: 1rem 1.25rem;
    max-height: 32dvh;
    overflow-y: auto;
  }

  .lb-counter { margin-bottom: 0.6rem; }

  .lb-rail {
    flex-direction: row;
    justify-content: center;
    gap: 2rem;
    border-right: 0;
    border-top: 1px solid var(--border);
    padding: 0.6rem 0 max(0.6rem, env(safe-area-inset-bottom));
    /* env(safe-area-inset-bottom) is the height of the home-indicator area
       on notched iPhones. max() means it has no effect on devices without
       one, so a single rule covers both. Without it the rail sits under
       the indicator and gets hard to tap. */
  }

  .lb-btn {
    width: 44px;
    height: 44px;
    /* 44px is Apple's minimum recommended touch target. The 38px desktop
       size is fine for a mouse but genuinely fiddly with a thumb — which
       is part of why those buttons felt unresponsive. */
  }
}


/* ============================================================
   ABOUT PAGE
   ============================================================ */
.about {
  max-width: 1100px;
  margin: 0 auto;
  padding: clamp(2rem, 6vw, 5rem) var(--pad) 5rem;

  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  /* 5fr / 7fr rather than 1fr / 1fr — the text column needs more room than
     the portrait, and asymmetric proportions look more considered than a
     dead-even split. */
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
}

@media (max-width: 760px) {
  .about {
    grid-template-columns: minmax(0, 1fr);   /* stack on narrow screens */
  }
}

.about__portrait {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  background: var(--surface);
  /* The grey shows through while the image loads, and stands in for it
     entirely if the file is missing — so the layout never collapses. */
}

.about__body h1 {
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 4vw, 2.6rem);
  font-weight: 400;
  letter-spacing: -0.01em;
  line-height: 1.1;
  margin: 0 0 1.5rem;
}

.about__body p {
  font-size: 0.95rem;
  line-height: 1.75;
  margin: 0 0 1.15rem;
  max-width: 62ch;
  /* Roughly 62 characters per line. Comfortable reading sits between 45 and
     75 — unbounded prose across a wide monitor is genuinely tiring to
     follow, because your eye loses its place returning to the next line. */
}

.about__body p:last-child { margin-bottom: 0; }

.about__links {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem;
  margin-top: 2.5rem;
  padding-top: 1.75rem;
  border-top: 1px solid var(--border);
}

.about__links a {
  font-size: 0.72rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-dim);
  text-decoration: none;
  transition: color 0.2s ease;
}

.about__links a:hover { color: var(--text); }

.about__links a:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 4px;
}

/* Currently / Next trip block on the About page. Two-column grid so the
   labels align — same pattern as the lightbox camera details. */
.about__meta {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.4rem 1.5rem;
  margin: 2.25rem 0 0;
  font-size: 0.78rem;
}

.about__meta dt {
  color: var(--text-dim);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.about__meta dd {
  margin: 0;
}


/* ============================================================
   MAP PAGE
   ============================================================ */
.map-section {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: clamp(0.75rem, 2vw, 1.5rem) var(--pad) 4rem;
}

.map-title {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 3.2vw, 2.2rem);
  font-weight: 400;
  letter-spacing: -0.01em;
  margin: 0 0 0.6rem;
}

.map-intro {
  color: var(--text-dim);
  font-size: 0.88rem;
  max-width: 80ch;
  margin: 0 0 1.75rem;
}

.map-panel {
  height: clamp(420px, 72dvh, 900px);
  /* An EXPLICIT height, and it matters more here than anywhere else:
     Leaflet measures its container when it initialises, and a container
     with no height renders as a blank grey box. Same definite-height
     lesson as the lightbox portraits.

     dvh, not vh — "dynamic viewport height" accounts for mobile browser
     chrome appearing and disappearing as you scroll. Plain vh measures the
     largest possible viewport, so a 72vh map on a phone ends up partly
     hidden behind the address bar. */

  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  /* Clips the tiles to the rounded corners — without it the square tile
     grid pokes out past the border radius. */
  background: var(--surface);
  z-index: 0;
  /* Leaflet assigns z-indexes up into the 600s internally. Establishing a
     stacking context here keeps all of that BELOW the sticky header (z 20)
     and the lightbox (z 100), which otherwise get covered by map panes. */
}


/* ---------- Pins ---------- */
/* .pin-wrap is Leaflet's divIcon container; .pin is our own span inside it.
   Leaflet controls the wrapper's position, so all styling goes on the span —
   fighting Leaflet for control of the wrapper is a losing battle. */
.pin-wrap { background: none; border: 0; }

.pin {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background: var(--text);
  border: 2px solid rgba(255, 255, 255, 0.95);
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.3);
  cursor: pointer;
  transition: transform 0.2s cubic-bezier(0.2, 0.7, 0.3, 1);
}

.pin-wrap:hover .pin {
  transform: scale(1.6);
  /* Grows on hover so a 12px dot is still easy to target, and gives you
     feedback about which one you're on when pins overlap. */
}

/* ---------- Pin popup ---------- */
.pin-popup { width: 186px; }

.pin-popup__thumb {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 2;
  padding: 0;
  border: 0;
  border-radius: 2px;
  overflow: hidden;
  background: var(--surface);
  cursor: pointer;
}

.pin-popup__thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.5s cubic-bezier(0.2, 0.7, 0.3, 1);
}

.pin-popup__thumb:hover img { transform: scale(1.06); }

.pin-popup__thumb:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 2px;
}

.pin-popup__label {
  font-size: 0.8rem;
  line-height: 1.35;
  margin: 0.6rem 0 0;
}

.pin-popup__meta {
  font-size: 0.66rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin: 0.2rem 0 0;
}

/* Leaflet ships its own popup chrome. These overrides make it match the rest
   of the site rather than looking like a default map tooltip. */
.leaflet-popup-content-wrapper {
  border-radius: 3px;
  box-shadow: 0 6px 28px rgba(0, 0, 0, 0.16);
  padding: 10px;
}

.leaflet-popup-content { margin: 0; }

.leaflet-container {
  font-family: var(--font-ui);
  background: var(--surface);
}

.leaflet-container a { color: var(--text); }

/* Attribution must stay visible — it's a licence condition for both
   OpenStreetMap data and CARTO tiles. Toned down, never hidden. */
.leaflet-control-attribution {
  font-size: 0.6rem;
  background: rgba(255, 255, 255, 0.82);
}

/* Desaturate the basemap so your photographs carry all the colour. This
   filters only the TILE pane, not the marker or popup panes, so pins and
   previews keep their real colours. Applying it to the pane rather than
   individual tiles also avoids visible seams at tile boundaries. */
.leaflet-tile-pane {
  filter: grayscale(1) brightness(1.05) contrast(0.9);
}