
  :root{
    --bg:#0e0e10;
    --bg-2:#161618;
    --fg:#e9e7e3;
    --fg-2:#a7a49e;
    --fg-3:#6f6c67;
    --line:#26262a;
    --accent:#9db08c;
    --serif:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
    --sans:"Helvetica Neue",Inter,system-ui,-apple-system,"Segoe UI",sans-serif;
  }
  *{box-sizing:border-box}
  html,body{margin:0;padding:0}
  body{background:var(--bg);color:var(--fg);font-family:var(--sans);-webkit-font-smoothing:antialiased}
  img{display:block;max-width:100%}
  button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}

  header{border-bottom:1px solid var(--line);position:sticky;top:0;z-index:40;background:rgba(14,14,16,.92);backdrop-filter:blur(10px)}
  .bar{max-width:1400px;margin:0 auto;padding:18px 30px;display:flex;align-items:center;gap:30px}
  .name{font-family:var(--serif);font-size:19px;letter-spacing:.01em;cursor:pointer;font-weight:400}
  nav{margin-left:auto;display:flex;gap:24px}
  nav button{font-size:12px;letter-spacing:.1em;text-transform:uppercase;color:var(--fg-3);padding:4px 0;border-bottom:1px solid transparent}
  nav button:hover{color:var(--fg)}
  nav button.on{color:var(--fg);border-bottom-color:var(--accent)}
  /* Desktop never sees this -- only the row-of-links nav above. Below the breakpoint
     (see the mobile block near the end of this stylesheet) that row becomes a dropdown
     and this replaces it in the bar. */
  #findBar{display:none;border-top:1px solid var(--line)}
  #findBar.on{display:block}
  #findBar .inner{max-width:1400px;margin:0 auto;padding:16px 30px 18px}
  #findBar input{display:block;width:100%;max-width:52ch;background:none;border:0;border-bottom:1px solid var(--fg-3);
    color:var(--fg);font:inherit;font-size:18px;padding:8px 0;outline:none;border-radius:0;-webkit-appearance:none;appearance:none}
  #findBar input::placeholder{color:var(--fg-3)}
  .gres{display:none;padding:10px 0 4px;max-height:60vh;overflow:auto}
  .gres.on{display:flex;flex-wrap:wrap;gap:6px 40px;align-items:flex-start}
  .gres section{min-width:220px}
  .gres h4{font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--fg-3);font-weight:400;margin:8px 0 4px}
  .gres button{display:block;text-align:left;padding:5px 0;font-size:15px;color:var(--fg-2)}
  .gres button:hover{color:var(--fg)} .gres button i{font-family:var(--serif);color:var(--fg-3);margin-left:8px}
  .gres p{margin:8px 0;font-size:14px;color:var(--fg-3)}
  @media(max-width:620px){#findBar .inner{padding:12px 16px 14px} #findBar input{font-size:16px} .gres.on{gap:4px 24px}}
  .hamburger{display:none;margin-left:auto;background:none;border:0;color:var(--fg);
    font-size:22px;line-height:1;cursor:pointer;padding:4px 6px}
  /* the serif name plus four nav items will not fit one phone line — give the nav its own */
  @media(max-width:620px){
    .bar{flex-wrap:wrap;gap:8px 18px;padding:14px 20px}
    .name{font-size:17px}
    nav{margin-left:0;width:100%;gap:18px}
  }

  main{max-width:1400px;margin:0 auto;padding:0 30px 20px}
  .view{display:none}
  .view.on{display:block}

  /* ---------- opening carousel ----------
     Breaks out of main's max-width to sit edge to edge. Frames are stacked and
     cross-faded rather than slid, so nothing moves under the cursor and there is no
     layout shift. */
  /* 3:2 matches the camera, so a landscape frame is shown practically whole. The earlier
     fixed height made a far wider box than the photographs and sliced the top and bottom
     off everything. max-height keeps it from swallowing the screen on a wide monitor. */
  /* Full width, and 3:2 to match the camera — so the photograph fills it exactly, with
     no bars and nothing cropped. There is deliberately no max-height: capping the height
     makes the box wider than 3:2, and the picture then letterboxes inside it. */
  .hero{position:relative;margin-left:calc(50% - 50vw);width:100vw;
    aspect-ratio:3/2;background:var(--bg-2);overflow:hidden;
    cursor:grab;touch-action:pan-y;user-select:none}
  .hero:active{cursor:grabbing}
  .hero img{-webkit-user-drag:none;user-select:none}
  .hero figure{position:absolute;inset:0;margin:0;opacity:0;transition:opacity 1.1s ease-in-out}
  .hero figure.on{opacity:1}
  /* Whole photograph, never cropped: it is contained inside the frame, and a blurred,
     enlarged copy of itself fills the space either side. That way a portrait and a
     landscape can sit in the same carousel without one of them being sliced up. */
  .hero .back{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
    filter:blur(34px) saturate(.8) brightness(.45);transform:scale(1.12)}
  /* Top-aligned, so when a photograph does not fill the frame the leftover space falls
     below it rather than being split. A band above the picture reads as a mistake; a
     band beneath it reads as the page continuing. */
  .hero .fore{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;
    object-position:center top}
  /* a soft floor so the dots and the page beneath it never fight the picture */
  .hero .veil{position:absolute;inset:auto 0 0 0;height:38%;pointer-events:none;
    background:linear-gradient(to bottom,rgba(14,14,16,0),rgba(14,14,16,.85))}
  .hero .dots{position:absolute;left:0;right:0;bottom:16px;display:flex;gap:4px;
    justify-content:center;z-index:2}
  /* The dot is small but the button around it is not — 26px square is a comfortable
     target for a fingertip, which a 2px dash was never going to be. */
  .hero .dots button{width:26px;height:26px;padding:0;background:none;
    display:flex;align-items:center;justify-content:center}
  .hero .dots button::after{content:"";width:9px;height:9px;border-radius:50%;
    background:rgba(255,255,255,.38);transition:background .3s,transform .3s}
  .hero .dots button.on::after{background:rgba(255,255,255,.95);transform:scale(1.15)}
  .hero .dots button:hover::after{background:rgba(255,255,255,.75)}
  @media(max-width:620px){.hero .dots{bottom:10px}}
  /* A museum label in the corner: what the photograph on screen is. Small on purpose —
     it names the picture without asking anyone to read, and the veil beneath keeps it
     legible over any image. Click opens the photograph. */
  .hero .label{position:absolute;left:28px;bottom:40px;z-index:2;max-width:60%;
    text-shadow:0 1px 3px rgba(0,0,0,.7);cursor:pointer;text-align:left}
  .hero .label .n{font-family:var(--serif);font-size:17px;color:#fff;line-height:1.3}
  .hero .label .bi{font-family:var(--serif);font-style:italic;font-size:14px;color:rgba(255,255,255,.72);margin-top:2px}
  .hero .label .loc{font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:rgba(255,255,255,.55);margin-top:5px}
  @media(max-width:620px){.hero .label{left:16px;bottom:36px;max-width:72%}
    .hero .label .n{font-size:15px} .hero .label .bi{font-size:13px}}
  /* On a phone the screen is tall, so a landscape frame throws most of it away. When the
     Vertical Carousel folder has photographs, the phone gets its own set in a 2:3 frame —
     the shape the camera shoots portrait — and it fills the screen properly. */
  .hero.tall{aspect-ratio:2/3}
  @media(prefers-reduced-motion:reduce){.hero figure{transition:none}}

  .lede{padding:26px 0 14px;max-width:60ch;font-family:var(--serif);font-size:19px;line-height:1.68;color:var(--fg-2)}
  .sub{padding-bottom:10px;max-width:88ch;font-size:13px;line-height:1.6;color:var(--fg-3)}
  .sechead{font-size:11px;letter-spacing:.2em;text-transform:uppercase;color:var(--fg-3);font-weight:600;
    border-bottom:1px solid var(--line);padding-bottom:11px;margin:44px 0 28px}

  /* ---------- album covers (used by both axes) ---------- */
  .albums{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:36px 26px}
  .album{cursor:pointer;text-align:left;padding:0;display:block;width:100%}
  .album .fr{background:var(--bg-2);overflow:hidden;aspect-ratio:4/3}
  .album img{width:100%;height:100%;object-fit:cover;transition:opacity .3s}
  .album:hover img{opacity:.82}
  .album .t{display:flex;align-items:baseline;gap:10px;padding:12px 1px 0}
  .album .t b{font-weight:400;font-size:17px;font-family:var(--serif)}
  /* "All selected" is the main way in, not a sibling of the subject albums — so it takes
     two columns to their one and leads the row. The subject albums then read as ways to
     narrow it down rather than as six equal choices. */
  /* Two columns wide and two rows tall, so it occupies a clean block and the albums flow
     around it. The frame stretches to fill rather than holding 4:3 — at double width a
     4:3 frame is also double height, which leaves the row ragged. */
  .whole .fr{display:grid;grid-template-columns:1fr 1fr;grid-template-rows:1fr 1fr;gap:3px}
  /* min-height:0 matters: without it a grid item refuses to shrink below its content,
     the rows grow to the images' intrinsic height, and the frame stops being 4:3 — so
     the card ends up taller than the albums beside it. */
  .whole .fr img{width:100%;height:100%;min-width:0;min-height:0;object-fit:cover}
  .whole .t b{color:var(--fg)}
  .whole .t span{font-size:11px;letter-spacing:.1em;text-transform:uppercase;
    color:var(--fg-3);margin-left:auto}
  /* The main way in. On a page made almost entirely of photographs, a line of type is
     what catches the eye — it stands out by not being another picture, and it costs no
     layout whatever the catalogue grows to. */
  .bigline{display:block;font-family:var(--serif);font-size:30px;line-height:1.3;
    color:var(--fg);padding:14px 0 0;text-align:left}
  .bigline span{border-bottom:1px solid var(--accent);padding-bottom:5px;
    transition:border-color .2s}
  .bigline:hover span{border-bottom-color:var(--fg)}
  @media(max-width:620px){.bigline{font-size:23px;padding-top:26px}}

  /* ---------- Selected: filter pills + masonry wall ---------- */
  /* Title/subtitle and pills share one row -- pills top-right -- rather than stacking,
     so the wall itself starts higher up the page instead of sitting under two full
     header blocks. */
  .selectedHead{display:flex;justify-content:space-between;align-items:flex-end;
    flex-wrap:wrap;gap:14px 24px;margin-bottom:24px}
  .selectedHead .listhead{padding-bottom:0}
  .pills{display:flex;gap:8px;flex-wrap:wrap;margin:0}
  .pill{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:var(--fg-3);
    border:1px solid var(--line);padding:8px 16px;border-radius:100px}
  .pill.on{background:var(--fg);color:var(--bg);border-color:var(--fg)}
  .pill:hover:not(.on){color:var(--fg);border-color:var(--fg-3)}
  /* Flex columns, not CSS multi-column: multi-column fills one column completely before
     starting the next, so a date-sorted list reads newest-to-oldest down column 1, then
     jumps back to a much older run at the top of column 2 -- confusing when the whole
     point of the order is newest-first. renderMasonry() deals the list round-robin across
     these columns instead, so a row read left-to-right stays close in date, same as the
     list itself. */
  /* Full-bleed, same trick as .hero -- breaks out of main's centred reading-width column
     to the true viewport edge, so the wall reads as a wall of photography rather than a
     list boxed into a text column. Title and pills above stay in the normal column;
     only the tiles themselves go edge to edge. */
  .masonry{margin-left:calc(50% - 50vw);width:100vw;padding:0 40px;
    display:flex;gap:18px;align-items:flex-start}
  .masonry .col{display:flex;flex-direction:column;gap:18px;flex:1;min-width:0}
  /* Each tile fades and lifts in the first time it crosses into view (see
     observeReveal()), rather than the whole wall just being there on load -- a wall of a
     few hundred photos discovered a row at a time reads as considered, not as a long
     list. Skipped for anyone who's asked for less motion, and .in is added straight
     away in that case so nothing stays invisible. */
  .masonry .item{cursor:pointer;position:relative;background:var(--bg-2);
    opacity:0;transform:translateY(26px);transition:opacity .7s ease,transform .7s ease}
  .masonry .item.in{opacity:1;transform:none}
  @media(prefers-reduced-motion:reduce){.masonry .item{opacity:1;transform:none;transition:none}}
  .masonry .item img{width:100%;display:block;transition:opacity .2s}
  .masonry .item:hover img{opacity:.85}
  .masonry .item .cap{position:absolute;left:0;right:0;bottom:0;padding:20px 12px 10px;
    font-size:12px;color:#fff;background:linear-gradient(to top,rgba(0,0,0,.75),transparent);
    opacity:0;transition:opacity .2s}
  .masonry .item:hover .cap{opacity:1}

  /* ---------- Places: country/state tiles + continent clusters ---------- */
  .continent{font-family:var(--sans);font-weight:600;font-size:11px;letter-spacing:.16em;
    text-transform:uppercase;color:var(--fg-3);margin:38px 0 18px}
  .continent:first-child{margin-top:0}
  .placeBack{font-size:12px;letter-spacing:.1em;text-transform:uppercase;color:var(--fg-3);
    padding:0 0 22px;display:block}
  .placeBack:hover{color:var(--fg)}
  .detailHead{display:flex;align-items:baseline;gap:14px;border-bottom:1px solid var(--line);
    padding-bottom:16px;margin-bottom:28px;flex-wrap:wrap}
  .detailHead h2{font-family:var(--serif);font-weight:400;font-size:26px;margin:0}
  .detailHead .n{font-size:11.5px;letter-spacing:.08em;text-transform:uppercase;
    color:var(--fg-3);margin-left:auto}
  .photoGrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(128px,1fr));gap:16px 12px}
  .photoCell{cursor:pointer}
  .photoCell .fr{aspect-ratio:1/1;background:var(--bg-2);overflow:hidden}
  .photoCell img{width:100%;height:100%;object-fit:cover;transition:opacity .2s}
  .photoCell:hover img{opacity:.8}

  /* ---------- Species: category collapse + expandable rows ---------- */
  .catHead{display:flex;align-items:center;gap:12px;width:100%;text-align:left;
    padding:22px 0;border-top:1px solid var(--line);cursor:pointer}
  .catHead:first-child{border-top:0}
  .catHead h2{font-family:var(--serif);font-weight:400;font-size:24px;margin:0;color:var(--fg)}
  .catHead .n{font-size:11.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--fg-3);
    margin-left:auto;white-space:nowrap}
  .catHead .chev{font-size:13px;color:var(--fg-3);transition:transform .15s}
  .catHead.open .chev{transform:rotate(90deg)}
  .catBody{display:none;padding-bottom:14px}
  .catBody.on{display:block}
  .expand{display:none;grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:12px;
    padding:4px 2px 20px;border-bottom:1px solid var(--line)}
  .expand.on{display:grid}
  .expand button{padding:0;display:block;width:100%;text-align:left}
  .expand .fr{aspect-ratio:1/1;background:var(--bg-2);overflow:hidden}
  .expand img{width:100%;height:100%;object-fit:cover}
  .expand .cap{font-size:10px;color:var(--fg-3);padding-top:5px;text-align:center;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

  /* ---------- Catalogue: every photograph, dated, newest first ---------- */
  /* Same square-thumbnail language as .expand, but always on -- this page has nothing to
     collapse, since date is the one axis nobody browses by expanding a row. Deliberately
     not the Selected masonry: that page shows off a photograph's own shape, this one is
     an index, so a flat grid that scans evenly matters more than any single frame.
     The caption is the masonry's own hover-overlay treatment, not .expand's always-on
     line -- a place's name repeated under every one of its own thumbnails added nothing,
     and a title needs room .expand's fixed one-line caption doesn't give it. Reserving
     that room only on hover keeps the grid itself dense and quiet at rest. */
  .dateGrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(130px,1fr));gap:3px;
    padding-bottom:8px}
  .dateGrid button{padding:0;display:block;width:100%;text-align:left;position:relative;
    cursor:pointer;background:var(--bg-2);overflow:hidden}
  .dateGrid .fr{aspect-ratio:1/1;background:var(--bg-2);overflow:hidden}
  .dateGrid img{width:100%;height:100%;object-fit:cover;display:block;transition:opacity .2s}
  .dateGrid button:hover img{opacity:.85}
  .dateGrid .cap{position:absolute;left:0;right:0;bottom:0;padding:20px 10px 8px;
    font-family:var(--serif);font-size:12.5px;line-height:1.3;color:var(--fg);
    background:linear-gradient(to top,rgba(6,6,7,.92),transparent);opacity:0;transition:opacity .22s}
  .dateGrid button:hover .cap{opacity:1}
  .monthBlock{margin-bottom:48px}
  /* A place sits between month and photograph -- serif italic marks it as a named thing
     rather than a structural label, the same way .bi already does for a Latin name under
     a heading, so it reads as content and not as a second, competing section header. */
  .placeBlock{margin-top:26px}
  .placeBlock:first-of-type{margin-top:16px}
  .placeHead{font-family:var(--serif);font-style:italic;font-size:15px;color:var(--fg-2);
    margin:0 0 12px}
  .placeHead .n{font-family:var(--sans);font-style:normal;font-size:11px;color:var(--fg-3);margin-left:8px}

  .back{font-size:12px;letter-spacing:.1em;text-transform:uppercase;color:var(--fg-3);padding:34px 0 20px;display:block}
  .back:hover{color:var(--fg)}

  /* ---------- detail ---------- */
  /* Tried letting the panel itself absorb whatever width a capped (see .plate img below)
     portrait photo didn't use -- technically no more dead space, but a caption panel
     that's a different width on every single photograph, ballooning wide next to a
     small portrait image, isn't actually better: it makes the panel the visually
     dominant thing on a page whose entire point is the photograph. Better: the panel
     stays one consistent width always (see .panel below), and .plateWrap -- a flex
     container around .plate -- takes the rest of the row and centers .plate within it,
     so a narrower photo gets breathing room on both sides instead of a lopsided gap on
     one. */
  .detail{display:flex;flex-wrap:wrap;gap:50px;align-items:flex-start}
  .plateWrap{flex:1 1 0;min-width:0;display:flex;justify-content:center}
  /* width:fit-content so the plate's own box hugs whatever width the image actually
     renders at -- it's what .plateWrap is centering, not just a background box. */
  .plate{position:relative;cursor:zoom-in;background:var(--bg-2);width:fit-content;max-width:100%}
  /* A portrait frame at full column width can run well past a screen's height, forcing a
     long scroll just to see the whole photograph. Capping height and letting width follow
     keeps a portrait photo's whole frame closer to one screen; a landscape frame is wide
     enough that max-width stays the binding constraint here regardless, so it renders
     exactly as before. Mobile reverts to full-width -- a narrow viewport doesn't have the
     same excess-height problem, and edge-to-edge is the more natural feel there. */
  .plate img{display:block;width:auto;max-width:100%;height:auto;max-height:78vh}
  .plate .expand{position:absolute;right:12px;bottom:12px;font-size:11px;letter-spacing:.1em;
    text-transform:uppercase;background:rgba(10,10,12,.72);padding:8px 13px;color:#fff;opacity:0;
    transition:opacity .2s;backdrop-filter:blur(6px)}
  .plate:hover .expand{opacity:1}
  /* Step through the run without going back to the grid. Hidden until the cursor is on
     the picture so they never sit on top of a photograph nobody is looking at. */
  .pnav{position:absolute;top:50%;transform:translateY(-50%);width:44px;height:44px;
    display:flex;align-items:center;justify-content:center;font-size:17px;color:#fff;
    background:rgba(10,10,12,.55);backdrop-filter:blur(6px);opacity:0;
    transition:opacity .2s,background .2s}
  .pnav.l{left:14px} .pnav.r{right:14px}
  .plate:hover .pnav{opacity:1}
  .pnav:hover{background:rgba(10,10,12,.85)}
  /* a touch screen has no hover, so show them there rather than hiding them for good */
  @media(hover:none){.pnav{opacity:1}}
  /* A fixed-ish width rather than flex:1 -- see .detail above for why: the panel should
     read the same on every photograph, not balloon wider next to a smaller image. Still
     allowed to shrink (never grow) between the mobile breakpoint and a narrow desktop
     window, down to the same floor the old grid track had. */
  .panel{flex:0 1 420px;min-width:330px;position:sticky;top:100px}
  .panel .crumb{font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--fg-3);margin-bottom:10px;
    display:flex;gap:8px;flex-wrap:wrap;align-items:center}
  .panel .crumb button{color:var(--fg-3);letter-spacing:.12em;text-transform:uppercase;font-size:11px;
    border-bottom:1px solid var(--line);padding-bottom:2px}
  .panel .crumb button:hover{color:var(--fg)}
  .panel h1{font-family:var(--serif);font-size:31px;font-weight:400;margin:0 0 3px;letter-spacing:-.01em;line-height:1.15}
  .panel .bi{font-family:var(--serif);font-style:italic;color:var(--fg-3);font-size:18px;margin:0 0 24px}
  /* capped independently of .panel's own width -- the panel can grow a good deal wider
     than this now that it absorbs whatever the plate doesn't use, but a comfortable
     reading measure doesn't get any wider just because there's room for it to */
  .prose{font-family:var(--serif);font-size:16.5px;line-height:1.75;color:var(--fg-2);max-width:56ch}
  .prose p{margin:0 0 16px}
  .meta{margin:28px 0 0;border-top:1px solid var(--line)}
  .meta div{display:flex;gap:18px;padding:10px 0;border-bottom:1px solid var(--line);font-size:12.5px;color:var(--fg-2)}
  .meta div b{flex:0 0 96px;font-weight:500;color:var(--fg-3);text-transform:uppercase;letter-spacing:.09em;font-size:10.5px;padding-top:2px}
  .pager{display:flex;justify-content:space-between;margin-top:54px;padding-top:20px;border-top:1px solid var(--line);
    font-size:12px;letter-spacing:.09em;text-transform:uppercase;color:var(--fg-3);gap:20px}
  .pager button:hover{color:var(--fg)}

  /* ---------- catalogue ---------- */
  .grouphead{font-size:11px;letter-spacing:.2em;text-transform:uppercase;color:var(--fg-3);
    font-weight:600;margin:34px 0 0;padding-bottom:9px}
  .grouphead:first-child{margin-top:0}
  .listhead{padding:46px 0 30px;max-width:58ch}
  .listhead h1{font-family:var(--serif);font-size:30px;font-weight:400;margin:0 0 10px}
  .listhead p{margin:0;font-family:var(--serif);font-size:16.5px;line-height:1.7;color:var(--fg-2)}
  .inlineLink{font:inherit;color:var(--fg);border-bottom:1px solid var(--accent);
    background:none;border-top:0;border-left:0;border-right:0;padding:0;margin:0;
    cursor:pointer;display:inline}
  .inlineLink:hover{color:var(--accent)}
  /* Wider than the other listheads (58ch): a photo column beside the text needs the room. */
  #v-about .listhead{max-width:96ch}
  #v-catalogue .listhead{max-width:70ch}
  /* A real two-column row for the photo and the opening paragraphs (before the first ##
     heading) -- not a CSS float, which only holds a fixed shape for as long as the text
     beside it is taller than the image, then drops everything after to full width
     regardless of how that reads. A flex row keeps the photo and the intro text as one
     deliberate block whatever length the text ends up being; sections after the first
     heading (How this is made, etc.) go back to full width below, in #aboutCopy as
     before. Hidden by default: the photo is optional, and JS only reveals it once
     DATA.aboutPhoto actually exists. Photo on the right, order:1 vs #aboutIntro's
     implicit order:0, rather than swapping them in the markup/JS. */
  .aboutTop{display:flex;gap:32px;align-items:flex-start;margin-bottom:8px}
  #aboutPhoto{order:1;width:320px;flex:0 0 320px;height:auto;background:var(--bg-2)}
  #aboutIntro{flex:1;min-width:0}
  #aboutIntro p{margin:0 0 17px}
  @media(max-width:760px){
    .aboutTop{flex-direction:column;gap:0}
    #aboutPhoto{order:0;width:100%;max-width:320px;flex-basis:auto;margin-bottom:20px}
  }
  #aboutCopy p{margin:0 0 17px}
  #aboutCopy a{color:var(--fg);border-bottom:1px solid var(--accent)}
  #aboutCopy a:hover{color:var(--accent)}
  #aboutCopy h2{font-size:11px;letter-spacing:.2em;text-transform:uppercase;color:var(--fg-3);
    font-weight:600;margin:34px 0 12px}
  /* Deliberately quiet: a line of type and a field, not a call to action. Posts straight
     to Buttondown, so there is no server here and nothing to keep running. */
  /* One shared footer instead of a signup block repeated (and separately styled) on the
     home page and again at the end of About -- every page ends at this same place now,
     #lb (a fixed full-screen overlay outside this flow) excepted. */
  #siteFooter{border-top:1px solid var(--line);margin-top:56px;padding:36px 0 60px}
  .notFound{padding:18px 0 0;color:var(--fg-3);font-size:14px}
  .catalogueLink{font-size:12px;letter-spacing:.1em;text-transform:uppercase;color:var(--fg-3);
    display:block;margin-bottom:26px}
  .catalogueLink:hover{color:var(--fg)}
  .signup{max-width:48ch}
  .signup p{font-size:14px;line-height:1.6;color:var(--fg-2);margin:0 0 16px;font-family:var(--sans)}
  .signup form{display:flex;gap:10px;flex-wrap:wrap}
  .signup input[type=email]{flex:1;min-width:220px;background:var(--bg-2);border:1px solid var(--line);
    color:var(--fg);font:inherit;font-size:15px;padding:14px 16px}
  .signup input[type=email]:focus{outline:none;border-color:var(--accent)}
  /* Filled, not the bordered ghost style everything else on the page uses -- the one
     place the site actually wants a click, so it gets the one solid button, matching
     the weight a selected filter pill carries elsewhere (.pill.on). */
  .signup button{font-size:12.5px;letter-spacing:.1em;text-transform:uppercase;
    background:var(--fg);color:var(--bg);border:1px solid var(--fg);padding:14px 26px;
    transition:opacity .2s}
  .signup button:hover{opacity:.85}
  /* The two runs of the catalogue. A serif heading, larger than a group name, so the
     split reads as structural rather than as just another category. */
  .runhead{font-family:var(--serif);font-weight:400;font-size:22px;color:var(--fg);
    margin:52px 0 2px;padding-bottom:0}
  .runhead:first-child{margin-top:8px}
  .rows{border-top:1px solid var(--line)}
  /* Fixed column widths, not 1fr/auto: every row is its own grid, so an `auto` column sizes
     to that row's own place name and the Latin column then starts somewhere different on
     each line. Naming the widths makes the three columns line up down the whole list. */
  .row{display:grid;grid-template-columns:56px minmax(0,24em) minmax(0,1fr) minmax(0,13em);
    gap:18px;align-items:center;padding:10px 2px;
    border-bottom:1px solid var(--line);cursor:pointer;width:100%;text-align:left}
  .row:hover{background:var(--bg-2)}
  .row .a{font-size:15px}
  .row .b{font-family:var(--serif);font-style:italic;color:var(--fg-3);font-size:14px}
  .row .c{font-size:11px;letter-spacing:.09em;text-transform:uppercase;color:var(--fg-3);
    text-align:right}
  /* the newest photograph of the species, so the list reads as pictures with names beside
     them rather than a filing cabinet (feedback, September 2026) */
  .row .rt{width:56px;height:42px;object-fit:cover;border-radius:2px;background:var(--bg-2);display:block}
  @media(max-width:700px){.row{grid-template-columns:48px 1fr auto}.row .b{display:none}
    .row .rt{width:48px;height:36px}}
  /* find-as-you-type on Species and Places. Deliberately quiet -- a line, not a box --
     since most visitors browse; it's there for "I want the Dolomites" without a taxonomy. */
  .find{display:block;margin-top:14px;background:none;border:0;border-bottom:1px solid var(--line);
    color:var(--fg);font:inherit;font-size:14px;padding:6px 0;width:24ch;outline:none;border-radius:0;
    -webkit-appearance:none;appearance:none}
  .find::placeholder{color:var(--fg-3)} .find:focus{border-bottom-color:var(--fg-3)}
  .hid{display:none!important}
  /* Catalogue filters: three quiet selects, styled like the find line, that narrow each
     other (a place chosen limits the species offered, and so on). */
  /* the filters sit to the right of the page title and intro, on the same band, so the
     content starts sooner; on a narrow screen they wrap beneath */
  .pagehead{display:flex;align-items:flex-end;justify-content:space-between;gap:20px 40px;flex-wrap:wrap}
  .pagehead .listhead{padding-bottom:26px}
  .filters{display:flex;flex-wrap:wrap;align-items:flex-end;gap:12px 30px;padding:4px 0 26px}
  .filters label{display:flex;flex-direction:column;gap:5px;font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--fg-3)}
  .filters select{background:none;border:0;border-bottom:1px solid var(--line);color:var(--fg);font:inherit;font-size:14px;
    padding:6px 22px 6px 0;min-width:12ch;max-width:28ch;outline:none;border-radius:0;cursor:pointer;
    -webkit-appearance:none;appearance:none;text-overflow:ellipsis;
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%236f6c67' stroke-width='1.4'/%3E%3C/svg%3E");
    background-repeat:no-repeat;background-position:right 4px center}
  .filters select:focus{border-bottom-color:var(--fg-3)}
  .filters select.set{color:var(--fg);border-bottom-color:var(--fg-3)}
  .filters .sub{opacity:.85}
  .filters .clear{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:var(--fg-3);padding:7px 0;border-bottom:1px solid transparent}
  .filters .clear:hover{color:var(--fg);border-bottom-color:var(--fg-3)}
  .catEmpty{color:var(--fg-3);font-size:15px;padding:10px 0 60px}
  @media(max-width:620px){.filters{gap:12px 22px} .filters label{font-size:12px} .filters select{font-size:16px;max-width:60vw}}
  /* photo page: the other photographs from the same place on the same day */
  .more{margin-top:28px;padding-top:16px;border-top:1px solid var(--line)}
  .more h3{font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--fg-3);font-weight:400;margin:0 0 10px}
  .more .strip{display:grid;grid-template-columns:repeat(auto-fill,minmax(84px,1fr));gap:8px}
  .more .strip button{padding:0;display:block} .more .strip img{width:100%;aspect-ratio:1;object-fit:cover;border-radius:2px;display:block;transition:opacity .2s}
  .more .strip button:hover img{opacity:.8}
  /* species page: one line from the newest caption, and places as links */
  .spHead .lead{color:var(--fg);font-size:16px;line-height:1.6;margin:10px 0 0;max-width:62ch}
  .spHead .places button{color:var(--fg-2)} .spHead .places button:hover{color:var(--fg)}
  /* photo page crumb: Family › Species · Place */
  .crumb .sep{opacity:.4;margin:0 6px}
  .crumb .crumbLine{margin-bottom:6px}
  /* Species grid (September 2026): one tile per species, subject-cropped cover, name only.
     Latin name, count and places live on the species page a click away. */
  .sgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:26px 20px;margin:0 0 44px}
  .sp{cursor:pointer;text-align:left;padding:0;display:block;width:100%}
  .sp .fr{background:var(--bg-2);overflow:hidden;aspect-ratio:4/3;border-radius:2px}
  .sp img{width:100%;height:100%;object-fit:cover;transition:opacity .25s}
  .sp:hover img{opacity:.85}
  .sp .n{font-size:14px;margin:10px 0 0;color:var(--fg-2)} .sp:hover .n{color:var(--fg)}
  .jump{display:flex;flex-wrap:wrap;gap:6px 18px;padding:14px 0 6px}
  .jump button{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:var(--fg-3);padding:2px 0;border-bottom:1px solid transparent}
  .jump button:hover{color:var(--fg);border-bottom-color:var(--fg-3)}
  .grouphead.grid{margin:34px 0 14px;font-family:var(--sans);font-weight:600;font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--fg-3);scroll-margin-top:80px}
  .spHead{padding:8px 0 22px;max-width:70ch}
  .spHead h1{font-family:var(--serif);font-weight:400;font-size:30px;margin:0 0 4px}
  .spHead .bi{font-family:var(--serif);font-style:italic;color:var(--fg-2);font-size:17px;margin:0 0 6px}
  .spHead p{color:var(--fg-2);font-size:15px;line-height:1.6;margin:0}
  @media(max-width:700px){.sgrid{grid-template-columns:repeat(2,1fr);gap:16px 12px}}
  /* Updates: one seasonal collection at a time */
  .listhead.update{max-width:66ch}
  .listhead.update .season{font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--fg-3);margin:0 0 10px}
  .listhead.update p{font-size:16px;line-height:1.65;color:var(--fg);margin:0 0 14px}
  .updateFoot{font-size:13px;color:var(--fg-3);margin:30px 0 0;line-height:1.7}
  .album .t .n{font-size:11px;letter-spacing:.09em;text-transform:uppercase;color:var(--fg-3)}
  .listhead.update{padding-top:18px}

  /* an animal-group row's chevron and the thumbnail strip it reveals -- one photograph
     or several, every row opens the same way rather than some jumping straight to a
     photo and others not, see build.py's SPECIES_NAME for why this exists */
  .row .chev{display:inline-block;font-size:11px;color:var(--fg-3);margin-right:8px;
    vertical-align:middle;transition:transform .15s}
  .row.open .chev{transform:rotate(90deg)}
  .row .c.count{color:var(--accent)}
  .expand{display:none;grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:12px;
    padding:4px 2px 20px;border-bottom:1px solid var(--line)}
  .expand.on{display:grid}
  .expand button{padding:0;display:block;width:100%;text-align:left}
  .expand .fr{aspect-ratio:1/1;background:var(--bg-2);overflow:hidden}
  .expand img{width:100%;height:100%;object-fit:cover}
  .expand .cap{font-size:10px;color:var(--fg-3);padding-top:5px;text-align:center;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

  .flag{margin:0 0 30px;padding:12px 15px;border-left:2px solid var(--accent);background:#131316;
    font-size:12.5px;line-height:1.6;color:var(--fg-2);max-width:70ch}

  /* ---------- full screen viewer ---------- */
  /* dvh tracks iOS Safari's shrinking/growing toolbars; vh does not, and sizing to it
     puts the bottom of the picture behind the browser bar. The vh line stays as the
     fallback for anything that doesn't know dvh. */
  #lb{position:fixed;inset:0;height:100vh;height:100dvh;z-index:100;background:#000;display:none}
  #lb.on{display:block}
  /* nothing to press if the browser has no Fullscreen API — iPhone Safari has none */
  #lb.no-fs #lbFS{display:none}
  #lb .stage{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;overflow:hidden;
    touch-action:none}
  #lb .stage img{max-width:none;max-height:none;width:auto;height:auto;object-fit:contain;flex:none;
    transform-origin:center center;cursor:pointer;will-change:transform;
    /* without these the browser starts its own image drag and swallows the gesture */
    user-select:none;-webkit-user-select:none;-webkit-user-drag:none;touch-action:none}
  #lb.zoomed .stage img{cursor:grab}
  #lb.zoomed.panning .stage img{cursor:grabbing}
  #lb .zoomlvl{min-width:44px}
  #lb #lbFit{display:none}
  #lb.zoomed #lbFit{display:inline-block}
  #lb .stage img.eased{transition:transform .22s cubic-bezier(.2,.7,.2,1),width .22s cubic-bezier(.2,.7,.2,1),height .22s cubic-bezier(.2,.7,.2,1)}
  #lb.zoomed .arrow{pointer-events:none}
  #lb .ui{position:absolute;inset:0;pointer-events:none;opacity:1;transition:opacity .45s}
  #lb.idle .ui{opacity:0}
  #lb .ui > *{pointer-events:auto}
  #lb .top{position:absolute;top:0;left:0;right:0;z-index:3;display:flex;align-items:center;gap:16px;padding:18px 22px;
    background:linear-gradient(to bottom,rgba(0,0,0,.55),transparent)}
  #lb .count{font-size:12px;letter-spacing:.12em;color:rgba(255,255,255,.62)}
  #lb .zoomlvl{font-size:11px;letter-spacing:.1em;color:rgba(255,255,255,.62);display:none}
  #lb.zoomed .zoomlvl{display:inline}
  #lb .tools{margin-left:auto;display:flex;gap:6px}
  #lb .tools button{font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:rgba(255,255,255,.82);
    padding:8px 13px;border:1px solid rgba(255,255,255,.16);border-radius:2px;background:rgba(0,0,0,.3)}
  #lb .tools button:hover{background:rgba(255,255,255,.14)}
  #lb .tools button.on{background:rgba(255,255,255,.9);color:#000;border-color:transparent}
  /* Full height, not inset below the top bar: the stage centres the image on the whole
     viewport regardless of the top bar sitting over it, so an arrow only centred on the
     area below the bar drifted visibly below the image's real centre -- especially
     noticeable on a phone, where a landscape photo is letterboxed tall in a portrait
     screen. The top bar still wins any overlap: it has a higher z-index (3 vs 1), so
     its buttons stay clickable regardless of the arrow's box extending behind them. */
  #lb .arrow{position:absolute;top:0;bottom:0;z-index:1;width:23%;display:flex;align-items:center;border:0;background:none;
    color:rgba(255,255,255,0);font-size:36px;transition:color .2s}
  #lb .arrow:hover{color:rgba(255,255,255,.85)}
  #lb .arrow.l{left:0;justify-content:flex-start;padding-left:26px}
  #lb .arrow.r{right:0;justify-content:flex-end;padding-right:26px}
  /* :hover alone leaves both arrows invisible on a touchscreen -- worse, Safari mobile
     tends to stick a tapped element's :hover on until something else is tapped, so
     whichever arrow was last touched looked like the only one that existed. Same idiom
     as .pnav above: just show them, at rest, wherever there's no real hover to reveal
     them with. */
  @media(hover:none){#lb .arrow{color:rgba(255,255,255,.55)}}
  #lb .info{position:absolute;left:0;right:0;bottom:0;z-index:2;padding:64px 30px 26px;
    background:linear-gradient(to top,rgba(0,0,0,.86),rgba(0,0,0,.55) 55%,transparent);display:none}
  #lb.info-on .info{display:block}
  #lb .info .inner{max-width:820px}
  #lb .info h2{font-family:var(--serif);font-weight:400;font-size:24px;margin:0 0 2px;color:#fff}
  #lb .info .bi{font-family:var(--serif);font-style:italic;color:rgba(255,255,255,.55);font-size:15px;margin:0 0 12px}
  #lb .info p{font-family:var(--serif);font-size:15px;line-height:1.66;color:rgba(255,255,255,.84);margin:0 0 9px}
  #lb .info .sm{font-size:11.5px;letter-spacing:.06em;color:rgba(255,255,255,.5);font-family:var(--sans);margin-top:12px}
  @media(max-width:760px){
    #lb .info{padding:52px 20px 22px}
    .detail{flex-direction:column;gap:26px}
    .plateWrap{width:100%}
    .plate{width:100%;max-width:none}
    .plate img{width:100%;max-width:none;height:auto;max-height:none}
    .panel{flex:0 0 auto;width:100%;min-width:0;max-width:none;position:static}
    .bar{padding:14px 20px;gap:14px;position:relative}
    .hamburger{display:block}
    /* A dropdown, not a row: hidden until .open, then a full-width vertical panel right
       below the header. .bar has position:relative so this anchors to it rather than to
       whatever ancestor happens to be positioned. */
    nav{display:none;position:absolute;left:0;right:0;top:100%;flex-direction:column;
      gap:0;margin-left:0;background:var(--bg);border-bottom:1px solid var(--line);
      padding:6px 20px 14px}
    nav.open{display:flex}
    nav button{padding:13px 0;border-bottom:1px solid var(--line)}
    nav button:last-child{border-bottom:0}
    main{padding:0 20px 20px}
    .masonry{padding:0 20px;gap:10px}
    .masonry .col{gap:10px}
  }
