/* ---------- Fonts ---------- */
/* Anonymous Pro: OFL, self-hosted from Google Fonts. Used for nav/headings,
   matching the monospace-ish feel of the current site's UI chrome. */
@font-face {
  font-family: 'anonymous-pro';
  src: url('/assets/fonts/anonymous-pro-regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'anonymous-pro';
  src: url('/assets/fonts/anonymous-pro-bold.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* Sofia Sans: OFL, self-hosted from Google Fonts. Free stand-in for the
   commercial Sofia Pro used on the current site until/unless licensed
   Sofia Pro files are dropped in — see automation/README.md for swap notes. */
@font-face {
  font-family: 'sofia-sans';
  src: url('/assets/fonts/sofia-sans-variable.woff2') format('woff2-variations');
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
}

:root {
  --font-body: 'sofia-sans', Helvetica, Arial, sans-serif;
  --font-mono: 'anonymous-pro', 'Courier New', monospace;

  /* Darker neutral-gray page background — still reads as "light," just not
     white, and doesn't fight photos the way true black/white would. Neutral
     (not warm) so it doesn't read as brown. */
  --color-bg: #383838;
  --color-text: #f0e8da;        /* off-white, a little warm -- the bg is dark enough now that text reads light-on-dark */
  --color-muted: #b6ac9b;       /* dimmer warm off-white for secondary text, still on the dark-ish bg */
  --color-border: #1e1e1e;      /* darker than --color-bg so hairlines are visible against it */
  --color-surface: #f2f2f2;     /* near-white "card" surface for menus/controls that sit on top of the gray bg */
  --color-surface-hover: #e2e2e2;
  --color-surface-text: #262929; /* dark text for the few things that sit on --color-surface's light card, not the dark bg -- dropdown menu, map empty-state */
  --color-link: #dfe9ff;        /* light blue that stays legible on the gray bg, for the birds.helsel.co banner */
  /* Topbar nav on gallery pages is a dark bar, darker still than the page
     bg, so it shares the same warm off-white text as the rest of the site. */
  --color-nav-bg: #2e3131;
  --color-nav-text: #f0e8da;

  --sidebar-width: 270px;
  --touch-target: 44px;         /* minimum comfortable tap target */
  --hero-padding: clamp(1.5rem, 5vw, 4rem);
  /* Width of the Leaflet maps (Post Office, Birding Spots) and the note under
     the Birding Spots one, so they always share a left edge. 85% since
     2026-09-19 (was 75%): still leaves a strip each side to rest the mouse
     while scrolling (~77px at the 1100px content width). Phones override to
     100% in the 600px block. */
  --map-width: 85%;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: var(--font-body);
  color: var(--color-text);
  background: var(--color-bg);
  line-height: 1.5;
  /* Declares the page dark (it has been since the 2026-09-18 refresh --
     this used to say `light`, from when the ground was a mid-gray). Two
     jobs: Chromium's force-dark ("auto dark mode") skips pages that declare
     dark, and it tells the browser to draw scrollbars and native controls
     dark. While this said `light`, a force-dark browser inverted light
     washes -- the selected Birding Spots list row rendered DARKER than its
     card instead of lighter. Set on :root too (below) since the viewport
     scrollbar follows the root element, not body. */
  color-scheme: dark;
}
:root {
  color-scheme: dark;
}

.site-footer {
  /* Empty for now -- just breathing room between the last element on a
     page and the bottom of the scroll, so content doesn't feel like it
     stops abruptly at the viewport edge. Real footer content can go here
     later without needing to touch this spacing. */
  height: 6rem;
}

a {
  color: inherit;
  text-decoration: none;
}

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

/* ---------- Anaglyph 3D hover effect ----------
   Ported from the current site's SmugMug custom CSS, same red/cyan
   text-shadow split + jitter animation, applied to our own nav links. */
.site-nav a {
  position: relative;
  transition: text-shadow 0.15s ease;
}
.site-nav a:hover {
  color: inherit !important;
  text-shadow:
    -2px 0 0 rgba(255, 0, 60, 0.8),
     2px 0 0 rgba(0, 255, 255, 0.8) !important;
  animation: anaglyph-jitter 0.3s steps(2) infinite;
}
@keyframes anaglyph-jitter {
  0%   { transform: translate(0, 0); }
  50%  { transform: translate(-1px, 0); }
  100% { transform: translate(1px, 0); }
}
/* The anaglyph reads great on the dark topbar/sidebar, but the same
   rgba(..., 0.8) red/cyan alpha-blend against the light dropdown-menu
   card lightens toward pastel pink/pale-cyan instead of popping --
   alpha blending over near-white desaturates it, over dark it stays
   vivid. Fully opaque colors restore the contrast for this one light-
   background spot without touching the effect anywhere else (needs
   !important to win over the base rule's own !important). */
.site-nav__dropdown-menu a:hover {
  text-shadow:
    -2px 0 0 #ff003c,
     2px 0 0 #00c8d6 !important;
}

/* ---------- Nav ---------- */
.site-nav {
  font-family: var(--font-mono);
}
.site-nav__brand a {
  font-weight: 700;
  font-size: 1.1rem;
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  min-height: var(--touch-target);
}
.site-nav__logo-mark {
  height: 2.25rem;
  width: auto;
}
.site-nav__logo-mark--invert {
  filter: invert(1) brightness(1.4);
}
.site-nav__links {
  list-style: none;
  margin: 0;
  padding: 0;
}
/* Hamburger toggle: only shown at phone width in sidebar (homepage) mode --
   see the 600px media query below. Hidden here so it never appears on
   tablet/desktop, where the stacked link list is already shown inline. */
.site-nav__menu-toggle {
  display: none;
  background: none;
  border: none;
  color: var(--color-nav-text);
  font-family: var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-size: 1rem;
  cursor: pointer;
  min-height: var(--touch-target);
  padding: 0 0.25rem;
}
.site-nav__menu-toggle-close {
  display: none;
}
.site-nav__links a {
  text-transform: uppercase;
  font-size: 1.35rem;
  letter-spacing: 0.03em;
  color: var(--color-muted);
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
}

/* Sidebar mode: homepage */
.site-nav--sidebar {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  width: var(--sidebar-width);
  padding: 2rem 1.5rem 2rem 3rem;
  border-right: 1px solid var(--color-border);
  overflow-y: auto;
}
.site-nav--sidebar .site-nav__brand {
  margin-bottom: 2rem;
}
.site-nav--sidebar .site-nav__links--vertical li {
  /* Generous gap to the NEXT item, but tight line-height within this one --
     otherwise a wrapped label like "The Post Office" reads as two separate
     menu items instead of one two-line one. */
  margin-bottom: 1.25rem;
  padding-bottom: 1.25rem;
  border-bottom: 1px solid rgba(240, 232, 218, 0.2);
}
.site-nav--sidebar .site-nav__links--vertical li:last-child {
  border-bottom: none;
}
.site-nav--sidebar .site-nav__links--vertical li.site-nav__group-end {
  border-bottom: none;
}
/* A plain, contentless list item -- flex-basis:100% forces its own line
   even in the tablet breakpoint's flex-wrap chip layout below, and is a
   harmless no-op in the default block-stacked layout and the phone
   overlay's column layout. Its own border-top (instead of relying on the
   previous item's border-bottom) reads as a clearly more deliberate break
   than the ~20%-opacity hairline between every other pair of items. */
.site-nav--sidebar .site-nav__links--vertical li.site-nav__divider {
  height: 0;
  margin-bottom: 1.25rem;
  padding: 0;
  border-bottom: none;
  border-top: 1px solid var(--color-muted);
  flex-basis: 100%;
}
/* Homepage menu items sized up further, independent of the gallery topbar's
   link size. */
.site-nav--sidebar .site-nav__links a {
  font-size: 1.7rem;
  line-height: 1.15;
}

/* Topbar mode: gallery pages -- a filled color bar instead of a thin
   border line, so the nav reads as its own header rather than a hairline.
   The bar's background spans the full viewport width, but its content
   (.site-nav__inner) is inset to the same max-width/padding as .gallery-page
   below it, so HOME | SEARCH | OTHER PHOTOS lines up with the photos'
   right edge instead of sitting flush against the browser edge. */
.site-nav--topbar {
  padding: 1rem 0;
  background: var(--color-nav-bg);
  /* Own stacking context above page content -- otherwise the OTHER PHOTOS
     dropdown (a child of this bar) loses to Leaflet's internal map panes
     on gallery pages with a map, since those panes' z-index is compared
     against this bar's ambient stacking context, not just the dropdown's
     own z-index. */
  position: relative;
  z-index: 900;
}
.site-nav--topbar .site-nav__inner {
  max-width: 1100px;
  margin: 0 auto;
  padding: 0 2rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
}
/* The bar itself is dark, so its own brand + top-level links need light
   text. The dropdown MENU (a light card floating below) is untouched --
   this only targets direct children of the topbar, not the nested
   .site-nav__dropdown-menu links two levels down, which stay dark-on-light. */
.site-nav--topbar .site-nav__brand a,
.site-nav--topbar .site-nav__inner > .site-nav__links > li > a {
  color: var(--color-nav-text);
}
/* .mini-nav shares this -- it reuses the exact same .site-nav__links--
   horizontal markup (see gallery.php/nav.php), but being its own sibling
   div outside .site-nav--topbar meant none of this actually applied to
   it (caught 2026-09-12: the mini-nav was rendering as a plain stacked
   list, not the horizontal HOME | OTHER PHOTOS | SEARCH row it was
   supposed to be). */
.site-nav--topbar .site-nav__links--horizontal,
.mini-nav .site-nav__links--horizontal {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.25rem;
  align-items: center;
}
/* Small vertical separators between HOME | SEARCH | OTHER PHOTOS. */
.site-nav--topbar .site-nav__links--horizontal > li,
.mini-nav .site-nav__links--horizontal > li {
  display: flex;
  align-items: center;
}
/* A real text glyph, not a drawn 1-2px box -- a CSS hairline at that width
   kept failing to render visibly (subpixel/anti-aliasing at typical zoom
   levels), whereas a font-rendered character can't have that problem. */
.site-nav--topbar .site-nav__links--horizontal > li:not(:first-child)::before,
.mini-nav .site-nav__links--horizontal > li:not(:first-child)::before {
  content: '|';
  margin-right: 1.25rem;
  color: var(--color-nav-text);
  opacity: 0.7;
  font-size: 1.3em;
}
.site-nav__dropdown {
  position: relative;
}
.site-nav__dropdown-toggle {
  cursor: pointer;
}
.site-nav__dropdown-menu {
  display: none;
  position: absolute;
  right: 0;
  top: 100%;
  list-style: none;
  /* No margin-top here on purpose: a gap between the toggle and this menu
     is dead space the mouse has to cross, and leaving that space breaks
     :hover (moving through it exits every hoverable descendant of
     .site-nav__dropdown) before the pointer ever reaches the menu. The
     menu sits flush against the toggle instead, with padding providing
     the same visual breathing room without an actual gap. */
  margin: 0;
  padding: 0.75rem 0 0.5rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  min-width: 180px;
  max-width: calc(100vw - 2rem);
  z-index: 10;
}
/* Hover works for desktop mice; the `.is-open` class (toggled by nav.js on
   click/tap) is what actually makes this usable on touch devices, since
   touch has no hover state to rely on. */
.site-nav__dropdown:hover .site-nav__dropdown-menu,
.site-nav__dropdown:focus-within .site-nav__dropdown-menu,
.site-nav__dropdown.is-open .site-nav__dropdown-menu {
  display: block;
}
.site-nav__dropdown-menu li {
  padding: 0;
}
.site-nav__dropdown-menu li a {
  padding: 0.5rem 1rem;
  width: 100%;
  /* The dropdown is a light card floating below the dark topbar, so its
     links need the dark surface-text color, not the page's light text. */
  color: var(--color-surface-text);
}
/* The current gallery used to be filtered out of this list entirely --
   nothing in the nav ever showed which page you were on besides the
   title text. Shown now, but as plain (non-link) dimmed text instead of
   a link back to the page you're already on. */
.site-nav__dropdown-current {
  display: block;
  padding: 0.5rem 1rem;
  color: var(--color-surface-text);
  opacity: 0.5;
  cursor: default;
}

/* ---------- Homepage ---------- */
.homepage {
  margin-left: var(--sidebar-width);
}
.hero {
  position: relative;
  height: 100vh;
  padding: var(--hero-padding);
  /* Explicit rather than relying on inheriting body's color -- the framing
     padding above/below the photo and the leftover space below a shorter
     mobile hero must always read as the exact same color, not two shades
     that happen to usually match. */
  background: var(--color-bg);
}
.hero__slide {
  position: absolute;
  inset: var(--hero-padding);
  opacity: 0;
  transition: opacity 0.8s ease;
  /* Needed once the slow zoom drift (below) is added -- without a clipping
     box, the scaled-up image bleeds past the intended frame edges instead
     of just filling it more. */
  overflow: hidden;
}
.hero__slide.is-active {
  opacity: 1;
}
.hero__slide img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ---------- Gallery pages ---------- */
.gallery-page {
  max-width: 1100px;
  margin: 0 auto;
  padding: 2rem;
}
.gallery-page__title {
  font-family: var(--font-mono);
  text-transform: uppercase;
  font-size: 2rem;
  margin: 0.5rem 0 1rem;
}
.gallery-page__description {
  color: var(--color-muted);
  max-width: 60ch;
}

.birds-banner {
  display: flex;
  /* Always side-by-side, never stacked, never scrolling (2026-09-12,
     mocked up first) -- the labels themselves shrink below 600px instead
     (.birds-banner__label-short) so nowrap always actually fits, rather
     than falling back to a wrap/stack or a scrollable row. */
  flex-wrap: nowrap;
  justify-content: center;
  align-items: center;
  gap: 0.75rem 1.5rem;
  /* Narrower than the content column (the photo grid below stays full
     width) and centered within it, on purpose -- matches the treatment
     already applied to .gallery-map for the same reason: a full-width
     bar here read as more prominent than two secondary links warrant. */
  width: 75%;
  margin: 0 auto 1.75rem;
  padding: 0.9rem 1.25rem;
  background: var(--color-nav-bg);
  border: 1px solid var(--color-border);
  border-radius: 0.75rem;
  font-family: var(--font-mono);
}
.birds-banner__link,
.birds-banner__button {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--color-text);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  /* Asymmetric -- tight around the icon badge on the left, roomier around
     the label on the right -- now that both links lead with an icon. */
  padding: 0.3rem 1.1rem 0.3rem 0.3rem;
  font-size: 0.85rem;
  text-decoration: none;
  white-space: nowrap;
}
.birds-banner__link:hover,
.birds-banner__button:hover {
  background: var(--color-surface-hover, var(--color-nav-bg));
  /* The hover background flips to a near-white fill, but the text color
     never followed -- left at --color-text (near-white too), so the
     label went essentially invisible against its own hover state.
     --color-surface-text is the same dark-on-light pairing already used
     for the dropdown menu/map empty-state, both of which sit on this
     same light surface color. */
  color: var(--color-surface-text);
  font-weight: 700;
}
.birds-banner__icon {
  flex: none;
  width: 1.7rem;
  height: 1.7rem;
  border-radius: 50%;
  background: rgba(223, 233, 255, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
}
.birds-banner__thumb {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
}
/* Short label hidden by default; swapped in below 600px (see the phone
   breakpoint) instead of the full label -- keeps both pills narrow enough
   to always fit side-by-side on a phone screen without wrapping/scrolling. */
.birds-banner__label-short {
  display: none;
}

/* ---------- Birding in Texas page ---------- */
.birding-breadcrumb {
  display: block;
  margin-bottom: 1rem;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--color-muted);
}
.birding-collapsible {
  margin-bottom: 1.5rem;
  border: 1px solid var(--color-border);
  border-radius: 0.75rem;
  background: var(--color-nav-bg);
  padding: 0 1.1rem;
}
.birding-collapsible[open] {
  padding-bottom: 1.1rem;
}
.birding-collapsible__summary {
  cursor: pointer;
  list-style: none;
  padding: 0.9rem 0;
  font-family: var(--font-mono);
  font-size: 1.15rem;
  font-weight: 600;
  color: var(--color-text);
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.birding-collapsible__summary-hint {
  font-size: 0.7rem;
  font-weight: 400;
  color: var(--color-muted);
}
.birding-collapsible__summary::-webkit-details-marker {
  display: none;
}
.birding-collapsible__summary::after {
  content: '+';
  font-size: 1.35rem;
  color: var(--color-muted);
  margin-left: 1rem;
}
.birding-collapsible[open] .birding-collapsible__summary::after {
  content: '\2212';
}
.birding-collapsible__summary:hover {
  color: var(--color-link);
}
.birding-locations-list {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.15rem 1.5rem;
  font-family: var(--font-mono);
  font-size: 0.85rem;
}
.birding-locations-list__item {
  display: flex;
  flex-direction: column;
  padding: 0.15rem 0.4rem;
  margin: 0 -0.4rem;
  border-bottom: 1px solid rgba(240, 232, 218, 0.12);
  border-radius: 0.25rem;
  transition: background-color 0.15s ease;
}
.birding-locations-list__item--active {
  /* A flat var(--color-bg) swap read as barely-there against this card's
     own --color-nav-bg (both dark, close in value) -- a semi-transparent
     white wash instead, so "selected" reads as a clear highlight rather
     than a faint shade shift, no matter what the underlying bg is.
     2026-09-19: wash 0.16 -> 0.22, plus a pink edge that is the selected
     pin's own colour (see .gallery-map__marker--active), so the row and
     its pin visibly belong together. */
  background: rgba(255, 255, 255, 0.22);
  box-shadow: inset 3px 0 0 #fc3996;
}
/* The muted secondary text is ~3.3:1 on that lighter fill -- bring it up to
   the main text colour (~6:1) while the row is selected. */
.birding-locations-list__item--active .birding-locations-list__place,
.birding-locations-list__item--active .birding-locations-list__links a,
.birding-locations-list__item--active .birding-locations-list__recent summary {
  color: var(--color-text);
}
.birding-locations-list__name {
  color: var(--color-text);
}
.birding-locations-list__place {
  color: var(--color-muted);
  font-size: 0.75rem;
}
.birding-locations-list__links {
  display: flex;
  gap: 0.75rem;
  margin-top: 0.1rem;
}
.birding-locations-list__links a {
  color: var(--color-muted);
  text-decoration: underline;
  font-size: 0.72rem;
}
.birding-locations-list__links a:hover {
  color: var(--color-link);
}
.birding-locations-list__recent {
  margin-top: 0.25rem;
  font-family: var(--font-mono);
  font-size: 0.72rem;
}
.birding-locations-list__recent summary {
  cursor: pointer;
  color: var(--color-muted);
}
.birding-locations-list__recent summary:hover {
  color: var(--color-link);
}
.birding-locations-list__recent ul {
  margin: 0.3rem 0 0;
  padding-left: 1.1rem;
  color: var(--color-text);
}

.birding-resources-columns {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  column-gap: 2rem;
}
.birding-resources-column__heading {
  margin: 0 0 0.5rem;
  font-family: var(--font-mono);
  font-size: 1rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-muted);
}
.birding-resources {
  list-style: none;
  font-family: var(--font-mono);
  font-size: 0.85rem;
}
.birding-resources__item {
  padding: 0.6rem 0;
  border-bottom: 1px solid rgba(240, 232, 218, 0.12);
}
.birding-resources__item:last-child {
  border-bottom: none;
}
.birding-resources__item a {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem;
  color: var(--color-text);
  text-decoration: none;
}
.birding-resources__item a:hover {
  color: var(--color-link);
}
.birding-resources__title {
  text-decoration: underline;
}
.birding-resources__note {
  color: var(--color-muted);
  font-size: 0.8rem;
  text-decoration: none;
}

.birding-submit {
  /* Now just the third .birding-collapsible box (collapsed by default,
     same border/background/summary treatment as the two sections above)
     instead of an always-open section set apart by its own top border --
     that constant full form was what made it command so much attention,
     especially on mobile where there's no room to visually de-emphasize
     it any other way. */
  max-width: 500px;
}
.birding-submit__note {
  color: var(--color-muted);
  margin: 0 0 1.5rem;
}
.birding-submit__success {
  color: var(--color-text);
}
.birding-submit__errors {
  color: #ff9a9a;
  margin: 0 0 1rem;
  padding-left: 1.2rem;
}
.birding-submit__form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
.birding-submit__field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  font-family: var(--font-mono);
  text-transform: uppercase;
  font-size: 0.8rem;
  letter-spacing: 0.02em;
}
.birding-submit__field input,
.birding-submit__field textarea {
  font-family: inherit;
  font-size: 1rem;
  text-transform: none;
  letter-spacing: normal;
  padding: 0.6rem 0.75rem;
  background: var(--color-surface);
  color: var(--color-surface-text);
  border: 1px solid var(--color-border);
}
.birding-submit__honeypot {
  /* Hidden from real visitors via CSS (not type="hidden", which some
     bots' form-fillers skip) rather than removed from the DOM, so it's
     still there for a bot to find and fill in. */
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
.birding-submit__button {
  align-self: flex-start;
  font-family: var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 0.75rem 1.5rem;
  background: var(--color-surface);
  color: var(--color-surface-text);
  border: 1px solid var(--color-border);
  cursor: pointer;
}
.birding-submit__button:hover {
  background: var(--color-surface-hover);
}

.gallery-map__popup-thumb {
  display: block;
  width: 160px;
  height: 110px;
  object-fit: cover;
  margin-bottom: 0.4rem;
}
.gallery-map__popup-title {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--color-surface-text);
  text-decoration: underline;
}
a.gallery-map__popup-title:hover {
  color: var(--color-link);
}

.gallery-map {
  /* Narrower than the content column on purpose (the list/cards below stay
     full width) -- a full-width Leaflet map left no room to rest the mouse
     while scrolling the page without the wheel getting captured as a zoom
     instead, forcing a scroll-out-then-scroll-down dance every time.
     Width is --map-width (top of file). */
  width: var(--map-width);
  height: 420px;
  margin: 0 auto 2rem;
  border: 1px solid var(--color-border);
  position: relative;
}
/* Touch-only tap-to-pan (see gallery-map.js): Leaflet's own CSS sets
   touch-action: none on the map container so it can own pinch/pan
   gestures -- that's a CSS-level block independent of the JS dragging
   handler, so disabling `map.dragging` in JS alone isn't enough to let a
   scroll swipe pass through. pan-y here explicitly hands vertical
   one-finger swipes back to the page; removed again once the visitor
   actually taps the map (see .gallery-map--tap-to-pan removal in JS). */
.gallery-map--tap-to-pan {
  touch-action: pan-y;
}
.gallery-map__tap-hint {
  position: absolute;
  inset: 0;
  z-index: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(20, 18, 16, 0.35);
  color: var(--color-text);
  font-family: var(--font-mono);
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  text-align: center;
  padding: 1rem;
  pointer-events: none;
}
.gallery-map__empty {
  padding: 2rem;
  color: var(--color-surface-text);
  text-align: center;
  background: var(--color-surface);
}
.birding-migration-card {
  /* A plain box now, not a link -- it used to be one giant <a> wrapping
     the whole card, styled just like the collapsible sections above it,
     so it read as another expand-in-place dropdown and people kept
     landing on BirdCast's site by accident instead of getting the
     in-place toggle they expected. Only .birding-migration-card__button
     below navigates anywhere now. */
  display: block;
  margin: 0 0 1.5rem;
  padding: 0.9rem 1.1rem;
  border: 1px solid var(--color-border);
  border-radius: 0.75rem;
  background: var(--color-nav-bg);
}
.birding-migration-card__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  /* was space-between, which pushed the button flush against the card's
     right edge -- margin: 0 auto on the button itself (below) now centers
     it within whatever gray space is actually left after the text, which
     needs flex-start here so this rule isn't also fighting to distribute
     space on its own. */
  justify-content: flex-start;
  gap: 0.75rem 1rem;
}
.birding-migration-card__text {
  min-width: 0;
}
.birding-migration-card__title {
  display: block;
  color: var(--color-text);
  font-weight: 600;
}
.birding-migration-card__note {
  display: block;
  margin-top: 0.2rem;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--color-muted);
}
.birding-migration-card__button {
  flex-shrink: 0;
  /* Auto margins on both sides absorb the leftover space in the row after
     the text block equally, centering the button within it -- rather than
     the old space-between behavior, which put all of that leftover space
     on the button's left only, hugging it against the card's right edge. */
  margin: 0 auto;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 0.5rem 0.9rem;
  background: var(--color-surface);
  color: var(--color-surface-text);
  border: 1px solid var(--color-border);
  border-radius: 0.4rem;
  text-decoration: none;
  white-space: nowrap;
}
.birding-migration-card__button:hover {
  background: var(--color-surface-hover);
}
.birding-map-note {
  /* Same width and centering as .gallery-map right above it (--map-width,
     and full width at phone size -- see the phone block), so the note's left
     edge sits under the map's bottom-left corner instead of at the page
     edge. */
  width: var(--map-width);
  margin: -1.5rem auto 2rem;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--color-muted);
}
.birding-locations-list__ebird-credit {
  margin: 0.5rem 0 1rem;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--color-muted);
}
.birding-locations-list__ebird-credit a {
  color: var(--color-muted);
}
.gallery-map__marker--active {
  filter: hue-rotate(120deg) saturate(1.5);
  transform: scale(1.3);
  transform-origin: bottom center;
}
.gallery-map__locate a {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  font-size: 1.2rem;
  background: #fff;
  color: #333;
  text-decoration: none;
}
.gallery-map__locate a:hover {
  background: #f0f0f0;
}

/* Row-based grid: photos.php groups photos into rows of matching orientation
   (two portraits together, or two landscapes together) so a row never mixes
   a tall portrait with a short landscape -- that was leaving a gap under
   whichever photo was shorter. Landscape pairs are scaled up to the same
   full row width as a portrait pair, rather than left at their smaller
   natural size, so both row types read as the same size of "unit." Odd
   photos left without a same-orientation partner get their own single-item
   row. */
.photo-grid {
  display: flex;
  flex-direction: column;
  gap: 2rem;
}
.photo-row {
  display: flex;
  gap: 1.5rem;
  /* A row can end up pairing two different heights (e.g. a landscape next
     to a near-square photo) -- center them on each other instead of
     hugging the shorter one to the top with dead space below it. */
  align-items: center;
}
.photo-row:has(.photo-grid__caption) {
  /* Titled/captioned photos (birds, post office) read better with a clean
     shared top edge across the row -- captions sit directly under each
     photo either way, but a top edge makes the row look intentional
     instead of like the images are floating at random heights. */
  align-items: flex-start;
}
.photo-row--single {
  /* An unpaired photo has no partner to sit next to -- center it in the
     row instead of leaving it flush left with empty space to the right. */
  justify-content: center;
}
.photo-row--single .photo-grid__item {
  max-width: 48%;
}
.photo-grid__item {
  margin: 0;
  flex: 1 1 0;
  min-width: 0;
}
.photo-grid__link {
  display: block;
  background: var(--color-surface);
}
.photo-grid__link img {
  width: 100%;
  height: auto;
}
.photo-grid__caption {
  margin-top: 0.5rem;
  font-family: var(--font-mono);
}
.photo-grid__caption-title {
  display: block;
  text-transform: uppercase;
  font-size: 0.85rem;
  font-weight: 700;
}
.photo-grid__caption-text {
  display: block;
  font-family: var(--font-body);
  color: var(--color-muted);
  font-size: 0.9rem;
  margin-top: 0.25rem;
}

/* ---------- Bird audio play button ---------- */
.bird-audio {
  margin-top: 0.5rem;
}
.bird-audio__row {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
}
.bird-audio__play {
  flex: none;
  /* Inverted from a solid white-filled circle to an outline: a plain
     white disc sitting in the middle of a photo card read as a loud,
     out-of-place UI blob. Transparent fill + a colored ring/glyph reads
     as a control overlaid on the page instead of a sticker on top of it.
     Slightly smaller too, to match. */
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  border: 1.5px solid var(--color-link);
  background: transparent;
  color: var(--color-link);
  cursor: pointer;
  font-size: 0.75rem;
  line-height: 1;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.bird-audio__play:hover {
  background: var(--color-link);
  color: var(--color-bg);
}
.bird-audio__attribution {
  margin: 0;
  font-size: 0.6rem;
  color: var(--color-muted);
  line-height: 1.4;
}
.bird-audio__attribution a {
  text-decoration: underline;
}

/* Progress bar shared by both the grid play button and the lightbox player.
   Two stacked fills over a track: a faint one for how much has downloaded
   (buffered), a solid one for playback position -- clicking anywhere on the
   track seeks to that point. Hidden until Play is first pressed. */
.audio-progress {
  position: relative;
  margin-top: 0.5rem;
  height: 5px;
  border-radius: 3px;
  background: var(--color-border);
  cursor: pointer;
}
.audio-progress[hidden] {
  display: none;
}
.audio-progress__buffered,
.audio-progress__played {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  border-radius: 3px;
  width: 0%;
  pointer-events: none;
}
.audio-progress__buffered {
  background: var(--color-muted);
  opacity: 0.4;
}
.audio-progress__played {
  background: var(--color-link, var(--color-nav-text));
}

/* ---------- Lightbox ---------- */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 4rem 5rem 2rem;
  background: rgba(20, 18, 16, 0.92);
}
.lightbox[hidden] {
  display: none;
}
.lightbox__stage {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.lightbox__image {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  cursor: zoom-in;
  transition: transform 0.2s ease, opacity 0.2s ease;
}
.lightbox__image.is-zoomed {
  transform: scale(1.8);
  cursor: zoom-out;
}
/* Navigation transition (prev/next, swipe, arrow keys) -- the outgoing
   photo slides toward the exit side and fades; the incoming one is jumped
   to the opposite edge with transitions suspended (--no-transition), then
   released so it slides back to center. See show()/renderPhoto() in
   lightbox.js. Not used for the initial open from the grid -- that still
   just appears, no slide-in from nowhere. */
.lightbox__image--slide-left {
  transform: translateX(-2.5rem);
  opacity: 0;
}
.lightbox__image--slide-right {
  transform: translateX(2.5rem);
  opacity: 0;
}
.lightbox__image--no-transition {
  transition: none;
}
/* Close/prev/next are plain glyphs on the dark backdrop, no button chrome,
   so they read like part of the viewer rather than boxed UI controls. */
.lightbox__close,
.lightbox__prev,
.lightbox__next {
  position: absolute;
  border: none;
  background: none;
  color: var(--color-nav-text);
  font-family: var(--font-mono);
  cursor: pointer;
}
.lightbox__close {
  top: 1.5rem;
  right: 1.5rem;
  text-transform: uppercase;
  font-size: 0.85rem;
  letter-spacing: 0.03em;
}
.lightbox__prev,
.lightbox__next {
  top: 50%;
  transform: translateY(-50%);
  font-size: 2.5rem;
  line-height: 1;
  padding: 0.5rem;
}
.lightbox__prev {
  left: 1rem;
}
.lightbox__next {
  right: 1rem;
}
.lightbox__info {
  flex: none;
  width: 100%;
  max-width: 700px;
  margin-top: 1.5rem;
  text-align: center;
  color: var(--color-nav-text);
}
.lightbox__title {
  margin: 0 0 0.35rem;
  font-family: var(--font-mono);
  text-transform: uppercase;
  font-size: 1.1rem;
  font-weight: 700;
}
.lightbox__caption-text {
  margin: 0;
  font-size: 0.9rem;
  opacity: 0.85;
}
.lightbox__audio {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-top: 0.75rem;
}
.lightbox__audio[hidden] {
  /* display:flex above otherwise beats the browser's default [hidden]
     rule outright -- any author rule wins over that UA default, hidden
     attribute or not, so this has to be spelled out explicitly. */
  display: none;
}
.lightbox__audio-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
}
.lightbox__audio .audio-progress {
  width: 100%;
  max-width: 260px;
}
.lightbox__play {
  flex: none;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-surface-text);
  cursor: pointer;
  font-size: 0.8rem;
}
.lightbox__play:hover {
  background: var(--color-surface-hover);
}
.lightbox__attribution {
  font-size: 0.7rem;
  opacity: 0.85;
  text-align: left;
}
.lightbox__attribution a {
  color: inherit;
  text-decoration: underline;
}
body.lightbox-open {
  overflow: hidden;
}

.back-to-top {
  position: fixed;
  right: 1.5rem;
  bottom: 1.5rem;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  border: 1px solid var(--color-border);
  background: var(--color-nav-bg);
  color: var(--color-nav-text);
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0.6;
  transition: opacity 0.15s ease;
  z-index: 50;
}
.back-to-top:hover {
  opacity: 1;
}
.back-to-top[hidden] {
  display: none;
}

/* =====================================================================
   Responsive
   ===================================================================== */

/* Tablet: collapse the fixed sidebar into a static top bar. */
@media (max-width: 800px) {
  .site-nav--sidebar {
    position: static;
    width: auto;
    height: auto;
    border-right: none;
    border-bottom: 1px solid var(--color-border);
    padding: 1rem 1.25rem;
  }
  .site-nav--sidebar .site-nav__brand {
    margin-bottom: 0.75rem;
  }
  .site-nav--sidebar .site-nav__links--vertical {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 1.25rem;
  }
  .site-nav--sidebar .site-nav__links--vertical li {
    margin-bottom: 0;
  }
  .homepage {
    margin-left: 0;
  }
  .hero {
    height: 70vh;
    /* --hero-padding frames the photo nicely at desktop width, but at
       tablet/phone width that same inset eats a large share of a much
       narrower screen -- looks oddly small and leaves a background-colored
       strip on the sides/bottom instead of a subtle frame. Full-bleed on
       those three sides; keep the top inset so the photo doesn't sit flush
       against the nav bar above it. */
    padding: var(--hero-padding) 0 0;
  }
  .hero__slide {
    inset: var(--hero-padding) 0 0;
  }
}

/* Phone: tighter spacing, larger tap targets, single-column photo grid. */
@media (max-width: 600px) {
  .birding-locations-list,
  .birding-resources-columns {
    grid-template-columns: 1fr;
  }
  .birding-resources-column + .birding-resources-column {
    margin-top: 1rem;
  }
  /* Smaller at phone width -- "Open in new tab" doesn't need to be as
     prominent as it is on desktop, and the full-size version was fairly
     large relative to the migration card's own text at this width. */
  .birding-migration-card__button {
    font-size: 0.65rem;
    padding: 0.4rem 0.65rem;
  }
  .site-nav--topbar {
    padding: 0.85rem 0;
  }
  /* Stack brand above links (instead of the default side-by-side, which
     was wrapping into three uneven rows -- brand alone, then HOME|SEARCH,
     then a lone divider + OTHER PHOTOS on its own line below that). */
  .site-nav--topbar .site-nav__inner {
    padding: 0 1.25rem;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.6rem;
  }
  .site-nav--topbar .site-nav__links--horizontal,
  .mini-nav .site-nav__links--horizontal {
    gap: 0.4rem 0.75rem;
  }
  /* Smaller so HOME | SEARCH | OTHER PHOTOS reliably fits on one row at
     phone widths instead of wrapping. */
  .site-nav--topbar .site-nav__links a,
  .mini-nav .site-nav__links a {
    font-size: 1rem;
  }
  .site-nav--topbar .site-nav__links--horizontal > li:not(:first-child)::before,
  .mini-nav .site-nav__links--horizontal > li:not(:first-child)::before {
    margin-right: 0.75rem;
    font-size: 1.1em;
  }
  /* Homepage mobile menu: a hamburger toggle instead of the tablet's
     wrapped inline links, which read as cramped/unclear at phone width.
     Tapping it turns the whole nav into a full-page overlay -- logo row on
     top with a Close toggle in its place, gallery links stacked below. */
  .site-nav--sidebar {
    padding: 0.85rem 1rem;
  }
  .site-nav--sidebar .site-nav__inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
  }
  .site-nav--sidebar .site-nav__brand {
    margin-bottom: 0;
  }
  .site-nav--sidebar .site-nav__menu-toggle {
    display: inline-flex;
    align-items: center;
  }
  .site-nav--sidebar .site-nav__links--vertical {
    display: none;
  }
  .site-nav--sidebar.is-menu-open {
    position: fixed;
    inset: 0;
    z-index: 950;
    background: var(--color-nav-bg);
    overflow-y: auto;
    border-bottom: none;
  }
  .site-nav--sidebar.is-menu-open .site-nav__inner {
    /* Stays row-direction on purpose -- brand and the Close toggle need to
       stay side by side on their own top line, like the closed state.
       flex-wrap (set above, unconditionally) is what pushes the links list
       onto its own full-width line below, without needing to stack the
       brand/toggle pair too. */
    align-items: center;
    /* Default align-content spreads a wrapped flex container's multiple
       lines out to fill any leftover height -- exactly the "huge gap"
       problem this whole open-state layout keeps tripping on. Pin all
       three lines (brand+toggle, divider, links) to the top instead. */
    align-content: flex-start;
    min-height: 100%;
  }
  .site-nav--sidebar.is-menu-open .site-nav__inner::after {
    /* Full-width divider between the brand/toggle line and the links list
       below -- can't be a border on .site-nav__brand or .site-nav__menu-toggle
       since neither spans the full row width on its own. */
    content: '';
    flex-basis: 100%;
    border-bottom: 1px solid rgba(240, 232, 218, 0.2);
    margin: 0.85rem 0;
    order: 1;
  }
  .site-nav--sidebar.is-menu-open .site-nav__links--vertical {
    display: flex;
    flex-direction: column;
    flex-basis: 100%;
    order: 2;
  }
  .site-nav--sidebar.is-menu-open .site-nav__links--vertical a {
    font-size: 1.6rem;
  }
  .site-nav--sidebar.is-menu-open .site-nav__menu-toggle-open {
    display: none;
  }
  .site-nav--sidebar.is-menu-open .site-nav__menu-toggle-close {
    display: inline;
  }
  body.nav-menu-open {
    overflow: hidden;
  }
  .site-nav__dropdown-menu {
    right: auto;
    left: 0;
  }

  .gallery-page {
    padding: 1.25rem;
  }
  .gallery-page__title {
    font-size: 1.5rem;
  }

  .hero {
    height: 60vh;
    /* A little more breathing room above the photo than the tablet
       breakpoint's plain --hero-padding top inset. */
    padding-top: calc(var(--hero-padding) + 0.75rem);
  }
  .hero__slide {
    top: calc(var(--hero-padding) + 0.75rem);
  }
  .hero__slide img {
    /* object-fit: cover (desktop/tablet) never distorts -- it only crops --
       but a tall, narrow phone viewport against a wide landscape photo
       means "cover" crops off a lot of the actual frame to fill that shape.
       Contain shows the whole photo untouched instead, letterboxed on
       --color-bg (already the .hero's own background) rather than cropped. */
    object-fit: contain;
  }

  .photo-grid {
    gap: 1.75rem;
  }
  .photo-row {
    flex-direction: column;
  }
  .photo-row--single .photo-grid__item {
    max-width: 100%;
  }

  .gallery-map {
    /* Full width again here -- a phone screen has no spare margin to give
       up, and the "rest the cursor beside the map" reasoning for the
       narrower desktop width doesn't apply to touch scrolling anyway. */
    width: 100%;
    height: 280px;
  }
  .birding-map-note {
    width: 100%;
  }

  .birds-banner {
    /* Full width here (a phone screen has no spare margin to give up),
       plus the short labels below swapped in -- the FULL labels ("BirdNET
       — San Antonio, TX" + "Central Texas Birding Spots") don't both fit
       side-by-side even at 100% width on a phone screen, and per feedback
       this should never wrap to stacked or need a scrollbar either. */
    width: 100%;
  }
  .birds-banner__label-full {
    display: none;
  }
  .birds-banner__label-short {
    display: inline;
  }

  .bird-audio__play {
    width: 1.7rem;
    height: 1.7rem;
    font-size: 0.65rem;
  }
  .bird-audio__attribution {
    font-size: 0.55rem;
    line-height: 1.3;
  }

  .lightbox {
    padding: 3.5rem 0.5rem 1.5rem;
  }
  .lightbox__prev {
    left: 0.25rem;
  }
  .lightbox__next {
    right: 0.25rem;
  }
}

/* =====================================================================
   Cascading swallow effect -- Birds gallery page only (gallery.php gates
   the actual HTML on $slug === 'birds', see the comment there for the
   asset's source/license and the fast-flight/long-pause design). Fixed
   full-viewport layer, meant to sit behind the topbar/photos (z-index 0
   vs. the topbar's 900), never intercepts clicks (pointer-events: none).
   ===================================================================== */
/* .gallery-page itself has no position set elsewhere in this file, so it
   paints as a plain static/in-flow element -- and static content always
   paints BELOW any positioned element (this fixed rain layer included),
   regardless of z-index, because "positioned" and "non-positioned" are
   two entirely separate paint passes, z-index only orders within the
   positioned one. Without this rule the birds/text ordering here doesn't
   even reach a z-index comparison -- the birds win by default and sit on
   top of the photos and title. Scoped to the birds page specifically
   (not plain .gallery-page) so this doesn't alter stacking on every other
   gallery, which has no fixed sibling to worry about anyway. */
.gallery-page--birds {
  position: relative;
  z-index: 1;
}

/* Page-title accent only (2026-09-12, revised down from also tinting
   every individual photo caption -- that read as noisy across a whole
   grid of photos; the per-photo captions below are back to plain
   --color-text, no override, same as before any of this). The gallery
   *name* still gets an identity: --color-link blended 55% toward white
   (cuts its blue/purple cast rather than just brightening it -- pulling
   R/G up to meet the already-maxed B channel is what actually
   neutralizes the blue, not just raising lightness) for every gallery
   except Landscapes, which keeps its own green instead. */
.gallery-page__title {
  color: #f1f5ff;
}
.gallery-page--landscapes .gallery-page__title {
  color: #cbdcc2;
}

/* Restrained film-grain -- menus only, never the photos, and (revised
   2026-09-12 per feedback) not the big page background either -- it read
   as too present across that much lighter, larger area. Scoped down to
   just the dark nav bars (topbar, sidebar, mini-nav below). A tileable
   SVG turbulence texture recolored to white at ~5% alpha (feColorMatrix
   maps luminance to alpha, RGB pinned to white), applied via
   background-image -- never to .photo-grid__link or the lightbox image,
   which stay untouched. Layering it as a background image (not a fixed
   full-viewport overlay) is what keeps it structurally incapable of
   landing on top of a photo: it can only ever show through these specific
   elements' own background, and a photo is an opaque <img> sitting on top
   of whatever's behind it either way. */
:root {
  --grain-overlay: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix type='matrix' values='0 0 0 0 1  0 0 0 0 1  0 0 0 0 1  0 0 0 0.045 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
}
.site-nav--topbar,
.site-nav--sidebar {
  background-image: var(--grain-overlay);
}
.swallow-rain {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}
.swallow-rain__bird {
  position: absolute;
  right: -20vw;
  width: 160px;
  height: 160px;
  /* animation-name is NOT set here -- gallery.php gives each bird its own
     @keyframes (swallow-fly-0, -1, ...) inline, since the fast-flight/
     long-pause split needs a different percentage breakpoint per bird
     (each has a different total flight+pause duration). */
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}
/* Fast(ish) translate from 0% to an early percentage (the actual flight
   across the screen), then HOLD at that already-off-screen end position
   for the rest of the timeline -- the hold is invisible (past the left
   edge already) but is what creates the long gap before the next pass
   instead of a constant stream of birds. Revised 2026-09-07: each bird's
   crossing is now its own little path (multiple waypoints, not one flat
   diagonal) so some read as a slower, wavier up/down swoop instead of
   both doing the identical straight-line climb. Down to 2 birds total
   as of 2026-09-12 (was 3) -- see gallery.php. */
@keyframes swallow-fly-0 {
  /* The direct one -- single gentle upward diagonal, fastest crossing
     of the two. */
  0%     { transform: translate(0, 0); }
  9.6%   { transform: translate(-70vw, -4vh); }
  19.2%  { transform: translate(-140vw, -10vh); }
  100%   { transform: translate(-140vw, -10vh); }
}
@keyframes swallow-fly-1 {
  /* The slow, wavy one -- longest crossing, dips and climbs twice
     (a lazier "riding the air" swoop) before leveling off high. */
  0%     { transform: translate(0, 0); }
  4.4%   { transform: translate(-32vw, 5vh); }
  8.8%   { transform: translate(-64vw, -8vh); }
  13.2%  { transform: translate(-96vw, 4vh); }
  17.6%  { transform: translate(-140vw, -13vh); }
  100%   { transform: translate(-140vw, -13vh); }
}
/* Independent, much shorter animation nested inside the flight above -- a
   quick up/down bob mimicking the lift from each wing flap, decoupled
   from the slow multi-second crossing so it reads at an actual
   wingbeat-like tempo regardless of how long the full flight takes.
   --bob-amp varies per bird (set inline in gallery.php) for variety. */
.swallow-rain__bird-flap {
  width: 100%;
  height: 100%;
  animation-name: swallow-flap;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}
@keyframes swallow-flap {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(calc(-1 * var(--bob-amp, 6px))); }
}
.swallow-rain__bird svg {
  width: 100%;
  height: 100%;
  display: block;
  fill: var(--color-text);
}
@media (prefers-reduced-motion: reduce) {
  .swallow-rain__bird, .swallow-rain__bird-flap { animation: none; }
}

/* =====================================================================
   DESIGN POLISH BRAINSTORM -- 2026-09-07, LOCAL MOCKUP ONLY, NOT DEPLOYED.
   Confirmed via grep before writing any of this: zero box-shadow anywhere
   in this file, and .photo-grid__link (the actual majority of every
   page's real content) had no hover feedback, no rounding, no depth at
   all. Everything below is additive -- no existing rule touched -- so
   it's trivial to delete this whole block and be back to exactly what's
   live if none of it lands.
   ===================================================================== */

/* Smooth scroll for the back-to-top button / any in-page anchor jump. */
html {
  scroll-behavior: smooth;
}

/* Themed selection + scrollbar instead of the browser's stock light-blue
   defaults, which look out of place against this site's dark theme. */
::selection {
  background: var(--color-link);
  color: var(--color-bg);
}
::-webkit-scrollbar {
  width: 12px;
  height: 12px;
}
::-webkit-scrollbar-track {
  background: var(--color-bg);
}
::-webkit-scrollbar-thumb {
  background: var(--color-border);
  border-radius: 6px;
  border: 3px solid var(--color-bg);
}
::-webkit-scrollbar-thumb:hover {
  background: var(--color-muted);
}

/* Themed focus ring instead of the browser's default blue outline --
   still clearly visible (accessibility), just matching the site instead
   of clashing with it. :focus-visible so it only shows for keyboard
   navigation, not every mouse click. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible,
summary:focus-visible {
  outline: 2px solid var(--color-link);
  outline-offset: 2px;
  border-radius: 2px;
}

/* Photo grid -- a gentle lift-and-zoom on hover. Revised 2026-09-07 per
   feedback: no rounded corners and a much lighter shadow on the photos
   themselves specifically -- rounding/shadow stay reserved for menu-ish
   surfaces (cards, buttons, banners) below, not the photos. Kept subtle
   on purpose (short travel, modest zoom) -- this is a photography
   portfolio, the photo is the point, not the hover effect. */
.photo-grid__link {
  display: block;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
  transition: box-shadow 0.25s ease, transform 0.25s ease;
}
.photo-grid__link img {
  transition: transform 0.4s ease, opacity 0.5s ease;
}
.photo-grid__link:hover {
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.3);
  transform: translateY(-3px);
}
.photo-grid__link:hover img {
  transform: scale(1.045);
}

/* Image fade-in-on-load (see assets/js/gallery-polish.js) -- the <img>
   starts transparent and fades to full opacity once it's actually
   decoded/painted, instead of popping in abruptly (especially noticeable
   on lazy-loaded off-screen grid images). .is-loaded is added by JS for
   both the "loaded from cache before JS ran" and the normal load-event
   case. If JS never runs (disabled), the plain CSS rule below still
   shows the image -- fade-in is a nice-to-have, never a blocker. */
.photo-grid__link img {
  opacity: 0;
}
.photo-grid__link img.is-loaded,
.photo-grid__link.is-loaded img {
  opacity: 1;
}
/* No-JS / JS-hasn't-run-yet fallback so images are never stuck invisible. */
.photo-grid__link:not(.js-fade) img {
  opacity: 1;
}

/* Skeleton shimmer placeholder -- shown behind the image while it loads,
   removed once .is-loaded lands (see above). This is a generic shimmer,
   not a true blurred-thumbnail preview (that would need actual low-res
   thumbnails generated per photo) -- simple placeholder, not a fake-detail
   preview of the real image. */
.photo-grid__item {
  position: relative;
}
.photo-grid__link.js-fade::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    100deg,
    var(--color-border) 30%,
    rgba(255, 255, 255, 0.06) 50%,
    var(--color-border) 70%
  );
  background-size: 200% 100%;
  animation: photo-shimmer 1.6s ease-in-out infinite;
  z-index: 0;
}
.photo-grid__link.js-fade.is-loaded::before {
  content: none;
}
.photo-grid__link img {
  position: relative;
  z-index: 1;
}
@keyframes photo-shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* Section entrance animations -- fade + slide up as grid items (and, on
   the Birding Spots page, cards/collapsibles) scroll into view, via
   IntersectionObserver in assets/js/gallery-polish.js. Elements start
   already in their final state so anything JS never reaches (disabled,
   or already in the viewport on load) still renders correctly. */
.entrance-fade {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.5s ease, transform 0.5s ease;
}
.entrance-fade.is-visible {
  opacity: 1;
  transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
  .entrance-fade {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* Cards -- a little shadow so they read as raised surfaces instead of a
   flat outline sitting on the exact same flat background color. */
.birding-collapsible,
.birding-migration-card,
.birds-banner__link,
.birds-banner__button {
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
  transition: box-shadow 0.2s ease, transform 0.2s ease;
}
.birds-banner__link:hover,
.birds-banner__button:hover {
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.4);
  transform: translateY(-1px);
}

/* Buttons -- smoother/eased hover instead of an instant color swap, a
   subtle lift, and tactile feedback on the actual click/tap. */
.birding-migration-card__button,
.birding-submit__button {
  transition: background-color 0.2s ease, box-shadow 0.2s ease, transform 0.15s ease;
}
.birding-migration-card__button:hover,
.birding-submit__button:hover {
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.35);
  transform: translateY(-1px);
}
.birding-migration-card__button:active,
.birding-submit__button:active {
  transform: translateY(0) scale(0.97);
}

/* Lightbox prev/next arrows -- currently background:none/border:none, so
   they float with zero depth over the photo. Give them a circular dark
   backing (subtle, doesn't compete with the photo) plus a shadow and a
   hover lift. The hover rule adds scale() alongside the existing
   translateY(-50%) vertical-centering transform (both are needed on the
   same declaration, since a second `transform` would just overwrite the
   first instead of combining). */
.lightbox__prev,
.lightbox__next {
  width: 2.75rem;
  height: 2.75rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.35);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
  transition: background-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
}
.lightbox__prev:hover,
.lightbox__next:hover {
  background: rgba(0, 0, 0, 0.55);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.5);
  transform: translateY(-50%) scale(1.08);
}
.lightbox__prev:active,
.lightbox__next:active {
  transform: translateY(-50%) scale(0.95);
}

/* Audio attribution redesign -- revised 2026-09-07. The full attribution
   text ("Lesser Goldfinch recorded by ... licensed CC ... via Xeno-canto")
   was competing for attention with the photo itself before playback even
   started. Now the row shows only a faint "Play sound" label; the real
   attribution (unchanged markup/content, just moved into its own .bird-audio
   block in gallery.php and hidden by default) is revealed by
   audio-player.js the moment playback is requested, appearing under the
   player instead of beside the play button.

   Layout, revised again same day per feedback: "Play sound" + the button
   move up onto the title's own line, button as the rightmost thing on
   the card. .photo-grid__caption becomes the flex row -- title grows to
   fill the leftover space (pushing .bird-audio__row to the right edge
   without needing an explicit margin-left:auto), while the caption text
   and the progress-bar/attribution block each claim flex-basis:100% so
   they fall to their own full-width line below instead of trying to
   share the title's line. */
.photo-grid__caption {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: 0.6rem;
}
.photo-grid__caption-title {
  flex: 1 1 auto;
  min-width: 0;
}
.bird-audio__row {
  flex: 0 0 auto;
  align-items: center;
}
.photo-grid__caption-text,
.bird-audio {
  flex-basis: 100%;
}
.bird-audio__label {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  /* Was `color: var(--color-muted); opacity: 0.55;` -- measured against the
     real page background that comes out to ~2.67:1, under WCAG AA's 4.5:1
     floor for normal-size text (getting back above it with that same base
     color needs ~0.9 opacity, which erases the "faint on purpose" look).
     #a9a091 (a fixed color instead of alpha blending) fixed the contrast
     but read as muddy/brown-grey; blended 30% further toward white
     (2026-09-12) to lighten that cast while staying visibly quieter than
     regular text -- still faint on purpose, just not muddy. 6.23:1 now. */
  color: #c3bcb2;
}
.bird-audio__attribution {
  margin-top: 0.4rem;
}
.bird-audio__attribution[hidden] {
  display: none;
}

/* =====================================================================
   2026-09-11 batch -- picked off the improvement brainstorm. Additive,
   same as everything above.
   ===================================================================== */

/* Homepage hero: slow Ken Burns drift. hero.js crossfades between slides
   on a 9s interval (slowed 2026-09-12, was 6s) via .is-active -- this
   rides that same class toggle, no JS change needed beyond keeping this
   duration roughly in step with it. 10.5s (a touch longer than the
   interval) so it's still gently moving through the crossfade instead of
   visibly resetting right as the next slide fades in. */
@keyframes hero-drift {
  from { transform: scale(1); }
  to { transform: scale(1.06); }
}
.hero__slide.is-active img {
  animation: hero-drift 10.5s ease-out forwards;
}
@media (prefers-reduced-motion: reduce) {
  .hero__slide.is-active img { animation: none; }
}

/* Lightbox position counter -- The Post Office only (see gallery.php,
   which only renders .lightbox__counter when $slug === 'post-office').
   83 photos deep with no sense of how far there is left to go was the
   actual problem; a 20-photo gallery doesn't need this. */
.lightbox__counter {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--color-muted);
  letter-spacing: 0.03em;
}

/* Mini-nav -- the #back-to-top button already proves this mechanism (a
   fixed control revealed once you've scrolled past the topbar); this is
   the same trick extended into a slim persistent way back to Home/Search/
   Other Photos, wired up in nav.js alongside the existing back-to-top
   scroll listener. Reuses .site-nav__links/.site-nav__dropdown markup
   wholesale so the existing dropdown click/hover handling in nav.js just
   works here too, with zero new JS for that part. */
.mini-nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 20;
  background: var(--color-nav-bg);
  background-image: var(--grain-overlay);
  border-bottom: 1px solid var(--color-border);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
}
.mini-nav[hidden] {
  display: none;
}
.mini-nav__inner {
  max-width: 1100px;
  margin: 0 auto;
  padding: 0.5rem 2rem;
}
.mini-nav .site-nav__links--horizontal {
  font-size: 1rem;
}
.mini-nav .site-nav__links--horizontal a {
  font-size: 1rem;
  min-height: auto;
}

/* "Load more" -- The Post Office's pagination (see gallery.php). A plain
   link styled to match the other pill controls on the page (the birds-
   banner buttons), not a full-width bar -- it's a secondary action at the
   bottom of a page, not a primary one. */
.load-more {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 0.5rem;
  margin: 1rem auto 0;
  width: fit-content;
  color: var(--color-text);
  background: var(--color-nav-bg);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  padding: 0.6rem 1.4rem;
  font-family: var(--font-mono);
  font-size: 0.95rem;
  text-decoration: none;
  transition: background-color 0.2s ease, transform 0.2s ease;
}
.load-more:hover {
  background: var(--color-surface-hover);
  color: var(--color-surface-text);
  transform: translateY(-1px);
}
.load-more__count {
  color: var(--color-muted);
  font-size: 0.8rem;
}
.load-more:hover .load-more__count {
  color: inherit;
  opacity: 0.7;
}

/* Search page. */
.search-form {
  display: flex;
  gap: 0.75rem;
  max-width: 32rem;
  margin: 1rem 0 1.5rem;
}
.search-form__input {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: 1rem;
  color: var(--color-text);
  background: var(--color-nav-bg);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  padding: 0.6rem 0.9rem;
}
.search-form__input::placeholder {
  color: var(--color-muted);
}
.search-form__button {
  font-family: var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-size: 0.9rem;
  color: var(--color-text);
  background: var(--color-surface-hover, var(--color-nav-bg));
  border: 1px solid var(--color-border);
  border-radius: 6px;
  padding: 0.6rem 1.2rem;
  cursor: pointer;
}
.search-form__button:hover {
  color: var(--color-surface-text);
}
.search-summary {
  color: var(--color-muted);
  font-family: var(--font-mono);
  font-size: 0.9rem;
}
.search-results {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 1.5rem;
  margin-top: 1.5rem;
}
.search-result {
  display: flex;
  flex-direction: column;
  color: inherit;
  text-decoration: none;
}
.search-result__thumb {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
  transition: transform 0.2s ease;
}
.search-result:hover .search-result__thumb {
  transform: translateY(-2px);
}
.search-result__title {
  margin-top: 0.5rem;
  font-family: var(--font-mono);
  text-transform: uppercase;
  font-size: 0.8rem;
  font-weight: 700;
}
.search-result__gallery {
  font-size: 0.78rem;
  color: var(--color-muted);
}


/* =====================================================================
   Visual refresh -- 2026-09-18. Six independent blocks (B, A, C, D, E, F),
   appended last so they win ties with the rules above. Each can be
   deleted on its own to revert just that change. Leaflet's stylesheet
   loads after this file, so the D block prefixes its selectors with
   .leaflet-container to out-rank it.
   ===================================================================== */

/* ---------- B ---------- */
/* B -- Deeper ground.
   #383838 is the mid-gray photographers edit against, not present on: it
   flattens the photos' contrast and makes the greens read washed. A deeper
   neutral, with the raised surfaces (nav, banner, cards) stepping up from it
   instead of down. Hairlines flip from "darker than the page" to "lighter
   than the page", which is the correct direction on a dark ground. */
:root {
  --color-bg: #1b1c1c;
  --color-nav-bg: #252727;
  --color-border: #363939;
}
.site-nav--sidebar {
  background-color: #202222;
}
.photo-grid__link {
  background: var(--color-nav-bg);
  box-shadow: 0 0 0 1px rgba(240, 232, 218, 0.05);
}
.photo-grid__link:hover {
  box-shadow: 0 0 0 1px rgba(240, 232, 218, 0.1), 0 10px 26px rgba(0, 0, 0, 0.5);
}

/* ---------- A ---------- */
/* A -- One control language.
   The dropdown, the Birding Spots form, and the "Open in new tab" button
   all use the light --color-surface card; the search page and banner pills
   are already dark. Bring the stragglers onto the dark system. */

/* Dropdown: raised dark panel, no more wrapped two-line labels */
.site-nav__dropdown-menu {
  background: #262828;
  border: 1px solid rgba(240, 232, 218, 0.14);
  border-radius: 8px;
  padding: 0.45rem;
  min-width: 15rem;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.5);
}
.site-nav__dropdown-menu li a,
.site-nav__dropdown-menu li .site-nav__dropdown-current {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  min-height: 0;
  padding: 0.6rem 0.8rem;
  border-radius: 5px;
  font-size: 1rem;
  white-space: nowrap;
}
.site-nav__dropdown-menu li a {
  color: var(--color-text);
}
.site-nav__dropdown-menu li a:hover {
  background: rgba(240, 232, 218, 0.08);
  text-shadow:
    -2px 0 0 rgba(255, 0, 60, 0.8),
     2px 0 0 rgba(0, 255, 255, 0.8) !important;
}
.site-nav__dropdown-menu li .site-nav__dropdown-current {
  color: var(--color-muted);
  opacity: 1;
}
.site-nav__dropdown-menu li .site-nav__dropdown-current::after {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-muted);
}

/* Form fields: dark, hairline, real focus ring */
.birding-submit__field input,
.birding-submit__field textarea {
  background: var(--color-bg);
  color: var(--color-text);
  border: 1px solid rgba(240, 232, 218, 0.22);
  border-radius: 6px;
  padding: 0.65rem 0.85rem;
}
.birding-submit__field input:focus,
.birding-submit__field textarea:focus {
  outline: none;
  border-color: var(--color-link);
  box-shadow: 0 0 0 3px rgba(223, 233, 255, 0.16);
}

/* Search field: the clear-"x" is drawn light by the browser now that the
   page declares color-scheme: dark (it was stock blue under `light`), so it
   only needs the pointer cursor. */
.search-form__input::-webkit-search-cancel-button {
  cursor: pointer;
}

/* Buttons: quiet outline, invert on hover */
.birding-submit__button,
.birding-migration-card__button,
.search-form__button {
  background: transparent;
  color: var(--color-text);
  border: 1px solid rgba(240, 232, 218, 0.4);
  border-radius: 6px;
}
.birding-submit__button:hover,
.birding-migration-card__button:hover,
.search-form__button:hover {
  background: var(--color-text);
  color: #1b1c1c;
  box-shadow: none;
}

/* ---------- C ---------- */
/* C -- Title and grid rhythm.
   The page title is the only heading and it currently sits tight between
   the banner above and the photos below. Give it presence, anchor it to the
   grid with a hairline, and open up the spacing between photos. */
.gallery-page__title {
  font-size: 2.4rem;
  line-height: 1.1;
  letter-spacing: 0.07em;
  margin: 0.25rem 0 2rem;
  padding-bottom: 1.15rem;
  border-bottom: 1px solid var(--color-border);
}
.photo-grid {
  gap: 2.75rem;
}
.photo-row {
  gap: 1.75rem;
}
.photo-grid__caption {
  margin-top: 0.8rem;
}
.photo-grid__caption-title {
  letter-spacing: 0.06em;
}

/* ---------- D ---------- */
/* D -- Maps that belong to the page.
   The Leaflet map is the brightest object on the Post Office and Birding
   Spots pages, with stock blue teardrop pins. Same map, same tiles, same
   behavior: tone the tiles down to sit in the dark UI, keep the pins blue
   (see the marker rule below), and restyle the controls. Width is the
   shared --map-width (see the comment on .gallery-map: it leaves a strip to
   rest the mouse while scrolling). */
.gallery-map {
  border-radius: 8px;
  overflow: hidden;
  border: 1px solid var(--color-border);
  background: #1f2222;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.28);
}
/* Leaflet paints .leaflet-container #ddd (loads after this file, ties go to
   it) -- visible wherever a tile hasn't arrived yet. */
.gallery-map.leaflet-container {
  background: #1f2222;
}
.gallery-map.leaflet-container .leaflet-tile-pane {
  filter: invert(1) hue-rotate(180deg) brightness(0.88) contrast(0.85) saturate(0.45);
}
/* Pins stay Leaflet's stock blue (an amber hue-rotate shipped first and was
   changed back on request) -- just lifted slightly so they hold up on the
   dark tiles, with a soft shadow. */
.gallery-map.leaflet-container .leaflet-marker-icon {
  filter: saturate(0.92) brightness(1.12)
          drop-shadow(0 2px 2px rgba(0, 0, 0, 0.45));
}
/* The selected-pin highlight (.gallery-map__marker--active, Birding Spots)
   is its own hue-rotate on this same element. The rule above out-ranks it,
   so restate it here or selecting a pin stops changing its colour. */
.gallery-map.leaflet-container .gallery-map__marker--active {
  filter: hue-rotate(120deg) saturate(1.5) brightness(1.12)
          drop-shadow(0 2px 2px rgba(0, 0, 0, 0.45));
}
.gallery-map.leaflet-container .leaflet-bar {
  border: 1px solid rgba(240, 232, 218, 0.18);
  box-shadow: none;
}
.gallery-map.leaflet-container .leaflet-bar a {
  background-color: #262828;
  color: var(--color-text);
  border-bottom-color: rgba(240, 232, 218, 0.14);
}
.gallery-map.leaflet-container .leaflet-bar a:hover {
  background-color: #303333;
  color: var(--color-text);
}
.gallery-map.leaflet-container .leaflet-control-attribution {
  background: rgba(27, 28, 28, 0.82);
  color: var(--color-muted);
}
.gallery-map.leaflet-container .leaflet-control-attribution a {
  color: var(--color-link);
}
.gallery-map.leaflet-container .leaflet-popup-content-wrapper,
.gallery-map.leaflet-container .leaflet-popup-tip {
  background: #262828;
  color: var(--color-text);
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.5);
}
.gallery-map.leaflet-container .leaflet-bar a.leaflet-disabled {
  background-color: #1f2121;
  color: rgba(240, 232, 218, 0.3);
}
/* Popup content: the title link was styled for the old light popup. */
.gallery-map.leaflet-container .leaflet-popup-content a.gallery-map__popup-title {
  color: var(--color-text);
}
.gallery-map.leaflet-container .leaflet-popup-content a.gallery-map__popup-title:hover {
  color: var(--color-link);
}
.gallery-map.leaflet-container a.leaflet-popup-close-button {
  color: var(--color-muted);
}
.gallery-map.leaflet-container a.leaflet-popup-close-button:hover {
  color: var(--color-text);
  background: transparent;
}

/* ---------- E ---------- */
/* E -- Compact phone header.
   At phone width the two-row header (brand, then links) takes a large slice
   of the first screen. Same two rows and same 44px tap targets on the
   links, with the dead space trimmed. */
@media (max-width: 600px) {
  .site-nav--topbar {
    padding: 0.5rem 0 0.4rem;
  }
  .site-nav--topbar .site-nav__inner {
    gap: 0;
  }
  .site-nav__brand a {
    min-height: 40px;
    font-size: 1rem;
    gap: 0.5rem;
  }
  .site-nav__logo-mark {
    height: 1.75rem;
  }
}

/* ---------- F ---------- */
/* F -- A footer that closes the page.
   Pages currently just stop after 6rem of empty space. A hairline on the
   same edges as the content, and one quiet line. Gallery-type pages only;
   the homepage keeps its sidebar layout. */
.site-nav--topbar ~ .site-footer {
  height: auto;
  max-width: 1100px;
  margin: 4.5rem auto 0;
  padding: 0 2rem 3rem;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-muted);
}
.site-nav--topbar ~ .site-footer::before {
  content: '';
  display: block;
  height: 1px;
  background: var(--color-border);
  margin-bottom: 1.5rem;
}
