:root {
  --bg: #0a0a0a;
  --bg-center: #1a1a1a;
  --topbar: #000000;
  --surface: #0f0f0f;
  /* Article cards sit a touch above --surface so they read as objects on the
     backplate rather than dissolving into it. Their own variable, because the
     lightbox and topbar want to stay flush with the page. */
  --card: #171717;
  --panel: #161616;
  --tile: #1e1e1e;
  --text: #f1f1f1;
  --strong: #ffffff;
  --soft: #cfcfcf;
  --muted: #9a9a9a;
  --faint: #5a5a5a;
  --border: #2e2e2e;
  --brand-mid: rgb(80, 80, 80);
  --brand-hi: rgb(120, 120, 120);
  --accent: #6be3ff;
  --c-amber: #e8b64c;
  --c-blue: #6be3ff;
}

/* Light mode is scoped: inside an article the whole page flips, but on the
   articles index only the cards do — the backplate stays dark. */
:root[data-theme="light"][data-page="article"],
:root[data-theme="light"][data-page="articles"] .article-tile {
  --bg: #ececea;
  --bg-center: #fafafa;
  --topbar: #ffffff;
  --surface: #ffffff;
  --card: #ffffff;
  --panel: #e4e4e4;
  --tile: #dcdcdc;
  --text: #1c1c1c;
  --strong: #000000;
  --soft: #333333;
  --muted: #6a6a6a;
  --faint: #b0b0b0;
  --border: #d0d0d0;
  --brand-mid: rgb(150, 150, 150);
  --brand-hi: rgb(80, 80, 80);
  --accent: #0077cc;
  /* The dark-mode article colours wash out on white — darken, same hues. */
  --c-amber: #8a5e00;
  --c-blue: #0a6e8c;
}

* {
  box-sizing: border-box;
}

/* No focus rings anywhere — arrow keys drive the lightbox, so :focus-visible
   would light up constantly and the outlines fight the aesthetic. */
*:focus,
*:focus-visible,
*:focus-within {
  outline: none;
}

* {
  -webkit-tap-highlight-color: transparent;
}

html {
  /* Stops the browser hijacking an upward drag as pull-to-refresh. */
  overscroll-behavior-y: none;
}

/* Articles read better on a flat plate than under the grid's gradient. */
:root[data-page="articles"] body,
:root[data-page="article"] body {
  background: var(--bg);
}

/* Ease the light/dark swap instead of snapping it. */
:root.theme-fading,
:root.theme-fading * {
  transition: background-color 180ms ease, color 180ms ease, border-color 180ms ease !important;
}

body {
  margin: 0;
  font-family: "Avenir Next", "Helvetica Neue", Helvetica, Arial, sans-serif;
  background: radial-gradient(circle at 20% 10%, var(--bg-center) 0%, var(--bg) 35%, var(--bg) 100%);
  color: var(--text);
  min-height: 100vh;
  overscroll-behavior-y: none;
}

a {
  color: inherit;
}

.topbar {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 0.4rem;
  /* 71px is the masthead's own height (142px asset at 2x). Pinned so the
     banner is identical on every page: the grid's row is otherwise ~3px
     taller than the articles' because the PUSH pill sets its height. */
  min-height: 71px;
  padding: 0.85rem 1.5rem;
  border-bottom: 1px solid var(--border);
  background: var(--topbar);
}

/* Masthead banner — everywhere the topbar is dark: the grid, the articles
   index (whose backplate stays dark even in light mode) and dark articles.
   The one exclusion is below.
   The banner var is set per load by assets/js/masthead.js; layered under two
   viewport-adaptive scrims: right ramps 100%→0 across the window's outer half,
   left holds a 25% veil under the wordmark, gone by PHOTOGRAPHY (~356px).
   Both re-derive from the window width, so the masking tracks a resize. */
.topbar {
  background-color: var(--topbar);
  background-image:
    linear-gradient(to left, rgb(0, 0, 0) 0%, rgba(0, 0, 0, 0) 50%),
    linear-gradient(to right, rgba(0, 0, 0, 0.25) 0px, rgba(0, 0, 0, 0) 356px),
    var(--masthead, none);
  background-size: auto, auto, cover;
  background-position: center;
  background-repeat: no-repeat;
}

/* Active menu items carry a red indicator dot, hung in the gap to the left so
   toggling never shifts the row. Every selected control gets one, on every
   page and in both rows — categories, tags, ARTICLES, and the theme toggles. */
.nav-toggle.active,
.tag-toggle.active,
.nav-articles.active {
  position: relative;
}

.nav-articles.active {
  color: var(--strong);
  font-weight: 700;
}

.nav-toggle.active::before,
.tag-toggle.active::before,
.nav-articles.active::before {
  content: "";
  position: absolute;
  left: -0.62em;
  top: calc(50% - 0.16em);
  width: 0.32em;
  height: 0.32em;
  border-radius: 50%;
  background: rgb(255, 0, 0);
  box-shadow: 0 0 4px rgba(255, 0, 0, 0.7);
}

/* A light-mode article page flips its whole topbar white, nav text included —
   a photo behind dark text would be unreadable, so that one drops the banner
   and its glows. Everywhere else keeps them. */
:root[data-theme="light"][data-page="article"] .topbar {
  background-image: none;
}

/* Legibility over the banner: a tight dark halo, not a visible glow. */
.mainnav a,
.mainnav button,
.mainnav .sep,
.subnav button,
.subnav .sep,
.scan-status {
  text-shadow: 0 0 4px rgba(0, 0, 0, 0.9), 0 1px 2px rgba(0, 0, 0, 0.8);
}

:root[data-theme="light"][data-page="article"] .mainnav a,
:root[data-theme="light"][data-page="article"] .mainnav button,
:root[data-theme="light"][data-page="article"] .mainnav .sep,
:root[data-theme="light"][data-page="article"] .subnav button,
:root[data-theme="light"][data-page="article"] .subnav .sep {
  text-shadow: none;
}

.subnav {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  font-size: 0.68rem;
  letter-spacing: 0.09em;
}

.subnav .sep {
  color: rgb(40, 40, 40);
}

/* Same ramp as .nav-toggle — the two sub-rows are the same control in
   different contexts, so they read identically: muted at rest, white and
   bold when selected. */
.tag-toggle {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  letter-spacing: inherit;
  color: var(--muted);
  cursor: pointer;
}

.tag-toggle.active {
  color: var(--strong);
  font-weight: 700;
}

.tag-toggle:hover {
  color: var(--text);
}

.mainnav {
  display: flex;
  flex: 1;
  /* Full row, not shrink-wrapped: margin-left:auto children (SCAN MEDIA, the
     article edit bar) push to the window's right edge. */
  width: 100%;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.9rem;
  font-size: 0.8rem;
  letter-spacing: 0.07em;
}

/* The edit toolbar shares this row on an article page — let the nav wrap as a
   whole rather than breaking "DARK MODE" across two lines. */
.mainnav .brand,
.mainnav .nav-link,
.mainnav .nav-toggle {
  white-space: nowrap;
}

.mainnav .sep {
  color: var(--faint);
}

.theme-toggle {
  margin-left: auto;
  background: none;
  border: none;
  padding: 0;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: 1rem;
}

.theme-toggle:hover {
  color: var(--text);
}

/* Wordmark: Orator Std baked to SVG paths by scripts/make_wordmark.py —
   the font is a local Adobe activation visitors don't have, so the exact
   look (75% squash, 0.08em tracking, stroked-bold RIZA) is frozen as
   vectors. font-size still drives the size: the SVG height is in em. */
.brand {
  display: inline-block;
  font-size: 1.45rem;
  white-space: nowrap;
}

.brand .wordmark {
  /* viewBox is 498 font-units tall on a 1000-unit em, squash pre-baked.
     The -0.027em lifts the ink onto the nav's optical line (measured). */
  position: relative;
  top: -0.027em;
  display: block;
  height: 0.498em;
  width: auto;
}

.wordmark g {
  transition: fill 160ms ease, stroke 160ms ease;
}

/* www. and .com carry the same weight as BRANDON. As their own dimmer tier
   they read as falling off the ends of the wordmark; matched, the whole URL
   holds together and RIZA stays the only accent. The wm-dim class still marks
   the segments make_wordmark.py emits, it just no longer prints darker. */
.wordmark .wm-dim,
.wordmark .wm-mid {
  fill: var(--brand-mid);
}

.wordmark .wm-hi {
  fill: var(--brand-hi);
  stroke: var(--brand-hi);
}

/* The wordmark is a button too — hover brightens each tier one step so it
   reads as clickable (home, which also rerolls the masthead). */
.brand:hover .wm-dim,
.brand:hover .wm-mid {
  fill: var(--brand-hi);
}

.brand:hover .wm-hi {
  fill: var(--strong);
  stroke: var(--strong);
}

.nav-toggle {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  letter-spacing: inherit;
  color: var(--muted);
  cursor: pointer;
}

.nav-toggle.active {
  color: var(--strong);
  font-weight: 700;
}

.nav-toggle:disabled {
  color: var(--faint);
  cursor: default;
}

/* Hard right of the nav: CONTACT for everyone, SCAN MEDIA beside it for the
   author. The wrapper carries the push so the layout holds either way. */
.nav-right {
  display: flex;
  align-items: center;
  /* Tighter than the main row: each social carries its own dot, so the gap
     only has to separate items, not host a separator. Split with the mark's
     own margin-right so the dot lands centred between the two labels. */
  gap: 0.35rem;
  margin-left: auto;
  /* CHEERS? hangs its panel off the end of this row. */
  position: relative;
}


/* PUSH publishes to the live server — red, like DELETE, because it is the
   other control here with consequences beyond this machine. */
.push-btn {
  padding: 0.14rem 0.65rem;
  border: none;
  border-radius: 999px;
  background: #c0392b;
  color: #fff;
  font: inherit;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  line-height: 1.5;
  cursor: pointer;
  white-space: nowrap;
}

.push-btn:hover:not(:disabled) {
  background: #e04b3a;
}

.push-btn:disabled {
  background: var(--panel);
  color: var(--faint);
  cursor: default;
}

.scan-status {
  color: var(--muted);
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  white-space: nowrap;
}

.nav-toggle:hover,
.nav-link:hover {
  color: var(--text);
}

.nav-link {
  color: var(--muted);
  text-decoration: none;
}

/* Social links: same type as the rest of the nav, muted at rest. On hover the
   label goes white and bold — the site's "selected" language, borrowed for a
   rollover — and the brand mark takes over the separator's slot.
   Nothing may move while that happens, so both changes are pre-allocated: the
   mark and the dot share one fixed box, and the label reserves its own bold
   width below. */
.nav-social {
  display: inline-flex;
  align-items: center;
}

/* Separator and brand mark, stacked in the same cell. The dot belongs to the
   item that follows it, which is what lets the mark replace it on hover
   instead of needing a slot of its own — the row costs no extra width. */
.nav-mark {
  position: relative;
  flex: none;
  display: grid;
  place-items: center;
  width: 1em;
  height: 1em;
  margin-right: 0.7em;
}

.nav-mark::before {
  content: "\2022";
  color: var(--faint);
  transition: opacity 160ms ease;
}

/* First of the run: nothing precedes it, so there is no dot to replace. */
.nav-lead .nav-mark::before {
  content: none;
}

.nav-social .nav-ico {
  position: absolute;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 160ms ease;
}

/* Mail is not a brand, so it follows the theme's link blue. */
.nav-mark.nav-mail {
  color: var(--c-blue);
}

.nav-social:hover .nav-mark::before,
.nav-social:focus-visible .nav-mark::before {
  opacity: 0;
}

.nav-social:hover .nav-ico,
.nav-social:focus-visible .nav-ico {
  opacity: 1;
}

.nav-social:hover .nav-ico,
.nav-social:focus-visible .nav-ico {
  opacity: 1;
}

/* A hidden bold twin of the label sets the width, so switching the visible
   text to 700 cannot reflow the row (the group is right-anchored, so any
   growth would shove every item to its left). */
.nav-label {
  display: inline-flex;
  flex-direction: column;
}

.nav-label::after {
  content: attr(data-label);
  height: 0;
  overflow: hidden;
  visibility: hidden;
  font-weight: 700;
  pointer-events: none;
}

.nav-social:hover .nav-label,
.nav-social:focus-visible .nav-label {
  color: var(--strong);
  font-weight: 700;
}

main {
  padding: 1.5rem;
}

.grid {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}

.grid-row {
  display: flex;
  gap: 0.55rem;
  width: 100%;
}

.tile {
  position: relative;
  border: 1px solid var(--border);
  border-radius: 0.35rem;
  overflow: hidden;
  cursor: zoom-in;
  background: var(--tile);
  flex: 0 0 auto;
}

.tile img,
.tile video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 240ms ease, filter 240ms ease;
}

/* Discourage casual lifting: no selection, no drag-to-desktop. */
.tile img,
.tile video,
#lightboxMedia img,
#lightboxMedia video,
#lightboxMedia canvas {
  user-select: none;
  -webkit-user-drag: none;
}

.tile:hover img,
.tile:hover video,
.tile:focus-within img,
.tile:focus-within video {
  transform: scale(1.25);
  filter: brightness(1.2);
}

.tile video {
  background: var(--tile);
}

/* Curated name, fading in with the hover zoom. */
.tile-title {
  position: absolute;
  inset: 0 0 auto 0;
  padding: 0.6rem 0.7rem;
  font-size: 0.98rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: #fff;
  text-shadow: 0 0 3px rgba(0, 0, 0, 0.9), 0 0 10px rgba(0, 0, 0, 0.6);
  opacity: 0;
  transition: opacity 240ms ease;
  pointer-events: none;
}

.tile:hover .tile-title,
.tile:focus-within .tile-title {
  opacity: 1;
}

/* Touch has no real hover: a tap-drag would otherwise stick a title (and the
   zoom) on whatever tile the finger landed on. Names live in the viewer. */
:root.touch .tile-title {
  display: none;
}

/* Capsule badge, lower right, fading in with the title on hover. The glow is
   negative — a soft dark halo, so the white stroke holds against a blown-out
   sky as readily as against dark rock. */
.tile-badge {
  position: absolute;
  right: 0.5rem;
  bottom: 0.5rem;
  /* Vertical padding skewed by the measured glyph-ink offset (caps-only text
     sits high on its line box); total stays 0.32rem so the height holds. */
  padding: calc(0.16rem + 1.25px) 0.5rem calc(0.16rem - 1.25px);
  border: 1px solid #fff;
  border-radius: 999px;
  color: #fff;
  font-size: 0.58rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  line-height: 1.5;
  white-space: nowrap;
  opacity: 0;
  transition: opacity 240ms ease;
  pointer-events: none;
}

.tile-badge.badge-360 {
  background: rgb(255, 0, 0);
  box-shadow: 0 0 6px rgba(0, 0, 0, 0.55), 0 1px 2px rgba(0, 0, 0, 0.7);
}

.tile-badge.badge-mega {
  background: rgb(0, 40, 255);
  box-shadow: 0 0 6px rgba(0, 0, 0, 0.55), 0 1px 2px rgba(0, 0, 0, 0.7);
}

.tile:hover .tile-badge,
.tile:focus-within .tile-badge {
  opacity: 1;
}

:root.touch .tile-badge {
  display: none;
}

:root.touch .tile:hover img,
:root.touch .tile:hover video,
:root.touch .tile:focus-within img,
:root.touch .tile:focus-within video {
  transform: none;
  filter: none;
}

.lightbox {
  /* One definition of the media box width; the top and bottom bars match it
     so their contents line up with the image edges rather than the dialog's
     (the dialog is wider whenever min-width wins). */
  --media-w: min(92vw, calc((92vh - 8.5rem) * var(--ar, 1.5)));
  width: fit-content;
  height: fit-content;
  min-width: min(640px, 94vw);
  max-width: 96vw;
  /* The side padding insets the media box itself; the bars share --media-w,
     so the caption's left edge and the image's left edge are the same line
     AND both sit off the dialog edge. */
  padding: 0 0.9rem;
  border: 1px solid var(--border);
  border-radius: 0.75rem;
  background: var(--surface);
  color: var(--text);
  overflow: hidden;
}

.lightbox::backdrop {
  background: rgba(0, 0, 0, 0.78);
}

/* EXPAND: pure media, longest edge fills the screen; top bar floats over.
   .expanded fills the viewport everywhere; OS fullscreen engages on top
   where the environment allows it. */
.lightbox:fullscreen,
.lightbox.expanded {
  position: fixed;
  inset: 0;
  margin: 0;
  min-width: 0;
  max-width: none;
  max-height: none; /* dialogs are UA-capped at 100% - 6px - 2em */
  width: 100vw;
  /* 100vh on iOS Safari is the LARGE viewport — the height the page would have
     if the toolbars were hidden — so the plate came out taller than what is
     actually on screen. The media box grew with it, the photo centred inside
     that box landed below the fold (reading as "the image did not load"), and
     the footer with the title and the SHARE / BUY A PRINT capsules sat under
     Safari's toolbar. dvh is the visible height, and it tracks the toolbars as
     they collapse. vh stays first as the fallback for anything older. */
  height: 100vh;
  height: 100dvh;
  border: none;
  border-radius: 0;
  background: rgb(0, 0, 0);
  display: flex;
  flex-direction: column;
}

/* While the plate is expanded, nothing else on the page paints. */
:root.plate-expanded .topbar,
:root.plate-expanded main {
  visibility: hidden;
}

:root.plate-expanded body {
  background: rgb(0, 0, 0);
}

/* Solid black bars, not a transparent shade — the plate keeps all its data. */
.lightbox:fullscreen,
.lightbox.expanded {
  --media-w: 100%;
  padding: 0;
}

.lightbox:fullscreen .lb-top,
.lightbox.expanded .lb-top,
.lightbox:fullscreen .lb-bottom,
.lightbox.expanded .lb-bottom {
  flex: 0 0 auto;
  background: rgb(0, 0, 0);
  padding-left: 0.9rem;
  padding-right: 0.9rem;
}

.lightbox:fullscreen #lightboxMedia,
.lightbox.expanded #lightboxMedia {
  aspect-ratio: auto;
  flex: 1 1 auto;
  width: 100%;
  height: auto;
  min-height: 0;
}

/* A fullscreened <video> is painted by the browser, not by this stylesheet —
   it needs no rules here. */

.lb-meta-group {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  min-width: 0;
}

.lb-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1.2rem;
  width: var(--media-w);
  margin: 0 auto;
  padding: 0.6rem 0;
  font-size: 0.78rem;
  letter-spacing: 0.05em;
}

.lb-meta {
  color: var(--muted);
  white-space: nowrap;
}

/* EXIF separators: same red pop as the nav's active dots. */
.lb-meta .meta-dot {
  margin: 0 0.5em;
  color: rgb(255, 0, 0);
  text-shadow: 0 0 4px rgba(255, 0, 0, 0.7);
}

/* The one capsule left up here is SHARE, riding the end of the title.
   Flex-centred with line-height 1 so the text is genuinely centred in the
   capsule instead of floating on a tall line box. */
.lb-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 1.35rem;
  /* Caps-only ink measured against real glyph bounds (canvas metrics): the
     empty descent space below the baseline parks the ink high, so the ink —
     not the em box — is what gets centred here. */
  padding: 3.35px 0.6rem 0;
  border: none;
  border-radius: 999px;
  color: #fff;
  font: inherit;
  font-size: 0.78rem;
  letter-spacing: 0.07em;
  line-height: 1;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color 140ms ease;
}

.pill-blue {
  background: rgb(0, 40, 255);
}

/* The red EXIF separator, borrowed to divide the two capsules. */
.share-wrap .meta-dot {
  margin: 0 0.55em;
  color: rgb(255, 0, 0);
  text-shadow: 0 0 4px rgba(255, 0, 0, 0.7);
}

/* Rollover. This used to be filter: brightness(1.18), which is a no-op on this
   fill — rgb(0, 40, 255) has red at 0 (nothing to multiply) and blue already
   pinned at 255 (clamped), so the only channel that could move was green, by
   seven levels. An explicit lighter blue is the only thing that reads.
   Weight is deliberately left alone: bold is wider, and these capsules sit
   inline with the title, so hovering one would shove the whole line around. */
.pill-blue:hover,
.pill-blue:focus-visible {
  background: rgb(76, 116, 255);
}

.lb-zoom {
  color: var(--strong);
  font-weight: 700;
}

.lb-controls {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

/* Bullet separator between the controls. */
.lb-btn + .lb-btn::before {
  content: "•";
  margin-right: 0.55rem;
  color: var(--faint);
}

.lb-count {
  color: var(--faint);
}

.lb-btn {
  background: none;
  border: none;
  padding: 0;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  letter-spacing: 0.07em;
}

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

#lightboxMedia {
  position: relative;
  aspect-ratio: var(--ar, 1.5);
  width: var(--media-w);
  margin: 0 auto;
  background: rgb(0, 0, 0);
  overflow: hidden;
}

/* Drag affordance for 360s: large, translucent, dead centre, and entirely
   click-through so it never intercepts the drag it is advertising. */
.pano-hint {
  position: absolute;
  left: 50%;
  top: 50%;
  /* --hint-x is driven per frame by the viewer so the icon travels with the
     swing, the way the cursor does during a real drag. */
  transform: translate(-50%, -50%) translateX(var(--hint-x, 0px));
  z-index: 2;
  width: clamp(3.5rem, 9vmin, 6rem);
  color: #fff;
  opacity: 0;
  pointer-events: none;
}

.pano-hint svg {
  display: block;
  width: 100%;
  height: auto;
  fill: currentColor;
  stroke: currentColor;
  filter: drop-shadow(0 0 6px rgba(0, 0, 0, 0.75));
  opacity: 0.72;
}

/* Authoring tools sit inside the frame, lower right, so curation is judged
   against the image itself. The container is click-through; only the capsule
   takes pointer events, or it would eat pano drags. */
.lb-tools {
  position: absolute;
  right: 0.7rem;
  bottom: 0.3rem;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.4rem;
  pointer-events: none;
}

.lb-tools > * {
  pointer-events: auto;
}

#lightboxMedia img,
#lightboxMedia video,
#lightboxMedia .pano {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

#lightboxMedia .pano {
  cursor: grab;
  touch-action: none;
}

#lightboxMedia .pano:active {
  cursor: grabbing;
}

/* Title and caption own the full width now that curation floats over the
   image. The dialog already shrink-wraps to the media box, so filling it is
   all that's needed — no second copy of the media's width calc to drift. */
.lb-bottom {
  /* The print enquiry hangs off this, above the footer and over the media. */
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.55rem;
  width: var(--media-w);
  margin: 0 auto;
  min-height: 4.6rem;
  padding: 0.75rem 0 0.9rem;
  font-size: 0.85rem;
}

/* Title line: coordinates float on the image's right edge, the title flows
   inline, and SHARE trails the title's last word. The coords div comes first
   in the markup so its float claims the first line. */
.lb-titlerow {
  margin-bottom: 0.3rem;
  line-height: 1.5;
}

.lb-titlerow .lb-title {
  display: inline;
  margin-bottom: 0;
}

.lb-titlerow .lb-coords {
  float: right;
  margin-left: 0.9rem;
}

.lb-coords {
  color: var(--muted);
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  outline: none;
  min-width: 1rem;
}

.lb-coords:focus {
  color: var(--text);
}

/* A parseable fix opens Google Earth, so it reads as a control — white and
   bold like every other live thing here, rather than muted label text. */
.lb-coords.has-fix {
  position: relative;
  color: var(--strong);
  font-weight: 700;
  cursor: pointer;
}

/* Rollover swaps the coordinates for what they do. Generated content, not a
   text swap: the real coordinates are contenteditable and get saved on blur,
   so rewriting them even briefly risks persisting the label. It's absolutely
   positioned with its own backing so the longer string masks the title
   underneath instead of reflowing the line. */
.lb-coords.has-fix::after {
  content: "Open Google Earth in a new tab…";
  position: absolute;
  right: 0;
  top: 0;
  padding-left: 0.9rem;
  background: var(--surface);
  color: var(--accent);
  font-weight: 700;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 120ms ease;
}

.lb-coords.has-fix:hover {
  color: transparent;
}

.lb-coords.has-fix:hover::after {
  opacity: 1;
}

/* While the author has the caret in it, it's a field again — no swapping. */
.lb-coords.has-fix:focus {
  color: var(--text);
}

.lb-coords.has-fix:focus::after {
  opacity: 0;
}

/* The expanded plate puts solid black behind the bar, so the mask must match. */
.lightbox:fullscreen .lb-coords.has-fix::after,
.lightbox.expanded .lb-coords.has-fix::after {
  background: rgb(0, 0, 0);
}

/* None of that applies on a touch device. There is no hover to explain the
   control with, the destination is the phone's map app rather than Earth, and
   sticky :hover would leave the coordinates blanked out after a tap — the swap
   firing with nothing to swap to. Tapping just opens the map. */
:root.touch .lb-coords.has-fix::after {
  content: none;
}

:root.touch .lb-coords.has-fix:hover {
  color: var(--muted);
}


/* No coordinates recorded: show nothing. The author still needs somewhere to
   click, so the placeholder surfaces on hover over the title line. */
.lb-coords:empty {
  min-width: 0;
}

.lb-coords[contenteditable="true"]:empty::before {
  content: attr(data-placeholder);
  color: var(--faint);
  opacity: 0;
  transition: opacity 160ms ease;
}

.lb-titlerow:hover .lb-coords[contenteditable="true"]:empty::before,
.lb-coords[contenteditable="true"]:focus:empty::before {
  opacity: 1;
}

/* SHARE trails the title's last word; the menu takes the button's place,
   growing to the right from the same spot. */
.share-wrap {
  position: relative;
  /* vertical-align: middle still leaves the capsule a couple of pixels low
     against the title's cap height (it centres on the x-height); the nudge
     closes the visible remainder. */
  top: -2px;
  display: inline-flex;
  align-items: center;
  margin-left: 0.55rem;
  vertical-align: middle;
}

/* Overlaid, not in-flow: the menu is taller than the button, and swapping it
   into the line grew the line box — the whole footer visibly shifted on every
   open. Hiding the button with visibility keeps its footprint, so nothing
   reflows; the menu floats over the line from the button's own spot. */
.share-menu {
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  z-index: 6;
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.2rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: #2a2a2a;
  white-space: nowrap;
}

.share-menu.hidden {
  display: none;
}

.share-wrap.open #shareBtn {
  visibility: hidden;
}

.share-menu button {
  display: flex;
  align-items: center;
  padding: 0;
  border: none;
  background: none;
  color: var(--soft);
  cursor: pointer;
  font: inherit;
}

.share-menu button:hover {
  color: #fff;
}

/* Brand colours live in the markup so each mark keeps its own palette. */
.share-menu svg {
  width: 1.15rem;
  height: 1.15rem;
  display: block;
}

.share-menu button:hover svg {
  filter: brightness(1.15);
}

.share-menu .share-copy {
  /* Same ink-centring correction as the SHARE pill, measured for this
     font size. */
  padding-top: 2px;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  white-space: nowrap;
}

.lb-text {
  min-width: 0;
}

.lb-title {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--strong);
  margin-bottom: 0.3rem;
  outline: none;
}

.lb-title[contenteditable="true"]:empty::before {
  content: attr(data-placeholder);
  color: var(--faint);
  font-weight: 400;
}

.lb-caption {
  color: var(--soft);
  line-height: 1.5;
  outline: none;
}

.lb-caption[contenteditable="true"]:empty::before {
  content: attr(data-placeholder);
  color: var(--faint);
}

/* An article caption is WRITTEN in this field and RENDERED under the photo, so
   the field has to obey the article's contract exactly: B brightens, and a
   colour the author set still beats it. Scoped to article pages — the grid's
   captions carry <font color> instead, which an element-targeting rule here
   would override. */
[data-page="article"] .lb-caption :is(strong, b) {
  color: var(--strong);
}

[data-page="article"] .lb-caption :is(.c-muted, .c-amber, .c-blue) :is(strong, b) {
  color: inherit;
}

/* Links style themselves — select text, hit LINK, done. No need to also
   remember to bold and colour it. */
.lb-caption a,
.lb-title a {
  color: var(--accent);
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
}

.lb-caption a:hover,
.lb-title a:hover {
  text-decoration: underline;
}

/* While authoring, a link is text you can edit — the arrow says so, and
   cmd-click follows it. */
:root.editing-media .lb-caption a,
:root.editing-media .lb-title a {
  cursor: text;
}

/* Author-only curation controls: pin, stars, tags. */
/* Black capsule floating over the image, not a row in the footer. One row at
   any reasonable window size; on a narrow one it wraps upward from the corner
   rather than spilling out of the frame. */
.curate-bar {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
  gap: 0.4rem 0.9rem;
  max-width: 100%;
  padding: 0.2rem 0.9rem;
  border-radius: 999px;
  /* Solid, not translucent: the image behind it was fighting the labels. */
  background: #000;
  white-space: nowrap;
}

.curate-bar.hidden {
  display: none;
}

.curate-bar button {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  font: inherit;
}

.curate-pin {
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  color: var(--faint);
}

.curate-pin.lit {
  color: var(--strong);
  font-weight: 700;
}

.curate-bar .hidden {
  display: none;
}

.curate-stars {
  display: flex;
  gap: 0.2rem;
}

.curate-stars button {
  font-size: 1.1rem;
  line-height: 1;
  color: var(--faint);
}

.curate-stars button.lit {
  color: #e8b64c;
}

.curate-cats,
.curate-tags {
  display: flex;
  gap: 0.55rem;
}

/* Retire this item. Red because it is the only control here that removes
   something — everything else in this bar is reversible with a second click. */
.curate-bar .curate-delete {
  /* Skewed vertically by the measured ink offset so the caps sit dead centre;
     the total stays 0.32rem, so the capsule height is unchanged. */
  padding: calc(0.16rem + 0.62px) 0.6rem calc(0.16rem - 0.62px);
  border-radius: 999px;
  background: #c0392b;
  color: #fff;
  font-size: 0.64rem;
  font-weight: 700;
  letter-spacing: 0.09em;
}

.curate-bar .curate-delete:hover:not(:disabled) {
  background: #e04b3a;
}

.curate-bar .curate-delete:disabled {
  background: var(--panel);
  color: var(--faint);
  cursor: default;
}

.curate-cats button {
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  color: var(--faint);
}

.curate-cats button.lit {
  color: #e8b64c;
  font-weight: 700;
}

.curate-tags button {
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  color: var(--faint);
}

.curate-tags button.lit {
  color: var(--soft);
  font-weight: 700;
}

.caption-tools {
  display: flex;
  align-items: center;
  gap: 0.45rem;
}

.caption-tools.hidden {
  display: none;
}

.caption-tools button {
  width: 1.5rem;
  height: 1.5rem;
  border: 1px solid var(--border);
  border-radius: 0.3rem;
  background: var(--panel);
  color: var(--text);
  cursor: pointer;
  font-size: 0.78rem;
}

.caption-tools .dot {
  border-radius: 999px;
  width: 1.1rem;
  height: 1.1rem;
}

/* Wider than the icon buttons — it carries a word. */
.caption-tools #linkBtn {
  width: auto;
  padding: 0 0.5rem;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.08em;
}

/* The grid's view count and the article plate's save status sit in the same
   place and read the same way — one rule for both. */
/* ---------- buy a print ---------- */
/* Sits over the plate rather than in the flow: the footer is already carrying
   the title and caption, and this appears for one photo at a time. */
.print-panel {
  position: absolute;
  right: 0;
  bottom: 100%;
  z-index: 3;
  width: min(21rem, 80vw);
  /* The plate clips anything that leaves it, and on a short window there is
     not much room above the footer — so the panel stays compact and scrolls
     inside itself rather than growing off the top of the frame. */
  max-height: 55vh;
  overflow-y: auto;
  margin-bottom: 0.6rem;
  padding: 0.8rem 0.9rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: 0.6rem;
  background: #0b0b0b;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.55);
  text-align: left;
}

.print-panel.hidden {
  display: none;
}

.print-close {
  position: absolute;
  top: 0.45rem;
  right: 0.5rem;
  z-index: 1;
  /* Its own square of panel background, so it reads as a control rather than
     as a character that wandered into the headline. */
  display: grid;
  place-items: center;
  width: 1.15rem;
  height: 1.15rem;
  padding: 0;
  border: none;
  border-radius: 0.25rem;
  background: #0b0b0b;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: 0.72rem;
  line-height: 1;
}

.print-close:hover {
  color: var(--strong);
}

.print-title {
  /* The close button sits in this line's top-right corner, and a long title
     wraps straight underneath it. Reserve the corner rather than hoping the
     title is short. */
  margin: 0 0 0.4rem;
  padding-right: 1.5rem;
  color: var(--strong);
  font-size: 0.9rem;
  font-weight: 700;
  /* Titles here run to a full line of place names. Two lines is enough to say
     which photograph this is; beyond that it starts pushing the send button
     out of a short plate, and the photo is on screen anyway. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

.print-copy {
  margin: 0 0 0.6rem;
  color: var(--soft);
  font-size: 0.74rem;
  line-height: 1.4;
}

.print-field {
  display: block;
  margin-bottom: 0.45rem;
}

.print-field > span {
  display: block;
  margin-bottom: 0.2rem;
  color: var(--muted);
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.1em;
}

.print-field select,
.print-field textarea {
  width: 100%;
  padding: 0.35rem 0.45rem;
  border: 1px solid var(--border);
  border-radius: 0.3rem;
  background: var(--panel);
  color: var(--text);
  font: inherit;
  font-size: 0.78rem;
  resize: vertical;
}

.print-send {
  width: 100%;
  height: 1.7rem;
  margin-top: 0.2rem;
}

.print-fine {
  margin: 0.55rem 0 0;
  color: var(--faint);
  font-size: 0.66rem;
  line-height: 1.4;
}

/* ---------- cheers ---------- */
/* A nav item that opens rather than navigates, so it needs the button chrome
   stripped to match the links either side of it. */
.nav-cheers {
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  font: inherit;
}

.cheers-panel {
  position: absolute;
  top: 100%;
  right: 0;
  z-index: 40;
  width: min(20rem, 86vw);
  margin-top: 0.5rem;
  padding: 1rem 1.1rem 0.85rem;
  border: 1px solid var(--border);
  border-radius: 0.6rem;
  background: #0b0b0b;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.55);
  text-align: left;
  white-space: normal;
}

.cheers-panel.hidden {
  display: none;
}

/* The nav puts a dark halo on its links and buttons so they survive the
   masthead photo behind them. In here there is no photo — the panel brings its
   own solid background — so the halo has nothing to do but thicken the type
   and muddy the caps on the amounts. */
.cheers-panel,
.cheers-panel a,
.cheers-panel button {
  text-shadow: none;
}

.cheers-close {
  position: absolute;
  top: 0.5rem;
  right: 0.6rem;
  padding: 0;
  border: none;
  background: none;
  color: var(--faint);
  cursor: pointer;
  font: inherit;
  font-size: 0.8rem;
}

.cheers-copy {
  margin: 0 0 0.6rem;
  color: var(--soft);
  font-size: 0.8rem;
  line-height: 1.5;
  letter-spacing: 0;
}

.cheers-ask {
  color: var(--strong);
  font-weight: 700;
}

.cheers-chips {
  display: flex;
  flex-wrap: wrap;
  /* Without this the capsules stretch to the row's height and stop matching
     SHARE — a flex item's default is to fill the line. */
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.7rem;
}

/* The amounts are the same capsule as SHARE and BUY A PRINT — same fill, same
   rollover — so they carry the whole styling from .lb-pill/.pill-blue and can
   never drift out of step with it. All an anchor needs on top is losing the
   underline. */
/* The amounts are a choice, so at rest they read as outlines and the chosen
   one fills in with the same blue as the rails below — the fill IS the
   selection, which is why .pill-blue is toggled rather than a class of its
   own. */
.cheers-chip {
  text-decoration: none;
  background: none;
  box-shadow: inset 0 0 0 1px var(--border);
  color: var(--muted);
}

/* Hovering shows what choosing would look like: the same fill the selected
   chip carries, so the rollover is a preview of the state rather than a
   decoration of its own. */
.cheers-chip:hover {
  box-shadow: none;
  background: rgb(0, 40, 255);
  color: #fff;
}

/* The selection has to hold once the pointer leaves, or nobody can tell their
   click registered before committing to a rail. The fill is restated here
   rather than left to .pill-blue: that rule sits earlier in the file at equal
   specificity, so `.cheers-chip { background: none }` was quietly winning and
   the chip only looked chosen while it happened to be hovered. */
.cheers-chip.pill-blue,
.cheers-chip.pill-blue:hover {
  box-shadow: none;
  background: rgb(0, 40, 255);
  color: #fff;
}

/* Where the money goes — revealed only once an amount has been chosen. The row
   holds its space from the start, so the reveal never shifts the panel under
   the pointer, and it fades rather than snapping. */
.cheers-rails {
  display: flex;
  align-items: center;
  gap: 1.1rem;
  min-height: 1.4rem;
  margin-top: 0.6rem;
  opacity: 0;
  visibility: hidden;
  transition: opacity 180ms ease;
}

.cheers-rails.on {
  opacity: 1;
  visibility: visible;
}

/* Grey until pointed at, then each takes its own brand colour. */
.cheers-rail {
  color: var(--faint);
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  text-decoration: none;
  transition: color 140ms ease;
}

.rail-paypal:hover,
.rail-paypal:focus-visible {
  color: #0070e0;
}

/* Venmo sets its wordmark oblique; the tilt is most of what identifies it. */
.rail-venmo {
  font-style: italic;
}

.rail-venmo:hover,
.rail-venmo:focus-visible {
  color: #008cff;
}

.cheers-unset {
  color: var(--faint);
  font-size: 0.7rem;
  line-height: 1.4;
}







.cheers-fine {
  margin: 0.7rem 0 0;
  color: var(--muted);
  font-size: 0.68rem;
}

#lightboxViews,
.lb-status {
  color: var(--muted);
  white-space: nowrap;
  font-size: 0.78rem;
  padding-top: 0.15rem;
}

.articles {
  max-width: none;
  margin: 0;
}

.article-tiles {
  /* Cards are a FIXED size, never fluid. A card is a viewport onto the
     article's opening: if it changes width with the window, the text re-wraps,
     the crop moves, and line-by-line tuning done at one window size is wrong at
     every other. Columns are laid at exactly --card-w and the row is centred;
     the leftover space goes to the margins, never into the cards. Height
     follows automatically — fixed header plus a fixed-ratio collage.
     This is the one number: change it here and every card follows.
     412 → 370: sized to the widest title on the index ("MOUNT WHITNEY DANGER
     ZONES", 323px of ink) plus the 32px of header padding and a dozen px of
     air. Going much below this wraps that title onto two lines. */
  --card-w: 370px;
  display: grid;
  grid-template-columns: repeat(auto-fill, var(--card-w));
  justify-content: center;
  gap: 1.1rem;
  padding: 0;
  list-style: none;
}

.article-tile {
  display: block;
  border: 1px solid var(--border);
  border-radius: 0.6rem;
  overflow: hidden;
  background: var(--card);
  text-decoration: none;
  color: inherit;
  transition: border-color 200ms ease, transform 200ms ease;
}

.article-tile:hover {
  border-color: var(--muted);
  transform: translateY(-2px);
}

/* Fixed, not min-height: the opening is a viewport, and a viewport is the same
   size whether or not the article fills it. Two short openings used to let
   their cards collapse while every other card was pinned by overflow. The
   HIDDEN badge takes its natural height and the opening flexes into whatever
   is left, so a held-back card is the same height as the rest. */
/* LOCKED. The masthead space is the same on every card and the collage always
   starts at the same line. Sized for the worst opening Brandon writes — two
   title lines over three subtitle lines, or one over four, whichever runs
   taller (~117px at card scale) — with headroom over the tallest he has
   (122px). Body text can never appear here, so this cannot creep. */
.article-tile header {
  display: flex;
  flex-direction: column;
  height: 9.4rem;
  padding: 0.7rem 1rem 0.55rem;
}

/* The card is a viewport onto the article's opening: the same blocks, the same
   spans, at card scale. Nothing here assigns roles — big white text on the
   page reads big and white here, small grey italic reads small grey italic.
   The height is the whole contract: what runs past it crops, and shortening
   the opening is an authoring decision made on the page, not a rule the
   editor enforces. */
.article-tile .tile-opening {
  flex: 1;
  min-height: 0; /* without this a flex item refuses to shrink below content */
  overflow: hidden;
  font-size: 0.84rem;
  line-height: 1.45;
  color: var(--soft);
}

/* Verbatim means margins too: blocks sit tight, and the blank lines the
   author typed are the only air — same contract as the article. */
.article-tile .tile-opening :is(h1, h2, h3, p) {
  margin: 0;
  font-weight: 400;
}

/* A blank line is a full line on the page, where it reads as separation. At
   card scale that same line is dead space, and it was pushing the divider
   rule past the crop on the denser cards — so the card keeps the pause and
   spends half the height on it. The article is untouched. */
.article-tile .tile-opening .blank {
  height: 0.5rem;
  overflow: hidden;
}

.article-tile .tile-opening strong {
  color: var(--strong);
}

/* Inline sizes, damped to card scale — the point is the hierarchy the author
   set, not the article's literal 2.4rem — while colour and weight come
   through exactly as written. Size is only size here too. */
.article-tile .s-title {
  font-size: 1.18rem;
  line-height: 1.2;
}

.article-tile .s-subtitle {
  font-size: 1.02rem;
  line-height: 1.3;
}

.article-tile .s-large {
  font-size: 0.92rem;
  line-height: 1.35;
}

.article-tile .c-muted { color: var(--muted); }
.article-tile .c-amber { color: var(--c-amber); }
.article-tile .c-blue { color: var(--c-blue); }

/* Same contract as the article page: bold never overrides an authored colour. */
.article-tile .tile-opening :is(.c-muted, .c-amber, .c-blue) strong {
  color: inherit;
}

/* Author-only: a hidden article is still on the index so it can be found and
   brought back, but it reads as held back rather than published. The public
   manifest never names it, so this only ever renders for him. */
.article-tiles li.is-hidden .article-tile {
  border-style: dashed;
  opacity: 0.55;
}

.article-tiles li.is-hidden .article-tile:hover {
  opacity: 1;
}

.article-tile .tile-hidden {
  align-self: flex-start;
  margin-top: 0.5rem;
  padding: calc(0.12rem + 0.62px) 0.45rem calc(0.12rem - 0.62px);
  border-radius: 999px;
  background: var(--c-amber);
  color: #101010;
  font-size: 0.58rem;
  font-weight: 700;
  letter-spacing: 0.12em;
}

.collage {
  display: grid;
  grid-template-columns: 2fr 1fr;
  grid-template-rows: 1fr 1fr 1fr;
  gap: 2px;
  aspect-ratio: 1.3;
}

/* Grid items default to min-height:auto, so a tall portrait photo would push
   the row past the collage's aspect-ratio box. Let the rows shrink instead. */
.collage > * {
  min-width: 0;
  min-height: 0;
}

.collage img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.collage > img:nth-child(1) {
  grid-column: 1;
  grid-row: 1 / span 2;
}

.collage > img:nth-child(2) {
  grid-column: 2;
  grid-row: 1;
}

.collage > img:nth-child(3) {
  grid-column: 2;
  grid-row: 2;
}

.collage > img:nth-child(4) {
  grid-column: 1;
  grid-row: 3;
}

.collage > img:nth-child(5),
.collage > .more {
  grid-column: 2;
  grid-row: 3;
  position: relative;
}

.collage > .more span {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: rgba(0, 0, 0, 0.45);
  color: #fff;
  font-size: 1.35rem;
  font-weight: 700;
}

/* ---------- Small screens ---------- */

@media (max-width: 700px) {
  main {
    padding: 0.75rem;
  }

  /* The nav breaks into lines here rather than shrinking to fit one: the
     wordmark takes the first, the sections take the second, the tags take the
     third. Squeezing all of it onto a single row meant 0.58rem type — legible
     on paper, tiring on a phone. Wrapping buys the size back. */
  .topbar {
    padding: 0.6rem 0.75rem;
    gap: 0.3rem;
  }

  .mainnav {
    gap: 0.3rem 0.5rem;
    font-size: 0.62rem;
  }

  .brand {
    /* Its own line, so it can be read at something like its real size. */
    flex: 1 0 100%;
    font-size: 1.2rem;
  }

  /* The separator belonged to the wordmark's right-hand side; there is
     nothing beside it any more. */
  .mainnav .brand + .sep {
    display: none;
  }

  /* No room for the outbound social row on a phone. CONTACT stays, and the
     selector is what tells them apart: the socials all open a new tab, CONTACT
     is a mailto that does not. No markup change, and it holds on every page
     the topbar appears on. */
  .nav-social[target="_blank"] {
    display: none;
  }

  /* CONTACT rejoins the run of sections instead of hanging off the right with
     the socials, and CHEERS? alone takes the far edge. */
  .nav-right {
    flex: 1;
    margin-left: 0;
    gap: 0.5rem;
  }

  .nav-cheers {
    margin-left: auto;
  }

  /* Each social carries a dot in front of it, which is the separator for the
     run it used to sit in. With that run gone the dots point at nothing — and
     dropping the cells returns about 17px per item, which is what lets CONTACT
     ride up onto the sections line instead of starting a new one. */
  .nav-right .nav-mark {
    display: none;
  }

  /* The whole second row goes on a phone. Everything in it is either too small
     to be worth the height — the tag filters — or authoring: the theme
     toggles (the device decides instead, see theme.js), NEW ARTICLE, IMPORT
     .DOCX, and the article edit bar, which appends itself here. */
  .subnav {
    display: none;
  }

  /* Same rule for the grid's authoring controls, which live in the top row
     rather than the second. PUSH publishes the site; it has no business being
     one mis-tap away on a phone. */
  #pushBtn,
  #scanBtn,
  #scanStatus {
    display: none;
  }

  .subnav {
    gap: 0.38rem;
    font-size: 0.5rem;
  }

  /* Lightbox: fit the plate to the screen, keep bars on their own lines. */
  .lightbox {
    min-width: 0;
    max-width: 100vw;
  }

  /* Controls get their own full-width line; exif sits centred beneath them. */
  .lb-top {
    flex-direction: column-reverse;
    align-items: stretch;
    gap: 0.3rem;
    padding: 0.5rem 0.6rem;
  }

  .lb-controls {
    justify-content: center;
    width: 100%;
    gap: 0.5rem;
    font-size: 0.82rem;
  }

  .lb-btn + .lb-btn::before {
    margin-right: 0.5rem;
  }

  /* Capture data is reference, not headline. It was truncating with an
     ellipsis, which loses the end of the line — and the date lives at the end.
     Wrapping to a second line instead costs a few pixels over the photo and
     keeps all of it; the size is set against the longest line in the library
     (shutter, aperture, ISO, lens, body, dimensions, date and time) so it
     lands on two lines at worst rather than growing without limit. */
  .lb-meta {
    white-space: normal;
    overflow: hidden;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    text-align: center;
    font-size: 0.5rem;
    line-height: 1.35;
    letter-spacing: normal;
  }

  .lb-count {
    display: none;
  }

  /* The text is caption-scale here, not article-scale. Every line it gives
     back is a line the photograph gets, and on a 375px screen that trade is
     always worth taking — the picture is what was come for. */
  .lb-bottom {
    flex-wrap: wrap;
    gap: 0.4rem;
    min-height: 0;
    padding: 0.5rem 0.6rem;
    font-size: 0.68rem;
  }

  /* Expanded goes edge to edge, so the last line was landing under the home
     indicator and Safari's bottom bar. Clear the inset, and keep a floor for
     the phones that report none. */
  .lightbox.expanded .lb-bottom,
  .lightbox:fullscreen .lb-bottom {
    padding-bottom: max(1.1rem, env(safe-area-inset-bottom, 0px));
  }

  .lightbox.expanded .lb-top,
  .lightbox:fullscreen .lb-top {
    padding-top: max(0.5rem, env(safe-area-inset-top, 0px));
  }

  /* The view count was sitting beside the EXIF line and crowding it off the
     screen. It is the least interesting number in the bar. */
  #lightboxViews {
    display: none;
  }

  .lb-text {
    flex: 1 1 100%;
  }

  .lb-title {
    font-size: 0.8rem;
  }

  .lb-coords {
    font-size: 0.6rem;
  }

  /* The capsules stay where they are: they are the only things in this bar
     anyone taps, and shrinking them with the type would take them below a
     comfortable target. */
  .lb-bottom .lb-pill {
    font-size: 0.7rem;
  }

  .curate-bar {
    flex: 1 1 100%;
    flex-wrap: wrap;
  }

  .lightbox {
    /* Same reason as the expanded plate: the unexpanded one sized its media
       box against a viewport height the phone never actually shows. */
    --media-w: min(100vw - 1.2rem, calc((100vh - 9rem) * var(--ar, 1.5)));
    --media-w: min(100vw - 1.2rem, calc((100dvh - 9rem) * var(--ar, 1.5)));
    padding: 0 0.6rem;
  }
}

/* The only width at which a card is allowed to change size: a screen too
   narrow to hold one. The alternative there is a horizontal scrollbar. */
@media (max-width: 440px) {
  .article-tiles {
    grid-template-columns: 1fr;
  }

  /* A grid item refuses to shrink below its min-content width, and an opening
     containing a typed ______ rule has a min-content of one very long word —
     so the card would stay full width and push the page sideways. The rule is
     meant to be clipped by the card edge, which .tile-opening already does. */
  .article-tiles > li {
    min-width: 0;
  }
}


/* Author-only: drag-to-reorder on the articles index. */
:root.ordering .article-tile {
  cursor: grab;
}

:root.ordering .article-tiles li.dragging {
  opacity: 0.4;
}

:root.ordering .article-tiles li.dragging .article-tile {
  border-color: #e8b64c;
}

.order-status {
  position: fixed;
  left: 50%;
  bottom: 1rem;
  transform: translateX(-50%);
  z-index: 60;
  padding: 0.4rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: 0.5rem;
  background: rgba(10, 10, 10, 0.96);
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.5);
  color: #cfcfcf;
  font-size: 0.7rem;
  letter-spacing: 0.06em;
}

.order-status[hidden] {
  display: none;
}
