@import url('https://fonts.googleapis.com/css2?family=Libre+Franklin:wght@500;600;700;800&family=EB+Garamond:ital,wght@0,400;0,500;0,600;1,400&display=swap');

:root {
  --paper: #fdfdfb;
  --ink: #14130f;
  --ink-soft: #57544a;
  --ink-faint: #9c988a;
  --accent: #b33b2e;
  --hairline: #e4e1d8;
  --hairline-strong: #cfcbbd;
}
:root[data-theme="dark"] {
  --paper: #171510;
  --ink: #ede9dd;
  --ink-soft: #aba595;
  --ink-faint: #756f60;
  --accent: #e2685a;
  --hairline: #322e24;
  --hairline-strong: #423d30;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  background: var(--paper);
  color: var(--ink);
  font-family: "Libre Franklin", -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}

.serif {
  font-family: "EB Garamond", "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua", Georgia, serif;
}

a { color: inherit; }
a, button { -webkit-tap-highlight-color: transparent; }

/* ---------- site header ---------- */
.site-header {
  position: relative;
  z-index: 40;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 32px 40px;
}
/* dims the page behind the open mobile nav dropdown — without it, the
   dropdown (same paper background as the page) reads as if the content
   underneath (e.g. the first project row) had simply vanished */
.nav-backdrop {
  display: none;
}
.nav-toggle {
  display: none;
  background: none;
  border: none;
  padding: 0;
  font-size: 22px;
  line-height: 1;
  color: var(--ink);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* below this width the nav no longer fits the logo on one line — swap it
   for a hamburger that opens a simple dropdown, instead of letting the
   links wrap onto a second row */
@media (max-width: 620px) {
  .nav-toggle { display: block; }
  .site-header nav {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
    background: var(--paper);
    border-bottom: 1px solid var(--hairline);
    padding: 16px 20px 20px;
    z-index: 30;
  }
  .site-header nav.open { display: flex; }
  .site-header nav a { margin-left: 0; padding: 6px 0; }
  .nav-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 25;
    background: rgba(20, 19, 15, 0.35);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.15s ease;
  }
  .nav-backdrop.visible {
    opacity: 1;
    pointer-events: auto;
  }
}
.site-header .logo {
  font-weight: 700;
  font-size: 17px;
  letter-spacing: 0.04em;
  text-decoration: none;
}
.site-header nav {
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.site-header nav a {
  margin-left: 22px;
  text-decoration: none;
  color: var(--ink-soft);
}
.site-header nav a.on,
.site-header nav a:hover {
  color: var(--ink);
}
.site-header nav a.lang-switch {
  color: var(--ink-faint);
}
.site-header nav a.lang-switch:hover { color: var(--ink); }
.site-header nav a.soon {
  color: var(--hairline-strong);
  cursor: default;
  pointer-events: none;
}
/* fixed to the viewport, outside the counter/caption/copyright row — its
   own dynamic gallery.js collision check hides the copyright instead of
   letting the two overlap once a narrower photo box pushes them together */
.focus-toggle {
  position: fixed;
  right: 40px;
  bottom: 37px;
  z-index: 5;
  font-family: "Libre Franklin", -apple-system, sans-serif;
  font-weight: 700;
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  background: none;
  border: none;
  padding: 0;
  color: var(--ink-soft);
  cursor: pointer;
}
.focus-toggle:hover { color: var(--ink); }

body.focus-mode .site-header,
body.focus-mode .project-head {
  display: none;
}
/* margin (not padding) shrinks .stage itself within its flex parent —
   the image inside keeps filling .stage exactly as it always does,
   so this can't distort or crop it the way resizing the image directly did.
   Centering the (stage + caption bar) pair as a group — rather than just
   giving .stage equal top/bottom margins — is what keeps the photo looking
   centered once the caption bar's own height is taken into account. */
body.focus-mode #gallery-view {
  /* settles the (stage + counter row) group at the bottom instead of
     centering it — all the extra room goes above the photo, so the
     photo grows upward while its bottom edge (and distance from the
     counter) never moves */
  justify-content: flex-end;
}
/* only the top margin shrinks in focus mode — the counter/caption row and
   footer keep their normal (non-focus) padding on purpose, so neither one
   jumps to a new position when focus mode is toggled on or off */
body.focus-mode .stage {
  width: auto;
  margin: 40px 5% 0;
}
/* the click/cursor zones normally stop at the edge of .stage — in focus
   mode they're switched to fixed positioning so they also cover the white
   border around the (now smaller) photo, stopping short of the counter/
   caption row and footer so those stay clickable on their own */
body.focus-mode .arrow-zone {
  position: fixed;
  top: 0;
  bottom: 136px;
}
/* phone in portrait: no side border on the photo either, counter/caption/
   toggle stay visible at the bottom as usual. Centered rather than
   bottom-anchored here (unlike the desktop default above) — with the
   caption locked to a single line (see .frame-caption) the counter row
   never changes height between photos, so centering doesn't reintroduce
   the jumping the bottom-anchor was originally there to prevent, and a
   centered photo lines up with the arrow zones (which span the same
   region) instead of leaving them pointing at empty space above it. */
@media (max-width: 680px) {
  body.focus-mode .stage {
    margin-left: 0;
    margin-right: 0;
    margin-top: 0;
  }
  body.focus-mode #gallery-view {
    justify-content: center;
  }
  /* the base 136px reservation above was sized for the desktop meta bar —
     the mobile one is a different height (it always reserves two lines
     for the caption, see .frame-caption), so the arrow zones need to
     match that or their centered arrow icons drift away from the actual
     (now centered) photo's vertical center */
  body.focus-mode .arrow-zone {
    bottom: 105px;
  }
}
/* phone rotated to landscape: the photo is the only thing on screen — no
   margin on any side and no counter/caption/toggle row either, for the
   largest possible image. This only matches a short, wide viewport (a
   phone in landscape), not a desktop/tablet window, and rotating back to
   portrait brings the controls back since the rule stops applying. */
@media (orientation: landscape) and (max-height: 500px) {
  body.focus-mode .stage {
    margin: 0;
  }
  body.focus-mode .stage-meta {
    display: none;
  }
  body.focus-mode .arrow-zone {
    bottom: 0;
  }
  /* normal (non-full-screen) mode only — a phone in landscape is so
     short that the header + title block above the photo can leave
     #gallery-view (and everything in it, down to the caption row) less
     room than it actually needs, pushing part of it past the bottom
     with no way to reach it while the page is locked to exactly the
     viewport height. Letting the page grow and scroll here fixes that;
     full-screen mode (its own rules above) stays locked on purpose,
     since it's a deliberately fixed, single-screen view. */
  body.stage-page:not(.focus-mode) {
    height: auto;
    min-height: 100vh;
    min-height: 100dvh;
    overflow-y: auto;
  }
}

/* ---------- home: project index ---------- */
/* locks the home page to the viewport, same as the gallery pages, so the
   copyright line always sits at the very bottom of the screen instead of
   just trailing whatever content happens to be above it */
body.locked-page {
  /* dvh accounts for mobile browsers' own toolbar (address bar, back/
     forward controls) showing/hiding — 100vh alone is measured against
     the tallest possible viewport, so content near the bottom (like the
     footer) ends up hidden behind that toolbar when it's visible */
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
body.locked-page .site-header,
body.locked-page .site-footer {
  flex: none;
}
body.locked-page .index-list,
body.locked-page .book-scroll,
body.locked-page .shows-grid,
body.locked-page .about-wrap {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
body.locked-page .index-list::-webkit-scrollbar,
body.locked-page .book-scroll::-webkit-scrollbar,
body.locked-page .shows-grid::-webkit-scrollbar,
body.locked-page .about-wrap::-webkit-scrollbar {
  display: none;
}
/* scroll cue: a crisp hairline that appears at whichever edge still has more
   content, and disappears once you've scrolled all the way there. The two
   local-attached (paper-solid) layers scroll with the content and cover the
   line whenever that edge is the true start/end of the content; the two
   scroll-attached hairlines sit fixed at the container's own top/bottom edge
   underneath them. Needed because the scrollbar above is hidden for the
   site's minimal look, so this is the only hint (besides the content looking
   visibly cut) that a locked-height page has more to show — most noticeable
   on a short viewport, like a phone in landscape. */
body.locked-page .index-list,
body.locked-page .book-scroll,
body.locked-page .shows-grid,
body.locked-page .about-wrap,
.stage-intro {
  background:
    linear-gradient(var(--paper), var(--paper)) center top,
    linear-gradient(var(--paper), var(--paper)) center bottom,
    linear-gradient(var(--hairline), var(--hairline)) center top,
    linear-gradient(var(--hairline), var(--hairline)) center bottom;
  background-repeat: no-repeat;
  background-size: 100% 14px, 100% 14px, 100% 1px, 100% 1px;
  background-attachment: local, local, scroll, scroll;
}

.index-list {
  max-width: 900px;
  margin: 40px auto 120px;
  padding: 0 40px;
}
.index-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 20px 0;
  border-top: 1px solid var(--hairline);
  text-decoration: none;
  color: var(--ink);
}
.index-row:first-child { border-top: none; }
.index-row .title {
  font-weight: 700;
  font-size: 26px;
  letter-spacing: -0.01em;
}
.index-row .meta {
  font-family: "Libre Franklin", -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-weight: 700;
  letter-spacing: 0.04em;
  font-size: 14px;
  color: var(--ink-soft);
  white-space: nowrap;
  padding-left: 24px;
}
.index-row.soon { color: var(--ink-faint); cursor: default; }
.index-row.soon .title { color: var(--ink-faint); }

/* ---------- project head ---------- */
.project-head {
  max-width: 900px;
  margin: 0 auto;
  padding: 0 40px 40px;
}
.project-nav {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 28px;
}
.next-project {
  font-weight: 700;
  font-size: 12px;
  text-decoration: none;
  color: var(--ink-soft);
}
.next-project:hover { color: var(--ink); }
.back {
  display: inline-block;
  font-weight: 700;
  font-size: 12px;
  text-decoration: none;
  color: var(--ink);
}
.kicker {
  text-transform: uppercase;
  font-size: 10.5px;
  letter-spacing: 0.1em;
  color: var(--accent);
  font-weight: 700;
  margin-bottom: 10px;
}
.project-head h1 {
  font-weight: 700;
  font-size: clamp(24px, 4vw, 34px);
  letter-spacing: -0.01em;
  margin: 0 0 14px;
}
.project-head .cite {
  font-family: "EB Garamond", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  font-weight: 500;
  letter-spacing: 0.01em;
  font-size: 15px;
  color: var(--ink-soft);
  line-height: 1.6;
  max-width: 62ch;
  margin: 0 0 22px;
}
.toggle {
  font-weight: 700;
  font-size: 12px;
}
.toggle button {
  font: inherit;
  background: none;
  border: none;
  padding: 0;
  color: var(--ink-faint);
  cursor: pointer;
}
.toggle button.on { color: var(--ink); }
.toggle .sep { color: var(--hairline-strong); font-weight: 400; margin: 0 8px; }

/* ---------- gallery (sequential) view ---------- */
/* on project pages, the whole page is locked to the viewport height —
   header/title/meta/footer take their natural size, .stage absorbs the rest,
   so nothing ever scrolls */
body.stage-page {
  /* see body.locked-page for why dvh, not just vh */
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
body.stage-page .site-header,
body.stage-page .project-head,
body.stage-page .site-footer {
  flex: none;
}
body.stage-page #gallery-view {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
body.stage-page #index-view {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding-top: 14px;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
body.stage-page #index-view::-webkit-scrollbar {
  display: none;
}
body.stage-page #index-view .contact-grid { margin-bottom: 40px; }
/* same scroll cue as the locked-page containers, see there for why */
body.stage-page #index-view {
  background:
    linear-gradient(var(--paper), var(--paper)) center top,
    linear-gradient(var(--paper), var(--paper)) center bottom,
    linear-gradient(var(--hairline), var(--hairline)) center top,
    linear-gradient(var(--hairline), var(--hairline)) center bottom;
  background-repeat: no-repeat;
  background-size: 100% 14px, 100% 14px, 100% 1px, 100% 1px;
  background-attachment: local, local, scroll, scroll;
}
body.stage-page .stage-meta { flex: none; }

.stage {
  position: relative;
  width: 100%;
  height: calc(100vh - 300px);
  min-height: 220px;
  background: var(--paper);
  overflow: hidden;
}
body.stage-page .stage {
  flex: 1;
  min-height: 220px;
  height: auto;
}
.stage img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  /* anchors the visible image to the bottom of the box instead of centering
     it — .stage's own aspect ratio shifts a bit between normal and focus
     mode, which can flip a photo from filling the box's full height to
     being letterboxed top/bottom (or vice versa); center anchoring would
     let the photo drift up/down when that happens, bottom anchoring keeps
     its bottom edge fixed in both modes, matching .stage's own fixed
     bottom edge */
  object-position: center bottom;
  background: var(--paper);
}
.stage-intro {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 40px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.stage-intro::-webkit-scrollbar { display: none; }
.stage-intro p {
  max-width: 56ch;
  font-size: 17px;
  line-height: 1.7;
  color: var(--ink-soft);
  font-weight: 500;
  letter-spacing: 0.01em;
  margin: 0;
}
.stage-intro[hidden] { display: none; }
.arrow-zone {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 50%;
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
.arrow-zone.left {
  left: 0;
  cursor: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ij48cGF0aCBkPSJNMTUgNSBMOCAxMiBMMTUgMTkiIGZpbGw9Im5vbmUiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iNiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PHBhdGggZD0iTTE1IDUgTDggMTIgTDE1IDE5IiBmaWxsPSJub25lIiBzdHJva2U9IiMxNDEzMGYiIHN0cm9rZS13aWR0aD0iMyIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PC9zdmc+") 12 12, pointer;
}
.arrow-zone.right {
  right: 0;
  cursor: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ij48cGF0aCBkPSJNOSA1IEwxNiAxMiBMOSAxOSIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSI2IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48cGF0aCBkPSJNOSA1IEwxNiAxMiBMOSAxOSIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMTQxMzBmIiBzdHJva2Utd2lkdGg9IjMiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvc3ZnPg==") 12 12, pointer;
}
/* on desktop, hovering the (invisible) click zones reveals a custom arrow
   cursor — but touch devices have no hover/cursor at all, so there's
   nothing telling a phone user these zones exist. Print a small, subtle
   chevron for them specifically. Matched on EITHER condition, not just
   hover:none: a phone should always qualify via max-width regardless of
   whether it reports hover capability correctly, and hover:none alone
   still covers a tablet in landscape at a "desktop" width. */
.arrow-zone::after {
  display: none;
}
@media (hover: none), (max-width: 680px) {
  /* a light "hairline" gray (this site's usual subtle-divider color) all
     but disappears against a photograph, which can be any color or
     brightness — a small dark, semi-opaque pill behind a white chevron
     stays legible over any image, the same reasoning behind the two-tone
     white+dark custom cursor SVGs used for hover on desktop */
  .arrow-zone::after {
    content: "";
    display: flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: rgba(20, 19, 15, 0.45);
    font-family: "EB Garamond", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
    font-size: 22px;
    color: #fff;
    pointer-events: none;
  }
  .arrow-zone.left::after {
    content: "‹";
    left: 12px;
  }
  .arrow-zone.right::after {
    content: "›";
    right: 12px;
  }
}

/* caption bar lives in normal flow BELOW the stage, never overlaid on the photo
   (an overlay would intrude on the image at some widths/aspect ratios).
   Two fixed-height rows — never min-height — so the space this whole block
   reserves is exactly the same on every photo/page regardless of caption
   length: a long caption clips to 2 lines with an ellipsis instead of
   growing the row and stealing height back from the photo. */
.stage-meta {
  padding: 32px 40px;
  display: flex;
  align-items: baseline;
  gap: 24px;
}
.frame-count,
.frame-caption {
  font-family: "EB Garamond", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  font-weight: 500;
  letter-spacing: 0.01em;
  font-size: 17px;
  color: var(--ink-soft);
}
.frame-count {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  flex: none;
}
.frame-caption {
  text-align: left;
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.focus-toggle { flex: none; }
/* mirrors the counter: same font, pinned to the row's far end (the photo's
   right edge, via stage-meta's own dynamic padding-right) — hidden below
   680px so a long caption gets that space back instead, since being able
   to read the caption matters more than the copyright line on a phone */
.stage-copy {
  flex: none;
  margin-left: auto;
  font-family: "EB Garamond", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  font-weight: 500;
  letter-spacing: 0.01em;
  font-size: 17px;
  color: var(--ink-soft);
  white-space: nowrap;
}
@media (max-width: 680px) {
  .stage-copy { display: none; }
  /* on a phone, giving up the copyright line's height back to the
     scrollable content (index/about/mostre/book) matters more than
     always showing it — it's still on every page at wider widths */
  body.locked-page footer.site-footer { display: none; }
}

/* ---------- index (contact sheet) view ---------- */
.contact-grid {
  max-width: 1100px;
  margin: 0 auto 120px;
  padding: 0 40px;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 4px;
}
.contact-tile {
  position: relative;
  aspect-ratio: 1;
  overflow: hidden;
  cursor: pointer;
  background: var(--hairline);
}
.contact-tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  filter: grayscale(0.1);
  transition: transform 0.2s;
}
.contact-tile:hover img { transform: scale(1.04); }
.contact-tile .n {
  position: absolute;
  left: 6px;
  bottom: 5px;
  font-size: 10px;
  font-weight: 700;
  color: #fff;
  text-shadow: 0 1px 3px rgba(0,0,0,0.6);
}

/* ---------- libro ---------- */
.book-layout {
  max-width: 1100px;
  margin: 0 auto;
  padding: 12px 40px 40px;
  display: grid;
  grid-template-columns: 400px 1fr;
  align-content: safe center;
  gap: 56px;
  /* the text column is much shorter than the cover image, so pinning it
     to the top (align-items:start) left a big empty gap below it — this
     way it centers vertically alongside the photo instead */
  align-items: center;
}
.book-layout .cover {
  width: 100%;
  display: block;
  box-shadow: 0 20px 40px rgba(0,0,0,0.16);
}
.book-links {
  margin-top: 22px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.book-links a {
  font-weight: 700;
  font-size: 12px;
  text-decoration: none;
  color: var(--ink);
  border-bottom: 1px solid var(--ink);
  padding-bottom: 2px;
  width: fit-content;
}

.book-presentations {
  max-width: 1100px;
  margin: 0 auto;
  padding: 20px 40px 60px;
}
.book-presentations .kicker { margin-bottom: 22px; }
.presentation-list {
  display: flex;
  flex-direction: column;
}
.presentation-item {
  display: flex;
  gap: 32px;
  padding: 22px 0;
  border-top: 1px solid var(--hairline);
}
.presentation-item:first-child { border-top: none; padding-top: 0; }
.presentation-item .date {
  flex: none;
  width: 130px;
  font-size: 17px;
  color: var(--ink-soft);
}
.presentation-body { flex: 1; }
.presentation-body .venue {
  font-family: "Libre Franklin", -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-weight: 700;
  letter-spacing: 0.04em;
  font-size: 14px;
  color: var(--ink);
  margin-bottom: 6px;
}
.presentation-body .desc {
  font-family: "EB Garamond", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  font-weight: 500;
  letter-spacing: 0.01em;
  font-size: 15px;
  color: var(--ink-soft);
  line-height: 1.6;
  max-width: 62ch;
  margin: 0 0 10px;
}
.presentation-link {
  display: inline-block;
  font-family: "Libre Franklin", -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-weight: 700;
  font-size: 12px;
  text-decoration: none;
  color: var(--ink);
  border-bottom: 1px solid var(--ink);
  padding-bottom: 2px;
}

/* ---------- mostre ---------- */
/* scoped to project-head right before .shows-grid (i.e. just Mostre) —
   thinner top/bottom padding than the shared default, so the photos get
   more of the screen instead of empty margin */
.project-head:has(+ .shows-grid) {
  padding-bottom: 8px;
}
/* same idea, scoped to Book (right before .book-scroll) — the cover sits
   right under the citation instead of a wide empty gap */
.project-head:has(+ .book-scroll) {
  padding-bottom: 8px;
}
.shows-grid {
  max-width: 1120px;
  margin: 0 auto;
  padding: 16px 40px;
  display: grid;
  grid-template-columns: 1fr;
  align-content: safe center;
  gap: 32px;
}
.show-card {
  display: flex;
  align-items: flex-start;
  gap: 32px;
}
.show-plate {
  flex: none;
  width: 640px;
  background: var(--hairline);
}
.show-plate img {
  width: 100%;
  height: auto;
  display: block;
}
.show-plate.empty {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 4/5;
  font-size: 11px;
  color: var(--ink-faint);
  text-align: center;
  padding: 20px;
}
.show-meta { flex: 1; padding-top: 0; }
.show-meta .venue {
  font-family: "Libre Franklin", -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-weight: 700;
  letter-spacing: 0.04em;
  font-size: 14px;
  color: var(--ink-soft);
  margin-bottom: 4px;
}
.show-meta .date {
  font-family: "EB Garamond", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  font-weight: 500;
  letter-spacing: 0.01em;
  font-size: 17px;
  color: var(--ink-soft);
}
.show-link {
  display: inline-block;
  margin-top: 6px;
  font-family: "Libre Franklin", -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-weight: 700;
  font-size: 12px;
  text-decoration: none;
  color: var(--ink);
  border-bottom: 1px solid var(--ink);
  padding-bottom: 2px;
}

/* ---------- about ---------- */
.about-wrap {
  max-width: 900px;
  margin: 0 auto;
  padding: 40px 40px;
  display: flex;
  flex-direction: column;
  justify-content: safe center;
}
.about-section-label {
  text-transform: uppercase;
  font-size: 10.5px;
  letter-spacing: 0.1em;
  color: var(--accent);
  font-weight: 700;
  margin-bottom: 12px;
}
.about-bio {
  font-family: "EB Garamond", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  font-weight: 500;
  letter-spacing: 0.01em;
  font-size: 17px;
  line-height: 1.7;
  color: var(--ink-soft);
  max-width: 62ch;
  margin: 0 0 44px;
}
.about-bio i { color: var(--ink); }
.about-block { margin-bottom: 36px; }
.about-block:last-child { margin-bottom: 0; }

/* ---------- contact form ---------- */
.contact-form {
  display: flex;
  flex-direction: column;
  gap: 20px;
  max-width: 420px;
  /* both live under the same "Contact" label, but a plain gap alone read
     as too close to the Instagram link above it — the line plus the
     bigger gap mark it as its own thing without a second label */
  margin-top: 36px;
  padding-top: 28px;
  border-top: 1px solid var(--hairline);
}
.contact-form label {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.contact-form span {
  text-transform: uppercase;
  font-size: 10.5px;
  letter-spacing: 0.1em;
  color: var(--ink-faint);
  font-weight: 700;
}
.contact-form input,
.contact-form textarea {
  font-family: "EB Garamond", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  font-size: 16px;
  color: var(--ink);
  background: none;
  border: none;
  border-bottom: 1px solid var(--hairline-strong);
  padding: 6px 0;
  border-radius: 0;
  resize: vertical;
}
.contact-form input:focus,
.contact-form textarea:focus {
  outline: none;
  border-bottom-color: var(--ink);
}
.contact-form button {
  align-self: flex-start;
  margin-top: 8px;
  font-family: "Libre Franklin", -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-weight: 700;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  background: none;
  border: 1px solid var(--ink);
  color: var(--ink);
  padding: 10px 24px;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.contact-form button:hover {
  background: var(--ink);
  color: var(--paper);
}
.cv-row {
  display: grid;
  grid-template-columns: 70px 1fr;
  gap: 16px;
  padding: 10px 0;
  border-top: 1px solid var(--hairline);
  font-size: 14px;
}
.cv-row:first-of-type { border-top: none; }
.cv-row .yr {
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.cv-row .what { color: var(--ink-soft); }
.cv-row .what b { color: var(--ink); font-weight: 500; }

[hidden] { display: none !important; }

footer.site-footer {
  /* auto side-margins on a flex item (locked-page's column layout) shrink
     it to content width and center it, overriding the usual stretch —
     width:100% forces it back to filling up to max-width so text-align
     actually has room to move the text to the right edge */
  width: 100%;
  max-width: 900px;
  margin: 0 auto;
  padding: 28px 40px 32px;
  border-top: 1px solid var(--hairline);
  font-family: "EB Garamond", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  font-weight: 500;
  letter-spacing: 0.01em;
  font-size: 17px;
  color: var(--ink-soft);
  text-align: right;
}
body.locked-page footer.site-footer {
  border-top: none;
  padding-top: 28px;
}
/* dropped entirely on gallery pages — counter, caption and the Full-Screen
   toggle already share one line right under the photo, and repeating the
   copyright there too was just extra chrome eating into the picture */
body.stage-page footer.site-footer {
  display: none;
}

/* on short viewports (small/laptop monitors) the fixed-size header, title
   block, caption row and footer were eating so much of the 100vh that the
   flexible photo area (.stage) had almost nothing left — this trims that
   chrome down so the photo gets most of the height back */
@media (max-height: 800px) {
  body.stage-page .site-header { padding: 16px 40px; }
  body.stage-page .project-head { padding: 0 40px 14px; }
  body.stage-page .back { margin-bottom: 10px; }
  body.stage-page .kicker { margin-bottom: 4px; }
  body.stage-page .project-head h1 { margin: 0 0 6px; }
  body.stage-page .project-head .cite { margin: 0 0 8px; }
  body.stage-page .stage-meta { padding: 10px 40px; }
  body.stage-page .focus-toggle { bottom: 15px; }
  body.focus-mode .arrow-zone { bottom: 110px; }
}
/* a phone that's both narrow and short matches the max-height:800px block
   above too, which also shrinks .stage-meta's own padding further (see
   body.stage-page .stage-meta there) — this needs to come after that
   block so the shorter value wins over the 110px meant for desktop's
   taller meta bar */
@media (max-width: 680px) and (max-height: 800px) {
  body.focus-mode .arrow-zone { bottom: 61px; }
}

@media (max-width: 680px) {
  .site-header { padding: 24px 20px; }
  .index-list, .project-head, footer.site-footer { padding-left: 20px; padding-right: 20px; }
  /* the base 40px/120px top/bottom margin was tuned for 3 projects — a
     4th row pushed the list past what a shorter phone screen (e.g. an
     iPhone SE-height one) has room for, forcing a scroll even though
     tightening this margin gets it all to fit without one */
  .index-list { margin: 20px auto 30px; }
  /* 40px padding + the citation's own 22px margin added up to a wide gap
     between the title block and whatever comes next (a book cover, an
     intro slide) — tightened for the narrower phone layout */
  .project-head { padding-bottom: 20px; }
  .project-head .cite { margin-bottom: 12px; }
  .contact-grid { grid-template-columns: repeat(3, 1fr); padding: 0 20px; }
  /* the intro text is often taller than the stage on a phone screen; centering
     it would clip both the top and bottom with no way to reach either end,
     so it starts at the top instead and scrolls internally to reveal the rest */
  .stage-intro { align-items: flex-start; padding: 24px 20px; }
  .stage-intro p { font-size: 15px; line-height: 1.6; }
  /* .arrow-zone sits later in the DOM than .stage-intro, so it stacks on top
     and swallows every touch — including the drag that should scroll the
     text below it. Raising the intro above it while it's the visible slide
     lets the finger actually reach the scrollable text on a phone. */
  .stage-intro:not([hidden]) { z-index: 1; }
  /* counter + caption share the first row, always a single line — a
     caption too long for it just truncates with an ellipsis, same as
     everywhere else, rather than wrapping. Full-Screen/Exit moves to a
     second row of its own (flex-basis:100% below), since giving it a
     row to itself is what leaves the caption the most room on the first
     one, in both normal and full-screen mode. */
  .stage-meta { flex-wrap: wrap; row-gap: 6px; }
  /* alignMetaToWidth() (gallery.js) sets this inline, hugging the
     counter to the photo's actual edge — fine when it's the only thing
     on the row, but sharing the row with the caption now, that inset can
     eat half the phone's width on its own and leave the caption barely
     any room. A small constant margin (matching the site's other mobile
     edge padding) instead of the dynamic photo-edge one gives the
     caption most of that space back — !important is the only way to
     win over the inline style. */
  .stage-meta {
    padding-left: 20px !important;
    padding-right: 20px !important;
  }
  .focus-toggle {
    position: static;
    right: auto;
    bottom: auto;
    flex-basis: 100%;
    text-align: right;
  }
  .index-row { flex-direction: column; align-items: flex-start; gap: 4px; }
  .index-row .meta { padding-left: 0; }
  /* align-content:safe center (base rule) is meant for the desktop
     photo+text row — stacked on mobile, the cover+text block is often
     much shorter than the scrollable area, so centering pushed the cover
     down into a big empty gap right below the title/citation instead of
     sitting close under it. Starting from the top reads better here. */
  .book-layout { grid-template-columns: 1fr; padding: 0 20px; align-content: start; }
  .book-layout .cover { max-width: 280px; }
  .shows-grid { grid-template-columns: 1fr; padding: 0 20px; }
  .show-card { flex-direction: column; }
  .show-plate { width: 100%; }
  .book-presentations { padding: 0 20px 40px; }
  .presentation-item { flex-direction: column; gap: 6px; }
  .presentation-item .date { width: auto; }
}

/* full-screen, phone rotated to landscape: bring the meta row back
   (the block above hid it entirely) as a single plain line — this is the
   only state this row can be in here, so there's no other layout to stay
   visually consistent with the way there is in portrait. Placed after
   the max-width:680px block on purpose: a phone that's narrow enough to
   match that block can still be wide enough to count as "landscape"
   once rotated, and this needs to win over it. */
@media (orientation: landscape) and (max-height: 500px) {
  body.focus-mode .stage-meta {
    display: flex;
    /* alignMetaToWidth() (gallery.js) intentionally doesn't recompute the
       photo-edge inset while focus mode is active — the inline padding
       showing here is just whatever normal (portrait) mode last set, for
       a completely different box shape. In landscape's short, wide box a
       pillarboxed photo can inherit an inset of 300px+, leaving nowhere
       near enough room for a caption on one line — !important is the
       only way to win over that inline style, since a stylesheet rule
       normally can't. */
    padding-left: 24px !important;
    padding-right: 24px !important;
  }
  body.focus-mode .frame-caption {
    display: block;
    white-space: nowrap;
    min-height: 0;
  }
  body.focus-mode .arrow-zone {
    bottom: 42px;
  }
}
