/* ============================================================
   Zane Ally — Photography
   Layout & type modeled on tunbjork.se's home screen structure.
   Font: system sans-serif stack (Helvetica Neue-adjacent) —
   tunbjork.se uses the licensed commercial face Unica77, which
   cannot be reproduced here without a license.
   ============================================================ */

:root {
  --color-bg: #ffffff;
  --color-text: #101820;
  --color-text-muted: #5a5f66;
  --color-border: #e2e2e2;
  /* Page-title palette, matching egglestonartfoundation.org's
     two-tone green wordmark (see .site-header__brand below —
     previously converted to greyscale, now restored to color). */
  --color-title-light: #3fa876;
  --color-title-dark: #2d5a3d;
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue",
    Helvetica, Arial, sans-serif;
  --container-max: 1440px;
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  /* Height of the page title in the masthead — the red dot is
     kept sized (and centred) to match whichever value is active.
     Pages carrying the large "Zane Ally" wordmark override this
     to --brand-size below; plain page titles (Films, Music, …)
     use this smaller default. */
  --title-size: clamp(1.875rem, 4.5vw, 3.1875rem);
  --brand-size: clamp(2.5rem, 7vw, 5.5rem);
}

.site-header:has(.site-header__brand) {
  --title-size: var(--brand-size);
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: 16px;
  line-height: 1.5;
  color: var(--color-text);
  /* Film grain texture over the base page color. The PNG is
     almost entirely transparent (faint dark speckling only),
     so it reads as a subtle grain wash rather than an image.
     .site-header paints its own opaque white on top, so the
     masthead bar stays clean — see below. */
  background-color: var(--color-bg);
  background-image: url("../images/film-grain-75.png");
  background-repeat: repeat;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

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

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

ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

button {
  font-family: inherit;
  cursor: pointer;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* ---------------------------------------------------------
   Header / masthead — a solid white bar pinned to the top of
   every page (mirrors tunbjork.se, whose title + icon stay put
   while the page scrolls beneath them), with a large title
   block below the icon row. Navigation lives behind the icon
   at every screen size — see .menu-toggle / .site-nav below —
   not as an inline link row, again matching the reference.
   --------------------------------------------------------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 200;
  background: var(--color-bg);
  color: var(--color-text);
  border-bottom: 1px solid var(--color-border);
}

.site-header__row {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  height: 42px;
  padding: 0 clamp(20px, 4vw, 48px);
}

.site-header__title {
  padding: 0 clamp(20px, 4vw, 48px) clamp(21px, 3.75vw, 42px);
}

.site-header__name {
  margin: 0;
  font-size: var(--title-size);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: -0.03em;
  line-height: 1;
  color: var(--color-title-dark);
}

.site-header__name a {
  display: inline-block;
}

/* Lighter green used for "Zane" in the homepage wordmark, reused
   to tint one word of a page title for the same two-tone
   contrast. */
.site-header__name-tint {
  color: var(--color-title-light);
}

/* Brand wordmark — "Zane Ally" in heavy uppercase, one line,
   with a subtle tonal shift between the two words, in the
   spirit of — and matching the color of —
   egglestonartfoundation.org's logo. Sized well above the old
   title for more presence in the masthead. */
.site-header__brand {
  display: inline-block;
}

.site-header__name .site-header__brand-line {
  display: inline;
  color: var(--color-title-light);
}

.site-header__name .site-header__brand-line--last {
  color: var(--color-title-dark);
}

.site-header__tagline {
  margin: 8px 0 0;
  font-size: 0.85rem;
  color: var(--color-text-muted);
  letter-spacing: 0.02em;
}

/* Icon: a red dot, always visible, that opens the nav panel —
   same trigger, same spot, as tunbjork.se's menu icon. */
.menu-toggle {
  display: flex;
  align-items: center;
  background: none;
  border: 0;
  color: inherit;
  padding: 8px;
  margin: -8px;
  /* Drops the whole button (hit-area included, not just the
     dot) from the icon row down to the vertical centre of the
     page title below it. Computed, not eyeballed — the title
     block's own height depends on --title-size and this section's
     responsive clamp()-based bottom padding, both of which change
     with viewport width, so a single fixed pixel offset can only
     ever be exactly right at one screen width. This mirrors that
     same math instead: half the icon row's own 42px height (its
     own vertical centre, where the button starts from) plus half
     of .site-header__title's rendered height below it (0 top
     padding + the title's own line-height: 1 box + the same
     bottom padding as that section uses). Two rules because a
     plain page title (Films, Music, …) has no tagline line under
     it, but the "Zane Ally" wordmark below does — see the second
     rule. */
  transform: translateY(
    calc(21px + (var(--title-size) + clamp(21px, 3.75vw, 42px)) / 2)
  );
}

.site-header:has(.site-header__brand) .menu-toggle {
  /* Same formula as above, plus the tagline line beneath the
     wordmark: its 8px top margin and its own rendered line height
     (0.85rem font-size × this page's inherited 1.5 line-height). */
  transform: translateY(
    calc(
      21px +
        (var(--title-size) + 8px + 1.275rem + clamp(21px, 3.75vw, 42px)) / 2
    )
  );
}

.menu-toggle__bar {
  display: block;
  width: var(--title-size);
  height: var(--title-size);
  border-radius: 50%;
  background: #d81e2c;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.16);
  /* Idle state: a slow, quiet breathing pulse, always running.
     transform-only + GPU-composited, so it costs nothing on the
     main thread — safe to leave on indefinitely. Overridden by
     the sharper click-triggered burst below while that's active. */
  animation: menu-toggle-pulse-loop 2.6s ease-in-out infinite;
}

@keyframes menu-toggle-pulse-loop {
  0%,
  100% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.08);
  }
}

@keyframes menu-toggle-pulse {
  0% {
    transform: scale(1);
  }
  35% {
    transform: scale(0.72);
  }
  70% {
    transform: scale(1.14);
  }
  100% {
    transform: scale(1);
  }
}

.menu-toggle__bar.is-animating {
  animation: menu-toggle-pulse 0.45s var(--ease);
}

@media (prefers-reduced-motion: reduce) {
  .menu-toggle__bar {
    animation: none;
  }

  .menu-toggle__bar.is-animating {
    animation: none;
  }
}

/* Panel: slides in from the right over the page (no dimming
   backdrop, page content stays visible), full viewport height,
   at every screen size — not just mobile. */
.site-nav {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 150;
  width: min(600px, 100vw);
  background: var(--color-bg);
  /* Label text color — icons are tinted separately, a shade
     lighter, on .site-nav__icon below. */
  color: var(--color-title-dark);
  padding: clamp(90px, 15vw, 140px) clamp(24px, 4vw, 48px) 40px;
  overflow-y: auto;
  box-shadow: -16px 0 40px rgba(0, 0, 0, 0.08);
  transform: translateX(100%);
  transition: transform 0.35s var(--ease);
}

body.nav-open .site-nav {
  transform: translateX(0);
}

body.nav-open,
body.modal-open {
  overflow: hidden;
}

.site-nav__list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.site-nav__list a {
  display: flex;
  align-items: center;
  gap: 28px;
  padding: 10px 0;
  font-size: 2.1rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.01em;
  position: relative;
}

.site-nav__icon {
  flex: 0 0 auto;
  display: inline-flex;
  width: 66px;
  height: 66px;
  /* Lighter of the two title greens, so icons read as a distinct
     tone from the label text next to them (dark green, set on
     .site-nav above). */
  color: var(--color-title-light);
}

.site-nav__icon svg {
  width: 100%;
  height: 100%;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.site-nav__label {
  opacity: 0;
  max-width: 0;
  overflow: hidden;
  white-space: nowrap;
  transition: opacity 0.2s var(--ease), max-width 0.2s var(--ease);
}

.site-nav__list a:hover .site-nav__label,
.site-nav__list a:focus-visible .site-nav__label {
  opacity: 1;
  max-width: 400px;
}

/* Touch devices have no hover state — keep labels visible so the
   nav stays usable without a pointer. */
@media (hover: none) {
  .site-nav__label {
    opacity: 1;
    max-width: none;
  }
}

@media (max-width: 480px) {
  .site-nav__list a {
    font-size: 1.7rem;
  }

  .site-nav__icon {
    width: 48px;
    height: 48px;
  }
}

/* ---------------------------------------------------------
   Hero slideshow
   --------------------------------------------------------- */

.hero {
  position: relative;
  height: calc(100svh - 150px);
  min-height: 420px;
  width: 100%;
  overflow: hidden;
  background: #101820;
}

@media (max-width: 720px) {
  .hero {
    height: calc(100svh - 180px);
  }
}

/* Tablet range for the homepage photo gallery specifically (not
   Books' shelf, which keeps its existing sizing at every width
   per the standing rule not to touch it uninvited). The mobile
   rule above only kicks in at 720px and narrower, so without
   this, a tablet just inherited the full desktop height with no
   adjustment at all. Crop behavior is untouched here — same
   split as desktop (see .hero__shelf-item--cover img) — only the
   frame's own height changes, same as it already does for phones. */
@media (min-width: 721px) and (max-width: 1024px) {
  .hero--gallery {
    height: calc(100svh - 165px);
  }
}

/* Hero variant: a horizontally scrollable shelf, used in place
   of a single full-bleed photo. Shared by two contexts:
   - .hero--shelf (books.html): uniform book-cover ratio, via the
     .hero__shelf-item--book modifier below.
   - .hero--gallery (index.html): real photos of varying ratios,
     each sized by its own natural width at the fixed hero
     height — see .hero__shelf-item img — so nothing is cropped
     and the hero's height never has to bend to fit them. */

.hero--shelf {
  /* Same fixed, viewport-relative height as the home page hero
     (inherited from .hero) — books are fit via object-fit: contain
     below rather than by resizing this section to match them.
     Light background so the drop shadow (dark) actually reads —
     it was invisible against the near-black photo hero colour. */
  background: #e9e9e7;
}

.hero--gallery {
  /* White, not the base .hero's dark navy — shows in the gaps
     beside any photo narrower than its full-width slot. */
  background: var(--color-bg);
}

.hero__shelf {
  height: 100%;
  display: flex;
  align-items: center;
  /* Not justify-content: center — with content this wide it
     insets the whole flex line, so scrollLeft: 0 lands partway
     through the row instead of on the first item. */
  justify-content: flex-start;
  gap: 24px;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  /* Real photos load in lazily at varying widths; without this,
     the browser's scroll anchoring "helpfully" shifts scrollLeft
     to compensate as each one settles, drifting the start position
     away from the intended photo by however many items load in
     before it. */
  overflow-anchor: none;
  /* Hide the scrollbar — the arrows/drag/swipe are the affordance. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.hero__shelf::-webkit-scrollbar {
  display: none;
}

.hero__shelf-item {
  /* Full height, edge-to-edge with the hero — never resized to
     fit content; three (or however many) won't always fit
     exactly, and that's fine: items can run past the frame's
     edges (see scroll-snap-align: center, which keeps whichever
     one is current centred regardless). */
  position: relative;
  display: block;
  flex: 0 0 auto;
  height: 100%;
  scroll-snap-align: center;
  scroll-snap-stop: always;
}

.hero__shelf-item img {
  /* Natural width at 100% height — the default for real photos
     of mixed aspect ratios, so each shows in full, uncropped.
     max-width: none overrides the global img{max-width:100%}
     reset, which would otherwise clip a wide photo down to its
     own (content-sized) container width. */
  display: block;
  height: 100%;
  width: auto;
  max-width: none;
}

.hero__shelf-item--photo {
  /* Full width of the hero — not sized to the photo's own
     rendered width — so only one is ever visible at a time
     (no neighbour peeking in at the edges). The photo itself is
     unchanged in size (still full height, natural width, via
     .hero__shelf-item img above); it's just centred within this
     wider slot, with the hero's own white background filling
     whatever space is left on either side. */
  flex-basis: 100%;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-bg);
}

/* On mobile/tablet, a landscape photo at natural width can be wider
   than this narrow slot (max-width: none on the img lets that
   happen on purpose — see .hero__shelf-item img above, so it's
   never cropped). Without clipping, that overflow bleeds past the
   slot's edges into the next photo over, breaking the "only one
   photo visible, centred" rule this section is meant to keep. Cuts
   in only here (not on desktop, where the hero is normally wide
   enough this never triggers) so the photo itself is untouched —
   still full height, still its own natural width — just clipped at
   the frame's edge instead of spilling into the neighbour. */
@media (max-width: 1024px) {
  .hero--gallery .hero__shelf-item--photo {
    overflow: hidden;
  }
}

.hero__shelf-item--cover img {
  /* A handful of photos read better full-bleed (cropped to fill
     the frame) than uncropped at their natural width — this
     restores that for just those images.

     This used to be width: 100% (of the slot, which is always the
     hero's full width). That meant the crop window was literally
     "hero width ÷ hero height" at whatever size the visitor's own
     screen happened to be — a very different shape (and so a very
     different, more zoomed-in slice of the photo) on a narrow phone
     than on a wide desktop window, even though the CSS was
     identical.

     Locking to one fixed ratio (~1.95:1, matching how these read on
     a typical desktop window) fixes that — same crop composition at
     every screen size. A height: 100%-always version was tried (full
     frame height, sides trimmed by the hero's overflow: hidden to
     make it fit) but on a narrow phone that trims away most of the
     photo's actual width — e.g. a "row of houses in fog" shrank down
     to one house with no fog in frame, losing the point of the shot.
     max-width/max-height here instead let the whole box shrink to
     fit — the full composition always stays visible, just smaller;
     these few photos read shorter than their neighbours on narrow
     screens as the trade-off. */
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  aspect-ratio: 253 / 130;
  object-fit: cover;
  object-position: center;
}

/* STANDING RULE (confirmed 2026-08-26, reversed once and put back —
   do not remove without an explicit request): on mobile/tablet,
   uniform height across every photo in the row — including the
   cropped ones — takes priority over those photos preserving their
   full composition. Switches back to full frame height with the
   sides trimmed instead of the box above shrinking to fit;
   .hero__shelf-item--photo already clips that overflow at this same
   breakpoint, so nothing new is needed to contain it. Desktop is
   untouched — still the shrink-to-fit version above.

   The ratio is also narrower here than desktop's ~1.95:1 (confirmed
   OK to leave open space beside the photo instead of trimming it
   tight) — 4:5 needs far less width for the same height, so on
   these narrower screens it shows much more of the photo before
   anything needs to overflow/trim, and on the wider end of this
   range (tablets) it likely won't need to trim at all, just centre
   with open space on the sides, the same way the uncropped photos
   already do. */
@media (max-width: 1024px) {
  .hero--gallery .hero__shelf-item--cover img {
    width: auto;
    height: 100%;
    max-width: none;
    max-height: none;
    aspect-ratio: 4 / 5;
  }
}

.hero__shelf-item--book {
  /* Book covers are all the same ratio, so — unlike photos —
     it's simpler to size the frame itself and let the cover
     fill it exactly, with a background/shadow/border treatment
     that wouldn't suit a bare photo. */
  aspect-ratio: 612 / 792;
  background: #101820;
  /* Same two-layer, offset-down shadow Blurb uses on its own cover
     image (blurb.com/b/12358395-icon), scaled down so its reach
     stays under half the 24px gap — keeps each book's shadow from
     touching its neighbour's without changing size or spacing. */
  box-shadow:
    0 5px 8px rgba(0, 0, 0, 0.1),
    0 3px 10px rgba(0, 0, 0, 0.095);
}

.hero__shelf-item--book img {
  width: 100%;
  object-fit: contain;
}

.hero__shelf-item--book::before,
.hero__shelf-item--book::after {
  /* Overlaid, not a real border — a border would shrink the
     content box by 1px and throw off the exact aspect-ratio
     match with the image, letterboxing it and revealing the
     item's own background as thin vertical slivers on the sides.
     Solid line, not a gradient fade. */
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 1px;
  background: #999;
}

.hero__shelf-item--book::before {
  top: 0;
}

.hero__shelf-item--book::after {
  bottom: 0;
}

.hero__shelf-nav {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 clamp(10px, 1.6vw, 20px);
  pointer-events: none;
}

.hero__shelf-nav .hero__arrow {
  pointer-events: auto;
  background: rgba(16, 24, 32, 0.55);
  backdrop-filter: blur(2px);
}

.hero__shelf-nav .hero__arrow:hover {
  background: rgba(16, 24, 32, 0.8);
}

.hero__shelf-nav .hero__arrow:disabled {
  opacity: 0.3;
  pointer-events: none;
}

.hero__arrow {
  background: none;
  border: 1px solid rgba(255, 255, 255, 0.55);
  color: #fff;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: border-color 0.2s var(--ease), background-color 0.2s var(--ease);
}

.hero__arrow:hover {
  border-color: #fff;
  background: rgba(255, 255, 255, 0.12);
}

.hero__arrow svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
}

/* ---------------------------------------------------------
   Intro / bio section
   --------------------------------------------------------- */

.intro {
  padding: clamp(72px, 12vw, 140px) clamp(20px, 4vw, 48px);
  max-width: 760px;
  margin: 0 auto;
}

.intro__block {
  font-size: clamp(1.15rem, 2.1vw, 1.4rem);
  line-height: 1.6;
  font-weight: 400;
  color: var(--color-text);
  background: var(--color-bg);
  padding: 6px 10px;
  margin: 0 0 clamp(28px, 4vw, 44px) -10px;
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.8s var(--ease), transform 0.8s var(--ease);
}

.intro__block:last-child {
  margin-bottom: 0;
}

.intro__block.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* ---------------------------------------------------------
   Closing / CTA section
   --------------------------------------------------------- */

.closing {
  border-top: 1px solid var(--color-border);
  padding: clamp(56px, 9vw, 100px) clamp(20px, 4vw, 48px);
  text-align: center;
  background: var(--color-bg);
}

.closing__eyebrow {
  font-size: 0.8rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin: 0 0 14px;
}

.closing__link {
  font-size: clamp(1.6rem, 4vw, 2.4rem);
  display: inline-block;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--color-text);
  transition: opacity 0.2s var(--ease);
}

.closing__link:hover {
  opacity: 0.6;
}

/* ---------------------------------------------------------
   Footer
   --------------------------------------------------------- */

.site-footer {
  border-top: 1px solid var(--color-border);
  padding: 40px clamp(20px, 4vw, 48px) 48px;
  display: flex;
  flex-wrap: wrap;
  gap: 20px 40px;
  align-items: baseline;
  justify-content: space-between;
}

.site-footer__brand {
  font-size: 0.95rem;
}

.site-footer__nav {
  display: flex;
  gap: 24px;
  flex-wrap: wrap;
}

.site-footer__nav a {
  font-size: 0.9rem;
  color: var(--color-text-muted);
}

.site-footer__nav a:hover {
  color: var(--color-text);
}

.site-footer__meta {
  width: 100%;
  font-size: 0.78rem;
  color: var(--color-text-muted);
  background: var(--color-bg);
  padding: 4px 8px;
  margin: 0 -8px;
  display: inline-block;
  border-top: 1px solid var(--color-border);
  padding-top: 20px;
}

/* ---------------------------------------------------------
   Inner pages (Biography / Contact) — text only, no photographs
   --------------------------------------------------------- */

.page-header {
  padding: clamp(48px, 9vw, 100px) clamp(20px, 4vw, 48px) clamp(32px, 5vw, 56px);
  max-width: 760px;
  margin: 0 auto;
  border-bottom: 1px solid var(--color-border);
  background: var(--color-bg);
}

.page-header__eyebrow {
  font-size: 0.8rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin: 0 0 14px;
}

.page-header__title {
  font-size: clamp(2rem, 5vw, 3rem);
  font-weight: 800;
  text-transform: uppercase;
  margin: 0;
  letter-spacing: -0.03em;
}

.page-content {
  padding: clamp(48px, 8vw, 88px) clamp(20px, 4vw, 48px) clamp(80px, 12vw, 140px);
  max-width: 760px;
  margin: 0 auto;
}

.page-content p {
  font-size: clamp(1.05rem, 1.6vw, 1.2rem);
  line-height: 1.7;
  margin: 0 0 24px;
  color: var(--color-text);
  background: var(--color-bg);
  padding: 6px 10px;
  margin-left: -10px;
  margin-right: -10px;
  display: inline-block;
}

.page-content p:last-child {
  margin-bottom: 0;
}

.page-content__note {
  margin-top: 48px;
  padding: 20px 22px;
  border: 1px dashed var(--color-border);
  font-size: 0.85rem;
  line-height: 1.6;
  color: var(--color-text-muted);
  background: var(--color-bg);
}

.contact-detail {
  margin: 0 0 28px -10px;
  background: var(--color-bg);
  padding: 6px 10px;
  display: inline-block;
}

.contact-detail dt {
  font-size: 0.78rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin-bottom: 6px;
}

.contact-detail dd {
  margin: 0;
  font-size: clamp(1.15rem, 2vw, 1.5rem);
}

.contact-detail dd a {
  border-bottom: 1px solid var(--color-border);
  transition: border-color 0.2s var(--ease);
}

.contact-detail dd a:hover {
  border-color: var(--color-text);
}

/* ---------------------------------------------------------
   Photography Books page
   --------------------------------------------------------- */

.page-content--wide {
  max-width: 1160px;
}

.book-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.book-row {
  display: flex;
  gap: clamp(24px, 4vw, 56px);
  align-items: flex-start;
  padding: clamp(32px, 5vw, 56px) 0;
  border-top: 1px solid var(--color-border);
}

.book-row:last-child {
  border-bottom: 1px solid var(--color-border);
}

.book-row__media {
  display: block;
  flex: 0 0 auto;
  width: clamp(140px, 18vw, 220px);
  aspect-ratio: 612 / 792;
  overflow: hidden;
  background: #f2f2f2;
  /* Same shadow Blurb uses on its own cover image, e.g.
     blurb.com/bookstore/detail/12358395-icon. */
  box-shadow:
    0 10px 16px rgba(0, 0, 0, 0.2),
    0 6px 20px rgba(0, 0, 0, 0.19);
}

.book-row__media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.4s var(--ease);
}

.book-row__media:hover img {
  transform: scale(1.03);
}

.book-row__content {
  flex: 0 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  background: var(--color-bg);
  padding: 6px 10px;
  margin: -6px -10px;
}

.book-row__title {
  margin: 0 0 6px;
  font-size: clamp(1.2rem, 2vw, 1.5rem);
  font-weight: 400;
  letter-spacing: -0.01em;
}

.book-row__meta {
  margin: 0 0 18px;
  font-size: 0.85rem;
  color: var(--color-text-muted);
}

.book-row__description {
  margin: 0;
  max-width: 640px;
  font-size: clamp(0.95rem, 1.3vw, 1.05rem);
  line-height: 1.7;
  color: var(--color-text);
}

.book-row__links {
  margin-top: 20px;
  display: flex;
  gap: 28px;
}

.book-row__links a {
  font-size: 0.8rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border-bottom: 1px solid var(--color-border);
  padding-bottom: 3px;
  transition: border-color 0.2s var(--ease);
}

.book-row__links a:hover {
  border-color: var(--color-text);
}

@media (pointer: coarse) {
  .book-row__links a {
    display: inline-block;
    padding: 13px 0;
    margin: -13px 0;
  }
}

@media (max-width: 640px) {
  .book-row {
    flex-direction: column;
  }

  .book-row__media {
    width: 100%;
    max-width: 220px;
  }
}

/* ---------------------------------------------------------
   Films page — same row format as the Photography Books list, no
   hero above it. 16:9 thumbnail instead of a book's 612:792.
   --------------------------------------------------------- */

.video-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.video-row {
  display: flex;
  gap: clamp(24px, 4vw, 56px);
  align-items: flex-start;
  padding: clamp(32px, 5vw, 56px) 0;
  border-top: 1px solid var(--color-border);
}

.video-row:last-child {
  border-bottom: 1px solid var(--color-border);
}

.video-row__media {
  position: relative;
  display: block;
  flex: 0 0 auto;
  width: clamp(220px, 32vw, 360px);
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: #101820;
  box-shadow:
    0 10px 16px rgba(0, 0, 0, 0.2),
    0 6px 20px rgba(0, 0, 0, 0.19);
}

.video-row__media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.4s var(--ease);
}

.video-row__media:hover img {
  transform: scale(1.03);
}

.video-row__play {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}

.video-row__play svg {
  width: 52px;
  height: 52px;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.4));
}

.video-row__content {
  flex: 0 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  background: var(--color-bg);
  padding: 6px 10px;
  margin: -6px -10px;
}

.video-row__title {
  margin: 0 0 6px;
  font-size: clamp(1.2rem, 2vw, 1.5rem);
  font-weight: 400;
  letter-spacing: -0.01em;
}

.video-row__meta {
  margin: 0 0 18px;
  font-size: 0.85rem;
  color: var(--color-text-muted);
}

.video-row__description {
  margin: 0;
  max-width: 640px;
  font-size: clamp(0.95rem, 1.3vw, 1.05rem);
  line-height: 1.7;
  color: var(--color-text);
}

.video-row__links {
  margin-top: 20px;
  display: flex;
  gap: 28px;
}

.video-row__links a {
  font-size: 0.8rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border-bottom: 1px solid var(--color-border);
  padding-bottom: 3px;
  transition: border-color 0.2s var(--ease);
}

.video-row__links a:hover {
  border-color: var(--color-text);
}

/* These are small uppercase text links — comfortably clickable
   with a mouse, but under the ~40px minimum recommended for a
   touch target. Pad out the tappable area on touch devices only,
   with a matching negative margin so the row's visible height and
   the gap between rows stays exactly as designed. */
@media (pointer: coarse) {
  .video-row__links a {
    display: inline-block;
    padding: 13px 0;
    margin: -13px 0;
  }
}

.video-row--pending .video-row__media {
  display: flex;
  align-items: center;
  justify-content: center;
  background: #fff;
  border: 1px dashed var(--color-border);
  box-shadow: none;
  color: var(--color-text-muted);
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-align: center;
  padding: 20px;
}

@media (max-width: 640px) {
  .video-row {
    flex-direction: column;
  }

  .video-row__media {
    width: 100%;
    max-width: 360px;
  }
}

/* ---------------------------------------------------------
   Music page — same row format as Films, but the media slot
   holds a live Spotify embed instead of a linked thumbnail.
   --------------------------------------------------------- */

.music-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.music-row {
  padding: clamp(32px, 5vw, 56px) 0;
  border-top: 1px solid var(--color-border);
}

.music-row:last-child {
  border-bottom: 1px solid var(--color-border);
}

.music-row__top {
  display: flex;
  gap: clamp(24px, 4vw, 56px);
  align-items: flex-start;
}

.music-row__media {
  display: block;
  flex: 0 0 auto;
  width: clamp(260px, 34vw, 400px);
  border-radius: 12px;
  overflow: hidden;
  box-shadow:
    0 10px 16px rgba(0, 0, 0, 0.2),
    0 6px 20px rgba(0, 0, 0, 0.19);
}

.music-row__media iframe {
  display: block;
  width: 100%;
  border: 0;
}

.music-row__media img {
  display: block;
  width: 100%;
  height: auto;
}

.music-row__content {
  flex: 0 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  background: var(--color-bg);
  padding: 6px 10px;
  margin: -2px -10px -6px;
}

.music-row__artist {
  margin: 0 0 2px;
  font-size: 0.85rem;
  color: var(--color-text-muted);
}

.music-row__title {
  margin: 0 0 6px;
  font-size: clamp(1.2rem, 2vw, 1.5rem);
  font-weight: 400;
  letter-spacing: -0.01em;
}

.music-row__meta {
  list-style: none;
  margin: 0 0 18px;
  padding: 0 0 0 4px;
}

.music-row__meta li {
  font-size: 0.85rem;
  color: var(--color-text-muted);
  line-height: 1.6;
}

.music-row__meta-label {
  color: var(--color-text-muted);
}

.page-content p.music-row__description {
  margin: 0;
  max-width: 640px;
  font-size: 0.85rem;
  line-height: 1.6;
  color: var(--color-text-muted);
}

.music-row__links {
  margin-top: 20px;
  display: flex;
  gap: 28px;
}

.music-row__links a {
  font-size: 0.8rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border-bottom: 1px solid var(--color-border);
  padding-bottom: 3px;
  transition: border-color 0.2s var(--ease);
}

.music-row__links a:hover {
  border-color: var(--color-text);
}

@media (pointer: coarse) {
  .music-row__links a {
    display: inline-block;
    padding: 13px 0;
    margin: -13px 0;
  }
}

/* A track-by-track list for entries without a Spotify/Bandcamp
   embed — each song title is itself the link out. */
.music-row__tracklist {
  list-style: none;
  margin: 18px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.music-row__tracklist a {
  font-size: 0.95rem;
  border-bottom: 1px solid transparent;
  transition: border-color 0.2s var(--ease);
}

.music-row__tracklist a:hover {
  border-color: var(--color-text);
}

@media (pointer: coarse) {
  .music-row__tracklist a {
    display: inline-block;
    padding: 11px 0;
    margin: -11px 0;
  }
}

.music-row__video {
  display: flex;
  gap: clamp(24px, 4vw, 56px);
  align-items: flex-start;
  margin-top: clamp(24px, 4vw, 40px);
}

.music-row__top .video-row__media,
.music-row__video .video-row__media {
  width: clamp(260px, 34vw, 400px);
}

@media (max-width: 640px) {
  .music-row__top,
  .music-row__video {
    flex-direction: column;
  }

  .music-row__media,
  .music-row__video .video-row__media {
    width: 100%;
    max-width: 400px;
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  .intro__block {
    transition: none !important;
  }
}

/* ---------------------------------------------------------
   Contact modal — replaces the old standalone Contact page.
   Triggered from the header nav's Contact link and the
   homepage's "Get in touch" link (see js/main.js). The form
   posts to Formspree so the destination address never appears
   in this page's source.
   --------------------------------------------------------- */

.contact-modal {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.contact-modal[aria-hidden="true"] {
  display: none;
}

.contact-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(16, 24, 32, 0.5);
}

.contact-modal__dialog {
  position: relative;
  width: 100%;
  max-width: 440px;
  max-height: calc(100vh - 48px);
  overflow-y: auto;
  background: var(--color-bg);
  padding: clamp(28px, 5vw, 40px);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.25);
}

.contact-modal__close {
  position: absolute;
  top: 14px;
  right: 14px;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 0;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--color-text-muted);
  cursor: pointer;
  transition: color 0.2s var(--ease);
}

.contact-modal__close:hover {
  color: var(--color-text);
}

.contact-modal__title {
  margin: 0 0 24px;
  font-size: clamp(1.6rem, 3vw, 2rem);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: -0.03em;
}

.contact-form__field {
  margin-bottom: 18px;
}

.contact-form__field label {
  display: block;
  margin-bottom: 6px;
  font-size: 0.78rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.contact-form__field input,
.contact-form__field textarea {
  display: block;
  width: 100%;
  padding: 10px 0;
  font-family: inherit;
  font-size: 1rem;
  color: var(--color-text);
  background: none;
  border: 0;
  border-bottom: 1px solid var(--color-border);
  border-radius: 0;
  resize: vertical;
  transition: border-color 0.2s var(--ease);
}

.contact-form__field input:focus,
.contact-form__field textarea:focus {
  outline: none;
  border-color: var(--color-text);
}

.contact-form__submit {
  margin-top: 8px;
  padding: 12px 28px;
  font-family: inherit;
  font-size: 0.85rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-bg);
  background: var(--color-text);
  border: 0;
  cursor: pointer;
  transition: opacity 0.2s var(--ease);
}

.contact-form__submit:hover {
  opacity: 0.85;
}

.contact-form__submit:disabled {
  opacity: 0.5;
  cursor: default;
}

.contact-form__status {
  margin: 14px 0 0;
  font-size: 0.9rem;
}

.contact-form__status[data-state="success"] {
  color: var(--color-text);
}

.contact-form__status[data-state="error"] {
  color: #b3261e;
}

/* ---------------------------------------------------------
   Video Game page — SH_FINAL92, played in-browser via Ruffle
   (see video-game.html). Stage is 600x300 (2:1), extracted
   from the game's original Flash Projector .exe.
   --------------------------------------------------------- */

.game-player-wrap {
  position: relative;
  max-width: 900px;
  margin: clamp(24px, 4vw, 40px) auto 0;
}

.game-player {
  position: relative;
  width: 100%;
  aspect-ratio: 2 / 1;
  background: #101820;
  box-shadow:
    0 10px 16px rgba(0, 0, 0, 0.2),
    0 6px 20px rgba(0, 0, 0, 0.19);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

/* Maximize button — always visible, top-right corner of the
   player, safe from Ruffle's container.innerHTML wipe since it
   lives outside #game-player rather than inside it. */
.game-fullscreen-btn {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: rgba(16, 24, 32, 0.55);
  color: #fff;
  transition: background 0.15s var(--ease);
}

.game-fullscreen-btn:hover,
.game-fullscreen-btn:focus-visible {
  background: rgba(16, 24, 32, 0.85);
}

.game-fullscreen-btn svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Maximized state — real :fullscreen (desktop + Android Chrome)
   sizes the wrapper to the viewport automatically, but its own
   width/aspect-ratio rules still need overriding to actually
   fill that space; the .is-fullscreen class also drives the
   manual CSS fallback used where the Fullscreen API is
   unavailable for non-video elements (notably iPhone Safari). */
.game-player-wrap:fullscreen,
.game-player-wrap.is-fullscreen {
  max-width: none;
  width: 100vw;
  /* Plain vh first as a fallback for browsers that don't
     recognize dvh at all (the whole declaration is otherwise
     skipped, not just the unit) — dvh then overrides it where
     supported, tracking the address bar showing/hiding instead
     of leaving a gap or overshooting the page underneath it. */
  height: 100vh;
  height: 100dvh;
  margin: 0;
  padding: clamp(12px, 3vw, 24px);
  background: #101820;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
}

.game-player-wrap.is-fullscreen {
  position: fixed;
  inset: 0;
  z-index: 500;
}

.game-player-wrap:fullscreen .game-player,
.game-player-wrap.is-fullscreen .game-player {
  width: 100%;
  max-width: calc((100dvh - 140px) * 2);
  margin: 0;
}

/* Rotate-to-play prompt — the game's stage is a wide 2:1 shape,
   so on a phone held in portrait it plays cramped. Rather than
   letting that happen, this overlay blocks play and asks for
   landscape instead. Hidden by default; only shown on an actual
   touch device (pointer: coarse) currently held in portrait —
   never on a resized desktop window. Disappears automatically
   the moment the phone is turned sideways, since the media
   query then stops matching. */
.game-rotate-hint {
  display: none;
  position: absolute;
  inset: 0;
  z-index: 20;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 24px;
  background: #101820;
  color: #fff;
  text-align: center;
}

.game-rotate-hint svg {
  width: 40px;
  height: 40px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  animation: game-rotate-hint-spin 1.8s ease-in-out infinite;
}

.game-rotate-hint p {
  /* Override .page-content p's white readability box below —
     this text already sits on a solid dark background, not the
     film-grain page background that rule is meant for. */
  background: none;
  padding: 0;
  margin: 0;
  margin-left: 0;
  margin-right: 0;
  display: block;
  font-size: 0.95rem;
  max-width: 260px;
  color: inherit;
}

@keyframes game-rotate-hint-spin {
  0%, 15% {
    transform: rotate(0deg);
  }
  45%, 70% {
    transform: rotate(-90deg);
  }
  100% {
    transform: rotate(-90deg);
  }
}

@media (orientation: portrait) and (pointer: coarse) {
  .game-rotate-hint {
    display: flex;
  }

  .game-fullscreen-btn {
    visibility: hidden;
    pointer-events: none;
  }
}

.game-player ruffle-object,
.game-player embed,
.game-player object {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.game-player__loading,
.game-player__error {
  margin: 0;
  padding: 0 24px;
  color: #fff;
  font-size: 0.9rem;
  text-align: center;
}

