/* Bozarth Photographic
   ------------------------------------------------------------------
   No accent colour anywhere. On a photography site every chromatic UI
   element competes with the work, so the interface is warm monochrome and
   the photographs supply all the colour. The one expressive piece of type
   is Ted's own signature at the top; everything else stays quiet.
   Warm near-black rather than blue-black, because these are warm images —
   Egyptian sandstone, Peruvian light, mill rust.
*/

:root {
  --ink:      #0C0B0A;
  --ink-2:    #141210;
  --line:     #26221E;
  --line-2:   #3A342E;
  --paper:    #EFEAE2;
  --muted:    #8C8378;
  /* Quiet, not unreadable. At #5C554E this was 2.7:1 on the page and carried
     every caption, count and gallery line — fine on a calibrated monitor
     indoors, gone on a phone in daylight. #837C72 is 4.8:1 on the page and
     4.5:1 on a card, and still reads as the recessive tone. */
  --faint:    #837C72;
  /* The only colour in the place, and it is never decoration: it means a thing
     is open, running, or done. Desaturated so it sits on warm black without
     shouting over a photograph. */
  --go:       #6E9B5B;

  --display: Fraunces, Georgia, "Times New Roman", serif;
  --body:    Archivo, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --data:    "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --gut:  6px;                       /* gallery gutter — tight, so images touch */
  --pad:  clamp(18px, 4vw, 44px);
  --rule: 1px solid var(--line);
}

* { box-sizing: border-box; }

/* figure carries a 40px UA margin that silently inset every frame. */
figure { margin: 0; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--body);
  font-size: 15px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

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

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

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

/* ── Masthead ──────────────────────────────────────────────────────
   The signature is the wordmark and the only flourish on the page. It gets
   room to breathe and a hairline beneath, the way a print is signed below
   the image rather than on it. */

/* The home page wears this header over the slideshow and every other page
   wears it on the ground, and the two were built to different measurements —
   62px of top padding here against 30px there, a 340px signature against 400px.
   Leaving Home therefore dropped the signature and the menu by about 32px, a
   jump on every click. One set of measurements, so the header holds still. */
.masthead { padding: clamp(14px, 2.6vh, 30px) var(--pad) 0; text-align: center; }

.sig { display: inline-block; width: min(400px, 54vw); }
.sig img { width: 100%; height: auto; }

.nav {
  margin-top: clamp(8px, 1.4vh, 16px);
  padding-bottom: 16px;
  border-bottom: var(--rule);
  display: flex; justify-content: center; flex-wrap: wrap;
  gap: clamp(16px, 3vw, 34px);
}

.nav a {
  font-family: var(--data);
  font-size: 11px; letter-spacing: .18em; text-transform: uppercase;
  color: var(--muted);
  padding-bottom: 3px;
  border-bottom: 1px solid transparent;
  transition: color .18s, border-color .18s;
}
.nav a:hover, .nav a[aria-current="page"] { color: var(--paper); border-bottom-color: var(--line-2); }

/* ── Page furniture ───────────────────────────────────────────────── */

main { padding: 0 var(--pad) 90px; }

.wrap { max-width: 1680px; margin: 0 auto; }

.page-head { padding: clamp(34px, 6vw, 70px) 0 clamp(20px, 3vw, 34px); }

.eyebrow {
  font-family: var(--data);
  font-size: 10.5px; letter-spacing: .22em; text-transform: uppercase;
  color: var(--faint); margin: 0 0 12px;
}

h1 {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(30px, 5.2vw, 58px);
  line-height: 1.04;
  letter-spacing: -.02em;
  margin: 0;
  font-variation-settings: "SOFT" 20, "WONK" 1, "opsz" 40;
}

.lede { max-width: 62ch; color: var(--muted); margin: 14px 0 0; font-size: 15.5px; }

.count { font-family: var(--data); font-size: 11.5px; color: var(--faint); letter-spacing: .1em; }

/* ── Justified rows ───────────────────────────────────────────────
   Every frame keeps its own aspect ratio and rows come out flush, the way a
   photographer lays prints on a table. flex-grow carries the aspect ratio, so
   the browser does the arithmetic and there is no layout JavaScript to jank. */

.rows { display: flex; flex-wrap: wrap; gap: var(--gut); }

.rows .cell {
  position: relative;
  flex-grow: var(--ar);
  flex-basis: calc(var(--ar) * 260px);
  height: 260px;
  overflow: hidden;
  background: var(--ink-2) center/cover no-repeat;
  cursor: zoom-in;
}
.rows .cell img {
  width: 100%; height: 100%; object-fit: cover;
  opacity: 0; transition: opacity .5s ease;
}
.rows .cell img.on { opacity: 1; }
/* Tail spacer keeps the last row from stretching a lone frame across the page. */
.rows::after { content: ""; flex-grow: 999999; }

@media (max-width: 720px) { .rows .cell { height: 168px; flex-basis: calc(var(--ar) * 168px); } }

/* ── Gallery index ────────────────────────────────────────────────── */

.tiles { display: grid; gap: clamp(14px, 2vw, 26px);
         grid-template-columns: repeat(auto-fill, minmax(268px, 1fr)); }

.tile { display: block; }
/* These are spans, and aspect-ratio does nothing on an inline box — without
   display:block the frames collapsed and the labels ran into each other. */
.tile .frame {
  display: block;
  position: relative; aspect-ratio: 4 / 3; overflow: hidden;
  background: var(--ink-2) center/cover no-repeat;
}
.tile .frame img {
  width: 100%; height: 100%; object-fit: cover;
  opacity: 0; transition: opacity .5s ease, transform 1.1s cubic-bezier(.2,.7,.2,1);
}
.tile .frame img.on { opacity: 1; }
.tile:hover .frame img { transform: scale(1.035); }

.tile .name {
  display: block;
  margin-top: 11px;
  font-family: var(--display); font-size: 17.5px; letter-spacing: -.01em;
  font-variation-settings: "SOFT" 20, "opsz" 20;
}
.tile .meta { display: block; font-family: var(--data); font-size: 10.5px;
              letter-spacing: .14em; text-transform: uppercase; color: var(--faint);
              margin-top: 3px; }

/* ── Locations, grouped ───────────────────────────────────────────
   The country is a heading, not a tile: it names the group and gets out of the
   way. The count sits beside it in the data face, so the size of a place is
   readable without counting tiles. */
.loc-group { margin-top: 68px; }
.loc-group:first-of-type { margin-top: 34px; }
/* A country has to out-rank the eighteen gallery names under it, and at 25px
   against their 17.5px it did not — it read as one more label in the same
   voice. Twice their size, in the display face at optical size, with the rule
   running the width of the section beneath it: the page now has two levels
   instead of one crowded one. */
.loc-h {
  display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap;
  margin: 0 0 22px; padding-bottom: 14px; border-bottom: 1px solid var(--line-2);
  font-family: var(--display); font-weight: 400;
  font-size: clamp(30px, 3.4vw, 42px);
  letter-spacing: -.02em; font-variation-settings: "SOFT" 20, "opsz" 40;
}
/* The heading is the way into the trip gallery where one exists, so it has to
   look like it can be clicked without turning into a blue link. */
.loc-h a { color: inherit; text-decoration: none;
           border-bottom: 1px solid var(--line-2); padding-bottom: 2px; }
.loc-h a:hover { border-bottom-color: var(--paper); }
.loc-n { font-family: var(--data); font-size: 10.5px; letter-spacing: .14em;
         text-transform: uppercase; color: var(--faint);
         position: relative; bottom: 2px; }

/* The way into the whole trip, said in words. Same data face as the counts it
   follows so it reads as the end of that line rather than a button stuck on the
   heading — but in the paper tone, because it is the one part of the line you
   can act on. The arrow is what actually marks it as a link; the underline
   under the country name never could. */
.loc-all {
  font-family: var(--data); font-size: 10.5px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--paper); text-decoration: none;
  position: relative; bottom: 2px;
  display: inline-flex; align-items: baseline; gap: 7px;
  border-bottom: 1px solid var(--line-2); padding-bottom: 3px;
  transition: color .18s, border-color .18s;
}
.loc-all .arw { transition: transform .18s; }
.loc-all:hover { border-bottom-color: var(--paper); }
.loc-all:hover .arw { transform: translateX(3px); }
@media (max-width: 620px) { .loc-h { font-size: 26px; } }

/* ── Contact sheet (Camera Roll) ──────────────────────────────────
   Camera Roll is the sequence of what Ted has actually shot, so it is drawn as
   a contact sheet: even frames, frame numbers set in mono beneath each one,
   and a date rail down the left marking where one shoot ends and the next
   begins. It is the one place on the site that shows the work as a roll
   rather than a selection. */

.sheet-day { display: grid; grid-template-columns: 128px 1fr; gap: clamp(14px, 2.4vw, 30px);
             padding: clamp(22px, 3.4vw, 40px) 0; border-top: var(--rule); }
.sheet-day:first-child { border-top: 0; }

.sheet-date { position: sticky; top: 18px; align-self: start; }
.sheet-date .d { display: block; font-family: var(--display); font-size: 30px;
                 line-height: 1; font-variation-settings: "SOFT" 20, "opsz" 30; }
.sheet-date .m { display: block; font-family: var(--data); font-size: 10.5px;
                 letter-spacing: .18em; text-transform: uppercase; color: var(--faint);
                 margin-top: 7px; }
.sheet-date .n { display: block; font-family: var(--data); font-size: 10.5px;
                 color: var(--faint); margin-top: 12px; }

.sheet { display: grid; gap: 10px 10px;
         grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); }

.frameno { font-family: var(--data); font-size: 9.5px; letter-spacing: .1em;
           color: var(--faint); margin-top: 5px; }

.sheet .cell { cursor: zoom-in; display: block; }
/* .frame and the date parts are spans; aspect-ratio and margins do nothing on
   an inline box, which is why the sheet came out as ragged rows with no frame
   numbers under them. */
.sheet .cell .frame { display: block;
                      position: relative; aspect-ratio: 1; overflow: hidden;
                      background: var(--ink-2) center/cover no-repeat; }
.sheet .cell img { width: 100%; height: 100%; object-fit: cover;
                   opacity: 0; transition: opacity .45s ease; }
.sheet .cell img.on { opacity: 1; }
.sheet .cell:hover .frameno { color: var(--paper); }

@media (max-width: 700px) {
  .sheet-day { grid-template-columns: 1fr; gap: 12px; }
  .sheet-date { position: static; display: flex; align-items: baseline; gap: 12px; }
  .sheet-date .m, .sheet-date .n { margin: 0; }
}

/* ── Lightbox ─────────────────────────────────────────────────────
   The image gets the whole screen. Metadata is hidden until asked for, then
   appears along the bottom edge like the rebate printed down the side of a
   strip of film — frame, stock, exposure. */

/* Above Leaflet. Its panes run to 400 and its controls to 1000, so a viewer at
   90 opened on the map page and the map drew straight over the photograph. */
.lb { position: fixed; inset: 0; z-index: 2000; background: #070606;
      display: none; opacity: 0; transition: opacity .22s ease; }
.lb.open { display: block; }
.lb.shown { opacity: 1; }

.lb-stage { position: absolute; inset: 0; display: flex;
            align-items: center; justify-content: center;
            padding: 3vmin var(--lb-gutter);
            transition: padding .2s ease; }
/* With the details open the photograph gives up the bottom of the screen
   rather than hiding behind them. The exact amount is measured in JS from the
   panel itself — a fixed 250px cut off the last row on frames with many
   keywords. */
/* The details live in a column beside the photograph, not a strip beneath it.
   Read top to bottom the way a spec sheet reads, and the frame keeps the whole
   height of the screen instead of surrendering the bottom of it. */
:root { --lb-col: 330px; }
.lb.info .lb-stage { left: var(--lb-col); padding-top: 52px; }
/* Expanded gives the screen to the photograph — no gutters, no arrows. */
.lb.full .lb-nav { display: none; }
@media (max-width: 900px) {
  :root { --lb-col: 0px; }
  .lb.info .lb-stage { left: 0; padding-bottom: 46vh; }
}
/* Height first, not fit-to-box. Fitting to the box makes a landscape frame
   short and a portrait frame tall, so the photograph and the black around it
   change shape completely as you arrow through a gallery. Pinning the height
   means every frame is presented at the same scale and only its width varies —
   the way a series is hung on a wall. */
/* The viewer's frame is visible as soon as it has pixels. It used to start at
   zero opacity and wait for a load event to reveal it, which meant a cached
   photograph — one you had just seen in the grid — arrived already loaded, the
   event never fired, and you got a black screen with the details beside it. */
/* Fill the stage and letterbox inside it, rather than sizing from the image's
   own dimensions. width:auto collapses to nothing whenever the intrinsic size
   is not yet known — which is how a photograph ended up 177px wide and looking
   like a black screen. Presentation is still consistent: every frame is fitted
   to the same box. */
.lb-stage img { width: 100%; height: 100%; object-fit: contain; opacity: 1; }
.lb-stage img.on { opacity: 1; }

.lb-bar { position: absolute; left: 0; right: 0; top: 0; padding: 14px 20px;
          display: flex; justify-content: space-between; align-items: center; gap: 16px;
          font-family: var(--data); font-size: 11px; letter-spacing: .12em;
          text-transform: uppercase; color: var(--muted);
          background: linear-gradient(#070606, rgba(7,6,6,0)); }
.lb-titles { flex: 1 1 auto; min-width: 0; }
.lb-title { display: block; color: var(--paper); letter-spacing: .06em;
            text-transform: none; font-family: var(--body); font-size: 13.5px;
            overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lb-caption { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.lb-btn { background: none; border: 0; color: var(--muted); cursor: pointer;
          font: inherit; letter-spacing: .12em; padding: 2px 4px; }
.lb-btn:hover { color: var(--paper); }

/* The click zones stay wide — a quarter of the screen either side is an easy
   target — but now they show what they do. The chevron sits quietly at 40% and
   comes up on hover, so it reads as available without ever competing with the
   photograph. */
/* The arrows get their own gutters and the photograph is inset to clear them,
   so nothing is ever laid over the work — which is the whole point of the
   viewer. Wide enough to hit without aiming, narrow enough to cost the
   photograph almost nothing. */
:root { --lb-gutter: 76px; }

.lb-nav {
  position: absolute; top: 0; bottom: 0; width: var(--lb-gutter);
  border: 0; background: none; cursor: pointer; padding: 0;
  display: flex; align-items: center; justify-content: center;
  color: var(--paper); opacity: .45;
  transition: opacity .18s ease;
}
.lb-prev { left: 0; }
.lb-next { right: 0; }
/* With the details open the left gutter starts where the photograph does. */
.lb.info .lb-prev { left: var(--lb-col); }
@media (max-width: 900px) { .lb.info .lb-prev { left: 0; } }
.lb-next .chev { transform: rotate(180deg); }
.lb-nav:hover { opacity: 1; }
.lb-nav:focus-visible { opacity: 1; outline: 2px solid var(--paper); outline-offset: -6px; }
.lb-nav[disabled] { opacity: 0; pointer-events: none; }

.chev {
  display: block; border-radius: 50%;
  background: rgba(8,7,7,.42);
  padding: 9px; box-sizing: content-box;
  filter: drop-shadow(0 2px 10px rgba(0,0,0,.7));
  transition: background .18s ease, transform .18s ease;
}
.lb-nav:hover .chev { background: rgba(8,7,7,.72); }
.lb-prev:hover .chev { transform: translateX(-2px); }
.lb-next:hover .chev { transform: rotate(180deg) translateX(-2px); }

/* Back sits top left, where a way out belongs, and names where it goes. */
/* A link, not a button. The pill read as a control competing with the
   photograph; the way back should be quiet text like everything else up here. */
.lb-back {
  display: inline-flex; align-items: center; gap: 8px;
  background: none; border: 0; padding: 2px 0; cursor: pointer;
  font-family: var(--data); font-size: 11px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--muted);
  border-bottom: 1px solid transparent;
  transition: color .15s, border-color .15s;
}
.lb-back:hover { color: var(--paper); border-bottom-color: rgba(255,255,255,.45); }
.lb-back .arrow { font-size: 13px; line-height: 1; }
@media (max-width: 640px) { .lb-back .lb-back-t { display: none; } }

/* The rebate strip. */
.lb-exif { position: absolute; left: 0; top: 0; bottom: 0; width: var(--lb-col);
           padding: 54px 22px 24px; display: none;
           overflow-y: auto; overscroll-behavior: contain;
           background: #0A0908; border-right: 1px solid rgba(239,234,226,.10); }
.lb-exif.on { display: block; }

@media (max-width: 900px) {
  .lb-exif { top: auto; left: 0; right: 0; width: auto; height: 46vh;
             padding: 18px 20px 22px;
             border-right: 0; border-top: 1px solid rgba(239,234,226,.10); }
}
/* One label column, one value column, every field always in the same row —
   the panel is the same shape whatever a given photograph carries. */
.lb-exif dl { margin: 0; display: grid;
              grid-template-columns: 92px minmax(0, 1fr);
              gap: 9px 14px; align-items: baseline;
              font-family: var(--data); font-size: 11.5px; }
.lb-exif .pair { display: contents; }
.lb-exif dt { color: var(--faint); letter-spacing: .12em; text-transform: uppercase;
              font-size: 9.5px; }
.lb-exif dd { margin: 0; color: var(--paper); min-width: 0; overflow-wrap: anywhere; }
.lb-exif .pair.empty dd { color: var(--faint); }

@media (max-width: 900px) {
  .lb-exif dl { grid-template-columns: 92px minmax(0, 1fr) 92px minmax(0, 1fr); }
}
@media (max-width: 620px) { .lb-exif dl { grid-template-columns: 92px minmax(0, 1fr); } }

/* ── Footer ───────────────────────────────────────────────────────── */

footer { border-top: var(--rule); padding: 30px var(--pad) 54px; text-align: center;
         font-family: var(--data); font-size: 10.5px; letter-spacing: .16em;
         text-transform: uppercase; color: var(--faint); }
footer a:hover { color: var(--paper); }

/* ── About ────────────────────────────────────────────────────────── */

.about { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
         gap: clamp(24px, 5vw, 70px); align-items: start; }
.about p { color: var(--muted); max-width: 58ch; }
.about p strong { color: var(--paper); font-weight: 500; }
@media (max-width: 820px) { .about { grid-template-columns: 1fr; } }

.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
         gap: 20px; margin-top: 34px; padding-top: 26px; border-top: var(--rule); }
.stat .v { font-family: var(--display); font-size: 30px; line-height: 1;
           font-variation-settings: "SOFT" 20, "opsz" 30; }
.stat .k { font-family: var(--data); font-size: 10px; letter-spacing: .18em;
           text-transform: uppercase; color: var(--faint); margin-top: 7px; }

@media (prefers-reduced-motion: reduce) {
  /* Leaflet is excluded deliberately. It ends a zoom on the pane's
     transitionend, so killing the transition leaves the map stuck mid-zoom and
     ignoring every zoom after the first. The map honours the preference by
     turning its own animation off in JS instead — see PhotoMap. */
  *:not(.leaflet-container):not(.leaflet-container *) {
    transition: none !important; animation: none !important;
  }
}

/* ── Map ──────────────────────────────────────────────────────────
   Pins are the photographs. A dot on a map tells you a shot happened; the
   frame itself tells you what it was, which is the only reason to open the
   map at all. */

.map-wrap { margin: 6px 0 clamp(24px, 4vw, 44px); }
#map { height: clamp(360px, 62vh, 680px); background: var(--ink-2); border: var(--rule); }

.pin { background: none; border: 0; }
/* A white ring alone disappears against pale rooftops and sand. Pairing it
   with a dark outer ring means the pin reads on any imagery — light or dark —
   without needing a colour that would compete with the photographs. */
.pin-img {
  display: block; width: 42px; height: 42px; border-radius: 50%;
  background-size: cover; background-position: center;
  box-shadow: 0 0 0 2px #fff,
              0 0 0 3.5px rgba(6, 6, 6, .62),
              0 5px 16px -5px rgba(0, 0, 0, .9);
  transition: transform .16s ease, box-shadow .16s ease;
}
.pin:hover .pin-img {
  transform: scale(1.24);
  box-shadow: 0 0 0 2.5px #fff,
              0 0 0 4.5px rgba(6, 6, 6, .75),
              0 10px 26px -8px rgba(0, 0, 0, .95);
}

.leaflet-container { background: var(--ink-2); font-family: var(--data); font-size: 10px; }
.leaflet-control-attribution { background: rgba(7,6,6,.72) !important; color: var(--faint) !important; }
.leaflet-control-attribution a { color: var(--muted) !important; }
.leaflet-bar a { background: var(--ink-2) !important; color: var(--paper) !important;
                 border-color: var(--line) !important; }
.leaflet-bar a:hover { background: var(--line) !important; }

/* A cluster is several frames stacked at one place — same ring, so it reads as
   a group of pins rather than a different kind of thing. */
.marker-cluster {
  background: none; border: 0; border-radius: 50%;
  color: #fff; font-family: var(--data); font-size: 12px; letter-spacing: .04em;
}
.marker-cluster div {
  background: rgba(10, 9, 8, .88); border-radius: 50%;
  width: 34px; height: 34px; margin: 3px; line-height: 34px; text-align: center;
  box-shadow: 0 0 0 2px #fff,
              0 0 0 3.5px rgba(6, 6, 6, .62),
              0 5px 16px -5px rgba(0, 0, 0, .9);
}

.map-note { font-family: var(--data); font-size: 10.5px; letter-spacing: .14em;
            text-transform: uppercase; color: var(--faint); margin: 10px 0 0; }

/* Toolbar that sits under a gallery title. */
.tools { display: flex; align-items: baseline; gap: 18px; flex-wrap: wrap; margin-top: 16px; }
.tools button, .tools a {
  font-family: var(--data); font-size: 11px; letter-spacing: .16em; text-transform: uppercase;
  color: var(--muted); background: none; border: 0; border-bottom: 1px solid var(--line-2);
  padding: 0 0 3px; cursor: pointer;
}
.tools button:hover, .tools a:hover { color: var(--paper); }

/* ── Hero ─────────────────────────────────────────────────────────
   One photograph, edge to edge, immediately under the signature. The site's
   argument is the work, so the work is the first thing on the page — no
   headline standing in front of it. */

.hero-full { margin: 0 0 clamp(30px, 5vw, 58px); }
.hero-cell {
  display: block; position: relative;
  width: 100%; height: min(84vh, 860px);
  flex: none; overflow: hidden; cursor: zoom-in;
  background: var(--ink-2) center/cover no-repeat;
}
.hero-cell img { width: 100%; height: 100%; object-fit: cover; object-position: center;
                 opacity: 0; transition: opacity .8s ease; }
.hero-cell img.on { opacity: 1; }
@media (max-width: 720px) { .hero-cell { height: 62vh; } }

/* ── Home stage ───────────────────────────────────────────────────
   The homepage is one photograph at a time, filling the screen, with the
   signature and the navigation floating on top of it. The work is the page;
   the chrome is a guest on it. A slow crossfade, never a slide — motion
   should not compete with a still image. */

.stage { position: relative; height: 100svh; min-height: 520px; overflow: hidden;
         background: var(--ink); }

.slides { position: absolute; inset: 0; }
.slides img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover; object-position: center;
  opacity: 0; transition: opacity 1.6s ease-in-out;
}
.slides img.on { opacity: 1; }

/* A scrim only where the type sits, so the top of the frame stays readable
   without dimming the whole photograph. */
/* No gradient at the top. The header sits in a defined band with a hard edge
   just below the menu — a deliberate strip laid over the photograph, not a
   wash bleeding down into it. */

.masthead--over {
  position: absolute; left: 0; right: 0; top: 0; z-index: 3;
  padding-top: clamp(14px, 2.6vh, 30px);
  padding-bottom: clamp(10px, 1.6vh, 18px);
  /* A thin, light band — enough to seat the type, not enough to take a third
     of the photograph. No blur: it read as a smear across the frame. */
  background: rgba(6,6,6,.34);
  border-bottom: 1px solid rgba(255,255,255,.10);
}
.masthead--over .nav { border-bottom: 0; padding-bottom: 0; }
/* Colour and shadow only. This block used to set font-size: 11.5px against the
   11px everywhere else, and half a pixel across eight letter-spaced items moved
   every one of them sideways when you left the home page. Anything that changes
   the metrics of this nav has to be set in one place, for all pages. */
.masthead--over .nav a { color: rgba(255,255,255,.90);
                         text-shadow: 0 1px 10px rgba(0,0,0,.65); }
/* The signature is white line art with no mass behind it, so a bright sky can
   swallow the thin strokes even through the scrim. */
.masthead--over .sig img { filter: drop-shadow(0 2px 14px rgba(0,0,0,.55)); }
.masthead--over .nav a:hover, .masthead--over .nav a[aria-current="page"] {
  color: #fff; border-bottom-color: rgba(255,255,255,.6);
}

/* Which frame you are on, and a way to jump. Deliberately faint — it is a
   position indicator, not a control panel. */
/* The caption and dots need their own footing — bright sand at the bottom of a
   frame swallows them exactly the way a bright sky swallows the nav. */
.stage::before {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 22%;
  z-index: 2; pointer-events: none;
  background: linear-gradient(rgba(4,4,4,0), rgba(4,4,4,.30) 55%, rgba(4,4,4,.62));
}

/* Bottom right rather than centre: the dots own the middle, and a cue that
   fights the caption for space is worse than none. It leaves as soon as the
   page has been scrolled — it has said its piece by then. */
.stage-more {
  position: absolute; right: clamp(14px, 3vw, 30px); bottom: 18px; z-index: 4;
  width: 34px; height: 34px; padding: 0; cursor: pointer;
  background: none; border: 0; opacity: .62;
  transition: opacity .3s ease, transform .3s ease;
  animation: nudge 2.8s ease-in-out infinite;
}
.stage-more::after {
  content: ""; position: absolute; left: 11px; top: 10px; width: 11px; height: 11px;
  border: solid rgba(255, 255, 255, .95); border-width: 0 1.5px 1.5px 0;
  transform: rotate(45deg);
}
.stage-more:hover { opacity: 1; }
.stage-more.gone { opacity: 0; pointer-events: none; }
@keyframes nudge {
  0%, 62%, 100% { transform: translateY(0); }
  78%           { transform: translateY(5px); }
}
@media (prefers-reduced-motion: reduce) { .stage-more { animation: none; } }

.stage-dots { position: absolute; left: 0; right: 0; bottom: 26px; z-index: 3;
              display: flex; justify-content: center; gap: 9px; }
.stage-dots button {
  width: 6px; height: 6px; padding: 0; border: 0; border-radius: 50%;
  background: rgba(255,255,255,.34); cursor: pointer; transition: background .25s, transform .25s;
}
.stage-dots button[aria-current="true"] { background: #fff; transform: scale(1.5); }
.stage-dots button:hover { background: rgba(255,255,255,.75); }

.stage-cap {
  position: absolute; left: 0; right: 0; bottom: 52px; z-index: 3; text-align: center;
  font-family: var(--data); font-size: 10.5px; letter-spacing: .2em; text-transform: uppercase;
  color: rgba(255,255,255,.62); text-shadow: 0 1px 12px rgba(0,0,0,.7);
}

@media (max-width: 720px) {
  .masthead--over .sig { width: 74vw; }
  .stage-cap { bottom: 46px; }
}

/* ── Search ───────────────────────────────────────────────────────── */

#q {
  width: min(520px, 100%); padding: 12px 0; font: inherit; font-size: 17px;
  color: var(--paper); background: none;
  border: 0; border-bottom: 1px solid var(--line-2); border-radius: 0;
}
#q::placeholder { color: var(--faint); }
#q:focus { outline: none; border-bottom-color: var(--paper); }
#q-count { min-height: 1em; }

.mailto { font-family: var(--data); font-size: 15px; letter-spacing: .04em;
          border-bottom: 1px solid var(--line-2); padding-bottom: 3px; }
.mailto:hover { color: #fff; border-bottom-color: var(--paper); }

/* ── Narrow screens ───────────────────────────────────────────────
   The nav is eight items of letterspaced mono; on a phone it was overrunning
   the viewport and dragging the whole document sideways with it, which is why
   the signature and every grid sat shifted off-centre. Tighter type and real
   wrapping keep it inside the screen. */

html, body { overflow-x: hidden; max-width: 100%; }

/* A grid track with a fixed minimum cannot shrink below it and blows the
   layout out on a narrow screen; min() lets the last track collapse instead. */
.tiles { grid-template-columns: repeat(auto-fill, minmax(min(268px, 100%), 1fr)); }
.sheet { grid-template-columns: repeat(auto-fill, minmax(min(132px, 100%), 1fr)); }
.rows .cell, .tile, .sheet .cell { min-width: 0; }

@media (max-width: 720px) {
  .nav { gap: 10px 14px; padding-left: 4px; padding-right: 4px; }
  .nav a { font-size: 10px; letter-spacing: .12em; }
  footer { letter-spacing: .1em; line-height: 1.9; }
}

@media (max-width: 420px) {
  .nav { gap: 10px 14px; }
  .nav a { font-size: 11px; letter-spacing: .11em; }
  .masthead--over .sig { width: min(300px, 66vw); }
}

/* "Also in" — the other galleries holding this same frame. */
.lb-also { margin-top: 10px; display: flex; flex-wrap: wrap; align-items: baseline;
           gap: 4px 16px; font-family: var(--data); font-size: 11px; }
.lb-also .k { color: var(--faint); letter-spacing: .16em; text-transform: uppercase; }
.lb-also a { color: rgba(255,255,255,.78); border-bottom: 1px solid rgba(255,255,255,.22);
             padding-bottom: 2px; }
.lb-also a:hover { color: #fff; border-bottom-color: #fff; }

/* Walking from one gallery to the next, rather than back through the index. */
.walk { display: flex; justify-content: space-between; gap: 20px;
        margin-top: clamp(34px, 6vw, 72px); padding-top: 26px; border-top: var(--rule); }
.walk a { max-width: 46%; }
.walk .next { text-align: right; }
.walk span { display: block; font-family: var(--data); font-size: 10px;
             letter-spacing: .2em; text-transform: uppercase; color: var(--faint);
             margin-bottom: 7px; }
.walk a { font-family: var(--display); font-size: clamp(17px, 2.4vw, 23px);
          line-height: 1.15; font-variation-settings: "SOFT" 20, "opsz" 24;
          color: var(--muted); transition: color .18s; }
.walk a:hover { color: var(--paper); }

/* ── The photograph's own details ─────────────────────────────────
   Camera data along the bottom edge like the rebate printed down a strip of
   film, and beside it the ground the frame was made on. */

.lb-facts { min-width: 0; }
.lb-place { margin-top: 18px; }
.lb-map { height: 170px; background: #14120f;
          border: 1px solid rgba(255,255,255,.14);
          position: relative; z-index: 0; }   /* inside the viewer, not over it */
/* No coordinates: the slot holds, the map does not draw. */
.lb-place.nowhere .lb-map { background: #100E0D; }
.lb-coords { margin: 6px 0 0; font-family: var(--data); font-size: 10px;
             letter-spacing: .14em; color: var(--faint); text-align: right; }
/* The zoom control was hidden to keep the panel clean, which left the small
   map with no way to zoom at all — the wheel belongs to the page until the map
   is clicked. Keep it, just smaller. */
.lb-map .leaflet-control-zoom { margin: 6px 0 0 6px; }
.lb-map .leaflet-control-zoom a { width: 22px; height: 22px; line-height: 21px; font-size: 15px; }
.lb-map .leaflet-container { background: #14120f; }

.lb-coords { justify-content: flex-start; }

/* ── One photograph on its own page ───────────────────────────────
   What a shared link opens: the frame large, its camera data, where it was
   made, and the galleries it belongs to. */

/* A photograph's own page is laid out like the viewer it opens from — data in
   a column on the left, the frame beside it — so reloading does not hand you a
   different design for the same photograph. */
.photo-page {
  max-width: 1500px; padding-top: clamp(20px, 3vw, 34px);
  display: grid; align-items: start;
  grid-template-columns: minmax(0, 330px) minmax(0, 1fr);
  column-gap: clamp(24px, 3.5vw, 56px);
  row-gap: clamp(16px, 2vw, 26px);
}
.photo-page > .crumb,
.photo-page > .walk { grid-column: 1 / -1; }
.photo-page > .photo-meta { grid-column: 1; grid-row: 2; }
.photo-page > .photo-full { grid-column: 2; grid-row: 2; }

.photo-full { display: block; margin: 0; }
/* Same rule on the photo page, so a frame is the same size here as in the
   modal it was opened from. */
.photo-full img { max-height: 78vh; max-width: 100%; width: auto; height: auto;
                  margin: 0; object-fit: contain; cursor: zoom-in; }

.photo-meta { display: block; margin: 0; padding: 0; border-top: 0; }
/* Where it was taken, small, above the caption — the same information the
   viewer puts in its Place row rather than at display size. */
.photo-where { margin: 0 0 10px; font-family: var(--data); font-size: 10px;
               letter-spacing: .2em; text-transform: uppercase; color: var(--faint); }
/* A data plate, not a paragraph. Labels and values sit in real columns that
   line up with each other and with the title above them — the flex-wrapped
   version reflowed into a different ragged shape for every photograph. */
/* One label column and one value column, the same as the viewer's panel. The
   four-column version was written when this block ran the width of the page;
   inside a 330px rail it collapses into overlapping text. */
.photo-facts {
  margin: 0;
  display: grid;
  grid-template-columns: 92px minmax(0, 1fr);
  column-gap: 14px;
  row-gap: 9px;
  align-items: baseline;
  font-family: var(--data);
  font-size: 11.5px;
}
.photo-facts .pair { display: contents; }
.photo-facts dt {
  color: var(--faint); letter-spacing: .16em; text-transform: uppercase;
  font-size: 10px; white-space: nowrap;
}
.photo-facts dd { margin: 0; color: var(--paper); }
/* Nothing needs to span now that there is one value column. */

/* The view count, which only ever appears for Ted. It is added to the end of
   the list by script, so it reads as one more fact about the frame rather than
   a badge stuck on top of it — the thirty-day figure trails behind the total
   in the same faint tone the labels use. */
.photo-facts .pair.views dd { font-variant-numeric: tabular-nums; }
.photo-facts .pair.views .since { color: var(--faint); }

/* Full width on a phone, where the rail becomes the page. */
@media (max-width: 900px) {
  .photo-facts { grid-template-columns: 92px minmax(0, 1fr) 92px minmax(0, 1fr); }
}
@media (max-width: 620px) {
  .photo-facts { grid-template-columns: 84px minmax(0, 1fr); }
}
.photo-also { margin-top: 20px; padding-top: 16px; border-top: var(--rule);
              display: flex; flex-wrap: wrap; gap: 4px 16px;
              align-items: baseline; font-family: var(--data); font-size: 11px; }
.photo-also .k { color: var(--faint); letter-spacing: .16em; text-transform: uppercase; }
.photo-also a { color: var(--muted); border-bottom: 1px solid var(--line-2); padding-bottom: 2px; }
.photo-also a:hover { color: var(--paper); border-bottom-color: var(--paper); }

.photo-map { width: 100%; margin-top: 20px; }
.photo-map #map { height: 190px; border: var(--rule); }
.photo-map .map-note { text-align: right; }
@media (max-width: 760px) { .photo-map { width: 100%; } .photo-map .map-note { text-align: left; } }

/* Arrows on a photo page: the same gallery, one frame either way. */
.photo-page .walk a { display: flex; align-items: baseline; gap: 10px;
                      font-size: clamp(15px, 2vw, 19px); }
.photo-page .walk .next { flex-direction: row-reverse; }
.photo-page .walk span { margin: 0; }

/* Keywords — the archive's own vocabulary, and each one is a search. */
.photo-keys, .lb-keys { margin-top: 14px; display: flex; flex-wrap: wrap;
                        align-items: baseline; gap: 4px 16px;
                        font-family: var(--data); font-size: 11.5px; }
.photo-keys .k, .lb-keys .k { color: var(--faint); letter-spacing: .16em;
                              text-transform: uppercase; margin-right: 4px; }
/* Words, not pills. Eight keywords in eight capsules reads as a control panel;
   set plainly they read as what they are — the vocabulary of the archive. The
   underline only appears under the pointer, so the list stays quiet until you
   reach for it. */
.photo-keys a, .lb-keys a {
  color: var(--muted); border: 0; padding: 0;
  border-bottom: 1px solid transparent;
  transition: color .15s, border-color .15s;
}
.photo-keys a:hover, .lb-keys a:hover {
  color: var(--paper); border-bottom-color: var(--paper);
}
/* In a column these wrap naturally — no horizontal scrolling needed. */
.lb-keys { margin-top: 18px; padding-top: 16px; border-top: 1px solid rgba(239,234,226,.10); }
.lb-also { margin-top: 14px; }
.lb-keys a, .lb-also a { white-space: nowrap; }
.lb-keys .none, .lb-also .none { color: var(--faint); font-family: var(--data); }
.lb-keys a { color: rgba(255,255,255,.66); }
.lb-keys a:hover { color: #fff; border-bottom-color: #fff; }

/* ── GPS ──────────────────────────────────────────────────────────
   Coordinates are the one fact on the page a reader might copy out, so they
   get their own weight rather than sitting at caption grey with everything
   else. */

/* The pin sits where a label would, at the same optical weight as the others.
   It has to ride the text baseline, not a flex box's centre line — an SVG in a
   flex container has no baseline of its own and floats above the coordinates. */
.pin-ico { color: var(--muted); }

.photo-map .map-note, .lb-coords {
  display: flex; align-items: center; justify-content: flex-end; gap: 7px;
  margin-top: 8px;
}
.photo-map .map-note .pin-ico, .lb-coords .pin-ico { display: block; }
.photo-map .map-note .v, .lb-coords .v {
  font-family: var(--data); font-size: 11.5px; letter-spacing: .02em;
  color: var(--muted);
}
@media (max-width: 760px) {
  .photo-map .map-note, .lb-coords { justify-content: flex-start; }
}

/* Back to the gallery. Sits above the frame because someone arriving from a
   shared link or a map pin has no history to go back through. */
.crumb { margin: 0 0 clamp(14px, 2vw, 22px); font-family: var(--data);
         font-size: 11px; letter-spacing: .16em; text-transform: uppercase; }
.crumb a { display: inline-flex; align-items: baseline; gap: 10px;
           color: var(--muted); border-bottom: 1px solid transparent;
           padding-bottom: 3px; transition: color .16s, border-color .16s; }
.crumb a:hover { color: var(--paper); border-bottom-color: var(--line-2); }
.crumb .arrow { font-size: 13px; }

/* The way out of the lightbox reads as going back, not as dismissing a dialog. */
.lb-close .arrow { font-size: 13px; }

/* ── Edges ────────────────────────────────────────────────────────
   Nothing is painted over a photograph. A frame gets a hairline drawn just
   outside it, which defines where the image stops on a near-black page —
   invisible against a bright frame, quietly useful on a dark one. In the
   lightbox the photograph also casts a shadow, so it sits on the ground the
   way a print sits on a wall rather than floating in it. */

.rows .cell, .sheet .cell .frame, .tile .frame, .photo-full img {
  /* A bevel rather than a hairline: the paper tone catches the top and left,
     black falls away down the right and bottom, and a short shadow lifts the
     whole thing off the page. Both edges are drawn outside the box, so nothing
     is painted over the photograph — the same rule the hairline followed. */
  box-shadow: -1px -1px 0 0 rgba(239, 234, 226, .22),
               1px  1px 0 0 rgba(0, 0, 0, .85),
               0 6px 16px -10px rgba(0, 0, 0, .9);
}
.tile:hover .frame, .rows .cell:hover {
  box-shadow: -1px -1px 0 0 rgba(239, 234, 226, .38),
               1px  1px 0 0 rgba(0, 0, 0, .9),
               0 10px 26px -12px rgba(0, 0, 0, .95);
}

.lb-stage img {
  box-shadow: 0 0 0 1px rgba(239, 234, 226, .10),
              0 30px 70px -30px rgba(0, 0, 0, .9),
              0 8px 24px -12px rgba(0, 0, 0, .7);
}

.photo-full img {
  box-shadow: 0 0 0 1px rgba(239, 234, 226, .09),
              0 26px 60px -32px rgba(0, 0, 0, .85);
}

/* ── About ────────────────────────────────────────────────────────
   The photographer, then the work he keeps. Portrait on the left at a size
   that holds its own against the type without dominating it. */

.about { grid-template-columns: minmax(0, 440px) minmax(0, 1fr); }

.portrait { margin: 0; }
.portrait img { width: 100%; height: auto; display: block;
                box-shadow: 0 0 0 1px rgba(239,234,226,.09),
                            0 26px 60px -32px rgba(0,0,0,.85); }
.portrait figcaption { margin-top: 10px; font-family: var(--data); font-size: 10.5px;
                       letter-spacing: .16em; text-transform: uppercase;
                       color: var(--faint); }

.contact { margin-top: clamp(28px, 4vw, 46px); padding-top: 24px; border-top: var(--rule); }
.contact .eyebrow { margin-bottom: 10px; }
.contact p { margin: 0 0 14px; }

@media (max-width: 820px) { .about { grid-template-columns: 1fr; } }

/* With a frame open the page underneath is pinned, not merely overflow-hidden:
   html is the scroll container here, so hiding overflow on body alone left the
   gallery scrolling behind the photograph. */
html.lb-locked, html.lb-locked body {
  overflow: hidden;
  height: 100%;
}
html.lb-locked body { position: fixed; left: 0; right: 0; width: 100%; }

/* ── Words ────────────────────────────────────────────────────────
   A caption sits under the title; notes sit under the camera data. Both are
   optional, and their absence changes nothing else on the page — the frame is
   the same size and the facts are in the same place whether a photograph has
   been written about or not. */

/* Caption then note then the camera data — the reading order of the viewer's
   panel, so a reload does not rearrange the page. */
.photo-caption { margin: 0 0 10px; color: var(--paper); font-weight: 400;
                 font-family: var(--body); font-size: 15.5px; line-height: 1.45; }
.photo-notes { margin: 0 0 20px; padding-bottom: 18px; border-bottom: var(--rule); }
.photo-notes p { margin: 0; color: var(--muted); font-size: 13px; line-height: 1.65; }

.gallery-caption { margin: 14px 0 0; color: var(--paper); font-size: 17px;
                   line-height: 1.5; max-width: 62ch; }
.gallery-notes { margin-top: 14px; }
.gallery-notes p { margin: 0; color: var(--muted); font-size: 14.5px; line-height: 1.7;
                   max-width: 72ch; }

/* The caption rides under the title in the lightbox bar. */
.lb-caption { display: block; color: var(--muted); font-family: var(--body);
              font-size: 12.5px; margin-top: 3px; }

/* Rights sit under the data, quieter than the measurements — a statement about
   the photograph rather than a reading off the camera. */
.lb-rights, .photo-rights {
  margin: 12px 0 0; font-family: var(--data); font-size: 10.5px;
  letter-spacing: .04em; color: var(--faint);
}
.photo-rights { margin-top: 18px; padding-top: 14px; border-top: var(--rule); }

/* ── Expanded ─────────────────────────────────────────────────────
   Every piece of chrome steps aside and the photograph takes the screen.
   Escape backs out one level: expanded → normal → closed. */

.lb.full .lb-exif,
.lb.full .lb-bar { display: none; }
.lb.full .lb-stage { left: 0; padding: 0; }
.lb.full .lb-stage img { box-shadow: none; }
.lb.full .lb-nav { width: 22%; }

/* The bar belongs over the photograph, not buried under the details column. */
.lb.info .lb-bar { left: var(--lb-col); }
@media (max-width: 900px) { .lb.info .lb-bar { left: 0; } }

/* No coordinates: drop the empty frame, keep the line that says so. */
.lb-place.nowhere .lb-map { display: none; }
.lb-place.nowhere { margin-top: 14px; }

/* ── Sign in and locked galleries ─────────────────────────────────
   The private side wears the same clothes as the public one — signing in
   should not feel like arriving at a different site. */

.pw-form { max-width: 380px; margin-top: 26px; }
.pw-form label { display: block; font-family: var(--data); font-size: 10px;
                 letter-spacing: .2em; text-transform: uppercase;
                 color: var(--faint); margin-bottom: 9px; }
.pw-form input {
  width: 100%; padding: 13px 14px; font: inherit; font-size: 16px;
  color: var(--paper); background: var(--ink-2);
  border: 1px solid var(--line-2); border-radius: 0;
}
.pw-form input:focus { outline: none; border-color: var(--paper); }
.pw-form button {
  margin-top: 16px; padding: 12px 26px; cursor: pointer;
  font-family: var(--data); font-size: 11px; letter-spacing: .18em;
  text-transform: uppercase; color: var(--ink);
  background: var(--paper); border: 1px solid var(--paper);
  transition: background .15s, color .15s;
}
.pw-form button:hover { background: transparent; color: var(--paper); }

.form-error {
  margin: 18px 0 0; max-width: 380px; padding: 11px 13px;
  font-family: var(--data); font-size: 12px; color: #f0b8a8;
  border: 1px solid rgba(240,184,168,.32); background: rgba(240,184,168,.06);
}

/* A locked client gallery shows its name and nothing of its contents. */
.tile-lock {
  position: absolute; inset: 0; display: flex;
  align-items: center; justify-content: center;
  font-family: var(--data); font-size: 10px; letter-spacing: .2em;
  text-transform: uppercase; color: var(--muted);
  background: repeating-linear-gradient(135deg, #141210, #141210 6px,
                                        #17150F 6px, #17150F 12px);
}

/* ── Account ──────────────────────────────────────────────────────── */

.form-notice {
  margin: 18px 0 0; max-width: 640px; padding: 11px 13px;
  font-family: var(--data); font-size: 12px; color: var(--paper);
  border: 1px solid var(--line-2); background: rgba(239,234,226,.05);
}
.pw-table { width: 100%; max-width: 900px; border-collapse: collapse;
            font-size: 14px; }
.pw-table th { text-align: left; font-family: var(--data); font-weight: 400;
               font-size: 10px; letter-spacing: .18em; text-transform: uppercase;
               color: var(--faint); padding: 0 14px 10px 0;
               border-bottom: var(--rule); }
.pw-table td { padding: 12px 14px 12px 0; border-bottom: var(--rule);
               vertical-align: middle; }
.pw-table .dim { color: var(--faint); font-family: var(--data); font-size: 11.5px; }
.inline-form { display: flex; gap: 8px; }
.inline-form input {
  flex: 1 1 auto; min-width: 0; padding: 8px 10px; font: inherit; font-size: 14px;
  color: var(--paper); background: var(--ink-2);
  border: 1px solid var(--line-2); border-radius: 0;
}
.inline-form input:focus { outline: none; border-color: var(--paper); }
.inline-form button {
  padding: 8px 16px; cursor: pointer; font-family: var(--data); font-size: 10.5px;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--paper); background: none; border: 1px solid var(--line-2);
}
.inline-form button:hover { color: var(--ink); background: var(--paper);
                            border-color: var(--paper); }

/* ── The way in ───────────────────────────────────────────────────
   Top right, quiet enough that a visitor reads past it and Ted knows exactly
   where it is. */

.site-signin, .site-signout {
  position: absolute; top: 14px; z-index: 6;
  font-family: var(--data); font-size: 10px; letter-spacing: .18em;
  text-transform: uppercase; color: var(--faint);
  border-bottom: 1px solid transparent; padding-bottom: 2px;
  transition: color .15s, border-color .15s;
}
.site-signin { right: clamp(14px, 3vw, 30px); }
.site-signout { right: clamp(14px, 3vw, 30px); top: 36px; }
.site-signin:hover, .site-signout:hover { color: var(--paper); border-bottom-color: var(--line-2); }

/* Over the homepage slideshow it needs to stay legible on a bright frame. */
.stage .site-signin, .stage .site-signout { color: rgba(255,255,255,.6); }

/* A gallery with nothing to show says so, rather than rendering a broken
   image. The video archive has no still frame to stand in for it. */
.tile-empty {
  position: absolute; inset: 0; display: flex;
  align-items: center; justify-content: center;
  font-family: var(--data); font-size: 10px; letter-spacing: .2em;
  text-transform: uppercase; color: var(--faint);
  border: 1px solid var(--line); background: var(--ink-2);
}

/* Photographs are not selectable and not draggable — the two accidental ways
   an image leaves a page. A screenshot is still a screenshot; see the note in
   the README about what is and is not possible here. */
img { -webkit-user-drag: none; user-select: none; -webkit-user-select: none; }

/* Share links, one per person, listed under the gallery they open. */
.shares { margin: 0 0 clamp(24px, 4vw, 40px); padding-top: 20px; border-top: var(--rule); }
.shares h2 { font-family: var(--data); font-size: 10px; letter-spacing: .2em;
             text-transform: uppercase; color: var(--faint); font-weight: 400;
             margin: 0 0 14px; }
.share-url { width: 100%; min-width: 220px; padding: 6px 8px; font-family: var(--data);
             font-size: 11.5px; color: var(--muted); background: var(--ink-2);
             border: 1px solid var(--line); }
.share-url:focus { outline: none; border-color: var(--paper); color: var(--paper); }

/* ── Events console ───────────────────────────────────────────────
   A shared link is only shared once someone can take it away in one press,
   and a state that can be changed should look like a switch, not a button
   whose label might be describing either where it is or where it will go. */

.events-table { max-width: 1180px; }
.link-cell { display: flex; gap: 6px; align-items: stretch; }
.link-cell .share-url { flex: 1 1 auto; }

.copy-btn, .row-acts .btn {
  padding: 6px 13px; cursor: pointer; white-space: nowrap;
  font-family: var(--data); font-size: 10px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--paper); background: none;
  border: 1px solid var(--line-2);
  transition: color .15s, background .15s, border-color .15s;
}
.copy-btn:hover, .row-acts .btn:hover {
  color: var(--ink); background: var(--paper); border-color: var(--paper); }
.copy-btn.done, .copy-btn.done:hover {
  color: var(--go); background: none; border-color: var(--go); }

.row-acts { display: flex; gap: 8px; align-items: center; }

.pw-table .switch, .pw-table .switch:hover {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 0; background: none; border: 0; cursor: pointer; }
.pw-table .switch .track { position: relative; flex: none; width: 40px; height: 20px;
                 border: 1px solid var(--line-2); background: var(--ink);
                 transition: background .16s, border-color .16s; }
.pw-table .switch .knob { position: absolute; top: 2px; left: 2px; width: 14px; height: 14px;
                background: var(--faint); transition: transform .16s, background .16s; }
.pw-table .switch .switch-t { font-family: var(--data); font-size: 10px; letter-spacing: .16em;
                    text-transform: uppercase; color: var(--faint); }
.pw-table .switch:hover .track { border-color: var(--paper); }
.pw-table .switch.on .track { border-color: var(--go); background: rgba(110,155,91,.20); }
.pw-table .switch.on .knob { transform: translateX(20px); background: var(--go); }
.pw-table .switch.on .switch-t { color: var(--go); }

/* A video still is a photograph; the badge says what it leads to. */
.tile-badge {
  position: absolute; left: 10px; bottom: 10px;
  font-family: var(--data); font-size: 9px; letter-spacing: .18em;
  text-transform: uppercase; color: var(--paper);
  background: rgba(12,11,10,.72); border: 1px solid rgba(239,234,226,.28);
  padding: 4px 9px;
}

/* ── Film ─────────────────────────────────────────────────────────
   A video sits in the same frame a photograph would, with the same height
   rule, so moving between the two does not resize the stage. */

/* Fit the frame the way a photograph does, rather than stretching to the
   stage and going long. */
.lb-video { display: none; width: 100%; height: 100%; object-fit: contain;
            background: #000; }
.lb.playing .lb-stage img { display: none; }
.lb.playing .lb-video { display: block; }
/* The invisible previous/next zones cover a quarter of each side of the stage.
   Over a photograph that is what makes it clickable; over a film it swallows
   the player's own controls and keeps them from ever settling. Arrow keys
   still move between frames. */
.lb.playing .lb-nav { display: none; }

/* On a grid tile, a film says so before you click it. */
.cell-play {
  position: absolute; left: 50%; top: 50%; width: 46px; height: 46px;
  margin: -23px 0 0 -23px; border-radius: 50%; pointer-events: none;
  background: rgba(12,11,10,.55); border: 1px solid rgba(239,234,226,.55);
}
.cell-play::after {
  content: ""; position: absolute; left: 18px; top: 14px;
  border-left: 15px solid rgba(239,234,226,.92);
  border-top: 9px solid transparent; border-bottom: 9px solid transparent;
}

/* ════════════════════════════════════════════════════════════════
   PHONE
   Not the desktop layout squeezed. A phone is held in one hand, read
   at arm's length, and navigated with a thumb — so the nav becomes a
   single swipeable line instead of two cramped rows, tiles pair up
   rather than running one-per-screen, and everything you tap is big
   enough to hit without aiming.
   ════════════════════════════════════════════════════════════════ */

@media (max-width: 720px) {

  /* ── Masthead ──────────────────────────────────────────────────
     The signature shrinks, and the nav becomes one line you can swipe
     rather than two rows that wrap unpredictably as items are added. */

  .masthead { padding-top: clamp(20px, 5vw, 34px); }
  .sig { width: min(230px, 58vw); }

  /* Ted, 2026-09-01: the single swipeable line hid "Clients" and "About"
     behind an edge nobody swipes. Every item shows, no scrolling: seven
     items wrap into two centred rows — four, then three at 390px — and the
     type is sized so that split holds down to the narrowest phones. */
  .nav {
    margin-top: 18px;
    flex-wrap: wrap;
    justify-content: center;
    overflow: visible;
    margin-left: 0; margin-right: 0;
    gap: 12px 18px;
    padding-left: var(--pad); padding-right: var(--pad);
  }
  .nav a {
    flex: 0 0 auto;
    font-size: 11.5px; letter-spacing: .13em;
    padding-bottom: 6px;                    /* a thumb-sized target */
    white-space: nowrap;
  }
  .masthead .nav {
    padding-top: 0; padding-bottom: 14px;
    padding-left: var(--pad); padding-right: var(--pad);
  }

  /* Over the homepage slideshow the band holds the same single line. */
  .masthead--over .nav { padding-bottom: 10px; }

  /* Sign in sits in the flow, not on top of the signature. */
  .site-signin, .site-signout {
    position: static; display: block; text-align: center;
    margin: 12px auto 0; padding: 0;
  }
  .stage .site-signin, .stage .site-signout { display: none; }

  /* ── Grids ─────────────────────────────────────────────────────
     Two across. One-per-screen turns 50 galleries into a scroll nobody
     finishes; two lets you take in a page at a glance and still see
     what each frame is. */

  .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px 8px; }
  .tile .name { font-size: 14px; margin-top: 8px; line-height: 1.25; }
  .tile .meta { font-size: 9px; letter-spacing: .1em; }

  .sheet { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; }
  .frameno { font-size: 8.5px; }

  .rows { gap: 4px; }
  .rows .cell { height: 150px; flex-basis: calc(var(--ar) * 150px); }

  /* ── Reading ───────────────────────────────────────────────────── */

  h1 { font-size: clamp(27px, 8.5vw, 40px); }
  .page-head { padding: clamp(22px, 6vw, 34px) 0 clamp(14px, 3vw, 22px); }
  .lede { font-size: 15px; }
  .gallery-notes p, .photo-notes p { font-size: 14px; }

  /* ── The viewer ────────────────────────────────────────────────
     Full bleed, chrome out of the way, and swipe to move. */

  /* No gutters on a phone — a swipe moves between frames, so the photograph
     takes the full width. */
  :root { --lb-gutter: 0px; }
  .lb-stage { padding: 0; }
  .lb.info .lb-stage { padding-top: 44px; }
  .lb-bar { padding: 10px 12px; }
  .lb-btn { font-size: 10px; }
  .lb-back { font-size: 10px; }
  .lb-title { font-size: 12.5px; }
  .lb-caption { font-size: 11.5px; }

  /* Invisible edge zones are a desktop idea; on a phone they fight the
     scroll of the details panel. Swiping moves between frames instead. */
  .lb-nav { display: none; }

  .lb-exif { padding: 16px 16px 22px; }
  .lb-exif dl { grid-template-columns: 84px minmax(0, 1fr); gap: 8px 12px; }
  .lb-exif dt { font-size: 9px; }
  .lb-exif dd { font-size: 11px; }

  /* ── Forms ─────────────────────────────────────────────────────── */

  .pw-form input, #q { font-size: 16px; }   /* 16px or iOS zooms on focus */
  .pw-form button { padding: 14px 26px; }
  .inline-form { flex-wrap: wrap; }
  .inline-form input { font-size: 16px; }

  .pw-table, .pw-table tr, .pw-table td, .pw-table th { display: block; width: 100%; }
  .pw-table th { display: none; }
  .pw-table td { border: 0; padding: 4px 0; }
  .pw-table tr { border-bottom: var(--rule); padding: 12px 0; }

  footer { padding-bottom: max(34px, env(safe-area-inset-bottom)); }
}

@media (max-width: 400px) {
  .tiles { gap: 8px 6px; }
  .sheet { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Stack the photo page on narrow screens: frame first, then its data. */
@media (max-width: 900px) {
  .photo-page { grid-template-columns: 1fr; }
  .photo-page > .photo-meta { grid-column: 1; grid-row: 3; }
  .photo-page > .photo-full { grid-column: 1; grid-row: 2; }
  .photo-meta { padding-top: 20px; border-top: var(--rule); }
}


/* Stack the photo page on narrow screens: frame first, then its data. */
@media (max-width: 900px) {
  .photo-page { grid-template-columns: 1fr; }
  .photo-page > .photo-meta { grid-column: 1; grid-row: 3; }
  .photo-page > .photo-full { grid-column: 1; grid-row: 2; }
  .photo-meta { padding-top: 20px; border-top: var(--rule); }
  .photo-full img { max-height: none; width: 100%; }
}

/* What was written about the frame, at the top of the panel. A caption in the
   title bar alone is easy to read past, and the note had nowhere to live. */
.lb-words { margin: 0 0 18px; padding-bottom: 16px;
            border-bottom: 1px solid rgba(239,234,226,.10); }
.lb-cap { margin: 0; color: var(--paper); font-size: 14.5px; line-height: 1.45; }
.lb-note { margin: 9px 0 0; color: var(--muted); font-size: 12.5px; line-height: 1.6; }

/* ── The viewer's top bar on a phone ──────────────────────────────
   Six controls in a row do not fit 390px, and stacking them let them land on
   top of each other. Back on the left, the essentials on the right, and the
   frame counter and Copy link step aside — the address bar is the link. */

@media (max-width: 720px) {
  .lb-bar { gap: 8px; align-items: center; }
  .lb-titles { display: none; }        /* the caption is in the panel now */
  .lb-pos, .lb-copy { display: none; }
  /* Only the trailing group of buttons — this also matched the titles span,
     and being more specific it beat the rule hiding them. */
  .lb-bar > span:last-child { display: flex; gap: 14px; align-items: center;
                              flex: 0 0 auto; }
  /* The gallery name can be long; it truncates rather than shoving Info and
     Close off the edge of the screen. */
  .lb-back { flex: 1 1 auto; min-width: 0; overflow: hidden; }
  .lb-back .lb-back-t {
    display: block; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .lb-bar > span:last-child { flex: 0 0 auto; }
  .lb-btn { padding: 6px 2px; }        /* a real tap target */

  /* Closed, the photograph has the whole screen. */
  .lb-stage { padding-top: 46px; }
  .lb.info .lb-exif { height: 58vh; }
  .lb.info .lb-stage { padding-bottom: 58vh; }
}

/* ── Event uploads ────────────────────────────────────────────────
   The page a guest lands on. It has to be obvious to someone who has never
   seen the site and is holding a phone at a wedding. */

.upload { max-width: 520px; margin: 0 0 clamp(26px, 4vw, 40px);
          padding: 20px; border: 1px solid var(--line-2); background: var(--ink-2); }
.upload label { display: block; font-family: var(--data); font-size: 10px;
                letter-spacing: .2em; text-transform: uppercase;
                color: var(--faint); margin-bottom: 8px; }
.upload input[type="text"], .upload input:not([type]) , .upload input[name="who"] {
  width: 100%; padding: 12px 13px; font: inherit; font-size: 16px;
  color: var(--paper); background: var(--ink);
  border: 1px solid var(--line-2); border-radius: 0;
}
.upload input[name="who"]:focus { outline: none; border-color: var(--paper); }
.upload input[type="file"] { width: 100%; font-family: var(--data); font-size: 13px;
                             color: var(--muted); padding: 10px 0; }
.upload button {
  margin-top: 16px; width: 100%; padding: 15px 20px; cursor: pointer;
  font-family: var(--data); font-size: 11px; letter-spacing: .18em;
  text-transform: uppercase; color: var(--ink);
  background: var(--paper); border: 1px solid var(--paper);
}
.upload button:hover { background: transparent; color: var(--paper); }
/* Pressed. The button stops being a button and becomes the progress report —
   the one place a guest looks after choosing photographs. */
.upload button.busy, .upload button.busy:hover, .upload button:disabled {
  color: var(--ink); border-color: var(--go); opacity: 1; cursor: progress;
  background: linear-gradient(to right,
    var(--go) 0 var(--pct, 100%), rgba(110, 155, 91, .30) var(--pct, 100%) 100%);
}
.upload .map-note { margin-top: 12px; }

/* ── The cover ────────────────────────────────────────────────────
   A client gallery opened cold on a wall of thumbnails. Every gallery worth
   sending someone opens on one photograph with the name over it — the frame
   does the work the page head was trying to do in type. */

.cover {
  position: relative; width: 100%; height: min(58vh, 560px);
  background: var(--ink-2) center/cover no-repeat;
  display: flex; align-items: flex-end;
}
/* The scrim exists only under the words: enough to read by, not enough to
   grey the photograph. */
.cover::after {
  content: ""; position: absolute; inset: auto 0 0 0; height: 62%;
  background: linear-gradient(to top, rgba(12,11,10,.88), rgba(12,11,10,0));
}
.cover-t { position: relative; z-index: 1; width: 100%;
           padding: 0 var(--pad) clamp(22px, 4vw, 40px); }
.cover-t h1 { margin: 6px 0 0; }
.cover-n { margin: 8px 0 0; font-family: var(--data); font-size: 11px;
           letter-spacing: .18em; text-transform: uppercase; color: var(--muted); }
.page-head--under { padding-top: clamp(18px, 3vw, 30px); }

/* ── A client's picks ─────────────────────────────────────────────
   The heart is drawn on every frame rather than on hover: on a phone there is
   no hover, and a control nobody can see is a feature nobody uses. */

.fav {
  position: absolute; top: 8px; right: 8px; width: 26px; height: 26px;
  padding: 0; cursor: pointer; z-index: 2; line-height: 0;
  background: rgba(12, 11, 10, .5); border: 1px solid rgba(239, 234, 226, .45);
  transition: background .14s, border-color .14s;
}
.fav svg { width: 16px; height: 16px; margin: 4px auto 0; display: block;
           fill: none; stroke: rgba(239, 234, 226, .9); stroke-width: 1.6;
           stroke-linejoin: round; }
.fav:hover { border-color: var(--paper); background: rgba(12, 11, 10, .78); }
.cell.faved .fav { background: rgba(12, 11, 10, .72); border-color: var(--go); }
.cell.faved .fav svg { fill: var(--go); stroke: var(--go); }
.rows.only-faved .cell:not(.faved) { display: none; }

/* Lives in .tools and keeps that styling; it only says when it is on. */
.fav-only[aria-pressed="true"] { color: var(--go); border-bottom-color: var(--go); }
.fav-n { font-family: var(--data); }

/* ── Views ────────────────────────────────────────────────────────
   Admin only. A grid of what people actually opened, biggest first, with the
   number over the frame — the photograph is the label. */

.views-h { font-family: var(--data); font-size: 10px; letter-spacing: .2em;
           text-transform: uppercase; color: var(--faint); font-weight: 400;
           margin: 34px 0 14px; }
.views-grid { display: grid; gap: 8px;
              grid-template-columns: repeat(auto-fill, minmax(min(150px, 100%), 1fr)); }
.views-cell { position: relative; display: block; aspect-ratio: 3 / 2;
              background: var(--ink-2); overflow: hidden; }
.views-cell img { width: 100%; height: 100%; object-fit: cover; }
.views-n {
  position: absolute; right: 6px; bottom: 6px;
  padding: 3px 8px; font-family: var(--data); font-size: 11px;
  color: var(--paper); background: rgba(12, 11, 10, .78);
  border: 1px solid rgba(239, 234, 226, .25);
}

/* ── Paging ───────────────────────────────────────────────────────
   Eight hundred frames in one document was a quarter of a megabyte before a
   single photograph loaded. Numbers rather than "load more": someone who
   wants 2014 should be able to get back to it. */

.pager { display: flex; align-items: center; justify-content: space-between;
         gap: 18px; margin: clamp(30px, 5vw, 56px) 0 clamp(20px, 4vw, 40px);
         padding-top: 22px; border-top: var(--rule); }
.pager-n { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; }
.pager a, .pager-step {
  font-family: var(--data); font-size: 11px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--muted);
}
.pager-n a { min-width: 32px; padding: 6px 8px; text-align: center;
             border: 1px solid transparent; }
.pager-n a:hover { color: var(--paper); border-color: var(--line-2); }
.pager-n a[aria-current="page"] { color: var(--ink); background: var(--paper);
                                  border-color: var(--paper); }
.pager-step { display: inline-flex; align-items: center; gap: 8px; min-width: 74px; }
a.pager-step:hover { color: var(--paper); }
.pager-step .arrow { font-size: 13px; }
@media (max-width: 620px) { .pager { flex-wrap: wrap; justify-content: center; } }

/* ── Asking ───────────────────────────────────────────────────────
   Anything the site needs to ask is asked in the site's own voice. A browser
   confirm() arrives in the operating system's type with the hostname on it and
   reads like a warning about the page rather than a question from it. */

.ask { padding: 0; max-width: min(400px, 92vw); color: var(--paper);
       background: var(--ink-2); border: 1px solid var(--line-2); }
.ask::backdrop { background: rgba(8, 7, 6, .8); }
.ask-body { padding: 22px 22px 4px; }
.ask-t { margin: 0; font-family: var(--display); font-size: 21px; line-height: 1.25; }
.ask-d { margin: 10px 0 0; font-size: 13.5px; line-height: 1.5; color: var(--muted); }
.ask-actions { display: flex; gap: 8px; justify-content: flex-end; padding: 18px 18px 18px; }
.ask-actions button {
  padding: 9px 17px; cursor: pointer; font-family: var(--data); font-size: 10px;
  letter-spacing: .16em; text-transform: uppercase; color: var(--paper);
  background: none; border: 1px solid var(--line-2);
}
.ask-actions .ask-no:hover { color: var(--ink); background: var(--paper);
                             border-color: var(--paper); }
.ask-actions .ask-yes { color: #C97A6C; border-color: #6E332A; }
.ask-actions .ask-yes:hover { color: #fff; background: #8E3B2F; border-color: #8E3B2F; }

/* ── Picking frames to take home ──────────────────────────────────
   A guest wants three photographs, not the whole day. The box is always
   drawn rather than appearing on hover, because half the people here are on
   a phone where there is no hover and an invisible control does not exist. */

.pick {
  position: absolute; top: 8px; right: 8px; width: 22px; height: 22px;
  padding: 0; cursor: pointer; z-index: 2;
  background: rgba(12, 11, 10, .55); border: 1px solid rgba(239, 234, 226, .55);
  transition: background .14s, border-color .14s;
}
.pick:hover { border-color: var(--paper); background: rgba(12, 11, 10, .8); }

.empty-note {
  margin: 0; padding: clamp(40px, 8vw, 90px) 0; text-align: center;
  font-family: var(--data); font-size: 12px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--faint);
}
/* The tick is drawn, not typed — two borders on a rotated box, so it stays
   crisp at any size and needs no icon font. */
.pick::after {
  content: ""; position: absolute; left: 7px; top: 4px; width: 6px; height: 10px;
  border: solid transparent; border-width: 0 2px 2px 0; transform: rotate(43deg);
}
.cell.picked .pick { background: var(--go); border-color: var(--go); }
.cell.picked .pick::after { border-color: var(--ink); }
.cell.picked { outline: 3px solid var(--go); outline-offset: -3px; }

.pickbar {
  position: sticky; bottom: 20px; z-index: 40;
  width: max-content; max-width: 100%; margin: 22px auto 6px;
  display: flex; align-items: center; gap: 14px;
  padding: 11px 14px; background: rgba(20, 18, 16, .96);
  border: 1px solid var(--line-2); box-shadow: 0 10px 34px rgba(0, 0, 0, .55);
}
/* With nothing picked the bar is just the invitation — Select all, on its own.
   The count, Clear and Download appear with the first frame chosen. */
.pickbar.none .pickbar-n,
.pickbar.none .pickbar-clear,
.pickbar.none .pickbar-web,
.pickbar.none .pickbar-del,
.pickbar.none .pickbar-go { display: none; }
.pickbar-n { font-family: var(--data); font-size: 11px; letter-spacing: .14em;
             text-transform: uppercase; color: var(--paper); white-space: nowrap; }
.pickbar button {
  padding: 8px 14px; cursor: pointer; white-space: nowrap;
  font-family: var(--data); font-size: 10px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--paper); background: none;
  border: 1px solid var(--line-2);
  transition: color .15s, background .15s, border-color .15s;
}
.pickbar button:hover { color: var(--ink); background: var(--paper);
                        border-color: var(--paper); }
.pickbar .pickbar-go { color: var(--ink); background: var(--go); border-color: var(--go); }
.pickbar .pickbar-go:hover { background: transparent; color: var(--go); }
.pickbar .pickbar-web { color: var(--go); border-color: var(--go); }
.pickbar .pickbar-web:hover { color: var(--ink); background: var(--go); }
/* Destructive, and dressed as such — it is the only red on the site. */
.pickbar .pickbar-del { color: #C97A6C; border-color: #6E332A; }
.pickbar .pickbar-del:hover { color: #fff; background: #8E3B2F; border-color: #8E3B2F; }

@media (max-width: 620px) {
  .pickbar { bottom: 10px; width: auto; margin: 18px 10px 6px;
             flex-wrap: wrap; justify-content: center; gap: 8px; }
  .pickbar-n { width: 100%; text-align: center; }
}

/* ── The share card ───────────────────────────────────────────────
   A QR code is read by a camera: dark on white, with its quiet zone, at a size
   that survives being held up across a table or printed and left on one. The
   code carries a viewBox, so every size here is the same code, sharp. */

.qr-dialog { padding: 0; border: 1px solid var(--line-2); background: var(--ink-2);
             color: var(--paper); max-width: min(440px, 92vw); }
.qr-dialog::backdrop { background: rgba(8, 7, 6, .8); }
.qr-card { padding: 22px 22px 16px; background: #fff; text-align: center; }
.qr-card svg { display: block; width: min(320px, 62vw); height: auto; margin: 0 auto; }
.qr-name { margin: 14px 0 0; font-family: var(--display); font-size: 19px; color: #0C0B0A; }
.qr-url { margin: 6px 0 0; font-family: var(--data); font-size: 11.5px;
          color: #5C554E; word-break: break-all; }
.qr-actions { display: flex; gap: 8px; justify-content: flex-end; padding: 12px 14px; }
.qr-actions button, .qr-actions .qr-dl {
  padding: 8px 16px; cursor: pointer; font-family: var(--data); font-size: 10px;
  letter-spacing: .16em; text-transform: uppercase; color: var(--paper);
  background: none; border: 1px solid var(--line-2);
}
.qr-actions button:hover, .qr-actions .qr-dl:hover {
  color: var(--ink); background: var(--paper); border-color: var(--paper); }

/* Printing an open code prints the code, not the console behind it. */
@media print {
  body.admin > *:not(.qr-dialog) { display: none !important; }
  .qr-dialog { display: block; position: static; max-width: none; border: 0;
               background: #fff; }
  .qr-dialog:not([open]) { display: none; }
  .qr-actions { display: none; }
  .qr-card svg { width: 74mm; }
}

/* ── Admin ────────────────────────────────────────────────────────
   Cards rather than a list, because each one carries a number worth seeing
   at a glance. */

.admin-grid { display: grid; gap: clamp(10px, 1.6vw, 16px);
              grid-template-columns: repeat(auto-fill, minmax(min(240px, 100%), 1fr)); }
.admin-card {
  display: block; padding: 22px 20px 20px;
  border: 1px solid var(--line); background: var(--ink-2);
  transition: border-color .16s, background .16s, transform .16s;
}
.admin-card:hover { border-color: var(--line-2); background: #17140F;
                    transform: translateY(-2px); }
.admin-card .k { display: block; font-family: var(--data); font-size: 10px;
                 letter-spacing: .2em; text-transform: uppercase; color: var(--faint); }
.admin-card .v { display: block; margin-top: 12px; font-family: var(--display);
                 font-size: 25px; line-height: 1.05; color: var(--paper);
                 font-variation-settings: "SOFT" 20, "opsz" 28; }
.admin-card .d { display: block; margin-top: 8px; font-size: 13px; color: var(--muted); }

/* Each card is a door; say so. Without this they read as statistics and
   nobody clicks them. */
.admin-card { cursor: pointer; }
.admin-card .go { display: block; margin-top: 16px; padding-top: 14px;
                  border-top: 1px solid var(--line);
                  font-family: var(--data); font-size: 10.5px;
                  letter-spacing: .14em; text-transform: uppercase;
                  color: var(--muted); transition: color .16s; }
.admin-card:hover .go { color: var(--paper); }

/* ════════════════════════════════════════════════════════════════
   THE CONSOLE
   A place for running the site, not a page of it. No gallery menu — those
   are already one click away on the public site and only add noise here.
   A rail of sections on the left, the work on the right.
   ════════════════════════════════════════════════════════════════ */

body.admin { background: #080807; }

.admin-shell { display: grid; grid-template-columns: 232px minmax(0, 1fr);
               min-height: 100vh; min-height: 100dvh; }

.rail {
  position: sticky; top: 0; align-self: start;
  height: 100vh; height: 100dvh;
  display: flex; flex-direction: column;
  padding: 26px 20px 22px;
  background: var(--ink-2); border-right: 1px solid var(--line);
}
.rail-sig { display: block; width: 150px; margin: 0 0 34px 2px; opacity: .9; }
.rail-sig img { width: 100%; height: auto; }

.rail-nav { display: flex; flex-direction: column; gap: 2px; }
.rail-nav a {
  display: block; padding: 10px 12px;
  font-family: var(--data); font-size: 11px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--muted);
  border-left: 2px solid transparent;
  transition: color .15s, background .15s, border-color .15s;
}
.rail-nav a:hover { color: var(--paper); background: rgba(239,234,226,.04); }
.rail-nav a[aria-current="page"] {
  color: var(--paper); background: rgba(239,234,226,.07);
  border-left-color: var(--paper);
}

.rail-foot { margin-top: auto; display: flex; flex-direction: column; gap: 10px;
             padding-top: 22px; border-top: 1px solid var(--line); }
.rail-foot a { font-family: var(--data); font-size: 10px; letter-spacing: .16em;
               text-transform: uppercase; color: var(--faint); }
.rail-foot a:hover { color: var(--paper); }

.admin-main { padding: clamp(26px, 3.5vw, 52px) clamp(20px, 3.5vw, 54px) 80px;
              min-width: 0; }
.admin-main .wrap { max-width: none; }
.admin-main .page-head { padding-top: 0; }
.admin-main footer { display: none; }

/* On a phone the rail becomes a strip across the top. */
@media (max-width: 820px) {
  .admin-shell { grid-template-columns: 1fr; }
  .rail { position: static; height: auto; flex-direction: row; align-items: center;
          gap: 14px; padding: 14px 16px; border-right: 0;
          border-bottom: 1px solid var(--line); overflow-x: auto; }
  .rail::-webkit-scrollbar { display: none; }
  .rail-sig { width: 108px; margin: 0 8px 0 0; flex: 0 0 auto; }
  .rail-nav { flex-direction: row; gap: 4px; flex: 0 0 auto; }
  .rail-nav a { border-left: 0; border-bottom: 2px solid transparent; padding: 8px 10px; }
  .rail-nav a[aria-current="page"] { border-left-color: transparent;
                                     border-bottom-color: var(--paper); }
  .rail-foot { margin: 0 0 0 auto; flex-direction: row; gap: 12px;
               padding-top: 0; border-top: 0; flex: 0 0 auto; }
  .admin-main { padding: 22px 16px 60px; }
}
