/* =============================================================
   The whole site.

   Three templates share this file:
     scroll   — work down the page (work.html, project pages)
     index    — projects.html
     prose    — about, 404

     1. Tokens
     2. Font
     3. Base
     4. Nav
     5. Content column
     6. Scroll template
     7. Projects index
     8. Project page
     9. Prose pages
    10. Footer
    11. Lightbox
    12. Mobile
    13. Reduced motion
   ============================================================= */


/* --- 1. Tokens ---------------------------------------------- */

:root {
  /* Neutral grey, not off-white: a warm ground fights the magenta and
     cobalt casts in the work. */
  --bg:       #fafafa;
  --fg:       #141414;
  --muted:    #707070;   /* 4.7:1 on --bg; #767676 fell just short of the 4.5:1 minimum */
  --rule:     #dcdcdc;

  --nav-w:    200px;
  --gutter:   64px;
  --gap-work: 120px;
  --gap-pair: 32px;     /* print and its sculpture: tighter, so they read as one */
  --text-max: 62ch;
  --img-max:  1400px;
  /* Images are capped on BOTH axes. Width alone means a portrait piece is
     physically twice a landscape one - same column width, double the height.
     A pixel cap, not vh: tying it to the viewer screen height means a
     landscape cannot fill the column on a short laptop. 1000px is about
     what a full-width landscape already reaches, so this leaves those
     alone and only brings the tall work back into line. */
  --img-max-h: 1000px;

  --font: "IBM Plex Mono", ui-monospace, monospace;

  /* TEXTURE IS OFF - see .page-texture below. These stay so it can be
     switched back on in one line. Not in the spec either way; with the
     grain on, the multiply pulled --bg from #fafafa down to about
     #f0f0f0, so this is the spec's grey as actually specified. */
  --texture:       url("../assets/grain-fine.jpg");
  --texture-scale: 620px;
  --texture-alpha: 0.40;
}


/* --- 2. Font ------------------------------------------------
   Self-hosted, never from a CDN. IBM Plex Mono, SIL Open Font License.

   These are IBM's own Latin1 subsets rather than the complete files:
   17KB each instead of 48KB, and they carry everything the site sets -
   ASCII, Latin-1, the dashes, x, (c) and the quote marks.

   The project nav uses ASCII <- and -> rather than the arrow glyphs,
   which live in IBM's Pi subset. That subset was 13.5KB for two
   characters, so it is not shipped at all.

   Two weights only, 400 and 500. Plex Mono's bold is heavy; 600+ reads
   like a warning label. */

@font-face {
  font-family: "IBM Plex Mono";
  src: url("../fonts/IBMPlexMono-Regular-Latin1.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0020-007E, U+00A0-00FF, U+0131, U+0152-0153, U+02C6, U+02DA,
    U+02DC, U+2013-2014, U+2018-201A, U+201C-201E, U+2020-2022,
    U+2026, U+2030, U+2039-203A, U+2044, U+20AC, U+2122, U+2212,
    U+FB01-FB02;
}

@font-face {
  font-family: "IBM Plex Mono";
  src: url("../fonts/IBMPlexMono-Medium-Latin1.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0020-007E, U+00A0-00FF, U+0131, U+0152-0153, U+02C6, U+02DA,
    U+02DC, U+2013-2014, U+2018-201A, U+201C-201E, U+2020-2022,
    U+2026, U+2030, U+2039-203A, U+2044, U+20AC, U+2122, U+2212,
    U+FB01-FB02;
}



/* --- 3. Base ------------------------------------------------ */

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  /* 14, not 15: mono reads larger than a proportional face at the same
     point size. */
  font: 400 14px/1.5 var(--font);
  -webkit-font-smoothing: antialiased;
  /* A column, so on a short page main can grow and push the footer
     to the bottom of the window instead of leaving it mid-screen. */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

img {
  display: block;
  max-width: 100%;
  /* with js/protect.js: no long-press save on iOS, no drag-out */
  -webkit-touch-callout: none;
  -webkit-user-drag: none;
  user-select: none;
}

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

/* Keyboard focus is visible; a mouse click does not leave a ring. */
:focus-visible { outline: 2px solid var(--fg); outline-offset: 3px; }

hr { border: 0; border-top: 1px solid var(--rule); margin: 0; }

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

/* NOT IN THE SPEC — see --texture above. Multiplied against the ground and
   sitting below the content, so it tooths the page without touching the
   work. On a light ground, multiply is the only blend that shows at all. */
.page-texture {
  /* TEXTURE OFF. Delete this one line to bring the grain back. display:none
     also stops the browser fetching the image at all, so it costs nothing
     while it is off. */
  display: none;

  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image: var(--texture);
  background-size: var(--texture-scale);
  mix-blend-mode: multiply;
  opacity: var(--texture-alpha);
}


/* --- 4. Nav ------------------------------------------------- */

.nav {
  position: fixed;
  z-index: 5;
  top: var(--gutter);
  left: var(--gutter);
  width: var(--nav-w);
  line-height: 1.9;
  letter-spacing: -0.01em;
}

.nav-name {
  display: block;
  font-size: 20px;
  font-weight: 500;
  letter-spacing: -0.02em;   /* mono at display size reads loose without it */
  line-height: 1.2;
  margin-bottom: 32px;
}

.nav a { display: block; }
.nav a:hover { text-decoration: underline; }

/* The current page steps back rather than forward. */
.nav a[aria-current="page"] { color: var(--muted); }
.nav a[aria-current="page"]:hover { text-decoration: none; }


/* --- 5. Content column -------------------------------------- */

main {
  position: relative;
  z-index: 2;
  flex: 1 0 auto;      /* takes the spare height on short pages */
  margin-left: calc(var(--nav-w) + var(--gutter) * 2);
  margin-right: var(--gutter);
  max-width: var(--img-max);
  /* Level with the nav. No hero, no title card — the first image is the
     first thing on the page. */
  padding-top: var(--gutter);
}


/* --- 6. Scroll template ------------------------------------- */

.piece { margin: 0 0 var(--gap-work); }

/* max-* on both axes, with width and height auto, so the image fits inside
   whichever constraint binds first and keeps its proportions. Landscapes
   hit the column width; tall work hits the height cap and sits narrower. */
.piece img {
  max-width: 100%;
  /* min() so it respects BOTH: never larger than --img-max-h, and never
     taller than the screen. A fixed pixel cap alone overflows a 1080p
     viewport; a vh cap alone stops anything filling the column on a short
     one. The smaller of the two is right in each case. */
  max-height: min(var(--img-max-h), 85vh);
  width: auto;
  height: auto;
}

/* Two or more sculptures of one piece sit side by side, so the group reads
   as a single landscape block rather than two tall images stacked. Both of
   these are the same 3:4, so they align exactly.

   The row spans the full column, like every other image, and is NOT held
   by the height cap: the cap would shrink it back below column width on a
   shorter screen, and matching the column is the point. */
.sculpture-row {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  width: 100%;
}
.sculpture-row > picture {
  display: block;
  flex: 1 1 0;
  min-width: 0;
}
.sculpture-row img {
  width: 100%;
  height: auto;
  max-width: none;
  max-height: none;
}

/* Every piece opens the lightbox (js/lightbox.js). */
.piece img { cursor: zoom-in; }

.piece figcaption { margin-top: 12px; letter-spacing: -0.01em; }
.piece .title { display: block; }
.piece .meta  { display: block; color: var(--muted); }

/* A print and its sculpture. The tight gap is what says they belong
   together, so it has to stay well under --gap-work. */
.pair { margin-bottom: var(--gap-work); }
.pair .piece { margin-bottom: var(--gap-pair); }
.pair .piece:last-child { margin-bottom: 0; }

/* 160px, rule, 24px, label, 96px. */
.divider { margin: 160px 0 96px; }
.divider hr { margin-bottom: 24px; }
.divider h2 { margin: 0; font-size: inherit; font-weight: 500; line-height: inherit; }


/* --- 7. Projects index -------------------------------------- */

/* Sized exactly like the pieces in a scroll: full column width, but never
   taller than the window, so a portrait cover does not run off the screen.
   fit-content keeps the link no wider than its image. */
.project-block {
  display: block;
  width: fit-content;
  max-width: 100%;
  margin-bottom: var(--gap-work);
  letter-spacing: -0.01em;
}
.project-block img {
  max-width: 100%;
  max-height: min(var(--img-max-h), 85vh);
  width: auto;
  height: auto;
  transition: opacity 200ms;
}
.project-block:hover img { opacity: 0.88; }
.project-block:hover .title { text-decoration: underline; }

.project-block .title { display: block; margin-top: 12px; }
.project-block .years { display: block; color: var(--muted); }


/* --- 8. Project page ---------------------------------------- */

.project-header { margin-bottom: 96px; }
.project-header h1 {
  margin: 0;
  font-size: 24px;
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: -0.02em;
}
.project-header p { margin: 0; color: var(--muted); letter-spacing: -0.01em; }
/* the note can be a full statement; keep it to a readable line length */
.project-header p + p { margin-top: 16px; max-width: var(--text-max); }

.project-nav {
  display: flex;
  justify-content: space-between;
  margin-top: 160px;
  color: var(--muted);
}
.project-nav a:hover { color: var(--fg); }


/* --- 9. Prose pages ----------------------------------------- */

.prose { max-width: var(--text-max); font-size: 16px; line-height: 1.7; }
.prose h1 { margin: 0 0 32px; font-size: 24px; font-weight: 500; line-height: 1.2; letter-spacing: -0.02em; }
.prose h2 { margin: 48px 0 16px; font-size: 20px; font-weight: 500; line-height: 1.3; }
.prose p  { margin: 0 0 1em; }
.prose a  { text-decoration: underline; }
.prose ul { margin: 0 0 1em; padding-left: 1.2em; }
.prose .links { list-style: none; padding: 0; margin-top: 32px; }
.prose .links li { margin: 0 0 0.25em; }


/* --- 10. Footer --------------------------------------------- */

/* Sits in the content column, aligned with the work above it, rather
   than running edge to edge across the window. */
.site-footer {
  margin: 96px var(--gutter) 0 calc(var(--nav-w) + var(--gutter) * 2);
  padding-bottom: var(--gutter);
  max-width: var(--img-max);
  font-size: 13px;
  line-height: 1.6;
  letter-spacing: -0.01em;
  color: var(--muted);
}


/* --- 11. Lightbox ------------------------------------------- */

/* The page behind does not scroll while the viewer is open. */
.lb-lock, .lb-lock body { overflow: hidden; }

.lb {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(12, 12, 12, 0.97);
  color: #999;
  opacity: 0;
  transition: opacity 150ms ease;
}
.lb[hidden] { display: none; }
.lb.lb-open { opacity: 1; }

.lb-figure { margin: 0; text-align: center; }
.lb-frame img {
  display: block;
  margin: 0 auto;
  max-width: 92vw;
  max-height: 88vh;
  object-fit: contain;
  cursor: default;
}
.lb-cap {
  margin-top: 16px;
  padding: 0 24px;
  font-size: 14px;
  letter-spacing: -0.01em;
}
.lb-cap span { display: block; }
.lb-cap span:first-child { color: #ddd; }

.lb button {
  position: absolute;
  padding: 12px 16px;
  border: 0;
  background: none;
  color: #999;
  font: inherit;
  cursor: pointer;
}
.lb button:hover { color: #fff; }
.lb :focus-visible { outline-color: #fff; }
.lb-close { top: 8px; right: 8px; font-size: 20px; line-height: 1; }
.lb-prev, .lb-next { top: 50%; transform: translateY(-50%); }
.lb-prev { left: 8px; }
.lb-next { right: 8px; }

/* Touch screens swipe instead. */
@media (hover: none) {
  .lb-prev, .lb-next { display: none; }
}


/* --- 12. Mobile --------------------------------------------- */

@media (max-width: 899px) {
  :root {
    --gutter:   24px;
    --gap-work: 72px;
  }

  /* Five links fit. No hamburger, no drawer, no overlay. */
  .nav {
    position: static;
    width: auto;
    padding: var(--gutter) var(--gutter) 0;
    display: flex;
    flex-wrap: wrap;
    column-gap: 20px;
    row-gap: 20px;
    line-height: 1.5;
  }
  .nav-name { width: 100%; margin-bottom: 0; }

  main {
    margin-left: var(--gutter);
    margin-right: var(--gutter);
    padding-top: 40px;
  }

  .project-header h1, .prose h1 { font-size: 20px; }

  .site-footer { margin-left: var(--gutter); }
}


/* --- 13. Reduced motion ------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  * { transition: none; }
}
