/* Deliberate Frames - dark theme with light accents.
   Committed dark: the gallery reads better against near-black, and a
   photograph should be the brightest thing on the page.

   Laid out mobile first. Every multi-column rule below is inside a
   min-width query, so the narrow case is the default rather than the
   exception. */

/* --- typeface ----------------------------------------------------------
   Outfit, a geometric sans, self-hosted rather than linked from Google's
   CDN: no third-party request on every page load, nothing about a visitor
   sent to Google, and one less runtime dependency on somebody else's
   host. The two files are the variable font Google Fonts serves, latin
   and latin-ext, and cover the whole 100-900 weight axis between them.

   The version is in the filename so the files can be cached for a year
   and hard. Refreshing the font means fetching new files under a new
   version and updating these three references - never overwriting in
   place, or year-old caches will keep serving the old outlines. */

@font-face {
  font-family: "Outfit";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/fonts/outfit-v15-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
    U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
    U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Outfit";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/fonts/outfit-v15-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
    U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
    U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
    U+A720-A7FF;
}

:root {
  color-scheme: dark;

  /* Ground */
  --ink-900: #0b0b0d; /* page */
  --ink-800: #121215; /* raised surface */
  --ink-700: #1c1c21; /* borders, hairlines */
  --ink-600: #2a2a31; /* input fields, hover */

  /* Light accents */
  --paper: #f4efe7; /* primary text, warm off-white */
  --paper-dim: #a6a099; /* secondary text */
  --accent: #d8c9a8; /* champagne, for rules and emphasis */
  --alarm: #e8a87c; /* expiry, warnings */

  --measure: 34rem;
  --wide: 72rem;
  --gutter: clamp(1.25rem, 4vw, 2.5rem);
  /* One family throughout. The two names are kept apart because the
     roles are still distinct - display type is set larger and tracked
     out, running text is not - and a future second face would only have
     to be swapped in on one of them. */
  --display: "Outfit", ui-sans-serif, system-ui, -apple-system,
    "Helvetica Neue", sans-serif;
  --text: var(--display);
}

* {
  box-sizing: border-box;
}

html {
  /* Nothing here should ever scroll sideways on a phone. */
  overflow-x: hidden;
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--ink-900);
  color: var(--paper);
  font: 400 16px/1.65 var(--text);
  -webkit-font-smoothing: antialiased;
  display: flex;
  flex-direction: column;
}

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

/* Only ever a wrapper offering WebP ahead of a JPEG <img>, which is the
   element that is actually laid out and styled. */
picture {
  display: contents;
}

a {
  color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.25em;
}

a:hover {
  color: var(--paper);
}

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

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

.page {
  width: 100%;
  max-width: var(--wide);
  margin: 0 auto;
  padding: var(--gutter);
  flex: 1;
}

/* The pages that fill themselves in from the API start out all but
   empty, so the footer would sit in view until the prints arrived and
   then be shoved off screen - a layout shift on every visit. Starting
   them a screen tall puts the footer below the fold from the first
   paint, where moving it costs nothing. */
[data-page="gallery"] .page,
[data-page="collection"] .page,
[data-page="item"] .page {
  min-height: 100vh;
  min-height: 100svh;
}

/* The splash and error pages, which have one short thing to say. */
.page--center {
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 2.5rem;
  text-align: center;
  min-height: 70vh;
}

.masthead {
  max-width: var(--measure);
}

.site-header {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem 1.5rem;
  width: 100%;
  max-width: var(--wide);
  margin: 0 auto;
  padding: var(--gutter) var(--gutter) 0;
}

/* On the front page the wordmark is the h1. The heading carries no type
   of its own: the wordmark below it is already set, and a display-sized
   h1 in the header would be the duplicate all over again. */
.wordmark-heading {
  margin: 0;
  font: inherit;
  letter-spacing: inherit;
  text-transform: none;
}

.wordmark {
  font: 400 1.1rem/1 var(--display);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--paper);
  text-decoration: none;
}

.site-nav {
  display: flex;
  gap: 1.25rem;
  font: 400 0.8rem/1 var(--text);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.site-footer {
  width: 100%;
  max-width: var(--wide);
  margin: 0 auto;
  padding: 2.5rem var(--gutter);
  border-top: 1px solid var(--ink-700);
  color: var(--paper-dim);
  font: 400 0.8rem/1.7 var(--text);
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  justify-content: space-between;
}

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

h1 {
  margin: 0 0 1.5rem;
  font-size: clamp(2rem, 7vw, 3.25rem);
  font-weight: 400;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

h2 {
  margin: 0 0 0.75rem;
  font-size: clamp(1.25rem, 3.5vw, 1.6rem);
  font-weight: 400;
  letter-spacing: 0.04em;
}

.rule {
  width: 4rem;
  height: 1px;
  margin: 0 auto 1.5rem;
  background: var(--accent);
}

.masthead .rule {
  margin-left: 0;
}

.page--center .masthead .rule {
  margin-left: auto;
}

.lede {
  margin: 0;
  color: var(--paper-dim);
  font-size: 1.05rem;
  max-width: var(--measure);
}

.note {
  margin: 0;
  max-width: var(--measure);
  color: var(--paper-dim);
  font: 400 0.85rem/1.6 var(--text);
  letter-spacing: 0.04em;
}

.prose {
  max-width: var(--measure);
}

.prose p,
.prose li {
  color: var(--paper-dim);
  font: 400 0.95rem/1.75 var(--text);
}

.prose strong {
  color: var(--paper);
  font-weight: 500;
}

/* --- the 24 hour rule, wherever it appears ----------------------------- */

/* Said on the gallery, on every item, at checkout, and on the download
   page. A buyer should not be able to reach the payment form without
   having been told, and told plainly. */
.terms-callout {
  margin: 0;
  padding: 1rem 1.15rem;
  border: 1px solid var(--ink-700);
  border-left: 2px solid var(--alarm);
  background: var(--ink-800);
  color: var(--paper-dim);
  font: 400 0.85rem/1.65 var(--text);
  max-width: var(--measure);
}

.terms-callout strong {
  color: var(--paper);
  font-weight: 500;
}

/* --- collections -------------------------------------------------------
   A collection is an arrangement of prints that are all in the gallery
   anyway, so its card is deliberately not a print card: a strip of
   member thumbnails rather than one image, and no price. Nothing here
   should look like a thing that can be bought. */

.section-heading {
  margin: 0;
  color: var(--paper-dim);
  font: 400 0.8rem/1 var(--text);
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

/* Present for a screen reader, absent on screen. Used for headings that
   the page's own masthead has already said out loud, but that the
   sections below still need in order to nest properly. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.eyebrow {
  margin: 0 0 0.5rem;
  color: var(--paper-dim);
  font: 400 0.7rem/1 var(--text);
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

.collections {
  display: grid;
  gap: 1.5rem;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 18rem), 1fr));
  margin: 1.25rem 0 0;
  padding: 0;
  list-style: none;
}

.collection-card {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  padding: 0.75rem;
  border: 1px solid var(--ink-700);
  color: inherit;
  text-decoration: none;
}

.collection-card:hover {
  border-color: var(--paper-dim);
}

/* Three thumbnails, equal width, cropped to a common height so a
   portrait and a landscape sitting side by side still read as a row.

   The row track has to be pinned to the strip's own height. Left to
   size itself it takes the tallest thumbnail's natural height - a
   percentage height on a grid item does not constrain the track that
   sizes it - and a 3000 px preview then grows out of the card and down
   over whatever the next section is. */
.collection-card__covers {
  display: grid;
  grid-auto-flow: column;
  /* Not plain 1fr, whose minimum is the content's own width: the
     columns would be resized by each picture as it arrived, and shift
     the card around while they did. */
  grid-auto-columns: minmax(0, 1fr);
  grid-template-rows: 6.5rem;
  gap: 0.35rem;
  height: 6.5rem;
  overflow: hidden;
  background: var(--ink-800);
}

.collection-card__covers img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.collection-card__title {
  margin: 0;
  font: 400 1.05rem/1.35 var(--display);
  letter-spacing: 0.02em;
}

.collection-card__count {
  margin: 0;
  color: var(--paper-dim);
  font: 400 0.8rem/1 var(--text);
  letter-spacing: 0.08em;
}

/* Two lines of the description, no more: the card is a way in, and the
   collection's own page has the whole of it. */
.collection-card__note {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  margin: 0;
  color: var(--paper-dim);
  font: 400 0.85rem/1.5 var(--text);
}

/* Where else a print can be found, on its own page. */
.item-collections {
  margin: 0;
  color: var(--paper-dim);
  font: 400 0.9rem/1.6 var(--text);
}

.item-collections a {
  color: var(--paper);
}

/* --- gallery ----------------------------------------------------------- */

.gallery {
  display: grid;
  gap: 2rem 1.5rem;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 16rem), 1fr));
  margin: 2.5rem 0 0;
  padding: 0;
  list-style: none;
}

/* Takes no room until the cards are in it. Its top margin would
   otherwise make the grid's block a visible box sitting where the
   collections strip is about to land, and it would count as shifted
   when the strip pushed it down. */
.gallery:empty {
  display: none;
}

.card {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  color: inherit;
  text-decoration: none;
}

.card__frame {
  position: relative;
  background: var(--ink-800);
  border: 1px solid var(--ink-700);
  overflow: hidden;
}

/* An obscured thumbnail could otherwise read as a broken or badly
   focused image, which is the wrong impression of the work. */
.card__flag {
  position: absolute;
  left: 0;
  bottom: 0;
  padding: 0.3rem 0.6rem;
  background: rgba(0, 0, 0, 0.72);
  color: var(--paper-dim);
  font: 400 0.65rem/1 var(--text);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.card__frame img {
  width: 100%;
  transition: opacity 0.25s ease;
}

.card:hover .card__frame img {
  opacity: 0.82;
}

.card__title {
  margin: 0;
  font: 400 1.05rem/1.35 var(--display);
  letter-spacing: 0.02em;
}

.card__price {
  margin: 0;
  color: var(--paper-dim);
  font: 400 0.8rem/1 var(--text);
  letter-spacing: 0.08em;
}

/* --- item page --------------------------------------------------------- */

.item {
  display: grid;
  gap: 2rem;
  margin-top: 1.5rem;
}

@media (min-width: 56rem) {
  .item {
    grid-template-columns: minmax(0, 1.6fr) minmax(18rem, 1fr);
    gap: 3rem;
    align-items: start;
  }

  .item__detail {
    position: sticky;
    top: 2rem;
  }
}

.item__frame {
  background: var(--ink-800);
  border: 1px solid var(--ink-700);
}

.item__detail {
  display: grid;
  gap: 1.25rem;
}

/* Said above the buy button, because a buyer should know that what they
   are looking at is not what they are paying for. */
.obscured-note {
  margin: 0;
  padding: 1rem 1.15rem;
  border: 1px solid var(--ink-700);
  border-left: 2px solid var(--accent);
  background: var(--ink-800);
  color: var(--paper-dim);
  font: 400 0.85rem/1.65 var(--text);
  max-width: var(--measure);
}

.obscured-note strong {
  color: var(--paper);
  font-weight: 500;
}

.price {
  margin: 0;
  font: 400 1.5rem/1 var(--display);
  letter-spacing: 0.04em;
}

/* Sits above the buy button for an obscured print - the button stays
   disabled until this is checked. */
.checkbox-field {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  color: var(--paper-dim);
  font: 400 0.85rem/1.5 var(--text);
  max-width: var(--measure);
  cursor: pointer;
}

.checkbox-field input {
  flex-shrink: 0;
  width: 1.1rem;
  height: 1.1rem;
  margin-top: 0.15rem;
  accent-color: var(--accent);
}

/* --- controls ---------------------------------------------------------- */

.button {
  display: inline-block;
  width: 100%;
  padding: 0.85rem 1.5rem;
  border: 1px solid var(--accent);
  background: transparent;
  color: var(--paper);
  font: 400 0.85rem/1.4 var(--text);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  /* Comfortably past the 44px minimum touch target on a phone. */
  min-height: 3rem;
}

.button:hover:not(:disabled) {
  background: var(--accent);
  color: var(--ink-900);
}

.button:disabled {
  opacity: 0.45;
  cursor: progress;
}

.button--quiet {
  border-color: var(--ink-600);
  color: var(--paper-dim);
}

@media (min-width: 40rem) {
  .button {
    width: auto;
  }
}

.field {
  display: grid;
  gap: 0.4rem;
}

.field label {
  color: var(--paper-dim);
  font: 400 0.75rem/1 var(--text);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.field input {
  width: 100%;
  padding: 0.8rem 0.9rem;
  border: 1px solid var(--ink-600);
  background: var(--ink-900);
  color: var(--paper);
  font: 400 1rem/1.4 var(--text);
  min-height: 3rem;
  /* Below 16px, iOS Safari zooms the page on focus. */
}

/* The contact form is the one place on the storefront that asks for more
   than a line: a select for what the message is about, and a textarea for
   the message itself. Both borrow the input's box so the three controls
   read as one form. */
.field select,
.field textarea {
  width: 100%;
  padding: 0.8rem 0.9rem;
  border: 1px solid var(--ink-600);
  background: var(--ink-900);
  color: var(--paper);
  /* 1rem for the same reason the input is: below 16px, iOS Safari zooms
     the page on focus. */
  font: 400 1rem/1.5 var(--text);
}

.field select {
  min-height: 3rem;
}

.field textarea {
  /* Taller, never wider: the form has a measure and dragging a textarea
     sideways would break it. */
  resize: vertical;
}

.field input:focus,
.field select:focus,
.field textarea:focus {
  border-color: var(--accent);
  outline: none;
}

.form {
  display: grid;
  gap: 1rem;
  max-width: 26rem;
}

.warning {
  margin: 0;
  color: var(--alarm);
  font: 400 0.85rem/1.6 var(--text);
}

.panel {
  display: grid;
  gap: 1.25rem;
  max-width: var(--measure);
  padding: 1.5rem;
  border: 1px solid var(--ink-700);
  background: var(--ink-800);
}

/* --- countdown --------------------------------------------------------- */

.countdown {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.countdown li {
  flex: 1 1 4rem;
  padding: 0.7rem 0.5rem;
  border: 1px solid var(--ink-700);
  background: var(--ink-900);
  text-align: center;
}

.countdown b {
  display: block;
  font: 400 1.6rem/1 var(--display);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
}

.countdown span {
  display: block;
  margin-top: 0.35rem;
  color: var(--paper-dim);
  font: 400 0.65rem/1 var(--text);
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.countdown--urgent b {
  color: var(--alarm);
}

.stack {
  display: grid;
  gap: 1.5rem;
}

.stack--loose {
  gap: 2.5rem;
}

[hidden] {
  display: none !important;
}

/* Respect users who ask for less motion or higher contrast. */
@media (prefers-contrast: more) {
  :root {
    --paper-dim: #cfc9c1;
    --accent: #f0e2c2;
  }
}

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