:root {
  /* Half of a crossfade: the visible image fades out, the new src swaps in
     (already warmed by prefetchImg, so effectively instant), then it fades
     back in. app.js reads this value so JS timing can't drift from the CSS.
     Keep the unit in ms — app.js parses the number directly. */
  --fade: 180ms;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

.hp {
  position: absolute;
  left: -9999px;
}

.skip-nav {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 999;
  padding: 0.5rem 1rem;
  background: #222;
  color: #fff;
  font-size: 0.85rem;
  text-decoration: none;
}

.skip-nav:focus { left: 0; }

:focus-visible {
  outline: 2px solid #222;
  outline-offset: 2px;
}

/* The series view is a tabindex="-1" landing spot, not a control: app.js moves
   focus here when a series opens so a keyboard or screen-reader user lands in
   the new content instead of back at the top of the document. Nothing can Tab
   to it, so the ring marks no reachable target — it just draws a 1400px box
   around the whole view, which is what a visitor sees on any deep link, since
   Chrome treats programmatic focus with no prior interaction as keyboard
   focus. Controls inside it keep their rings. */
.series-view:focus,
.series-view:focus-visible { outline: none; }

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

body {
  font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
  background: #fff;
  color: #222;
  -webkit-font-smoothing: antialiased;
  line-height: 1.5;
}

header {
  padding: 3rem 2rem 2rem;
  text-align: center;
}

header h1 {
  font-size: 1.2rem;
  font-weight: 400;
  letter-spacing: 0.2em;
}

/* Wraps rather than overflows: the links plus their letter-spacing exceed a
   320px viewport, which is what body { overflow-x: hidden } used to mask.
   Wrapping keeps it contained regardless of how many links or how long a
   name the header carries. */
nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.75rem 2rem;
  padding: 0 2rem 2.5rem;
  font-size: 0.88rem;
  letter-spacing: 0.12em;
  position: relative;
}

nav a {
  color: #666;
  text-decoration: none;
  transition: color 0.2s;
}

nav a:hover, nav a.active { color: #008555; }

nav a.ig { display: inline-flex; align-items: center; }
nav a.ig svg { width: 16px; height: 16px; fill: #666; transition: fill 0.2s; }
nav a.ig:hover svg { fill: #00A86B; }

/* ---- Series Grid (landing for Work) ---- */
.series-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5rem;
  padding: 0 3rem;
  max-width: 1400px;
  margin: 0 auto;
}

/* Series tiles and thumbnails are real <button>s so Enter/Space, focus order,
   and AT role come from the platform instead of a tabindex/keydown shim.
   These strip the UA button chrome without touching the semantics. */
.series-tile,
.thumb-rail .thumb {
  appearance: none;
  -webkit-appearance: none;
  background: none;
  border: 0;
  margin: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  text-align: inherit;
  display: block;
  cursor: pointer;
}

.series-tile {
  opacity: 0;
  transition: opacity 0.35s ease;
  width: 100%;
}

.series-tile.loaded { opacity: 1; }

/* <span> so it's valid inside the <button> tile; block for the aspect box. */
.series-tile .tile-img {
  display: block;
  overflow: hidden;
  aspect-ratio: 1;
  background: #f5f5f5;
}

.series-tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.5s ease;
  border: 2px solid #e0e0e0;
}

/* View fade-in */
.fade-view {
  opacity: 0;
  transition: opacity 0.35s ease;
}

.fade-view.in { opacity: 1; }

.series-tile:hover img { transform: scale(1.04); }

/* Cover crops that read better off-centre are a property of the artwork, not
   of the stylesheet — set per-work via `focalPoint` in data/artworks.json. */

.series-label {
  display: block;
  padding: 0.8rem 0 0;
  text-align: center;
}

.series-label span {
  color: #333;
  font-size: 0.82rem;
  font-weight: 300;
  letter-spacing: 0.12em;
}

/* ---- Home slideshow ---- */
.home {
  position: relative;
  max-width: 1100px;
  margin: 0 auto;
  padding: 0 3rem;
}

.home-slide {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.home-slide .img-wrap {
  width: 100%;
  height: 72vh;
  display: flex;
  align-items: center;
  justify-content: center;
}

.home-slide img {
  max-width: 100%;
  max-height: 72vh;
  width: auto;
  height: auto;
  object-fit: contain;
  display: block;
  opacity: 0;
  transition: opacity var(--fade) ease;
  border: 2px solid #e0e0e0;
}

.home-slide img.visible { opacity: 1; }
.home-slide img.fading { opacity: 0; }

.home-info {
  text-align: center;
  padding-top: 1rem;
}

.home-info .title {
  font-size: 0.95rem;
  font-weight: 300;
  letter-spacing: 0.04em;
  color: #333;
}

.home-info .meta {
  font-size: 0.8rem;
  color: #767676;
  margin-top: 0.25rem;
  letter-spacing: 0.03em;
}

.home-nav {
  position: absolute;
  top: 40%;
  background: none;
  border: none;
  color: #767676;
  font-size: 2rem;
  cursor: pointer;
  padding: 0.5rem;
  transition: color 0.2s;
  user-select: none;
}

.home-nav:hover { color: #008555; }
.home-prev { left: 0.5rem; }
.home-next { right: 0.5rem; }

/* ---- Series Detail View ---- */
.series-view {
  display: none;
  height: calc(100vh - 160px);
  max-width: 1400px;
  margin: 0 auto;
  padding: 0 2rem;
  opacity: 0;
  transition: opacity 0.35s ease;
}

.series-view.visible { display: flex; gap: 1.5rem; }
.series-view.in { opacity: 1; }


nav .back-link {
  display: none;
  position: absolute;
  left: max(3rem, calc((100% - 1400px) / 2 + 3rem));
  color: #767676;
  text-decoration: none;
  font-size: 0.72rem;
  transition: color 0.2s;
}

nav .back-link:hover { color: #008555; }
nav .back-link.visible { display: inline; }

/* Main image area */
.main-image {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-width: 0;
  min-height: calc(100vh - 280px);
}

.main-image img {
  max-width: 100%;
  max-height: calc(100vh - 280px);
  width: auto;
  height: auto;
  object-fit: contain;
  display: block;
  border: 2px solid #e0e0e0;
  transition: opacity var(--fade) ease;
}

.main-image img.swapping { opacity: 0; }

/* Mobile carousel (built in JS, shown only at <=600px; see media query) */
.carousel { display: none; }
/* Home thumbnail footer is mobile-only (desktop home stays a clean hero) */
#home-thumb-rail { display: none; }

.main-image-info {
  text-align: center;
  padding-top: 1rem;
}

.main-image-info .title {
  font-size: 0.95rem;
  font-weight: 300;
  letter-spacing: 0.04em;
  color: #333;
}

.main-image-info .meta {
  font-size: 0.8rem;
  color: #767676;
  margin-top: 0.25rem;
  letter-spacing: 0.03em;
}

.inquire-link {
  display: inline-block;
  margin-top: 0.5rem;
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  color: #767676;
  text-decoration: none;
  transition: color 0.2s;
}

.inquire-link:hover { color: #008555; }

/* Thumbnail rail (right side, vertical) */
.thumb-rail {
  width: 80px;
  flex-shrink: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 4px 0;
  scrollbar-width: none;
}

.thumb-rail::-webkit-scrollbar { display: none; }

.thumb-rail .thumb {
  width: 100%;
  aspect-ratio: 1;
  cursor: pointer;
  overflow: hidden;
  opacity: 0.4;
  transition: opacity 0.25s;
  flex-shrink: 0;
}

.thumb-rail .thumb:hover { opacity: 0.75; }
.thumb-rail .thumb.active { opacity: 1; }

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

/* ---- About ---- */
.about {
  display: none;
  max-width: 900px;
  margin: 0 auto;
  padding: 2rem 2rem 4rem;
  line-height: 1.8;
  font-size: 0.95rem;
  color: #444;
}

.about.visible { display: block; }
.about p { max-width: 600px; margin-left: auto; margin-right: auto; }
.about p + p { margin-top: 1rem; }

.cv {
  margin-top: 2.5rem;
  padding-left: calc((100% - 600px) / 2);
}

.cv h3 {
  font-size: 0.82rem;
  font-weight: 400;
  letter-spacing: 0.12em;
  color: #333;
  margin-top: 2rem;
  margin-bottom: 0.5rem;
}

.cv ul {
  list-style: none;
  font-size: 0.88rem;
  color: #555;
  line-height: 1.8;
}

.cv .cv-year {
  display: inline-block;
  width: 4.5rem;
  flex-shrink: 0;
  color: #767676;
}

.cv li {
  display: flex;
}

.cv a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid #ddd;
  transition: border-color 0.2s;
}

.cv a:hover { border-color: #008555; }

/* ---- Blog ---- */
.blog {
  display: none;
  max-width: 600px;
  margin: 0 auto;
  padding: 2rem 2rem 4rem;
}

.blog.visible { display: block; }

.blog-posts { list-style: none; }
.blog-posts p { font-size: 0.95rem; color: #444; }

.blog-post {
  padding: 1.5rem 0;
  border-bottom: 1px solid #eee;
}

.blog-post:first-child { padding-top: 0; }

.blog-post .post-title {
  font-size: 0.95rem;
  font-weight: 300;
  letter-spacing: 0.04em;
  color: #333;
}

.blog-post .post-title a {
  color: inherit;
  text-decoration: none;
}

.blog-post .post-title a:hover { color: #008555; }

.blog-post .post-date {
  font-size: 0.8rem;
  color: #999;
  margin-top: 0.25rem;
  letter-spacing: 0.03em;
}

.blog-post .post-excerpt {
  font-size: 0.9rem;
  color: #666;
  margin-top: 0.5rem;
  line-height: 1.6;
}

/* ---- Contact ---- */
.contact {
  display: none;
  max-width: 480px;
  margin: 0 auto;
  padding: 2rem 2rem 4rem;
}

.contact.visible { display: block; }

.contact form { display: flex; flex-direction: column; gap: 1rem; }

.contact input,
.contact textarea,
.ml-row input {
  font-family: inherit;
  font-size: 0.9rem;
  padding: 0.7rem 0.8rem;
  border: 1px solid #ddd;
  background: #fafafa;
  color: #222;
  outline: none;
  transition: border-color 0.2s;
}

.contact input:focus,
.contact textarea:focus,
.ml-row input:focus { border-color: #999; }

.contact textarea { resize: vertical; min-height: 120px; }

.contact button,
.ml-row button {
  font-family: inherit;
  font-size: 0.8rem;
  letter-spacing: 0.1em;
  padding: 0.75rem 1.3rem;
  border: 1px solid #222;
  background: #222;
  color: #fff;
  cursor: pointer;
  transition: background 0.2s, color 0.2s;
}

.contact button { align-self: flex-start; }

.contact button:hover,
.ml-row button:hover { background: #fff; color: #222; }

.contact .or-email {
  margin-top: 1.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid #eee;
  font-size: 0.85rem;
  color: #767676;
}

.contact .email-link {
  display: inline-block;
  margin-top: 0.5rem;
  color: #222;
  text-decoration: none;
  border-bottom: 1px solid #ccc;
  padding-bottom: 1px;
  transition: border-color 0.2s;
}

.contact .email-link:hover { border-color: #222; }

.contact .form-sent {
  font-size: 0.9rem;
  color: #444;
  line-height: 1.6;
}

.mailing-list {
  margin-top: 2rem;
  padding-top: 2rem;
  border-top: 1px solid #eee;
}

.mailing-list p {
  font-size: 0.85rem;
  color: #767676;
  margin-bottom: 0.75rem;
}

.ml-row {
  display: flex;
  gap: 0.5rem;
}

.ml-row input { flex: 1; }

.ml-msg {
  font-size: 0.82rem;
  margin-top: 0.5rem;
  color: #767676;
}

.ml-msg.error { color: #c44; }
.ml-msg.success { color: #008555; }

/* ---- Footer ---- */
footer {
  text-align: center;
  padding: 3rem 2rem;
  font-size: 0.7rem;
  color: #767676;
  letter-spacing: 0.05em;
}

/* ---- Responsive ---- */
@media (max-width: 900px) {
  .series-grid { grid-template-columns: repeat(2, 1fr); }
  .thumb-rail { width: 60px; }
}

@media (max-width: 600px) {
  header { padding: 2rem 1rem 1.5rem; }
  header h1 { font-size: 1.1rem; }
  nav { gap: 0.6rem 1.2rem; font-size: 0.7rem; padding: 0 1rem 1.5rem; }
  .series-grid { grid-template-columns: 1fr; gap: 1rem; padding: 0 1.5rem; }
  .series-label span { font-size: 0.82rem; }

  /* The absolutely-positioned back link overlaps the centered nav on narrow
     screens; hide it on mobile (the "Work" nav link returns to the grid). */
  nav .back-link,
  nav .back-link.visible { display: none; }

  /* Home becomes a swipe carousel with a thumbnail footer on mobile; the
     single crossfade image and the redundant arrows are hidden. */
  .home { padding: 0 1rem; }
  .home-nav { display: none; }
  .home-slide .img-wrap { display: none; }
  #home-thumb-rail { display: flex; margin-top: 1.5rem; }

  .series-view.visible {
    flex-direction: column;
    height: auto;
    gap: 1rem;
    padding: 0 1rem;
  }
  /* Remove the desktop min-height so the image isn't framed by empty space. */
  .main-image { min-height: auto; }

  /* Swap the single crossfading image for a native scroll-snap carousel. */
  #main-img { display: none; }
  .carousel {
    display: flex;
    width: 100%;
    /* Fit the box to the artwork instead of to a fixed 60vh. An uncropped
       painting can only ever touch two of a box's four edges, so a fixed box
       left a landscape floating in ~300px of empty screen with its caption
       stranded below, while the portrait beside it filled the box — the same
       series looking like two different sizes. app.js sets --ar to the
       artwork's width/height and the height follows from it, so every work
       fills the full width at its own proportions.

       The width term mirrors the layout: 100vw less .series-view's 1rem
       padding, the slide's own 1rem, and the frame on each side of the image.
       --ar-unknown is narrower than any artwork, so a work with no tier data —
       or a carousel app.js never reached — loses the min() and lands on the
       60vh cap rather than on a guess. */
    --frame: 2px;
    --ar-unknown: 0.01;
    height: min(
      calc((100vw - 4rem - var(--frame) * 2) / var(--ar, var(--ar-unknown)) + var(--frame) * 2),
      60vh
    );
    transition: height var(--fade) ease;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .carousel::-webkit-scrollbar { display: none; }
  .carousel .slide {
    flex: 0 0 100%;
    height: 100%;
    box-sizing: border-box;
    padding: 0 1rem;
    scroll-snap-align: center;
    scroll-snap-stop: always;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .carousel .slide img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    border: var(--frame) solid #e0e0e0;
    opacity: 1; /* override .home-slide img { opacity: 0 } from the crossfade */
  }
  .thumb-rail {
    width: 100%;
    flex-direction: row;
    overflow-x: auto;
    overflow-y: hidden;
    gap: 3px;
    padding: 0;
  }
  .thumb-rail .thumb {
    width: 52px;
    aspect-ratio: 1;
  }
}

/* ---- Form states ---- */
button:disabled { opacity: 0.5; cursor: default; }

.form-error {
  font-size: 0.85rem;
  color: #c44;
  margin-top: 0.75rem;
  line-height: 1.6;
}

/* ---- Degraded state (app.js failed) ---- */
/* Every view starts hidden and only JS reveals it, so a broken app.js would
   otherwise render a blank page. boot.js sets .js-failed on <html> when app.js
   fails to load, parse, or finish its synchronous init; these rules fall back
   to the same reveal-everything state as css/noscript.css. The home hero has a
   real src in the HTML, and About/Contact are full content, so the degraded
   page is still usable. */
.js-failed .fade-view,
.js-failed .about,
.js-failed .contact {
  opacity: 1 !important;
  display: block !important;
}

.js-failed .home { display: block !important; }
/* .home-slide img starts at opacity 0 awaiting JS's .visible class */
.js-failed .home-slide img { opacity: 1 !important; }
/* Arrows and the empty JS-built grid have nothing to act on */
.js-failed .home-nav,
.js-failed .series-grid { display: none !important; }

/* ---- Reduced motion ---- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
