/* Self-hosted so the page fetches no third party: Cormorant Garamond for the
   display face, Inter for everything else, latin and latin-ext only. */

@font-face {
  font-family: "Cormorant Garamond";
  font-style: normal;
  font-weight: 300 500;
  font-display: swap;
  src: url("/fonts/cormorant-garamond-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-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Cormorant Garamond";
  font-style: normal;
  font-weight: 300 500;
  font-display: swap;
  src: url("/fonts/cormorant-garamond-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: "Inter";
  font-style: normal;
  font-weight: 300 600;
  font-display: swap;
  src: url("/fonts/inter-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-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 300 600;
  font-display: swap;
  src: url("/fonts/inter-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;
}

/* ═══════════════════════════════════════════════════════════════════════════
   Michal Switala — two modes, one design system

   The story (/) is ink, photographs and text, and nothing else.
   The shop (/shop/*) is paper, and is where the photographs become objects.

   The photographs are the only colour in either. Everything else is paper, ink,
   and hairlines.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  /* surfaces */
  --ink: #0a0a0b;
  --ink-raised: #131316;
  --ink-sunken: #050506;
  --paper: #f2efe9;
  --paper-raised: #faf8f4;
  --paper-sunken: #e6e1d8;

  /* type colour, dark ground */
  --on-ink: #f4f2ee;
  --on-ink-soft: rgb(244 242 238 / 0.62);
  --on-ink-faint: rgb(244 242 238 / 0.34);
  --on-ink-hair: rgb(244 242 238 / 0.14);

  /* The one colour in the system that is not paper, ink or a hairline. It is
     dark enough to sit quietly in the ink rather than announce itself, and it is
     used twice: on the opening line of the story, and on the scroll cue's rail. */
  --red-dark: #d14438;

  /* type colour, paper ground */
  --on-paper: #14140f;
  --on-paper-soft: rgb(20 20 15 / 0.62);
  --on-paper-faint: rgb(20 20 15 / 0.4);
  --on-paper-hair: rgb(20 20 15 / 0.14);

  --font-display: "Cormorant Garamond", "Cormorant Garamond", Georgia, serif;
  --font-sans: "Inter", "Inter", -apple-system, BlinkMacSystemFont, sans-serif;

  --gutter: clamp(1.25rem, 4.5vw, 5rem);
  --grid-gap: clamp(0.75rem, 1.7vw, 1.5rem);
  --measure: 36ch;
  --maxw: 1720px;
  --header-h: 4.25rem;

  /* motion — one curve for everything */
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --dur: 0.55s;
  --dur-slow: 1.1s;
}

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* the header is fixed; anchors must clear it */
  scroll-padding-top: var(--header-h);
}

body {
  margin: 0;
  background: var(--ink);
  color: var(--on-ink);
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

h1,
h2,
h3,
h4,
p,
figure,
blockquote,
dl,
dd {
  margin: 0;
}

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

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

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

:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 3px;
}

::selection {
  background: var(--on-ink);
  color: var(--ink);
}

/* ─── grain ───────────────────────────────────────────────────────────────── */

.grain {
  position: fixed;
  inset: 0;
  z-index: 9000;
  pointer-events: none;
  opacity: 0.05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ─── layout primitives ───────────────────────────────────────────────────── */

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

.ground-dark {
  background: var(--ink);
  color: var(--on-ink);
}

.ground-paper {
  background: var(--paper);
  color: var(--on-paper);
}

.section {
  padding-block: clamp(4rem, 10vh, 8rem);
}

/* ─── type ────────────────────────────────────────────────────────────────── */

.label {
  font-family: var(--font-sans);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  line-height: 1.2;
}

.display {
  font-family: var(--font-display);
  font-weight: 300;
  line-height: 0.94;
  letter-spacing: -0.015em;
  text-wrap: balance;
}

/* Every display size is bounded by width *and* height. The stage is one screen
   tall, so a heading that only knows how wide the window is will run off the
   bottom of a short one. */
.display-hero {
  font-size: clamp(2.5rem, min(12vw, 16svh), 10rem);
}

.display-xl {
  font-size: clamp(2.25rem, min(6.5vw, 12svh), 5rem);
}

.display-lg {
  font-size: clamp(1.75rem, min(3.6vw, 8.5svh), 3rem);
}

.display-md {
  font-size: clamp(1.35rem, 2.2vw, 1.9rem);
  line-height: 1.06;
}

.soft {
  color: var(--on-ink-soft);
}

.ground-paper .soft {
  color: var(--on-paper-soft);
}

.faint {
  color: var(--on-ink-faint);
}

.ground-paper .faint {
  color: var(--on-paper-faint);
}

.lede {
  font-size: clamp(1.0625rem, 1.4vw, 1.2rem);
  line-height: 1.62;
  font-weight: 300;
  color: var(--on-ink-soft);
}

.ground-paper .lede {
  color: var(--on-paper-soft);
}

.prose {
  max-width: var(--measure);
  font-size: 0.9375rem;
  line-height: 1.78;
  font-weight: 300;
  color: var(--on-ink-soft);
}

.ground-paper .prose {
  color: var(--on-paper-soft);
}

.prose + .prose {
  margin-top: 1.4em;
}

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

/* ─── reduced motion ──────────────────────────────────────────────────────── */

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

/* ─── buttons ─────────────────────────────────────────────────────────────── */

.btn {
  --btn-fg: var(--on-ink);
  --btn-hair: var(--on-ink-hair);
  display: inline-flex;
  align-items: center;
  gap: 0.7em;
  padding: 0.95em 1.5em;
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--btn-fg);
  border: 1px solid var(--btn-hair);
  position: relative;
  overflow: hidden;
  isolation: isolate;
  transition: color 0.4s var(--ease);
}

.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: currentColor;
  transform: scaleY(0);
  transform-origin: 50% 100%;
  transition: transform 0.45s var(--ease-out);
}

.btn:hover::before,
.btn:focus-visible::before {
  transform: scaleY(1);
}

.btn:hover,
.btn:focus-visible {
  color: var(--ink);
}

.ground-paper .btn {
  --btn-fg: var(--on-paper);
  --btn-hair: var(--on-paper-hair);
}

.ground-paper .btn:hover,
.ground-paper .btn:focus-visible {
  color: var(--paper);
}

.btn-solid {
  --btn-hair: transparent;
  background: var(--on-ink);
  color: var(--ink);
}

.btn-solid::before {
  background: var(--ink-raised);
}

.btn-solid:hover,
.btn-solid:focus-visible {
  color: var(--on-ink);
}

.ground-paper .btn-solid {
  background: var(--on-paper);
  color: var(--paper);
}

.ground-paper .btn-solid::before {
  background: #000;
}

.ground-paper .btn-solid:hover,
.ground-paper .btn-solid:focus-visible {
  color: var(--paper);
}

.btn-arrow .arrow {
  transition: transform 0.45s var(--ease-out);
}

.btn-arrow:hover .arrow,
.btn-arrow:focus-visible .arrow {
  transform: translateX(4px);
}

/* text link with an underline that draws in */
.link {
  position: relative;
  display: inline-block;
  padding-bottom: 2px;
}

.link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background: currentColor;
  opacity: 0.4;
  transform-origin: 0 50%;
  transition: transform 0.4s var(--ease-out), opacity 0.4s;
}

.link:hover::after,
.link:focus-visible::after {
  opacity: 1;
  transform: scaleX(1.02);
}

/* ═══ the story ═══════════════════════════════════════════════════════════ */

.story {
  /* The height of the timeline above the photographs — the air above the rail,
     the tick, and the line of hours — stated once so the rail can be measured
     here and reserved against by every screen's budget below. A photograph can
     then never be pushed under it on a short window. */
  --rail-h: calc(clamp(0.75rem, 2.5vh, 1.5rem) + 0.7rem + 0.825rem);
  background: var(--ink);
  color: var(--on-ink);
}

/* The stage holds one beat at a time and never moves; the sequence is the empty
   scroll that carries it. Base rules stack the beats instead, which is what a
   browser without JavaScript — or a reader who asked for less movement — gets:
   the same story, read top to bottom, with nothing hidden and nothing moving. */
.story-sequence {
  position: relative;
}

.story-stage {
  position: relative;
}

.story-beat {
  display: grid;
  /* One row of the beat's height when the beat has one — a percentage height on
     the screen inside it can only resolve against a row that is itself sized. On
     the beat's own minimum instead, the row takes the content and the screen is
     centred in the stage, which is what the stacked fallback wants. */
  grid-template-rows: 1fr;
  align-content: center;
  min-height: 100svh;
  padding-top: var(--header-h);
}

/* The opening is a photograph, not a panel: it owns the whole stage. */
.story-beat.beat-opening {
  align-content: stretch;
  min-height: 100svh;
  padding: 0;
}

/* With the inline `is-sequencing` class in place, the stage pins and the beats
   stack up behind it. Nothing here runs before the first paint, so the page
   never reflows into the sequence after it has been read. */
.is-sequencing .story-sequence {
  height: calc(var(--beats, 1) * 100svh);
}

.is-sequencing .story-stage {
  position: sticky;
  top: 0;
  height: 100svh;
  overflow: hidden;
}

.is-sequencing .story-beat {
  position: absolute;
  inset: 0;
  min-height: 0;
  opacity: 0;
  will-change: auto;
}

/* Only the beats actually on the stage are promoted to their own layer. */
.is-sequencing .story-beat[data-beat-state="on"] {
  will-change: opacity, transform;
}

/* A beat that is neither on the stage nor a beat away is taken out of the render
   altogether and parked a screen below the fold. The browser does not fetch
   photographs it is not rendering, and it is not rendering this — so the series
   costs what the reader has actually reached, and the same document serves the
   stacked fallback and a reader without scripting, where every photograph is
   simply in the page. */
.is-sequencing .story-beat:not([data-beat-state="on"]):not([data-beat-state="near"]) {
  content-visibility: hidden;
  transform: translate3d(0, 100svh, 0);
}

/* ─── the timeline ─────────────────────────────────────────────────────────── */

/* The event as a rail of hours along the top of the stage, 10:00 to 22:00. It is
   the story's index as well as its clock: in the sequence it is pinned to the
   stage, the sequence lights the hour of the picture being read and fills the
   line of progress, and each hour is a button that returns the stage to the
   first photograph set in that hour (see `useSequence`), so it holds still while
   the photographs dissolve past each other underneath it. Off the stage, in the
   stacked fallback, it is in flow above the story and nothing is lit: with no
   sequence there is no hour being read, only the shape of the day — but its hours
   still jump the page to the photographs. It is held clear of the header, which
   is fixed over the top of it. */
.story-timeline {
  width: 100%;
  max-width: var(--maxw);
  margin: var(--header-h) auto 0;
  padding: clamp(0.75rem, 2.5vh, 1.5rem) var(--gutter) 0;
}

.is-sequencing .story-timeline {
  position: absolute;
  z-index: 3;
  inset: var(--header-h) 0 auto;
  /* Placed by the stage, not in flow, so the header is already accounted for. */
  margin: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.6s var(--ease);
}

/* The hero and the closing words are in no hour, so the rail is not there at all
   on them — it comes with the first picture and goes before the last one. The
   hours are its only clickable parts, so while the rail is invisible over the
   hero or the closing words nothing on it takes a pointer either. */
.is-sequencing .story-timeline[data-hour] {
  opacity: 1;
  pointer-events: auto;
}

/* A `full` photograph goes edge to edge with nothing above it but the header, so
   the rail carries its own veil rather than trusting the negative under it. */
.is-sequencing .story-timeline::after {
  content: "";
  position: absolute;
  z-index: -1;
  inset: 0 0 auto;
  height: calc(100% + 1.5rem);
  background: linear-gradient(to bottom, rgb(5 5 6 / 0.72), rgb(5 5 6 / 0));
}

/* The rail: one hairline the width of the stage, with an hour marked on it every
   thirteenth of the way along. Hours are equal parts of an hour each, so the mark
   the reader is at is as far along as the day has gone. The hairline doubles as
   the day's progress — it fills from the left as far as the hour being read —
   which is why there is no second line of progress under the header. */
.story-hours {
  position: relative;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  margin: 0;
  padding: 0;
  list-style: none;
}

.story-hours::before {
  content: "";
  position: absolute;
  inset: 0.25rem 0 auto;
  height: 1px;
  background: var(--on-ink-hair);
}

/* The same hairline, filled from the left as far as the hour of the picture being
   read — the progress and the rail of hours are one line rather than two stacked
   under the header. `--sequence` is written to the document by the sequence on
   every frame (see `useSequence`) and is the fraction of the rail the hour being
   read has reached, stepped *between* the marks by the pictures themselves — the
   photographs of an hour share that hour's width, each advancing the line its own
   slice — so the line moves with the hour rather than the scroll, and it is a
   transform, so it costs no layout. Where no sequence is running — no JavaScript,
   or a reader who asked for less movement — the variable is never set and the
   rail stays the hairline it starts as. */
.story-hours::after {
  content: "";
  position: absolute;
  inset: 0.25rem 0 auto;
  height: 1px;
  background: var(--on-ink);
  transform: scaleX(var(--sequence, 0));
  transform-origin: 0 50%;
}

.story-hour {
  position: relative;
  padding-top: 0.7rem;
  text-align: center;
  color: var(--on-ink-faint);
  transition: color 0.5s var(--ease);
}

/* The line of hours. Set as a block, so the mark is exactly as tall as the label
   on it and the rail is exactly the height the budget above reserves. */
.story-hour-label {
  display: block;
  line-height: 1.2;
}

/* The hour as something to go to: the rail is the story's index, and the whole
   column — the width of an hour, the height of its label — is what a pointer or
   a finger lands on. Off it goes brighter than the hour it is not, and an hour
   no photograph names is disabled rather than taken somewhere it was not asked
   to go. */
.story-hour-hit {
  display: block;
  width: 100%;
  border: 0;
  margin: 0;
  padding: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: center;
  cursor: pointer;
  transition: color 0.5s var(--ease);
}

.story-hour-hit:not(:disabled):hover,
.story-hour-hit:focus-visible {
  color: var(--on-ink);
}

.story-hour-hit:focus-visible {
  outline: 1px solid var(--on-ink);
  outline-offset: 2px;
}

.story-hour-hit:disabled {
  cursor: default;
}

/* The tick an hour is marked by, hanging on the rail. */
.story-hour::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  width: 1px;
  height: 0.5rem;
  background: currentColor;
  transform: translateX(-50%);
  transition: height 0.5s var(--ease), background-color 0.5s var(--ease);
}

.story-hour-label::after {
  content: ":00";
}

/* The hour the beat being read is set in. The only thing on the rail that moves:
   the sequence writes `data-lit` to the one mark and takes it from the last, so
   the rail follows the photographs without a render. */
.story-hour[data-lit] {
  color: var(--on-ink);
}

.story-hour[data-lit]::before {
  height: 0.85rem;
  width: 2px;
  background: var(--on-ink);
}

/* Too narrow for `10:00` thirteen times across: the hour alone says which hour it
   is, which is all the rail is for. The same width the layouts lose their free
   column at, so the rail changes with the rest of the page rather than on its own
   measurement. */
@media (max-width: 899px) {
  .story-hour-label::after {
    content: none;
  }
}

/* ─── opening ─────────────────────────────────────────────────────────────── */

.story-opening {
  position: relative;
  height: 100%;
  min-height: 32rem;
  overflow: hidden;
  display: grid;
  align-items: start;
  padding-top: var(--header-h);
}

.story-opening-slot {
  position: absolute;
  inset: -3% 0;
  transform: scale(var(--settle, 1));
}

.story-opening-slot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.story-opening-veil {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(
      to bottom,
      rgb(5 5 6 / 0.94) 0%,
      rgb(5 5 6 / 0.4) 42%,
      rgb(5 5 6 / 0.62) 100%
    );
}

/* The hero's own box, top to bottom: the title sits where it always has, at the
   top, and the byline is held at the foot by the free space between them — so the
   two share the measure the title is set in without either being positioned. */
.story-opening-inner {
  position: relative;
  align-self: stretch;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding: clamp(1rem, 4vh, 2.5rem) var(--gutter) clamp(3.5rem, 12vh, 8rem);
}

.story-eyebrow,
.story-meta {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.story-eyebrow > span[aria-hidden] {
  flex: 0 1 clamp(1.5rem, 6vw, 5rem);
  height: 1px;
  background: currentColor;
  opacity: 0.45;
}

.story-eyebrow {
  opacity: 0;
  transform: translateY(16px);
  animation: rise 1.2s var(--ease-out) 0.2s forwards;
}

.story-opening h1 {
  margin-top: clamp(0.9rem, 2.5vh, 1.75rem);
  opacity: 0;
  transform: translateY(34px);
  animation: rise 1.5s var(--ease-out) 0.35s forwards;
}

/* The opening line is the one piece of type on the story that is allowed a
   colour. A dark red cannot be read over a photograph — the ground under it
   swings from black to whatever the plate happens to hold — so the line carries
   its own ground in a stack of shadows, widening as they go. A shadow follows the
   shape of the letters, so there is no panel and no edge to see: the ink gathers
   around the words and thins out into the veil, and the red has something fixed
   to sit on wherever the photograph happens to be bright. */
.story-sub {
  margin-top: clamp(0.9rem, 2vh, 1.5rem);
  max-width: 32ch;
  color: var(--red-dark);
  text-shadow:
    0 0 6px rgb(5 5 6 / 0.9),
    0 0 18px rgb(5 5 6 / 0.7),
    0 0 48px rgb(5 5 6 / 0.5),
    0 0 110px rgb(5 5 6 / 0.3);
  opacity: 0;
  transform: translateY(24px);
  animation: rise 1.5s var(--ease-out) 0.55s forwards;
}

.story-meta {
  margin-top: auto;
  padding-top: clamp(1.25rem, 3vh, 2rem);
  opacity: 0;
  transform: translateY(18px);
  animation: rise 1.5s var(--ease-out) 0.7s forwards;
}

.story-meta > span[aria-hidden] {
  opacity: 0.5;
}

@keyframes rise {
  to {
    opacity: 1;
    transform: none;
  }
}

/* The one instruction on the story: the reader is told to scroll, and told
   plainly. It is set a step above the other labels, with the word sitting over
   the rail so the pair reads downward, and the rail is heavier and longer than
   any other hairline on the page — so the gesture is the first thing the eye
   finds after the title rather than a footnote tucked into the corner. */
.story-cue {
  position: absolute;
  right: var(--gutter);
  bottom: clamp(3.5rem, 12vh, 8rem);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
  opacity: 0;
  transform: translateY(10px);
  animation: rise 1.4s var(--ease-out) 1s forwards;
}

.story-cue .label {
  font-size: clamp(1rem, 1vw + 0.5rem, 1.5rem);
  letter-spacing: 0.28em;
  /* Tracking leaves a gap after the last letter, which would set the word left of
     the rail it is centred over; the same gap is given back on the other side. */
  padding-left: 0.28em;
}

.scroll-rail {
  position: relative;
  width: 3px;
  height: clamp(4rem, 11vh, 7rem);
  background: var(--on-ink-hair);
  overflow: hidden;
}

.scroll-rail::after {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  width: 100%;
  height: 45%;
  /* The moving line carries the story's one colour, and its own glow so it stays
     legible where the hero photograph is bright — the same reason the opening
     line carries a stack of shadows. */
  background: var(--red-dark);
  box-shadow: 0 0 6px rgb(209 68 56 / 0.6);
  animation: rail 2.4s var(--ease) infinite;
}

@keyframes rail {
  0% {
    transform: translateY(-100%);
  }
  60%,
  100% {
    transform: translateY(250%);
  }
}

/* ─── the screen ───────────────────────────────────────────────────────────── */

/* One screen is a grid: the photographs, and the words if the file gives the
   screen any. The two attributes come straight from content/story.yaml — the
   layout says which side the picture is on, the text position says where the
   words go — and this is the only place those two decisions become geometry.
   Each case below states the columns and the cell each part sits in; the
   children read them, so a case is one rule rather than three. */
.story-layout {
  --pic-col: 1.55fr;
  --words-col: minmax(0, 1fr);
/* One budget, shared. `--room` is everything on a screen that is not the
   photograph: the header, the rail of hours above it, the air this layout keeps,
   the air the slot keeps above and below the frame, a line of caption, and the
   gap between the picture and the words. What is left of the stage,
   `--stage-h`, is all the photograph and the words can have between them, and they
   take turns: the frame is capped, and the words take what the frame did not —
   never less than a paragraph, which is where a long passage starts to scroll
   rather than to push the photograph off the stage. The 3:2 of the negative turns
   the height of the frame into the width it is drawn at. */
  --layout-pad: clamp(0.75rem, 3vh, 2.5rem);
  --slot-pad: clamp(2.5rem, 8vh, 7rem);
  --caption-h: 2.5rem;
  --plate-room: calc(var(--slot-pad) * 2 + var(--caption-h));
  --room: calc(var(--header-h) + var(--rail-h) + 2 * var(--layout-pad) + var(--plate-room) + var(--grid-gap));
  --stage-h: calc(100svh - var(--room));
  --words-min: min(15rem, 22svh);
  /* The space between two photographs, which is the space between anything else
     on a screen. A two-up is two pictures, not one picture printed twice. */
  --pic-gap: var(--grid-gap);
  /* Where there is no free column, the photograph is as wide as the screen
     allows and the width decides its height. A ceiling, and the frame is drawn at
     `min(100%, ...)`, so the column has the last word either way. */
  --frame-full-h: calc((100vw - 2 * var(--gutter)) / 1.5);
  --frame-h: min(64svh, var(--stage-h));
  --words-max: max(calc(var(--stage-h) - var(--frame-h)), var(--words-min));
  display: grid;
  gap: clamp(1.5rem, 3vw, 3rem);
  align-content: safe center;
  height: 100%;
  /* In the sequence a screen is the whole stage, so it is told how much of it it
     has and scrolls itself if its words turn out to need more. Off the stage, in
     the stacked fallback, a screen is only as tall as what it holds. */
  overflow-y: auto;
  /* A grid item with auto margins stops stretching to fill its cell: the margins
     take the slack instead and everything inside collapses. Told to fill the
     cell and then centred by `justify-self`, which does allow auto margins. */
  width: 100%;
  justify-self: center;
  max-width: var(--maxw);
  padding: var(--layout-pad) var(--gutter);
}

.story-pictures {
  grid-column: var(--pic-at, 1);
  grid-row: var(--pic-row, 1);
  order: 1;
  display: block;
}

.story-words {
  grid-column: var(--words-at, 1);
  grid-row: var(--words-row, 2);
  order: var(--words-order, 2);
  align-self: center;
  max-width: 36ch;
  /* Beside the picture, only the screen bounds the words. Stacked with it, they
     may have what the photograph has not taken — and no more, so that a long
     passage pushes the picture smaller rather than off the stage. A cap the
     passage outgrows is a screen of its own: the words scroll, the photograph
     stays where it is. */
  max-height: 72svh;
  overflow-y: auto;
}

.story-words .display {
  margin-bottom: 0.6em;
}

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

/* — the words beside the picture, on the free side of it — */
.story-layout[data-text-position="right"] {
  --cols: var(--pic-col) var(--words-col);
  --words-at: 2;
  --words-row: 1;
}

.story-layout[data-text-position="left"] {
  --cols: var(--words-col) var(--pic-col);
  --pic-at: 2;
  --words-at: 1;
  --words-row: 1;
}

/* — the words above or below, in the picture's own column, so picture and words
     share an edge the way they do in a book — */
.story-layout[data-text-position="top"] {
  --cols: var(--pic-col) var(--words-col);
  --words-at: 1;
  --words-row: 1;
  --pic-row: 2;
  --words-order: 1;
  align-content: space-between;
}

.story-layout[data-text-position="bottom"] {
  --cols: var(--pic-col) var(--words-col);
  --words-at: 1;
  --words-row: 2;
  align-content: space-between;
}

.story-layout[data-layout="single_right"][data-text-position="top"] {
  --cols: var(--words-col) var(--pic-col);
  --pic-at: 2;
  --words-at: 2;
  --words-row: 1;
  --pic-row: 2;
  --words-order: 1;
  align-content: space-between;
}

.story-layout[data-layout="single_right"][data-text-position="bottom"] {
  --cols: var(--words-col) var(--pic-col);
  --pic-at: 2;
  --words-at: 2;
  --words-row: 2;
  align-content: space-between;
}

/* — two photographs take the whole width, so the words are not a row beside or
     under them: from the width where a column of type fits alongside the pair
     they take the companion's own column — see the two-up below — and until then
     the screen is one column, the way every screen is below that width. */
.story-layout[data-layout="right_double"],
.story-layout[data-layout="left_double"] {
  --cols: minmax(0, 1fr);
  --words-at: 1;
}

/* — the words asked for the side the picture is already on, so they are set over
     it rather than refused: a wash of the ground colour carries them, and the
     photograph is left whole behind — */
.story-layout[data-over] {
  --cols: minmax(0, 1fr);
  --pic-at: 1;
  --words-at: 1;
  --words-row: 1;
}

.story-layout[data-over] .story-words {
  justify-self: center;
  max-width: 38ch;
  max-height: 46svh;
  padding: clamp(1.5rem, 3vw, 2.75rem);
  background: rgb(10 10 11 / 0.82);
}

/* The columns are declared once, wherever the case named them. */
.story-layout {
  grid-template-columns: var(--cols, minmax(0, 1fr));
}

/* Below the width where a free column exists, a screen is one column: the
   photograph, then the words beneath it, or above it when the file put them
   there. The order is the order of the file either way. */
@media (max-width: 899px) {
  .story-layout {
    grid-template-columns: minmax(0, 1fr);
    align-content: safe center;
  }

  /* Stacked, the picture and the words are neighbours, not two ends of the
     screen. The wide layout spreads them to the top and the foot of a screen
     whose words sit beside the photograph; once they are one column that would
     strand the words at the bottom, a whole screen away from the picture. The
     selector matches the top/bottom rules by specificity so it can undo them. */
  .story-layout[data-text-position] {
    align-content: safe center;
  }

  .story-pictures,
  .story-words {
    grid-column: 1;
    grid-row: auto;
  }

  .story-layout .story-words {
    max-height: min(38svh, var(--words-max));
  }

  /* Everything stacks, so whichever side the file put the words on, they take
     their share of the height and the photograph keeps the rest. A two-up is
     stacked too, but its frames have their own budget below. */
  .story-layout[data-text-position]:not([data-layout="right_double"]):not(
      [data-layout="left_double"]
    ) {
    --frame-h: min(44svh, var(--frame-full-h), max(calc(var(--stage-h) - var(--words-min)), 16svh));
  }

  .story-layout[data-over][data-text-position]:not([data-layout="full"]) .story-words {
    max-width: 36ch;
    max-height: min(38svh, var(--words-max));
    padding: 0;
    background: none;
  }
}

/* ─── plates ──────────────────────────────────────────────────────────────── */

/* The slot is the room the caption and the settled photograph sit in. Its air is
   the same `--slot-pad` the screen's budget reserves, so the cap can never
   promise the photograph more room than the slot gives it. */
.story-plate-slot {
  padding-block: var(--slot-pad, clamp(2.5rem, 8vh, 7rem));
}

/* The frame of a photograph. Not a control: no cursor of its own, and nothing
   happens to it under the pointer — a picture in the story is looked at, and the
   ways to buy one are the links at the end of it. */
.story-plate-frame {
  position: relative;
  display: block;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  background: var(--ink-sunken);
  /* A fraction larger off-beat than on it, written by the sequence. */
  transform: scale(var(--settle, 1));
}

.story-plate-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1.001);
  transition: transform 1.6s var(--ease-out);
}

/* The corner the caption is printed in gets a little more shadow to sit in, so a
   caption is readable on a bright frame and on a black one. */
.story-plate-frame::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(
    85% 75% at 100% 100%,
    rgb(5 5 6 / 0.72),
    rgb(5 5 6 / 0.34) 46%,
    transparent 72%
  );
  pointer-events: none;
}

/* The caption is printed on the photograph, in its right bottom corner and
   right-aligned: it describes this picture, so it belongs to the picture rather
   than to the margin under it. It is held off the photograph's own edges by
   `--caption-inset` across, and set closer to the bottom edge by `--caption-lift`,
   so the reference sits near the corner instead of floating above it. The slot's
   air is added back to the anchor, because the figure is that much taller than
   the photograph. Set over an image it carries more of its own contrast than it
   does on the ink, so the caption is near white. */
.story-plate-caption {
  --caption-inset: clamp(1rem, 3.5vw, 3rem);
  --caption-lift: clamp(0.75rem, 1.75vw, 1.75rem);
  position: absolute;
  z-index: 1;
  right: var(--caption-inset);
  bottom: calc(var(--slot-pad, 0px) + var(--caption-lift));
  display: flex;
  align-items: baseline;
  justify-content: flex-end;
  gap: 0.6rem;
  max-width: min(100%, 34rem);
  margin: 0;
  color: var(--on-ink);
  text-align: right;
  text-shadow: 0 1px 12px rgb(5 5 6 / 0.6);
}

.story-plate {
  position: relative;
  margin: 0 auto;
  width: min(100%, calc(var(--frame-h) * 1.5));
}

/* How much of the stage the photograph gets is decided by what else the screen
   is holding. Each rule below is a share of what is left. */
.story-layout:not([data-text-position]) {
  --layout-pad: 0px;
  --frame-h: min(82svh, var(--frame-full-h), var(--stage-h));
}

.story-layout[data-text-position] {
  --frame-h: min(60svh, var(--frame-full-h), var(--stage-h));
}

/* Stacked — the words above or below the photograph, in one column with it. The
   frame is capped so the words keep their share, and the column is as wide as
   the frame is allowed to be tall, or the photograph would sit in a wide column
   with the space beside it going unused. */
.story-layout[data-text-position="top"],
.story-layout[data-text-position="bottom"] {
  --frame-h: min(44svh, var(--frame-full-h), max(calc(var(--stage-h) - var(--words-min)), 16svh));
  --cols: min(calc(var(--frame-h) * 1.5), 72%) auto;
  justify-content: center;
}

.story-layout[data-over] {
  --frame-h: min(74svh, var(--frame-full-h), var(--stage-h));
}

/* ─── two photographs: a larger plate and a smaller companion ─────────────── */

/* A two-up is not two equal pictures. The screen says which side the smaller one
   takes — `right_double` puts it on the right of the lead, `left_double` on the
   left — and the plates are read lead first, companion second. The companion is
   half the lead: half its height, and the width that half height draws at, so
   the larger photograph is twice the smaller one in both directions. The two
   start on the same line along their tops, so the smaller one ends higher, and
   both captions are printed on their own photograph — the way a single's is, in
   the corner of the frame — because a two-up sets no label on the page. What the
   page holds instead is the screen's own words, and from the width where a
   column of type fits alongside the pair they are set in the companion's column,
   under the smaller photograph, in the gap the two unequal frames open. */
.story-layout[data-layout="right_double"],
.story-layout[data-layout="left_double"] {
  /* The companion's own height, and the width each frame is drawn at: the lead
     is one and a half times its height wide, the companion the same 3:2 at half
     the height — so the pair and the gap between them measure two and a quarter
     frames plus one gap. */
  --small-fh: calc(var(--frame-h) / 2);
  --lead-w: calc(var(--frame-h) * 1.5);
  --small-w: calc(var(--small-fh) * 1.5);
  /* Neither frame keeps the slot's air, and no caption is owed under the pair:
     both captions are printed on the photographs, so the stage is asked for the
     frames and their words, and for the air the frames themselves stand in. */
  --slot-pad: 0px;
  --plate-room: calc(var(--slot-pad) * 2);
}

@media (min-width: 560px) {
  /* Side by side. The pair is one row, so the height the lead may take is what
     the screen has left once the companion and the gap are measured in width:
     two and a quarter frames and a gap have to fit across the screen. */
  .story-layout[data-layout="right_double"],
  .story-layout[data-layout="left_double"] {
    --pair-full-h: calc((min(var(--maxw), 100vw) - 2 * var(--gutter) - var(--pic-gap)) / 2.25);
  }

  .story-layout[data-layout="right_double"]:not([data-text-position]),
  .story-layout[data-layout="left_double"]:not([data-text-position]) {
    --frame-h: min(82svh, var(--pair-full-h), var(--stage-h));
  }

  /* With words, until the screen is wide enough to hold them beside the pair,
     the row shares the stage with them: the frames give up the height a
     paragraph is owed rather than push it off the screen. */
  .story-layout[data-layout="right_double"][data-text-position],
  .story-layout[data-layout="left_double"][data-text-position] {
    --frame-h: min(
      40svh,
      var(--pair-full-h),
      max(calc(var(--stage-h) - var(--words-min) - var(--pic-gap)), 14svh)
    );
  }

  .story-layout[data-layout="right_double"] .story-pictures,
  .story-layout[data-layout="left_double"] .story-pictures {
    display: grid;
    gap: var(--pic-gap);
    justify-content: center;
    /* The frames sit level along their top edges: the two photographs start on
       the same line, and the smaller one ends higher. */
    align-items: start;
  }

  /* The columns follow the side the companion takes, not the reading order: each
     plate is placed by the layout below, so the wide column is where the lead
     lands and the narrow one where the companion does — the wider first on a
     `right_double`, the narrower first on a `left_double`. */
  .story-layout[data-layout="right_double"] .story-pictures {
    grid-template-columns: minmax(0, var(--lead-w)) minmax(0, var(--small-w));
  }

  .story-layout[data-layout="left_double"] .story-pictures {
    grid-template-columns: minmax(0, var(--small-w)) minmax(0, var(--lead-w));
  }

  /* The plates are in reading order; the layout says which column each takes. */
  .story-layout[data-layout="right_double"] .story-plate {
    grid-column: 1;
  }

  .story-layout[data-layout="left_double"] .story-plate {
    grid-column: 2;
  }

  .story-layout[data-layout="right_double"] .story-plate.is-companion {
    grid-column: 2;
  }

  .story-layout[data-layout="left_double"] .story-plate.is-companion {
    grid-column: 1;
  }
}

/* From the width where the pair and a column of type fit side by side, the words
   leave the row of their own and take the companion's column: the heading and
   the text are printed under the smaller photograph, and the pair — no longer
   sharing the stage's height with a row of type — takes the room that gives it
   back. The columns are the pair's own, measured from the same widths and
   separated by the same gap, so the words fall in the companion's column
   whatever the pair ends up measuring. */
@media (min-width: 900px) {
  .story-layout[data-layout="right_double"][data-text-position],
  .story-layout[data-layout="left_double"][data-text-position] {
    gap: var(--pic-gap);
    justify-content: center;
    align-content: safe center;
  }

  /* The columns are the pair's own, in the same order the frames are laid in, so
     the words fall in the companion's column whatever the pair measures. */
  .story-layout[data-layout="right_double"][data-text-position] {
    --cols: minmax(0, var(--lead-w)) minmax(0, var(--small-w));
  }

  .story-layout[data-layout="left_double"][data-text-position] {
    --cols: minmax(0, var(--small-w)) minmax(0, var(--lead-w));
  }

  /* The words are under the companion now, so they cost the pair no height of
     its own and it is measured exactly as it is without them. */
  .story-layout[data-layout="right_double"][data-text-position="bottom"],
  .story-layout[data-layout="left_double"][data-text-position="bottom"] {
    --frame-h: min(82svh, var(--pair-full-h), var(--stage-h));
  }

  /* The pair fills both columns; the words take the companion's. */
  .story-layout[data-layout="right_double"][data-text-position] .story-pictures,
  .story-layout[data-layout="left_double"][data-text-position] .story-pictures {
    grid-column: 1 / -1;
    grid-row: 1;
  }

  .story-layout[data-layout="right_double"][data-text-position] .story-words {
    grid-column: 2;
  }

  .story-layout[data-layout="left_double"][data-text-position] .story-words {
    grid-column: 1;
  }

  /* At the foot — the default — the words hang from the smaller photograph's own
     foot, top-aligned in its column and given the room the stage has left under
     it. A passage that outgrows that room scrolls rather than pushing the pair
     off the stage. */
  .story-layout[data-layout="right_double"][data-text-position="bottom"] .story-words,
  .story-layout[data-layout="left_double"][data-text-position="bottom"] .story-words {
    grid-row: 1;
    align-self: start;
    margin-top: calc(var(--small-fh) + var(--pic-gap));
    max-height: min(38svh, calc(var(--stage-h) - var(--small-fh) - var(--pic-gap)));
  }

  /* Asked for at the top, the words stand above the pair instead — in the
     companion's column all the same, closing on the smaller photograph's top
     edge, and capped by what the pair has not taken of the stage. */
  .story-layout[data-layout="right_double"][data-text-position="top"] .story-words,
  .story-layout[data-layout="left_double"][data-text-position="top"] .story-words {
    grid-row: 1;
    align-self: end;
    max-height: min(38svh, calc(var(--stage-h) - var(--frame-h) - var(--pic-gap)));
  }

  .story-layout[data-layout="right_double"][data-text-position="top"] .story-pictures,
  .story-layout[data-layout="left_double"][data-text-position="top"] .story-pictures {
    grid-row: 2;
  }
}

/* The words stacked with the picture share the stage with it. */
.story-layout[data-text-position="top"] .story-words,
.story-layout[data-text-position="bottom"] .story-words {
  max-height: min(38svh, var(--words-max));
}

/* Stacked on narrow screens the two plates sit one above the other — the lead
   first, the companion under it — both drawn at the screen's own width. At this
   width the companion is no smaller than the lead and a corner of it is no place
   to set a line of type, so neither plate is captioned at all: the photograph's
   alt text still reads the line out, and nothing is set on the ink under the
   frame. A plate takes the whole column here, the way a centred photograph does,
   so a pair is taller than one stage: rather than shrinking the pictures to fit,
   the beat scrolls itself and the words follow under the pair. */
@media (max-width: 559px) {
  .story-layout[data-layout="right_double"],
  .story-layout[data-layout="left_double"] {
    /* No air stands around a frame, and no caption is owed under it: the pictures
       are shown whole, one over the other. */
    --slot-pad: 0px;
    /* The words follow the pair closely, the way the pair's own gap does. */
    gap: var(--grid-gap);
    /* The frame is the width of the column — `--frame-full-h` states that width
       as a height — so the picture is as wide as the screen and its own 3:2
       decides how tall it stands, with nothing left over to cap it against a
       stage it will not fit on. */
    --frame-h: var(--frame-full-h);
  }

  /* Both plates are the same size: the base width — the frame's own height drawn
     at the negative's 3:2, bounded by the column — serves both, so nothing here
     redraws the companion at half the lead. The two stack, so the gap between
     them is the pair's own. */
  .story-layout[data-layout="right_double"] .story-plate + .story-plate,
  .story-layout[data-layout="left_double"] .story-plate + .story-plate {
    margin-top: var(--pic-gap);
  }

  /* The caption comes off both plates: the picture is left whole, and nothing is
     set on the ink under it — the frame's own alt text carries the line. */
  .story-layout[data-layout="right_double"] .story-plate-caption,
  .story-layout[data-layout="left_double"] .story-plate-caption {
    display: none;
  }
}

/* ─── a centred photograph ────────────────────────────────────────────────── */

/* A centred photograph takes the width of the screen, so it has no free side to
   hand the words: they are set over it, on a scrim, anchored to its top or its
   foot. The picture is the screen; the words are a caption on it. These come
   after the rules above on purpose — a centre screen is a layout of its own, and
   the stacked and overlaid cases are the ones that bend for it. */
.story-layout[data-layout="center"] {
  --cols: minmax(0, 1fr);
  --pic-at: 1;
  --pic-row: 1;
  --words-at: 1;
  --words-row: 1;
  /* The photograph is the screen, so the slot's air above and below it goes with
     it: a panel anchored to an edge of the frame has to find the frame there. */
  --slot-pad: 0px;
  align-content: safe center;
}

.story-layout[data-layout="center"][data-text-position] {
  --frame-h: min(74svh, var(--frame-full-h), var(--stage-h));
}

@media (min-width: 900px) {
  .story-layout[data-layout="center"] .story-words {
    justify-self: center;
    max-width: 38ch;
    max-height: 46svh;
    padding: clamp(1.5rem, 3vw, 2.75rem);
    background: rgb(10 10 11 / 0.82);
  }

  /* The panel's edge sits on the photograph's edge, and its own air is what
     carries the words in over the image. Words at the foot are lifted clear of
     the caption, which is printed in the photograph's own bottom right corner. */
  .story-layout[data-layout="center"][data-text-position="top"] .story-words {
    align-self: start;
  }

  .story-layout[data-layout="center"][data-text-position="bottom"] .story-words {
    align-self: end;
    margin-bottom: calc(var(--caption-h) + 4rem);
  }
}

/* ─── a full photograph ───────────────────────────────────────────────────── */

/* A full photograph takes the whole stage, edge to edge, and is drawn to fill it
   (cropped where the stage is not its ratio). The words, the plate number and the
   plate's caption have no other room on the screen, so all three sit over the
   image: the words on a scrim at the top or foot, and the reference on a light
   bar at the foot, the way a caption is printed on a poster. These rules come
   after every other layout on purpose — a full screen is a layout of its own,
   and the budgets above it are wrong for it. */
.story-beat:has([data-layout="full"]) {
  padding: 0;
}

.story-layout[data-layout="full"] {
  position: relative;
  height: 100%;
  width: 100%;
  max-width: none;
  padding: 0;
  overflow: hidden;
  background: var(--ink-sunken);
  /* The slot's air is not there in this layout, so the budget stops reserving it
     and the caption's anchor is measured from the photograph's own edge. */
  --slot-pad: 0px;
  /* One column the full width, one row the full height: the cell the photograph
     sits in is the screen itself, the way the opening's is. Left to the content
     it would measure nothing — the frame is out of flow inside it — and a
     photograph in a row of no height is a photograph that is not there. The
     words are out of flow too, on their scrim, so no row is theirs to take. */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: 100%;
  align-content: stretch;
}

.story-layout[data-layout="full"] .story-pictures {
  height: 100%;
}

.story-layout[data-layout="full"] .story-plate {
  width: 100%;
  height: 100%;
  margin: 0;
}

.story-layout[data-layout="full"] .story-plate-slot {
  height: 100%;
  padding: 0;
}

.story-layout[data-layout="full"] .story-plate-frame {
  position: absolute;
  inset: 0;
  aspect-ratio: auto;
  height: 100%;
}

.story-layout[data-layout="full"] .story-plate-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The words, on a scrim, anchored to the top or the foot of the whole screen. */
.story-layout[data-layout="full"] .story-words {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  width: min(100%, 40rem);
  max-height: 60svh;
  margin: 0;
  padding: clamp(1.5rem, 3vw, 2.75rem);
  background: rgb(10 10 11 / 0.62);
}

.story-layout[data-layout="full"][data-text-position="top"] .story-words {
  top: calc(var(--header-h) + clamp(1rem, 3vh, 2rem));
}

.story-layout[data-layout="full"][data-text-position="bottom"] .story-words {
  bottom: clamp(4.5rem, 15vh, 9rem);
}

/* ─── a screen of words alone ─────────────────────────────────────────────── */

/* A `text_only` screen has no photograph to make room for, so the heading and the
   text are centred in the page both ways: the whole stage is theirs. It is a
   pause between photographs — the reader is asked to read rather than to look —
   and the rail above the stage leaves with the photographs, so the screen is the
   words and nothing else. The shared budget still applies: the header keeps its
   own space, and a long passage scrolls inside the screen rather than pushing
   under it. These rules come after the layouts they must not fight on purpose. */
.story-layout[data-layout="text_only"] {
  --layout-pad: clamp(1.5rem, 5vh, 4.5rem);
  align-content: safe center;
}

.story-layout[data-layout="text_only"] .story-words {
  justify-self: center;
  max-width: 38ch;
  max-height: calc(100svh - var(--header-h) - var(--rail-h) - 2 * var(--layout-pad));
}

/* ─── closing ─────────────────────────────────────────────────────────────── */

.story-outro {
  padding-block: clamp(3rem, 9vh, 10rem);
}

.outro {
  display: grid;
  gap: clamp(1.25rem, 2.5vw, 2rem);
  max-width: 52rem;
}

.outro .display {
  max-width: 22ch;
}

.outro-shop {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: clamp(1rem, 2vw, 1.5rem);
}

.outro-link {
  display: inline-flex;
  align-items: center;
  gap: 0.9em;
  padding: 0.9em 1.35em;
  border: 1px solid var(--on-ink-hair);
  color: var(--on-ink);
  transition: border-color 0.4s var(--ease), background-color 0.4s var(--ease);
}

.outro-link:hover,
.outro-link:focus-visible {
  border-color: var(--on-ink);
  background: rgb(244 242 238 / 0.06);
}

.outro-link .arrow {
  transition: transform 0.45s var(--ease-out);
}

.outro-link:hover .arrow,
.outro-link:focus-visible .arrow {
  transform: translateX(4px);
}

/* A short window — a laptop with the browser's chrome showing, a phone held
   sideways — leaves less room than the story's type wants. The stage is the
   whole screen, so on a short one the beats step down rather than run off it. */
@media (max-height: 680px) {
  .story-layout {
    --layout-pad: clamp(0.75rem, 2.5vh, 2rem);
    --slot-pad: clamp(1.5rem, 4vh, 3rem);
    gap: clamp(0.75rem, 2vh, 1.5rem);
  }

  .story-outro {
    padding-block: clamp(1.25rem, 4vh, 3rem);
  }

  .story-words .display,
  .outro .display {
    font-size: clamp(1.5rem, 4.4vh, 2rem);
  }

  /* The opening already fills the stage; a floor under it only ever fights the
     window it is in. */
  .story-opening {
    min-height: 0;
  }

  /* A short window cannot hold a hero photograph and a readable caption both, so
     the words get their share of the stage back and the photograph is the one
     that steps down for it. */
  .story-layout[data-layout="center"][data-text-position] {
    --frame-h: min(58svh, var(--frame-full-h), max(calc(var(--stage-h) - var(--words-min)), 30svh));
  }

  .story-layout[data-layout="right_double"],
  .story-layout[data-layout="left_double"] {
    --pic-gap: clamp(0.75rem, 2vh, 1.5rem);
  }
}

/* Very short — a phone held sideways, a window dragged up to nothing. The type
   steps down again and the air between the beats all but goes, so that every
   beat still lands whole on its stage. */
@media (max-height: 520px) {
  .story-layout {
    --layout-pad: clamp(0.5rem, 1.5vh, 1rem);
    --slot-pad: clamp(0.75rem, 3vh, 2rem);
    --caption-h: 2rem;
    /* There is not enough of such a window for a paragraph and a photograph both
       at their best, so the words' floor drops: the photograph keeps the room it
       needs to be looked at, and a long passage scrolls its own screen rather
       than pushing the bottom of the page off the stage. */
    --words-min: min(15rem, 18svh);
    gap: clamp(0.5rem, 1.5vh, 1rem);
  }

  .story-outro {
    padding-block: clamp(0.5rem, 2vh, 1.5rem);
  }

  .prose {
    font-size: 0.875rem;
    line-height: 1.7;
  }

  .outro {
    gap: clamp(0.5rem, 1.5vh, 1rem);
  }
}

/* A window this short cannot hold a whole screen's words at a legible size. The
   type steps down, and a passage that still outgrows the screen scrolls itself
   while the photograph stays put beside it. */
@media (max-height: 400px) {
  .story-cue {
    display: none;
  }

  .prose {
    font-size: 0.8125rem;
    line-height: 1.6;
  }
}

/* ═══ header ═════════════════════════════════════════════════════════════ */

.header {
  --fg: var(--on-ink);
  --hair: var(--on-ink-hair);
  position: fixed;
  inset: 0 0 auto;
  z-index: 800;
  height: var(--header-h);
  display: flex;
  align-items: center;
  color: var(--fg);
  border-bottom: 1px solid transparent;
  transition: background-color 0.5s var(--ease), border-color 0.5s var(--ease),
    color 0.5s var(--ease);
}

.header.is-paper {
  --fg: var(--on-paper);
  --hair: var(--on-paper-hair);
}

.header.is-stuck {
  border-bottom-color: var(--hair);
}

.header.is-ink.is-stuck {
  background: rgb(10 10 11 / 0.72);
  backdrop-filter: blur(18px) saturate(140%);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
}

.header.is-paper.is-stuck {
  background: rgb(242 239 233 / 0.78);
  backdrop-filter: blur(18px) saturate(140%);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
}

.header-inner {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.25rem;
}

.wordmark {
  font-family: var(--font-display);
  font-size: 1.1rem;
  font-weight: 400;
  letter-spacing: 0.06em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* The one place the story lets you out: prints and the book, side by side. */
.shop-nav {
  display: flex;
  border: 1px solid var(--hair);
  flex: 0 0 auto;
}

.shop-nav-link {
  position: relative;
  padding: 0.62em 1.05em;
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  opacity: 0.62;
  transition: opacity 0.35s var(--ease), background-color 0.35s var(--ease);
}

.shop-nav-link + .shop-nav-link {
  border-left: 1px solid var(--hair);
}

.shop-nav-link:hover,
.shop-nav-link:focus-visible {
  opacity: 1;
}

.shop-nav-link[aria-current="page"] {
  opacity: 1;
  background: color-mix(in srgb, var(--fg) 10%, transparent);
}

/* ═══ the shop ════════════════════════════════════════════════════════════ */

.shop {
  background: var(--paper);
  color: var(--on-paper);
  padding-top: var(--header-h);
}

/* The lede is a full-width row of its own: it runs the width of the shell
   rather than sitting in a measure beside the currency switch. */
.shop-head {
  display: grid;
  gap: 1rem 2rem;
  padding-bottom: clamp(1.75rem, 4vw, 3rem);
  border-bottom: 1px solid var(--on-paper-hair);
}

.shop-head-top {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1.5rem 2rem;
}

.shop-lede {
  max-width: 46ch;
  margin-top: 1rem;
}

.shop-head .display {
  margin-top: 0.75rem;
}

.shop-head .shop-lede {
  max-width: none;
}

.shop-head-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1rem;
}

/* ─── plate picker ────────────────────────────────────────────────────────── */

.plate-picker {
  display: flex;
  gap: 0.4rem;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  scroll-snap-type: x proximity;
  padding: clamp(1rem, 2vw, 1.5rem) 0;
  /* the row runs off the page, so the edges have to say so */
  mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 3rem), transparent);
}

@supports not (mask-image: linear-gradient(90deg, #000, transparent)) {
  .plate-picker {
    mask-image: none;
  }
}

.plate-picker::-webkit-scrollbar {
  display: none;
}

/* Required: a next/image `fill` child positions against its nearest positioned
   ancestor, or it escapes to the document origin. */
.picker-thumb {
  position: relative;
  flex: 0 0 auto;
  width: 4rem;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  opacity: 0.42;
  border: 1px solid transparent;
  scroll-snap-align: center;
  transition: opacity 0.35s var(--ease), border-color 0.35s var(--ease);
}

@media (min-width: 900px) {
  .picker-thumb {
    width: 4.5rem;
  }
}

.picker-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.picker-thumb:hover {
  opacity: 0.75;
}

.picker-thumb.is-on {
  opacity: 1;
  border-color: var(--on-paper);
}

/* ─── the print matrix ────────────────────────────────────────────────────── */

.chooser {
  display: grid;
  gap: clamp(1.75rem, 3vw, 3rem);
}

@media (min-width: 1000px) {
  .chooser {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
    align-items: start;
  }
}

/* The card is paper, like the page around it: the caption is set under the
   photograph, and a caption on ink under a print on the shop page reads as a
   mistake. The dark ground belongs to the image alone, where a photograph that
   does not quite fill its frame is letterboxed rather than washed out. */
.chooser-preview {
  position: relative;
  border: 1px solid var(--on-paper-hair);
  background: var(--paper);
}

/* A fall-back shape only: the frame is given each photograph's own measured ratio
   in the markup, and `contain` means even a wrong one letterboxes rather than
   crops. A print is offered whole — a crop of it is a different picture. */
.chooser-preview-frame {
  position: relative;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  background: var(--ink-sunken);
}

.chooser-preview-frame img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Under the photograph, not on it: the caption belongs to the plate the way a
   museum label does, so the picture itself is never written over. */
.chooser-preview-tag {
  display: flex;
  background: var(--paper);
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  padding: 0.7rem 1.15rem 0.8rem;
  border-top: 1px solid var(--on-paper-hair);
  color: var(--on-paper);
}

.chooser-preview-tag .faint {
  color: var(--on-paper-faint);
}

.option-group-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: 0.7rem;
  border-bottom: 1px solid var(--on-paper-hair);
  margin-bottom: 0.9rem;
}

/* Equal-width boxes, one above the other: a grid so every option spans the
   full width of the panel however long its text runs. */
.options {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.5rem;
}

.option {
  display: inline-flex;
  flex-direction: column;
  gap: 0.3rem;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--on-paper-hair);
  color: var(--on-paper-soft);
  text-align: left;
  transition: border-color 0.35s var(--ease), color 0.35s var(--ease),
    background-color 0.35s var(--ease);
}

.option:hover {
  border-color: var(--on-paper);
  color: var(--on-paper);
}

.option[aria-pressed="true"] {
  color: var(--on-paper);
  border-color: var(--on-paper);
  background: rgb(20 20 15 / 0.05);
}

.option-name {
  font-size: 0.8125rem;
  font-weight: 400;
  line-height: 1.25;
}

.option-note {
  font-size: 0.6875rem;
  opacity: 0.62;
  line-height: 1.3;
}

.option-price {
  font-size: 0.6875rem;
  opacity: 0.75;
  font-variant-numeric: tabular-nums;
}

/* ─── totals ──────────────────────────────────────────────────────────────── */

.summary {
  margin-top: 2rem;
  border-top: 1px solid var(--on-paper-hair);
  padding-top: 1.25rem;
  display: grid;
  gap: 0.55rem;
}

.summary-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1.5rem;
  font-size: 0.8125rem;
  color: var(--on-paper-soft);
}

.summary-row .tnum {
  white-space: nowrap;
}

.summary-line {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
}

.summary-row.is-total {
  margin-top: 0.6rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--on-paper-hair);
  color: var(--on-paper);
  font-size: 1rem;
}

.summary-row.is-total .amount {
  font-family: var(--font-display);
  font-size: 1.75rem;
  line-height: 1;
}

.summary-cta {
  margin-top: 1.5rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}

.qty {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--on-paper-hair);
}

.qty button {
  width: 2.25rem;
  height: 2.25rem;
  display: grid;
  place-items: center;
  font-size: 0.9375rem;
  line-height: 1;
  transition: background-color 0.3s var(--ease), opacity 0.3s var(--ease);
}

.qty button:hover:not(:disabled) {
  background: rgb(20 20 15 / 0.07);
}

.qty button:disabled {
  opacity: 0.28;
  cursor: default;
}

.qty-value {
  min-width: 2.25rem;
  text-align: center;
  font-size: 0.8125rem;
  border-inline: 1px solid var(--on-paper-hair);
  line-height: 2.25rem;
}

/* ─── the order ───────────────────────────────────────────────────────────── */

/* `.notify` is the same ruled block for a page that takes an address rather than
   an order: the book, which is not out yet. */
.order,
.notify {
  margin-top: 2.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--on-paper-hair);
}

.order-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 0.5rem;
}

.order-grid {
  display: grid;
  gap: 0 1rem;
}

@media (min-width: 620px) {
  .order-grid {
    grid-template-columns: 1fr 1fr;
  }
}

/* ─── free-text field ─────────────────────────────────────────────────────── */

.field {
  display: grid;
  gap: 0.55rem;
  margin-top: 1.25rem;
}

.field-label {
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  line-height: 1;
  color: var(--on-paper-faint);
}

.field textarea,
.field input {
  width: 100%;
  padding: 0.8rem 0.95rem;
  background: transparent;
  border: 1px solid var(--on-paper-hair);
  color: var(--on-paper);
  font: inherit;
  font-size: 0.875rem;
  font-weight: 300;
  resize: vertical;
  transition: border-color 0.35s var(--ease);
}

.field textarea::placeholder,
.field input::placeholder {
  color: currentColor;
  opacity: 0.35;
}

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

.notes {
  list-style: none;
  margin: 2rem 0 0;
  padding: 0;
  display: grid;
  gap: 0.45rem;
}

.notes li {
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  line-height: 1.5;
  color: var(--on-paper-faint);
}

.fineprint {
  margin-top: 1.75rem;
  max-width: 62ch;
  font-size: 0.75rem;
  line-height: 1.65;
  color: var(--on-paper-faint);
}

/* Payment and returns, shown on both shop pages. Ruled like the order block so
   it reads as another part of the same offer. */
.payment {
  margin-top: 2rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--on-paper-hair);
}

.payment .notes {
  margin-top: 0.75rem;
}

/* ─── currency switch ─────────────────────────────────────────────────────── */

.currency {
  display: inline-flex;
  align-items: center;
  gap: 0.15rem;
  border: 1px solid var(--on-paper-hair);
  padding: 0.25rem;
}

.currency button {
  padding: 0.4rem 0.6rem;
  font-size: 0.6875rem;
  letter-spacing: 0.12em;
  opacity: 0.5;
  transition: opacity 0.3s var(--ease), background-color 0.3s var(--ease);
}

.currency button:hover {
  opacity: 1;
}

.currency button[aria-pressed="true"] {
  opacity: 1;
  background: rgb(20 20 15 / 0.08);
}

/* ─── the book ────────────────────────────────────────────────────────────── */

.book-grid {
  display: grid;
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
}

@media (min-width: 900px) {
  .book-grid {
    grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  }
}

.book-object {
  position: relative;
  display: grid;
  place-items: center;
  padding: clamp(1.5rem, 4vw, 3rem);
  background: var(--paper-sunken);
  min-height: clamp(18rem, 44vw, 30rem);
  overflow: hidden;
}

.book-cover {
  position: relative;
  width: min(64%, 20rem);
  aspect-ratio: 1 / 1;
  background: var(--ink);
  overflow: hidden;
  box-shadow: 0 2rem 3.5rem -1.5rem rgb(0 0 0 / 0.55),
    0 0 0 1px rgb(0 0 0 / 0.1);
  transform: rotate(-1.6deg);
  transition: transform 0.9s var(--ease-out);
}

.book-object:hover .book-cover {
  transform: rotate(0deg) translateY(-0.4rem);
}

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

.book-grid .display-xl {
  margin-top: 0.75rem;
}

/* The blurb runs the full width of the column, level with the spec list below
   it rather than stopping at the reading measure. */
.book-grid .prose {
  margin-top: 1.25rem;
  max-width: none;
}

.spec-list {
  display: grid;
  gap: 0;
  margin-top: 2rem;
  border-top: 1px solid var(--on-paper-hair);
}

.spec-row {
  display: grid;
  grid-template-columns: minmax(6rem, 8.5rem) minmax(0, 1fr);
  gap: 1rem;
  padding: 0.8rem 0;
  border-bottom: 1px solid var(--on-paper-hair);
  font-size: 0.8125rem;
}

.spec-row dt {
  font-size: 0.6875rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--on-paper-faint);
  padding-top: 0.15em;
}

.spec-row dd {
  font-weight: 300;
  color: var(--on-paper-soft);
}

/* Where the price was. The book is not out yet, so this is the line that says
   so, in the size the price used to be — nothing to pay, and a word about what
   to do instead. */
.book-status {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.5rem 1.25rem;
  margin-top: 1.75rem;
}

.book-status .label {
  max-width: 22rem;
  line-height: 1.5;
}

.book-after {
  margin-top: 2.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--on-paper-hair);
  display: grid;
  gap: 1.25rem;
}

.book-after .notes {
  margin-top: 0;
}

/* ─── about ───────────────────────────────────────────────────────────────── */

/* The one page here that is read rather than shopped through, so its text is
   given a measure twice the story's — but it keeps the contact form's column
   width, so reading the bio and filling in the form below it stay one thing. */
.about {
  display: grid;
  gap: clamp(1.25rem, 3vw, 2rem);
  align-items: start;
  max-width: 46rem;
}

/* The whole page is read at one measure, so nothing in it keeps the narrower
   one the story and the fineprint elsewhere are set to. */
.about .prose,
.about .fineprint {
  max-width: none;
}

/* The closing lines are set apart from the bio themselves, not by whatever
   happens to be its last paragraph. */
.about-text .label {
  margin-top: 2.5rem;
}

/* One square photograph, named by id in content/story.yaml, floated to the left
   so the bio runs around it — a margin note beside the text rather than a column
   that shortens the measure for the whole page. With no image in the file the
   figure is not rendered and the text runs full width. */
.about-body {
  display: flow-root;
}

.about-portrait {
  float: left;
  margin: 0 clamp(1.25rem, 3vw, 2.5rem) 0.75rem 0;
  width: clamp(9rem, 20vw, 15rem);
}

/* The square is a hole in the paper, not a card: the image in `assets/` is cut
   out, so anything painted behind it would read as a background the photograph
   does not have. The frame keeps the square footprint and crops the image to it. */
.about-portrait-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  overflow: hidden;
}

.about-portrait-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* A phone has no room for text beside a square, so it shrinks to a thumbnail the
   measure can still get past. */
@media (max-width: 620px) {
  .about-portrait {
    width: clamp(6.5rem, 30vw, 9rem);
    margin-right: 1rem;
  }
}

/* ─── contact ─────────────────────────────────────────────────────────────── */

/* Two shop sections on one page — the About page and the contact form under it
   — would each bring their own section padding, so the seam between them is
   twice what a section boundary should be. It is one block of reading, not two,
   so the gap is one block's worth. */
.shop-section + .shop-section {
  padding-top: 0;
  padding-bottom: clamp(3rem, 7vh, 5rem);
}

.contact {
  display: grid;
  gap: 1.25rem;
  max-width: 52rem;
}

.contact-form {
  margin-top: clamp(2rem, 4vw, 3rem);
  max-width: 46rem;
}

/* ═══ footer ═════════════════════════════════════════════════════════════ */

.footer {
  border-top: 1px solid var(--on-ink-hair);
  background: var(--ink);
  color: var(--on-ink);
  padding-block: clamp(2.5rem, 6vw, 4rem);
}

.footer.is-paper {
  border-top-color: var(--on-paper-hair);
  background: var(--paper);
  color: var(--on-paper);
}

.footer-inner {
  display: grid;
  gap: 1.5rem 2rem;
}

@media (min-width: 900px) {
  .footer-inner {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: end;
  }
}

.footer-brand {
  display: grid;
  gap: 0.5rem;
}

.footer-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1.75rem;
}

.footer-nav a {
  color: var(--on-ink-soft);
  transition: color 0.35s var(--ease);
}

.footer.is-paper .footer-nav a {
  color: var(--on-paper-soft);
}

.footer-nav a:hover,
.footer-nav a:focus-visible {
  color: var(--on-ink);
}

.footer.is-paper .footer-nav a:hover,
.footer.is-paper .footer-nav a:focus-visible {
  color: var(--on-paper);
}

.footer-meta {
  display: grid;
  gap: 0.4rem;
  grid-column: 1 / -1;
  font-size: 0.75rem;
  color: var(--on-ink-faint);
}

.footer.is-paper .footer-meta {
  color: var(--on-paper-faint);
}

@media (min-width: 900px) {
  .footer-meta {
    grid-column: 2;
    justify-items: end;
    text-align: right;
  }
}

/* ═══ utility ════════════════════════════════════════════════════════════ */

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  top: 0.5rem;
  left: 0.5rem;
  z-index: 9999;
  padding: 0.75rem 1.1rem;
  background: var(--on-ink);
  color: var(--ink);
  transform: translateY(-160%);
  transition: transform 0.3s var(--ease-out);
}

.skip-link:focus {
  transform: none;
}
.picker-thumb.is-selected {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

.selected-cart {
  margin: 1.5rem 0;
  padding: 1.5rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 0.5rem;
}

.selected-cart-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1rem;
}

.selected-cart-items {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.selected-cart-item {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: 1rem;
  border-bottom: 1px solid var(--line);
}

.selected-cart-item:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

.selected-cart-item-left {
  display: flex;
  gap: 0.75rem;
  align-items: center;
  flex: 1;
}

.selected-cart-item-left .label {
  margin: 0;
}

.selected-cart-item {
  align-items: center;
}

.selected-cart-thumb {
  position: relative;
  width: 72px;
  height: 48px;
  flex-shrink: 0;
  border: 1px solid var(--line);
  overflow: hidden;
}

.btn-small {
  padding: 0.25rem 0.4rem;
  font-size: 0.875rem;
}

.btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}

.btn-icon svg {
  width: 1rem;
  height: 1rem;
  color: currentColor;
}

.btn-danger {
  border-color: var(--line);
}
/* The one thing a request cannot be sent without, besides an address. */
.consent {
  display: flex;
  gap: 0.65rem;
  align-items: flex-start;
  margin-top: 1.25rem;
  font-size: 0.9375rem;
  line-height: 1.5;
  color: var(--on-paper);
  cursor: pointer;
}

/* Drawn rather than native: a native box follows the OS colour scheme and reads
   as already ticked, dark on the paper ground, before anyone has agreed. */
.consent input {
  appearance: none;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  width: 1.05rem;
  height: 1.05rem;
  margin: 0.2rem 0 0;
  border: 1px solid var(--on-paper);
  background: transparent;
  cursor: pointer;
}

.consent input::after {
  content: "";
  width: 0.3rem;
  height: 0.55rem;
  margin-top: -0.12rem;
  border: solid var(--paper);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
  opacity: 0;
}

.consent input:checked {
  background: var(--on-paper);
}

.consent input:checked::after {
  opacity: 1;
}

.consent input:focus-visible {
  outline: 2px solid var(--on-paper);
  outline-offset: 2px;
}

.order .btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
