:root {
  --bg: #ffffff;
  --fg: #1c1c1c;
  --muted: #666666;
  --line: #e3e3e3;
  --accent: #1a5d8f;
  --sidebar-bg: #f4f1ec;
  --nav-level-0: #f4f1ec;
  --nav-level-1: #ebe7df;
  --nav-level-2: #7c7c7c;
  --nav-level-3: #dbe4ec;
  /* Shows through where assets/header.jpg is missing, so the topbar stays a
     dark band with legible white text instead of flipping to white-on-white. */
  --header-fallback: #3f4a52;
  --sidebar-w: 16rem;
  --max-text: 38rem;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: 16px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

img { display: block; max-width: 100%; }
a { color: var(--accent); }

/* The mobile menu is a checkbox, so it needs no JavaScript and stores nothing.
   It stays in the accessibility tree and keyboard-focusable - never display:none it. */
.nav-toggle { position: absolute; width: 1px; height: 1px; opacity: 0; }

/* Scrim: lets a tap outside the open mobile panel close it. Inert at desktop
   widths, where the sidebar is never an overlay. */
.nav-scrim { display: none; }

.topbar {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.9rem 1rem;
  border-bottom: 1px solid var(--line);
  background: var(--header-fallback) url("header.jpg") center / cover no-repeat;
  color: #ffffff;
}
/* Darkens the photo so the title stays readable whatever picture is dropped in.
   Heavier on the left, where the brand and the hamburger sit. */
.topbar::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, #000000a6, #00000033);
  pointer-events: none;
}
.topbar > * { position: relative; }
.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-weight: 600;
  font-size: 1.15rem;
  letter-spacing: 0.02em;
  text-decoration: none;
  color: #ffffff;
  text-shadow: 0 1px 3px #00000099;
}
.brand-logo { width: auto; height: 3rem; object-fit: contain; }
.hamburger {
  display: none;
  color: #ffffff;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  font-size: 1.5rem;
  cursor: pointer;
}
/* White, not --accent: the topbar is now a dark photo, where the dark blue
   accent is close to invisible. */
.nav-toggle:focus-visible ~ .topbar .hamburger { outline: 2px solid #ffffff; }

.sidebar { padding: 1rem; border-bottom: 1px solid var(--line); background: var(--sidebar-bg); }
.content {
  min-width: 0;
  min-height: 100%;
  padding: 1rem;
  background: linear-gradient(180deg, #bdb6ae 0%, #6b6762 100%);
}

.nav, .nav ul { margin: 0; padding: 0; list-style: none; }
/* Keep nested lists full width so each level's background spans the sidebar;
   the link padding below carries the visual hierarchy instead. */
.nav ul { margin: 0; padding: 0; }
.nav-item + .nav-item { border-top: 1px solid var(--line); }
.nav-item + .nav-item > a { padding-top: 0.5rem; }
.nav a {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  min-height: 44px;
  padding: 0.25rem;
  color: var(--fg);
  text-decoration: none;
}
.nav-depth-0 > a, .nav-depth-1 > a { padding-left: 0.25rem; }
.nav-depth-2 > a { padding-left: calc(1.75rem + 1px); }
.nav-depth-3 > a { padding-left: calc(3.25rem + 2px); }
.nav-depth-4 > a {
  padding-left: calc(4.75rem + 3px);
  background: linear-gradient(to right, var(--nav-level-3), var(--sidebar-bg));
}
.nav-depth-0 > a { background: linear-gradient(to right, var(--nav-level-0), var(--sidebar-bg)); }
.nav-depth-1 > a { background: linear-gradient(to right, var(--nav-level-1), var(--sidebar-bg)); }
.nav-depth-2 > a { background: linear-gradient(to right, var(--nav-level-2), var(--sidebar-bg)); }
.nav-depth-3 > a { background: linear-gradient(to right, var(--nav-level-3), var(--sidebar-bg)); }
.nav-date { font-size: 0.8em; line-height: 1.3; color: var(--muted); }
.nav-title { line-height: 1.3; }
.nav a:hover { text-decoration: underline; }
.nav .is-current > a { font-weight: 600; color: var(--accent); }

h1 { margin: 1rem 0 0.25rem; font-size: 1.6rem; }
.date {
  margin: 0 0 1.25rem;
  color: var(--muted);
  text-shadow: 0 1px 2px #5a403099;
}
.story { max-width: var(--max-text); }

.hero { position: relative; display: block; width: 100%; aspect-ratio: 16 / 9; overflow: hidden; }
.hero::after {
  position: absolute;
  inset: 0;
  content: "";
  background: linear-gradient(to top, #000000b8, #00000066 45%, transparent 100%);
  pointer-events: none;
}
.hero picture { display: block; width: 100%; height: 100%; }
.hero img { width: 100%; height: 100%; object-fit: cover; }
.hero-meta { position: absolute; right: 1rem; bottom: 1rem; left: 1rem; z-index: 1; color: #ffffff; text-shadow: 0 1px 3px #000000; }
.hero-meta h1 { margin: 0; color: inherit; }
.hero-meta .date { margin: 0.25rem 0 0; color: inherit; }

.grid {
  display: grid;
  /* Phones only - the desktop breakpoint pins the tracks to a flat 180px.
     min(180px, 46%) rather than a bare 180px: on the narrowest phones two
     180px columns plus the gap and .content's padding overflow, so the grid
     would fall back to one column. 46% guarantees two fit down to 320px. */
  grid-template-columns: repeat(auto-fill, minmax(min(180px, 46%), 1fr));
  gap: 0.5rem;
  margin-top: 1.5rem;
}
.tile { display: block; overflow: hidden; }
/* height: auto is what makes aspect-ratio work at all: the markup carries
   width="400" height="400", and an attribute height counts as a fixed CSS
   length that beats aspect-ratio. Without auto the tile is 180px wide and
   400px tall - oblong, not square. object-fit: cover then crops the picture
   into the square, which is wanted here (unlike in the lightbox). */
.tile img {
  width: 100%;
  height: auto;
  aspect-ratio: 1;
  object-fit: cover;
  transform: scale(1);
  transition: transform 180ms ease;
}
.tile:hover img, .tile:focus-visible img { transform: scale(1.04); }

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 1.25rem;
}
.card {
  display: block;
  overflow: hidden;
  border: 1px solid var(--line);
  background: rgb(78 68 58 / 12%);
  color: inherit;
  text-decoration: none;
}
.card img { width: 100%; height: auto; aspect-ratio: 4 / 3; object-fit: cover; transform: scale(1); transition: transform 180ms ease; }
.card:hover img, .card:focus-visible img { transform: scale(1.04); }
.card h2 { margin: 0.75rem 0.75rem 0.25rem; font-size: 1.1rem; }
.card .date, .card .excerpt { margin: 0 0.75rem 0.75rem; }
.card .date,
.card .excerpt {
  color: #ffffff;
  text-shadow: 0 1px 2px #5a4030aa;
}
.card .excerpt { font-size: 0.9rem; }

@media (prefers-reduced-motion: reduce) {
  .card img, .tile img { transition: none; }
}

/* Phones: the sidebar slides in over the page, sub-trees stay expanded. */
@media (max-width: 767px) {
  .hamburger { display: inline-flex; }
  /* The sidebar paints over .topbar when open, so the topbar needs its own
     stacking context above it - otherwise the hamburger is buried under the
     open panel and there is no way to close the menu again. */
  .topbar { position: relative; z-index: 30; }
  .sidebar {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    width: min(20rem, 80vw);
    z-index: 20;
    overflow-y: auto;
    /* Must match the desktop sidebar, not --bg: the panel is the same menu. */
    background: var(--sidebar-bg);
    border-right: 1px solid var(--line);
    transform: translateX(-100%);
    transition: transform 0.2s ease;
  }
  .nav-toggle:checked ~ .shell .sidebar { transform: none; }
  .nav-toggle:checked ~ .nav-scrim { display: block; position: fixed; inset: 0; z-index: 10; }
}

@media (min-width: 768px) {
  .shell { display: grid; grid-template-columns: var(--sidebar-w) 1fr; align-items: start; }
  .sidebar {
    position: sticky;
    top: 0;
    /* .shell uses align-items: start, so the sidebar is only as tall as its
       own list. Now that it is a coloured panel, a short menu would leave a
       cut-off grey block instead of a column - min-height fills it out, and
       together with max-height it pins the panel to exactly one viewport. */
    min-height: 100vh;
    max-height: 100vh;
    overflow-y: auto;
    border-right: 1px solid var(--line);
    border-bottom: 0;
  }
  .topbar { padding: 1.1rem 2rem; }
  .brand { font-size: 1.3rem; }
  .brand-logo { height: 4rem; }
  .content { padding: 1.5rem 2rem; }
  /* Flat 180px tracks, not minmax(180px, 1fr): a 1fr track stretches the
     leftover row width into the columns, so the thumbnails would end up
     wider than 180px and, with .tile img's aspect-ratio: 1, taller too.
     Fixed tracks keep every thumbnail exactly 180 x 180. */
  .grid { grid-template-columns: repeat(auto-fill, 180px); }
  .hero { aspect-ratio: 3 / 1; }
  h1 { font-size: 2rem; }
  /* Sub-trees collapse to the current branch once there is a permanent sidebar. */
  .nav li > ul { display: none; }
  .nav .is-open > ul { display: block; }
}

@media (prefers-reduced-motion: reduce) {
  .sidebar, .lb-chrome { transition: none; }
}

/* ---------- Lightbox ---------- */

body.lb-open { overflow: hidden; overscroll-behavior: contain; }

.lb {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  /* dvh follows the collapsing address bar; without it the picture is clipped
     on iOS Safari and Chrome for Android. The vh line above is the fallback. */
  height: 100dvh;
  z-index: 100;
  display: none;
  background: #000000;
}
.lb[open] { display: block; }

.lb-stage {
  position: absolute;
  inset: 0;
  /* flex, not grid: the img's max-height: 100% has to resolve against
     something with a definite height. In a grid the img's containing block is
     its grid area, and an auto-sized row counts as indefinite, so the
     percentage silently becomes none - only max-width applied, the picture
     stayed full height and overflow: hidden cropped it. A flex item's
     containing block is this box, which inset gives a definite height. */
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  /* The browser must not claim pan or pinch - the lightbox handles both. */
  touch-action: none;
}
.lb-stage img {
  /* width/height must be auto, and must stay auto: lightbox.js sets the img's
     width/height attributes to the picture's real pixel size, and those count
     as fixed CSS lengths. With fixed lengths, max-width and max-height clamp
     each axis on its own - the box grows to the full stage, the picture
     overflows it and .lb-stage's overflow: hidden crops it. With both auto,
     the intrinsic-ratio constraint rules shrink it proportionally instead, so
     the whole picture fits the stage. */
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  /* Flex items get an automatic minimum size from their content; zeroing it
     keeps that from fighting the two max constraints. */
  min-width: 0;
  min-height: 0;
  object-fit: contain;
  will-change: transform;
}

.lb-chrome {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 1;
  transition: opacity 0.2s ease;
}
.lb-chrome > * { pointer-events: auto; }
/* Children re-declare pointer-events: auto above (and .lb-close/.lb-download
   re-declare it a second time below, since .lb-top/.lb-bottom now default to
   none), so the idle override must be scoped to all of them too - otherwise
   an invisible close button and download link still sit over the picture
   after the chrome fades, and the arrows still catch a swipe meant for the
   stage. */
.lb.is-idle .lb-chrome,
.lb.is-idle .lb-chrome > *,
.lb.is-idle .lb-close,
.lb.is-idle .lb-download { pointer-events: none; }

.lb-top, .lb-bottom {
  position: absolute;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.75rem 1rem;
  color: #ffffff;
  /* Full-width, ~68px tall bars would otherwise swallow every swipe/tap that
     starts in their strip. Only their interactive children need to catch
     pointer events. */
  pointer-events: none;
}
.lb-close, .lb-prev, .lb-next, .lb-download { pointer-events: auto; }
.lb-top {
  top: 0;
  justify-content: space-between;
  padding-top: calc(0.75rem + env(safe-area-inset-top, 0px));
  background: linear-gradient(#000000aa, #00000000);
}
.lb-bottom {
  bottom: 0;
  justify-content: space-between;
  padding-bottom: calc(0.75rem + env(safe-area-inset-bottom, 0px));
  background: linear-gradient(#00000000, #000000aa);
}
.lb-caption { font-size: 0.95rem; }
.lb a { color: #ffffff; }
.lb button {
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  border: 0;
  background: none;
  color: #ffffff;
  font-size: 1.5rem;
  cursor: pointer;
}
.lb-prev, .lb-next {
  position: absolute;
  top: 50%;
  display: none;
  transform: translateY(-50%);
  font-size: 2.5rem;
}
.lb-prev { left: 0.5rem; }
.lb-next { right: 0.5rem; }

/* Desktop: contain the picture and put the chrome outside it, where it is free. */
@media (min-width: 768px) {
  .lb { background: #000000cc; }
  .lb-stage { inset: 2rem 5rem 6rem; }
  .lb-bottom { background: none; }
  .lb-prev, .lb-next { display: block; }
  .lb.is-idle .lb-chrome { opacity: 1; }
  /* The idle rule above keeps the chrome visible on desktop, so its four
     actual controls must also stay clickable - but only those four: naming
     .lb-chrome or .lb-chrome > * here would restore pointer-events on the
     full-viewport chrome container and the full-width top/bottom bars too,
     turning them back into an invisible click/scroll/drag blocker over the
     whole stage for the rest of the session. */
  .lb.is-idle .lb-close,
  .lb.is-idle .lb-prev,
  .lb.is-idle .lb-next,
  .lb.is-idle .lb-download { pointer-events: auto; }
}
