/* ==========================================================================
   Tokens
   ========================================================================== */

:root {
  /* "only" stops browsers in dark mode from auto-darkening the page. */
  color-scheme: only light;

  --paper: #ffffff;
  --ink: #000000;
  --ink-2: #6b6b6b;          /* secondary text: dates, meta */
  --rule: #e6e6e6;           /* hairlines */
  --edge: var(--ink);        /* the edge of each print's white sheet */
  --placeholder: #f1f1f1;
  --placeholder-ink: #a3a3a3;

  --font: "Libre Franklin", "Franklin Gothic", "Helvetica Neue", Arial, sans-serif;

  --gutter: clamp(16px, 4vw, 64px);   /* page side margin */
  --max: 1680px;
  --col-gap: clamp(24px, 3vw, 48px);
}

/* ==========================================================================
   Base
   ========================================================================== */

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

html {
  background: var(--paper);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

html:has(.viewer[open]) {
  overflow: hidden;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font);
  font-size: 1rem;
  line-height: 1.45;
  font-feature-settings: "kern", "liga";
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, p, ol, ul, figure {
  margin: 0;
}

ol, ul {
  padding: 0;
  list-style: none;
}

img {
  display: block;
}

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

button {
  font: inherit;
  color: inherit;
}

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

.skip {
  position: absolute;
  left: var(--gutter);
  top: -100px;
  padding: 8px 12px;
  background: var(--ink);
  color: var(--paper);
  z-index: 30;
}

.skip:focus {
  top: 12px;
}

/* ==========================================================================
   Header

   A bar of its own, closed off by a hairline: the name in the middle,
   Work and About together on the right. Its contents line up with the
   1440px grid; the hairline runs full width.

   On the Work page the bar is pinned to the top of the window the whole
   way down. Over the opening screen it shows only the name; Work, About
   and the hairline fade in once the grid has slid halfway up
   (.in-room, set by gallery.js).
   ========================================================================== */

:root {
  --bar: 58px;               /* header height */
}

.site-header {
  --inset: max(var(--gutter), calc((100% - 1440px) / 2 + var(--gutter)));
  display: flex;
  align-items: center;
  height: var(--bar);
  padding-inline: var(--inset);
  border-bottom: 1px solid var(--rule);
  background: var(--paper);
  font-size: 0.8125rem;
  line-height: 1;
}

/* minmax(0, 1fr) keeps the two outer columns exactly equal whatever is in
   them (plain 1fr lets a wide one grow), so the name is always centred. */
.site-nav {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: 24px;
  width: 100%;
}

/* The name sits in the middle column, so it stays centred on the page;
   Work and About share the right-hand column. */
.site-nav .wordmark {
  grid-column: 2;
}

/* If Work and About don't fit beside the name (a narrow phone with large
   system text), About drops under Work rather than pushing into the name. */
.site-nav__links {
  grid-column: 3;
  justify-self: end;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 6px 24px;
}

/* Phones: tighter spacing so Work and About fit their column beside the
   centred name. */
@media (max-width: 599px) {
  .site-nav {
    column-gap: 12px;
  }
  .site-nav__links {
    column-gap: 18px;
  }
}

@media (max-width: 359px) {
  .site-nav__links {
    column-gap: 12px;
  }
}

.wordmark {
  font-weight: 700;
}

.page-work .site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 20;
  border-bottom-color: transparent;
  transition: border-color 400ms ease;
}

.page-work .site-nav__side {
  opacity: 0;
  pointer-events: none;
  transition: opacity 400ms ease;
}

.page-work.in-room .site-header {
  border-bottom-color: var(--rule);
}

.page-work.in-room .site-nav__side,
.page-work .site-header:focus-within .site-nav__side {
  opacity: 1;
  pointer-events: auto;
}

/* With the name opening, the big name already fills the screen, so the
   header's copy waits for the grid too. */
.page-work[data-opening="name"]:not(.in-room) .site-header:not(:focus-within) .wordmark {
  opacity: 0;
}

.page-work .wordmark {
  transition: opacity 400ms ease;
}

.site-nav a {
  text-underline-offset: 0.3em;
  text-decoration-thickness: 1px;
}

.site-nav a:hover,
.site-nav a[aria-current="page"] {
  text-decoration-line: underline;
}

/* ==========================================================================
   Opening screen

   Pinned to the viewport while the gallery slides up over it, like a sheet
   of paper laid on top. Two modes, set by data-opening on .opening:

   print  One small print alone in the white. The .runway after it adds
          scroll distance: over the first 60% of a screen gallery.js grows
          the print to full size, then it holds there for another 40%,
          caption showing, before the gallery starts to slide up.
   name   The name as a wordmark, fading as you scroll.
   ========================================================================== */

.opening {
  position: sticky;
  top: 0;
  z-index: 0;
  display: grid;
  place-items: center;
  height: 100vh;
  height: 100svh;
  padding-inline: var(--gutter);
}

.opening__name {
  font-size: clamp(3rem, 9vw, 8.5rem);
  font-weight: 800;
  letter-spacing: -0.035em;
  line-height: 1;
  text-align: center;
}

.opening__print {
  display: block;
  width: 132px;              /* gallery.js takes over from here */
  padding: 0;
  border: 0;
  background: none;
  text-align: left;
  cursor: zoom-in;
}

/* --reveal goes 0 to 1 over the end of the growth (set by gallery.js). */
.opening__print .work__caption {
  opacity: var(--reveal, 0);
}

/* Print mode keeps the name for screen readers and search engines only. */
.opening[data-opening="print"] .opening__name {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.opening[data-opening="name"] .opening__print {
  display: none;
}

/* No snap points here: the print grows under free scrolling.
   60% of a screen to grow (GROW in gallery.js) + 40% holding at full size. */
.opening[data-opening="print"] + .runway {
  height: 100vh;
  height: 100svh;
}

@supports (animation-timeline: scroll()) {
  .opening[data-opening="name"] .opening__name {
    animation: recede linear both;
    animation-timeline: scroll(root);
    animation-range: 0 80vh;
  }

  @keyframes recede {
    to { opacity: 0; }
  }
}

/* ==========================================================================
   Gallery

   Each work is a 4:5 white sheet, the same proportion as an exported print.
   The photograph is centred in a box 86% wide and 64% tall, so portraits
   and landscapes come out at the same size they do on paper. The caption
   hangs below the photograph, aligned to its left edge.
   ========================================================================== */

/* The viewing room: one screen tall, sliding up over the pinned opening.
   The header bar sits at its top; below it, .gallery is its own scroll
   area where the browser's native snapping moves one row at a time and
   locks it in the centre, in a single continuous motion.

   .gallery only becomes scrollable (.is-live, set by gallery.js) once the
   room fills the screen, so on the way in, the page itself keeps
   scrolling and the opening is never snapped. */
.paper {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100svh;
  padding-top: var(--bar);   /* the pinned header covers this strip */
  background: var(--paper);
}

.gallery {
  flex: 1;
  min-height: 0;
  overflow-y: hidden;
  scrollbar-width: none;
}

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

/* Snapping switches on with scrolling, so it settles on the row that is
   already showing rather than one it remembered from earlier. */
.gallery.is-live {
  overflow-y: auto;
  scroll-snap-type: y mandatory;
}

/* Flex rather than grid so a short last row (1 or 2 prints) sits centred
   instead of hugging the left edge. --lead (set by gallery.js) is the
   space that lets the first and last rows rest in the centre too. */
.works {
  --cols: 1;
  --gap: clamp(16px, 2.5vw, 40px);
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--gap);
  max-width: 1440px;
  margin-inline: auto;
  padding: var(--lead, 48px) var(--gutter);
}

/* --sheet-max (set by gallery.js) keeps a print shorter than the room,
   so a row always fits on screen, even on a phone turned sideways. */
.works > li {
  flex: 0 0 calc((100% - (var(--cols) - 1) * var(--gap)) / var(--cols));
  min-width: 0;
  max-width: var(--sheet-max, none);
  scroll-snap-align: center;
}

@media (min-width: 700px)  { .works { --cols: 2; } }
@media (min-width: 1100px) { .works { --cols: 3; } }

.work {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  text-align: left;
  cursor: zoom-in;
}

/* A hairline edge makes the white sheet visible on the white page, so every
   print reads as the same size whatever its orientation. */
.work__sheet {
  position: relative;
  display: grid;
  place-items: center;
  aspect-ratio: 4 / 5;
  container-type: inline-size;
  background: var(--paper);
  box-shadow: inset 0 0 0 var(--edge-w, 1px) var(--edge);   /* --edge-w: see the cover on touch screens */
  transition: box-shadow 200ms ease;
}

/* On hover the edge thickens a touch, like pressing on the paper. */
.work:hover .work__sheet {
  box-shadow: inset 0 0 0 2px var(--edge);
}

/* Fit inside 86cqw × 80cqw (80cqw = 64% of the sheet's 125cqw height). */
.work__plate {
  position: relative;
  display: block;
  width: min(86cqw, 80cqw * var(--ratio-n));
}

.work__media {
  display: block;
  width: 100%;
  aspect-ratio: var(--ratio);
}

img.work__media {
  height: auto;
  background: var(--placeholder);   /* visible only while loading */
}

.work__no {
  position: absolute;
  top: 7cqw;
  left: 7cqw;
  font-size: 0.6875rem;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
}

.work__caption {
  position: absolute;
  top: 100%;
  left: 0;
  display: block;
  padding-top: 0.9em;
  font-size: 0.875rem;
  line-height: 1.35;
  white-space: nowrap;
}

.work__title {
  display: block;
  font-style: italic;
  text-underline-offset: 0.2em;
  text-decoration-thickness: 1px;
}

.work__date {
  display: block;
  color: var(--ink-2);
}

.work:hover .work__title {
  text-decoration-line: underline;
}

.site-footer {
  padding: clamp(96px, 12vw, 160px) var(--gutter) 32px;
  text-align: center;
  font-size: 0.75rem;
  color: var(--ink-2);
}

/* Inside the room the footer follows the last row's own spacing. */
.gallery .site-footer {
  padding-top: 0;
  scroll-snap-align: end;
}

.site-footer a {
  text-underline-offset: 0.25em;
  text-decoration-thickness: 1px;
}

.site-footer a:hover {
  text-decoration-line: underline;
}

/* Small Instagram and GitHub marks above the footer line. */
.social {
  display: flex;
  justify-content: center;
  gap: 20px;
  margin-bottom: 18px;
}

.social a {
  display: block;
  color: var(--ink);
  transition: color 200ms ease;
}

.social a:hover {
  color: var(--ink-2);
}

.social svg {
  display: block;
  width: 18px;
  height: 18px;
}

/* ==========================================================================
   Placeholder boxes (entries with src: null)
   ========================================================================== */

.placeholder {
  display: flex;
  align-items: flex-end;
  padding: 10px 12px;
  background: var(--placeholder);
  color: var(--placeholder-ink);
  font-size: 0.75rem;
  letter-spacing: 0.01em;
}

/* ==========================================================================
   Wall label (viewer): name / title / date, as in a gallery.
   ========================================================================== */

.label {
  font-size: 1rem;
  line-height: 1.3;
}

.label__artist {
  font-weight: 700;
  margin-bottom: 0.6em;
}

.label__title {
  font-style: italic;
}

/* ==========================================================================
   Viewer (lightbox)
   ========================================================================== */

.viewer {
  width: 100vw;
  height: 100vh;
  height: 100dvh;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: var(--paper);
  color: var(--ink);
}

.viewer[open] {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
}

.viewer::backdrop {
  background: var(--paper);
}

.viewer__bar,
.viewer__foot {
  display: flex;
  justify-content: space-between;
  gap: 24px;
  padding-inline: var(--gutter);
}

.viewer__bar {
  align-items: baseline;
  padding-block: 20px;
}

.viewer__count {
  font-size: 0.875rem;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}

.viewer__foot {
  align-items: flex-end;
  padding-block: 20px 28px;
}

.viewer__nav {
  display: flex;
  gap: 28px;
}

/* The cover opens on its own: nothing to step through. */
.viewer.is-solo .viewer__nav {
  visibility: hidden;
}

/* The stage is a size container so the photo can be fitted to whichever
   of its width or height runs out first. */
.viewer__stage {
  container-type: size;
  display: grid;
  place-items: center;
  margin-inline: var(--gutter);
  cursor: zoom-out;
}

.viewer__media {
  --fit: min(100cqw, 100cqh * var(--ratio-n));
  width: var(--fit);
  aspect-ratio: var(--ratio);
  cursor: default;
}

img.viewer__media {
  height: auto;
  opacity: 0;
  transition: opacity 240ms ease;
}

img.viewer__media.is-loaded {
  opacity: 1;
}

.text-button {
  padding: 4px 0;
  border: 0;
  background: none;
  font-size: 1rem;
  cursor: pointer;
  text-underline-offset: 0.3em;
  text-decoration-thickness: 1px;
}

.text-button:hover {
  text-decoration-line: underline;
}

@media (max-width: 599px) {
  .viewer__foot {
    flex-direction: column;
    align-items: stretch;
    gap: 20px;
  }
  .viewer__nav {
    justify-content: space-between;
  }
}

/* ==========================================================================
   About

   One narrow centred column, like a printed page: "About", then the
   biography. The page fills the screen so the footer rests at the bottom.
   ========================================================================== */

.page {
  max-width: var(--max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.page-about {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100svh;
}

.about {
  flex: 1;
  width: 100%;
  max-width: calc(34rem + 2 * var(--gutter));
  padding-top: clamp(64px, 12vw, 176px);
}

.about__title {
  font-size: clamp(2.25rem, 4.5vw, 3.5rem);
  font-weight: 800;
  letter-spacing: -0.035em;
  line-height: 0.95;
}

.about__bio {
  margin-top: 48px;
}

.about__label {
  margin-bottom: 16px;
  font-size: 0.9375rem;
  font-weight: 700;
}

.prose p + p {
  margin-top: 1em;
}

.prose {
  font-size: 1.0625rem;
  line-height: 1.55;
}

.prose a {
  text-decoration-line: underline;
  text-underline-offset: 0.25em;
  text-decoration-thickness: 1px;
}

/* ==========================================================================
   Touch screens (phones and tablets; .is-touch is set by gallery.js)

   Desktop is untouched. On a touch screen:
   - the cover grows by being scaled (transform), which the graphics chip
     does without laying the page out again, so it keeps up with a finger;
   - the grid scrolls with the page instead of in its own area, and the
     browser's own snapping, built into the swipe's momentum, settles each
     print in the middle of the screen below the header.
   ========================================================================== */

.is-touch .opening__print {
  will-change: transform;
}

.is-touch .opening__print .work__sheet {
  transition: none;
}

.is-touch .paper {
  height: auto;
}

.is-touch .gallery,
.is-touch .gallery.is-live {
  flex: none;
  overflow: visible;
  scroll-snap-type: none;
}

html.is-touch {
  scroll-snap-type: y proximity;
  scroll-padding-top: var(--bar);   /* centre prints in the space below the header */
}

/* ==========================================================================
   Motion
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .work__sheet,
  img.viewer__media,
  .page-work .site-header,
  .page-work .site-nav__side,
  .page-work .wordmark {
    transition: none;
  }
  .opening[data-opening] .opening__name {
    animation: none;
  }
  .opening[data-opening="print"] + .runway {
    height: 0;
  }
}
