/* Design tokens and components extracted from Portfolio.dc.html.
   The source file styled everything inline; the same figure, label-column and
   rule patterns repeat across five views, so they live here once. */

:root {
  --bg: #fbfbfa;
  --ink: #141414;
  --body: #2a2a27;
  --secondary: #55554f;
  --muted: #9a9a97;
  --muted-soft: #8a8a86;
  --caption: #7d7d78;
  --rule: #e6e6e3;
  --frame-border: #e2e2df;
  --hatch-a: #ededea;
  --hatch-b: #f5f5f2;
  --hover: #6b6b6b;

  --display: "Archivo Narrow", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --text: "Archivo", "Helvetica Neue", Helvetica, Arial, sans-serif;

  --container: 1180px;
  --gutter: 40px;
  --label-col: 220px;

  /* Height of the sticky header: 12px padding x2 + the 40px logo + the 1px
     rule. Kept in sync by hand with .site-header__inner padding and
     .wordmark__logo — nothing measures it, so change either and this must
     follow. */
  --header-h: 65px;
}

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

/* .site-header is sticky, so an :target lands underneath it: a /work/#<series>
   link — shared before series had pages, and redirected to the home index —
   would otherwise scroll its card half behind the header.
   scroll-padding-top on the scroller offsets every in-page anchor at once. */
html {
  scroll-behavior: smooth;
  scroll-padding-top: var(--header-h);
  scrollbar-gutter: stable;
}

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--text);
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; text-decoration: none; }
a:hover { color: var(--hover); }
::selection { background: var(--ink); color: var(--bg); }

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

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

/* --- layout --------------------------------------------------------------- */

.container {
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

.page { min-height: 100vh; display: flex; flex-direction: column; }
.page__main { flex: 1; }

.section {
  padding: 64px 0 80px;
  border-top: 1px solid var(--rule);
}
.section--flush { border-top: 0; }

/* The recurring 220px label column beside a content column. */
.split {
  display: grid;
  grid-template-columns: var(--label-col) 1fr;
  gap: var(--gutter);
  align-items: start;
}

.measure { max-width: 640px; }
.measure--wide { max-width: 700px; }

/* --- type ----------------------------------------------------------------- */

.eyebrow {
  font-family: var(--display);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 12px;
  color: var(--muted);
}
.eyebrow--wide { letter-spacing: 0.2em; }
/* The home page's slogan is its <h1> for search, and must still look like
   every other eyebrow rather than a bold heading. */
h1.eyebrow { font-weight: inherit; }

.display {
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(30px, 5vw, 52px);
  line-height: 1.05;
  max-width: 720px;
  text-wrap: balance;
}

.prose {
  font-size: 16px;
  line-height: 1.75;
  color: var(--body);
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.prose p { text-wrap: pretty; }

/* Markdown bodies (Series page layout) can emit more than paragraphs. All of
   it is scoped under .prose. Most elements need no margin of their own — the
   flex gap above already spaces siblings — but two rules below add one on
   purpose: .prose h2/h3 get margin-top so a heading reads as a bigger break
   than the 18px gap gives a plain paragraph, and .prose figure gets a smaller
   margin so an inline figure sits tighter than that gap would leave it.
   .prose is also used by portfolio/templates/portfolio/bio.html, not just the
   series page — these rules are inert there today only because bio's copy is
   plain autoescaped text with no headings, lists, blockquotes, links or
   figures for the selectors to match. That is coincidence, not scoping: if
   bio's copy ever gains a link or a heading, these rules apply there too.

   There is deliberately no measure cap on .prose children. Each of the three
   pages that use .prose caps its own container instead — .measure (640px) on
   /bio/, .measure--wide (700px) on the information pages, and the series
   page's 1fr column beside the 220px label. A cap here would apply to
   paragraphs but not to an inline figure (which needs the full column), and
   the two edges disagreeing by that difference is what the caller sees. */
.prose h2,
.prose h3 {
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-top: 14px;
}
.prose h2 { font-size: 19px; }

/* /about/ only (see portfolio/views.py:about): section headings sit in the
   label column, level with the top of the section they name, instead of
   inline above it.

   The prose gets a grid of its own repeating .split's two columns, pulled
   left by exactly one label column plus one gutter — so its second column
   lands on the text where the text already was, and its first column lands
   under the page eyebrow. Auto-placement does the rest: a heading claims
   column 1, and because a definite column that moves the cursor leftwards
   starts a new row, the block after the heading lands in column 2 of that
   same row rather than below it.

   Nothing here is scoped by page name. The class comes from the view, so
   /faq/ and /privacy/ — fifteen headings between them, each wanting to sit
   above its answer — are untouched. */
.split--marginal .prose {
  display: grid;
  grid-template-columns: var(--label-col) minmax(0, 1fr);
  column-gap: var(--gutter);
  row-gap: 18px;
  margin-left: calc(-1 * (var(--label-col) + var(--gutter)));
  /* .measure--wide caps this container at 700px, which now has to carry the
     label column too if the text is to keep the same measure it had. */
  max-width: calc(700px + var(--label-col) + var(--gutter));
}
.split--marginal .prose > * { grid-column: 2; }
.split--marginal .prose h2 {
  grid-column: 1;
  /* Set as the page eyebrow, not as a heading: in the label column it is
     doing the eyebrow's job, and at 19px semibold it would out-weigh the
     prose it labels. .eyebrow's own declarations, repeated rather than shared
     — the element is an h2 and has to stay one for the document outline. */
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0.14em;
  color: var(--muted);
}
/* The section break. Both items on the heading's row take the same top
   margin, so the row starts lower and the two stay level; the 18px row-gap
   that separates paragraphs is not enough to say "new section". `h2 + *` is
   only ever the first block under a heading, so a section of several
   paragraphs is spaced once, at its top. */
.split--marginal .prose h2,
.split--marginal .prose h2 + * { margin-top: 40px; }
.prose h3 { font-size: 15px; color: var(--secondary); }
.prose ul,
.prose ol { padding-left: 20px; display: flex; flex-direction: column; gap: 6px; }
.prose li { text-wrap: pretty; }
.prose blockquote {
  border-left: 1px solid var(--rule);
  padding-left: 18px;
  color: var(--secondary);
  font-style: italic;
}
.prose a { text-decoration: underline; text-underline-offset: 2px; }
.prose hr { border: 0; border-top: 1px solid var(--rule); width: 100%; }
.prose code {
  font-size: 13.5px;
  background: var(--hatch-b);
  padding: 1px 4px;
}

/* Tighter than the 18px flex gap: an inline figure belongs to the paragraph
   it illustrates. Its width is the column's, like every other child. */
.prose figure { margin: 8px 0; }
.prose figure .frame { width: 100%; }

/* --- header --------------------------------------------------------------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 40;
  background: rgba(251, 251, 250, 0.92);
  backdrop-filter: blur(6px);
  border-bottom: 1px solid var(--rule);
}

.site-header__inner {
  max-width: var(--container);
  margin: 0 auto;
  padding: 12px var(--gutter);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 24px;
}

.wordmark {
  font-family: var(--display);
  font-weight: 600;
  font-size: 17px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 14px;
}

/* The 2A mark (the name set in a three by three grid), generated by
   brand/logos.py. Decorative here — the name beside it is the link text, so
   alt is empty. */
.wordmark__logo { display: block; width: 40px; height: 40px; flex: none; }

.nav {
  display: flex;
  flex-wrap: wrap;
  gap: 22px;
  font-family: var(--display);
  font-size: 13px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.nav a {
  padding-bottom: 3px;
  border-bottom: 1.5px solid transparent;
  color: var(--muted-soft);
}
.nav a.is-active { color: var(--ink); border-bottom-color: var(--ink); }

.nav__count { font-variant-numeric: tabular-nums; }

/* --- footer --------------------------------------------------------------- */

.site-footer {
  border-top: 1px solid var(--rule);
  margin-top: 40px;
}

.site-footer__inner {
  max-width: var(--container);
  margin: 0 auto;
  padding: 30px var(--gutter);
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 16px;
  font-family: var(--display);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 12px;
  color: var(--muted);
}

/* --- image frames --------------------------------------------------------- */

.frame {
  aspect-ratio: 4 / 3;
  /* The containing block for .frame__badge, which sits over the corner of the
     photograph. Nothing else in a frame is positioned. */
  position: relative;
  background-image: repeating-linear-gradient(
    45deg,
    var(--hatch-a) 0px, var(--hatch-a) 11px,
    var(--hatch-b) 11px, var(--hatch-b) 22px
  );
  border: 1px solid var(--frame-border);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.frame--zoom { cursor: zoom-in; }

.frame__img { width: 100%; height: 100%; object-fit: cover; }

/* A frame that takes its height from the image instead of cropping to a fixed
   ratio. Product detail pages use it so a print is shown whole; the grid keeps
   the cropped square so the cards line up. */
.frame--natural { aspect-ratio: auto; }
.frame--natural .frame__img { height: auto; object-fit: contain; }

.frame__label,
.frame__badge {
  font-family: var(--display);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #a6a6a2;
  background: var(--bg);
  padding: 5px 10px;
  border: 1px solid var(--frame-border);
  text-align: center;
}

/* The chip that says a print is gone, over the top-left of its photograph.
   Same box as .frame__label above — a bordered word on an image is already
   this site's one such object, drawn for the hatched placeholder — but
   inverted, off-white on ink, because this one has to be read before the
   picture is, and an off-white chip vanished into pale prints. The word stays in the card's meta row as well, where
   the price would otherwise be: on the image it is seen, in the row it is
   read, and the row would look truncated without it.

   pointer-events: none keeps the frame a single target. The grid card's frame
   is a link and the product page's is a lightbox trigger, so a chip that
   swallowed the click would put a dead spot in the corner of both. */
.frame__badge {
  position: absolute;
  top: 8px;
  left: 8px;
  color: var(--bg);
  background: var(--ink);
  border-color: var(--ink);
  pointer-events: none;
}

.caption {
  margin-top: 7px;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--caption);
  font-style: italic;
}

/* --- grids ---------------------------------------------------------------- */

/* The index is the main event, so it gets the larger cell: 340px min yields
   three columns in the 1100px container (4 x 340 + 3 x 20 gaps overflows). */
/* The gap under the header lives here, not on the eyebrow above it: the
   eyebrow section is conditional (SiteSettings.tagline may be blank) and the
   grid is not, so an empty tagline must not leave the cards flush against the
   header. When the eyebrow does render, its own bottom padding is the gap and
   this one stands down. */
.grid-index {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
  gap: 20px;
  padding-top: 48px;
  padding-bottom: 80px;
}
.section + .grid-index { padding-top: 0; }

.grid-figures {
  margin-top: 34px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 14px;
}

/* Denser than the index by one column: 200px min yields five across 1100px. */
.grid-shop {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 30px 20px;
}

.span-2 { grid-column: span 2; }

.card { display: flex; flex-direction: column; gap: 10px; }

.card__meta {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  align-items: baseline;
  font-family: var(--display);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.card__title { font-size: 13px; }
.card__year { font-size: 12px; color: var(--muted); }

/* --- series page ---------------------------------------------------------- */

/* One .work-entry per page now that /work/ is gone, always with --page below.
   It kept a negative scroll-margin-top while it was an anchor target on that
   index; nothing anchors it here. */
.work-entry {
  padding: 40px 0 64px;
  border-top: 1px solid var(--rule);
}

.work-entry__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 26px;
}

.work-entry__title {
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: clamp(22px, 3vw, 34px);
}

.work-entry__year {
  font-family: var(--display);
  font-size: 15px;
  letter-spacing: 0.1em;
  color: var(--muted);
}

.details { font-size: 13px; line-height: 1.7; color: var(--secondary); }
.details__label {
  font-family: var(--display);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 11px;
  color: var(--muted);
  margin-bottom: 4px;
}

/* The series page is one work-entry on its own, so it needs no rule above it
   — the site header already draws one. */
.work-entry--page { border-top: 0; }

/* --- artwork detail ------------------------------------------------------- */

.artwork__frame { max-width: 900px; }
/* The artwork page names its grid areas so the written matter can sit under
   the series label on desktop and after the image on a phone. */
/* .artwork__main spans the first two rows; without an explicit
   grid-template-rows, a spanning item's extra height is split evenly between
   the auto rows, pushing the aside row down beside the middle of the image.
   The pager is pulled out of .artwork__main into its own third row so that on
   mobile the aside can sit above it, detached from the image. */
.split--artwork {
  grid-template-areas:
    "eyebrow main"
    "aside   main"
    ".       pager";
  grid-template-rows: auto 1fr auto;
}
.artwork__eyebrow { grid-area: eyebrow; }
/* Scoped to this page: .eyebrow is shared with the index and the series
   pages, which keep the lighter weight and the smaller size. Here the work's
   own title heads the page, so it has to out-rank the series link beneath it —
   at the shared 12px it read as the smaller of the two. */
.artwork__eyebrow .eyebrow { font-weight: 600; font-size: 14px; color: var(--ink); }
/* The series back-link under the work title: quieter than the eyebrow above
   it, and left in sentence case so the two lines do not read as one block. */
.artwork__series {
  margin-top: 8px;
  font-size: 12px;
  line-height: 1.6;
  color: var(--muted);
}
.artwork__series a:hover { color: var(--hover); }
.artwork__main { grid-area: main; }
.artwork__pager { grid-area: pager; }
/* The left-hand column: statement, then facts, then the enquiry line. */
.artwork__aside { grid-area: aside; margin-top: 22px; }
.artwork__facts {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 26px;
  font-size: 12px;
  line-height: 1.6;
  color: var(--body);
}
.artwork__statement {
  font-size: 12px;
  line-height: 1.7;
  color: var(--muted);
}
/* The global reset strips paragraph margins; without this the paragraphs run together. */
.artwork__statement p + p { margin-top: 12px; }
.artwork__status {
  margin-top: 18px;
  font-family: var(--display);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 12px;
  color: var(--muted);
}
.artwork__status a { border-bottom: 1px solid var(--frame-border); padding-bottom: 2px; color: var(--ink); }

.pager {
  display: flex;
  justify-content: space-between;
  gap: 20px;
  margin-top: 54px;
  padding-top: 22px;
  border-top: 1px solid var(--rule);
  font-family: var(--display);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 12px;
  color: var(--muted);
}

/* --- cv / press / news ---------------------------------------------------- */

.cv-group { border-top: 1px solid var(--rule); padding-top: 26px; }
.cv-rows { display: flex; flex-direction: column; gap: 9px; }
.cv-row {
  display: grid;
  grid-template-columns: 62px 1fr;
  gap: 18px;
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--body);
}
.cv-row__year { color: var(--muted); font-variant-numeric: tabular-nums; }

.list-row {
  display: block;
  border-top: 1px solid var(--rule);
  padding: 18px 0;
}
.list-row__inner {
  display: flex;
  justify-content: space-between;
  gap: 20px;
  align-items: baseline;
}
.list-row__title { font-size: 16px; line-height: 1.5; color: var(--body); max-width: 520px; }
.list-row__source { font-size: 13px; color: var(--muted); white-space: nowrap; }

.news-item {
  border-top: 1px solid var(--rule);
  padding: 22px 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.news-item__date {
  font-family: var(--display);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 12px;
  color: var(--muted);
}
.news-item__text { font-size: 16px; line-height: 1.6; color: var(--body); text-wrap: pretty; }

/* --- contact -------------------------------------------------------------- */

.contact-lede {
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(24px, 4vw, 40px);
  line-height: 1.15;
}
.contact-rows { display: flex; flex-direction: column; gap: 16px; font-size: 16px; line-height: 1.6; }
.contact-rows > a,
.contact-rows > div { border-bottom: 1px solid #dcdcd8; padding-bottom: 10px; }
.contact-social {
  display: flex;
  gap: 22px;
  font-family: var(--display);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 13px;
  padding-top: 4px;
  border-bottom: 0 !important;
}

/* --- shop ----------------------------------------------------------------- */

.shop-card__meta {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  align-items: baseline;
}
.shop-card__title { font-size: 14px; color: var(--body); }
.shop-card__price { font-size: 14px; color: var(--muted); white-space: nowrap; }
.shop-card__detail { font-size: 12px; color: var(--muted); }
.shop-card__sold {
  font-family: var(--display);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 11px;
  color: var(--muted);
}

/* The shop grid: every tile the same square, so a square print and a 70 × 100
   one line up across the row, and the print sits whole inside it rather than
   being cropped to fit. The hatch and frame border belong to the empty
   placeholder; once there is a photograph the border moves onto the image
   itself, so it traces the print's own edge — which is what keeps a print
   with white margins from dissolving into the page. */
.frame--fit { aspect-ratio: 1 / 1; }
.frame--fit:has(.frame__img) { background: none; border: 0; }
.frame--fit .frame__img {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  border: 1px solid var(--frame-border);
}

/* The product page: image beside the written matter. These were inline styles
   on the template, which is why the page never stacked on a phone — an inline
   grid-template-columns cannot be overridden from a media query, so the text
   column kept its share of a 390px viewport and wrapped one word per line. */
.product {
  display: grid;
  grid-template-columns: minmax(0, 420px) minmax(0, 1fr);
  gap: 40px;
  align-items: start;
}
.product__info { display: flex; flex-direction: column; gap: 18px; }
/* What the object is — size, paper, one of one — read as one block, closer
   together than the gaps between price, button and note. */
.product__facts { display: flex; flex-direction: column; gap: 6px; }
/* Main photo and its extra views share the left column, so the two-column
   grid above still sees exactly two children. */
.product__media { display: flex; flex-direction: column; gap: 12px; }
/* Three across, fixed rather than auto-fill: the tile has to stay a thumbnail
   at any count. auto-fit would stretch a lone extra view to the full column
   and make it a second main photo, which is the whole thing this strip is
   not. A fourth photo wraps onto a second row at the same size. */
.product__thumbs {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}
/* Uniform squares, cropped: the strip reads as a set only when the tiles
   match, so shape comes from here rather than from each photo. */
.product__thumbs .frame { aspect-ratio: 1 / 1; }
.product__title {
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: clamp(20px, 3vw, 28px);
}
.product__price { font-size: 18px; font-variant-numeric: tabular-nums; }
/* Supporting text, not a headline: below the 18px price and just above the
   12px notes, in body colour so three paragraphs stay comfortable to read. */
.product__description {
  margin-top: 18px;
  font-size: 13px;
  line-height: 1.7;
  color: var(--body);
  text-wrap: pretty;
}
/* Plain text split on blank lines by |linebreaks, as artwork statements are.
   The global reset strips paragraph margins, so without this they run together. */
.product__description p + p { margin-top: 1em; }
.product__buy { margin-top: 6px; }
/* The shipping note belongs to the button above it: cancel most of the
   column's 18px gap and .cart-note's own top margin, leaving 8px. */
.product__buy + .cart-note,
.product__info > .button + .cart-note { margin-top: -10px; }

.button {
  font-family: var(--display);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 12px;
  background: var(--ink);
  color: var(--bg);
  border: 1px solid var(--ink);
  padding: 13px 22px;
  cursor: pointer;
  align-self: flex-start;
}
.button:hover { background: var(--bg); color: var(--ink); }
.button[disabled] {
  background: transparent;
  color: var(--muted);
  border-color: var(--frame-border);
  cursor: not-allowed;
}
.button--ghost { background: transparent; color: var(--ink); }
.button--ghost:hover { background: var(--ink); color: var(--bg); }

.qty {
  font-family: var(--text);
  font-size: 14px;
  padding: 10px;
  width: 72px;
  border: 1px solid var(--frame-border);
  background: var(--bg);
  color: var(--ink);
}

.cart-line {
  display: grid;
  grid-template-columns: 90px 1fr auto;
  gap: 20px;
  align-items: start;
  border-top: 1px solid var(--rule);
  padding: 22px 0;
}
.cart-line__title { font-size: 15px; color: var(--body); }
.cart-line__detail { font-size: 12px; color: var(--muted); margin-top: 4px; }
.cart-line__actions { display: flex; gap: 10px; align-items: center; margin-top: 12px; }
.cart-line__total { font-size: 15px; color: var(--body); white-space: nowrap; font-variant-numeric: tabular-nums; }

.cart-total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  border-top: 1px solid var(--ink);
  padding-top: 18px;
  margin-top: 8px;
  font-family: var(--display);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 14px;
}
.cart-total__amount { font-variant-numeric: tabular-nums; }

.cart-note { font-size: 12px; color: var(--muted); margin-top: 14px; line-height: 1.6; }
/* Links inside grey notes would otherwise be the same grey as the words
   around them. Set them darker and at 500 — the heaviest Archivo weight
   loaded, so nothing is synthesised — with the underline kept for hover; a
   resting underline looked heavy against 12px grey text. */
.cart-note a,
.product__facts a {
  color: var(--body);
  font-weight: 500;
}
.cart-note a:hover,
.product__facts a:hover {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

.link-underline { border-bottom: 1px solid var(--frame-border); padding-bottom: 2px; }

/* --- contact form --------------------------------------------------------- */

.form { display: flex; flex-direction: column; gap: 26px; max-width: 620px; }

.form__field { display: flex; flex-direction: column; gap: 8px; }
.form__field label {
  font-family: var(--display);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 12px;
  color: var(--muted);
}

/* Bottom rules rather than boxes. The site draws every division as a hairline
   rule; a boxed input would be the first control here that looks bought-in. */
.form__input {
  font-family: var(--text);
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--rule);
  border-radius: 0;
  padding: 8px 0;
  width: 100%;
}
/* No outline reset here: the global :focus-visible rule supplies the keyboard
   ring, and removing it would leave keyboard users with only a hairline. */
.form__input:focus { border-bottom-color: var(--ink); }

.form__textarea { resize: vertical; min-height: 150px; }

.form__error { font-size: 13px; line-height: 1.5; color: #9c5252; }

/* Off-screen rather than display:none — some bots skip fields that are not
   rendered, and this one only works if they fill it in. */
.form__trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* --- messages ------------------------------------------------------------- */

.messages { list-style: none; margin: 24px 0 0; display: flex; flex-direction: column; gap: 8px; }
.messages li {
  font-size: 13px;
  line-height: 1.5;
  padding: 12px 14px;
  border: 1px solid var(--frame-border);
  background: #f3f3f0;
  color: var(--body);
}
.messages li.error { border-color: #d8bcbc; background: #f9f0f0; }

/* --- lightbox ------------------------------------------------------------- */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: rgba(20, 20, 20, 0.86);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 5vh 5vw;
  cursor: zoom-out;
  border: 0;
  width: 100%;
  height: 100%;
}
.lightbox[hidden] { display: none; }

.lightbox__figure {
  width: min(78vw, 760px);
  max-height: 80vh;
  display: flex;
  align-items: center;
  justify-content: center;
}
.lightbox__figure img { max-height: 80vh; width: auto; object-fit: contain; }

.lightbox__caption {
  margin-top: 16px;
  color: #d8d8d4;
  font-size: 13px;
  font-style: italic;
  max-width: 640px;
  text-align: center;
}

/* Previous / next, for a lightbox group of more than one image. Fixed to the
   viewport edges so they never overlap the picture, whatever its shape. */
.lightbox__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  padding: 16px;
  background: none;
  border: 0;
  color: #d8d8d4;
  font-size: 28px;
  line-height: 1;
  cursor: pointer;
}
.lightbox__nav:hover,
.lightbox__nav:focus-visible { color: #fff; }
.lightbox__nav[hidden] { display: none; }
.lightbox__nav--prev { left: 1.5vw; }
.lightbox__nav--next { right: 1.5vw; }

.lightbox__hint {
  margin-top: 8px;
  color: var(--caption);
  font-family: var(--display);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 11px;
}

/* --- reviewer preview ------------------------------------------------------ */

.nav__form { display: inline; margin: 0; }
.nav__button {
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: var(--muted-soft);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}
.nav__button:hover { color: var(--ink); }

.review-lede { font-size: 15px; line-height: 1.6; color: var(--body); max-width: 620px; }

.review-albums {
  list-style: none;
  margin: 0;
  padding: 8px 0 100px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 40px 28px;
}
.review-album a { display: flex; flex-direction: column; gap: 10px; color: inherit; }
.review-album__cover {
  display: block;
  aspect-ratio: 1;
  background: var(--hatch-b);
  border: 1px solid var(--frame-border);
  overflow: hidden;
}
/* contain, not cover: the same square frame as the album grid, so a cover
   shows the whole image rather than a crop of it. */
.review-album__cover img { width: 100%; height: 100%; object-fit: contain; display: block; }

.review-back {
  display: inline-block;
  margin-top: 32px;
  font-family: var(--display);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 12px;
  color: var(--muted-soft);
}
.review-back:hover { color: var(--ink); }
.review-album__title {
  font-family: var(--display);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 15px;
  color: var(--ink);
}
.review-album__meta { font-size: 13px; color: var(--caption); }

.review-grid {
  list-style: none;
  margin: 0;
  padding: 8px 0 100px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 32px 24px;
}
.review-tile { display: flex; flex-direction: column; gap: 8px; }
.review-tile__frame {
  display: block;
  aspect-ratio: 1;
  background: var(--hatch-b);
  border: 1px solid var(--frame-border);
  overflow: hidden;
  cursor: zoom-in;
}
.review-tile__frame img { width: 100%; height: 100%; object-fit: contain; display: block; }
.review-tile__caption { font-size: 13px; color: var(--caption); }

.stars { display: flex; flex-wrap: wrap; align-items: center; gap: 2px; margin: 0; }
.stars button[value] {
  font-size: 20px;
  line-height: 1;
  padding: 2px;
  background: none;
  border: 0;
  cursor: pointer;
  color: var(--rule);
}
.stars button.is-on { color: var(--ink); }
.stars button.stars__clear {
  font-family: var(--display);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  margin-left: 8px;
}
.stars .stars__clear[hidden] { display: none; }
.stars__status { font-size: 12px; color: #9c5252; margin-left: 8px; }

/* The viewer: dark, full screen, image as large as the viewport allows. Unlike
   the public lightbox it does not close on a click, because clicking is how
   reviewers rate. */
.gallery {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: #111;
  color: #e8e8e4;
  display: grid;
  grid-template-columns: 64px minmax(0, 1fr) 64px;
  grid-template-rows: minmax(0, 1fr) auto;
}
.gallery[hidden] { display: none; }
.gallery__stage {
  grid-column: 2;
  grid-row: 1;
  margin: 0;
  min-height: 0;
  padding: 24px 0 8px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.gallery__stage img { max-width: 100%; max-height: 100%; object-fit: contain; }
.gallery__prev,
.gallery__next,
.gallery__close {
  background: none;
  border: 0;
  color: inherit;
  cursor: pointer;
  line-height: 1;
}
.gallery__prev,
.gallery__next { grid-row: 1; align-self: center; font-size: 44px; padding: 16px; }
.gallery__prev { grid-column: 1; }
.gallery__next { grid-column: 3; }
.gallery__close { position: absolute; top: 12px; right: 16px; font-size: 30px; padding: 4px 8px; }
.gallery__bar {
  grid-column: 1 / -1;
  grid-row: 2;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 10px 24px;
  padding: 14px 16px 22px;
  font-size: 13px;
}
.gallery__count {
  font-family: var(--display);
  letter-spacing: 0.12em;
  color: #a8a8a4;
  font-variant-numeric: tabular-nums;
}
.gallery__caption { font-style: italic; }
.gallery__keys { font-size: 11px; color: #7d7d78; }
.gallery__status { min-width: 12ch; color: #a8a8a4; }
.gallery .stars button[value] { font-size: 30px; color: #4a4a47; }
.gallery .stars button.is-on { color: #f2f2ee; }
.gallery .stars button.stars__clear { font-size: 11px; color: #a8a8a4; }

/* Phones swipe; the arrow columns would only take width from the image. */
@media (max-width: 600px) {
  .gallery { grid-template-columns: 0 minmax(0, 1fr) 0; }
  .gallery__prev,
  .gallery__next,
  .gallery__keys { display: none; }
}

/* --- responsive ----------------------------------------------------------- */

@media (max-width: 860px) {
  :root { --gutter: 24px; }

  .split { grid-template-columns: 1fr; gap: 18px; }
  /* One column: there is no label column left to sit in, so headings go back
     above their sections and the pull-left has to go with them. */
  .split--marginal .prose {
    grid-template-columns: minmax(0, 1fr);
    margin-left: 0;
    max-width: 700px;
  }
  .split--marginal .prose > *,
  .split--marginal .prose h2 { grid-column: 1; }
  /* Above its section again, so only the heading carries the break. */
  .split--marginal .prose h2 { margin-top: 32px; }
  .split--marginal .prose h2 + * { margin-top: 0; }
  /* One column: the print, then title, price, description and the buy button
     under it. The 420px image column would otherwise starve the text. */
  .product { grid-template-columns: 1fr; gap: 24px; }
  /* One column: series label, then the work, then the statement, then the pager. */
  .split--artwork { grid-template-areas: "eyebrow" "main" "aside" "pager"; grid-template-rows: none; }
  .artwork__aside { margin-top: 28px; }

  .work-entry__head { margin-bottom: 18px; }

  /* A two-column figure in a one-column grid would overflow. */
  .grid-figures { grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); }

  /* Fixed rather than auto-fill: the index's 340px cell needs 700px of content
     box for two columns, so auto-fill would collapse a 600px tablet to one
     column. A tablet is wide enough for two; a phone gets one (see below). */
  .grid-index { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 560px) {
  /* Stacked wordmark over a nav that wraps to two lines on a phone, so the
     header roughly doubles. Exact height depends on where the nav wraps; the
     40px .work-entry padding absorbs the difference either way. */
  :root { --header-h: 120px; }

  .site-header__inner { flex-direction: column; align-items: flex-start; gap: 12px; }
  .nav { gap: 14px; font-size: 12px; }
  .section { padding: 40px 0 56px; }
  /* One series per row. Two 140px cells fit a phone, but a series cover shown
     that small is a thumbnail rather than the work — and the title beneath it
     wrapped to two lines in half the cases. The index is the only place a
     series is advertised, so it gets the full width here. */
  .grid-index { grid-template-columns: 1fr; padding-top: 40px; }
  .section + .grid-index { padding-top: 0; }
  .grid-figures { grid-template-columns: 1fr; }
  .span-2 { grid-column: span 1; }
  .cart-line { grid-template-columns: 64px 1fr; }
  .cart-line__total { grid-column: 2; }
}

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

/* --- offline page --------------------------------------------------------- */

/* The holding page: pre-launch and maintenance both land here. It has no
   header, no nav and no footer, so it centres in the viewport rather than
   flowing under .page — the whole page is one column of five things. */

.offline {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--gutter) 24px;
}

.offline__inner {
  width: 100%;
  max-width: 640px;
  text-align: center;
}

.offline__wordmark {
  font-family: var(--display);
  font-weight: 600;
  font-size: 13px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 32px;
}

/* --natural, so an uploaded image keeps its own proportions instead of being
   cropped to a ratio nobody chose. With no upload the {% picture %} tag draws
   the hatched placeholder, which is why this page is worth looking at before
   any photograph exists — hence the min-height, which gives that placeholder
   something to be.

   The height cap is what keeps the whole page on one screen. A tall or square
   photograph at the full column width pushes the heading and the contact link
   below the fold, and a holding page that has to be scrolled to reach its own
   one sentence has failed at the only thing it does. svh rather than vh so
   mobile browsers measure with their toolbars showing.

   inline-block, so the border hugs the image once its width is no longer the
   full column — otherwise a portrait photograph sits in a wide empty box. */
.offline__figure {
  display: inline-block;
  max-width: 100%;
  margin-bottom: 36px;
}
.offline__figure .frame__img {
  display: block;
  width: auto;
  max-width: 100%;
  max-height: 52svh;
}

.offline__heading {
  font-family: var(--display);
  font-weight: 500;
  font-size: 28px;
  letter-spacing: 0.02em;
  color: var(--ink);
  line-height: 1.25;
}

.offline__message {
  margin-top: 14px;
  font-size: 15px;
  line-height: 1.65;
  color: var(--secondary);
}

.offline__contact {
  margin-top: 36px;
  font-family: var(--display);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 12px;
}
.offline__contact a { color: var(--muted); text-decoration: none; }
.offline__contact a:hover { color: var(--ink); }

@media (max-width: 600px) {
  .offline__heading { font-size: 23px; }
}
