/* Water Column.
 *
 * The tokens are copied from CV-SiteNet shared/css/alarivean.css, which is the network's
 * design system, and copied rather than imported because the two repositories have no
 * shared build. That fork is a real cost and it is recorded here rather than discovered
 * later: a palette change there does not reach this file, and SiteNet's contrast tests
 * do not see this copy.
 *
 * What is NOT copied is the rest of that sheet. It is two and a half thousand lines of
 * hero sections, buttons, navigation and custom elements bound to a Node builder. This
 * publication has no nav, no button and no call to action, because it asks the reader for
 * nothing. What it needs is a measure, a type scale, somewhere to put a footnote, and one
 * image at the head of each issue.
 *
 * That last one arrived after the rest. Each issue names its own photograph in its
 * manifest, it lives under library/img/watercolumn with the provenance sidecar
 * library/img/README.md requires, and a smaller copy of it runs behind that issue's index
 * entry as texture. None of them is a photograph of the water its issue is about, and the
 * sidecars say so in writing.
 *
 * How far the texture is allowed to come up is not a taste decision and is not settled by
 * eye. tools/check_texture.py measures it against the photographs themselves and sets
 * --texture. Raising --texture without it fails the build.
 */

:root {
  --abyss: #001324;
  --deep: #001b48;
  --ocean: #004581;
  --marine: #3086a6;
  --sky: #97cbdc;
  --mist: #dde8f0;
  --foam: #f4f8fb;
  --white: #ffffff;
  --gold: #fdd55b;

  --text-strong: #001324;
  --text-body: #1c2c3a;
  --text-muted: #5d7288;
  --text-inverse: #f4f8fb;
  --line: rgba(0, 19, 36, 0.12);

  --f-sans: Roboto, system-ui, -apple-system, "Segoe UI", sans-serif;
  --f-tech: Archivo, var(--f-sans);

  --t-xs: 0.8125rem;
  --t-sm: 0.9375rem;
  --t-base: 1.0625rem;
  --t-lg: 1.1875rem;
  --h1: clamp(2rem, 1.2rem + 3.4vw, 3.25rem);
  --h2: clamp(1.25rem, 1.05rem + 0.8vw, 1.5rem);

  --lead-snug: 1.2;
  --lead-normal: 1.5;
  --lead-relaxed: 1.65;
  --track-tight: -0.02em;
  --track-eyebrow: 0.16em;

  --s-2: 0.5rem;
  --s-3: 0.75rem;
  --s-4: 1rem;
  --s-6: 1.5rem;
  --s-8: 2rem;
  --s-12: 3rem;
  --s-16: 4rem;

  --container-read: 40rem;
  --gutter: 1.5rem;

  /* How far the index entry's image comes up behind the text. Set by
     tools/check_texture.py against the photographs themselves, not by eye. The measured
     ceiling on this page is 0.42. This sits well under it: the entry should read as
     type over a photograph, not as a photograph with type on it, and the headroom means
     re-cropping one image cannot silently put the index under the floor. */
  --texture: 0.2;
}

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  /* The index entries bleed to 100vw, which is the window including its scrollbar. Left
     alone that buys a horizontal scrollbar on every page that has a vertical one. */
  overflow-x: hidden;
  background: var(--foam);
  color: var(--text-body);
  font-family: var(--f-sans);
  font-size: var(--t-base);
  line-height: var(--lead-relaxed);
}

a { color: var(--ocean); }
a:hover { color: var(--marine); }
:focus-visible { outline: 2px solid var(--marine); outline-offset: 2px; }

/* ── masthead ─────────────────────────────────────────────────────────── */

.masthead {
  max-width: var(--container-read);
  margin: 0 auto;
  padding: var(--s-12) var(--gutter) var(--s-6);
  border-bottom: 1px solid var(--line);
}

.masthead__name {
  display: inline-block;
  margin: 0;
  font-family: var(--f-tech);
  font-size: var(--t-sm);
  font-weight: 500;
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--text-strong);
  text-decoration: none;
}

.masthead__meta {
  margin: var(--s-2) 0 0;
  font-size: var(--t-xs);
  color: var(--text-muted);
}

.masthead--home { border-bottom: none; padding-bottom: var(--s-4); }

.masthead__blurb {
  max-width: 32rem;
  margin: var(--s-4) 0 0;
  color: var(--text-muted);
}

/* ── the frontispiece ─────────────────────────────────────────────────── */

.frontispiece {
  margin: 0;
  /* Full width of the window rather than of the measure. The photograph is the one thing
     on the page that is not text, and cramming it into a 40rem column makes it look like
     a figure inside an argument instead of the head of an issue. */
  height: clamp(9rem, 20vw, 15rem);
  overflow: hidden;
  background: var(--deep);
}

.frontispiece img {
  display: block;
  width: 100%;
  height: 100%;
  /* Covered, not stretched. The drawing this replaced had no proportions worth keeping
     and was stretched to fit; a photograph does, and the masters are already cropped to
     8:3 so a wide band loses very little from the top and bottom. */
  object-fit: cover;
}

/* ── the issue ────────────────────────────────────────────────────────── */

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

.issue h1 {
  margin: var(--s-8) 0 var(--s-4);
  font-size: var(--h1);
  line-height: var(--lead-snug);
  letter-spacing: var(--track-tight);
  color: var(--text-strong);
  font-weight: 700;
}

.standfirst p {
  margin: 0 0 var(--s-6);
  font-size: var(--t-lg);
  line-height: var(--lead-normal);
  color: var(--text-muted);
}

.issue__section { margin: var(--s-12) 0; }

.issue__section h2 {
  margin: 0 0 var(--s-4);
  font-size: var(--h2);
  line-height: var(--lead-snug);
  color: var(--text-strong);
  font-weight: 700;
}

.prose p { margin: 0 0 var(--s-4); max-width: 68ch; }
.prose p:last-child { margin-bottom: 0; }

.pullquote {
  margin: var(--s-8) 0;
  padding-left: var(--s-4);
  border-left: 3px solid var(--marine);
}

.pullquote p {
  margin: 0;
  font-size: var(--t-lg);
  line-height: var(--lead-normal);
  color: var(--text-strong);
}

.footnote {
  margin: var(--s-6) 0 0;
  font-size: var(--t-xs);
  line-height: var(--lead-normal);
  color: var(--text-muted);
}

/* ── what was added after publication ─────────────────────────────────── */

.addenda {
  margin: var(--s-16) 0 var(--s-8);
  padding-top: var(--s-6);
  border-top: 2px solid var(--line);
}

.addendum { margin-bottom: var(--s-8); }

.addendum__head {
  margin: 0 0 var(--s-3);
  font-family: var(--f-tech);
  font-size: var(--t-xs);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--marine);
}

.addendum__corrects {
  margin: 0 0 var(--s-3);
  font-size: var(--t-sm);
  color: var(--text-muted);
}

/* ── the index ────────────────────────────────────────────────────────── */

.issues { list-style: none; margin: var(--s-8) 0; padding: 0; }

.issues__item {
  position: relative;
  isolation: isolate;
  padding: var(--s-6) 0;
  border-bottom: 1px solid var(--line);
}

/* The issue's own image, behind the entry and nowhere else. The sides of the page stay
   empty, which is what the rest of this stylesheet does everywhere else.
 *
 * The opacity is not a taste decision and must not be raised by eye. Whatever is behind
 * the entry is spending the standfirst's contrast, so the ceiling is worked out from the
 * photographs rather than guessed: `python tools/check_texture.py` composites each one
 * over the page at this opacity, finds the darkest pixel that can fall under text, and
 * reports the contrast that results. It runs in the self-test, so raising --texture past
 * what the images can carry fails the build rather than shipping. */
.issues__item::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image: var(--frontispiece);
  background-size: cover;
  background-position: center;
  opacity: var(--texture);
  transition: opacity 120ms linear;
}

/* --text-body here too, for the same reason as the standfirst below: everything in an
   index entry sits over a photograph, so everything in it has to clear the floor over
   one. It still reads as the quiet line because it is small, not because it is pale. */
.issues__meta {
  margin: 0 0 var(--s-2);
  font-size: var(--t-xs);
  color: var(--text-body);
}

.issues__item h2 {
  margin: 0 0 var(--s-2);
  font-size: var(--h2);
  line-height: var(--lead-snug);
  letter-spacing: var(--track-tight);
}

.issues__item h2 a { color: var(--text-strong); text-decoration: none; }
.issues__item h2 a:hover { color: var(--ocean); }

/* The whole entry is the link.
 *
 * One anchor, stretched over the entry by a transparent ::after, rather than a link on
 * each part of it or a script that listens for clicks. The markup does not change and
 * neither does what a screen reader or a keyboard gets: one link per issue, announced by
 * its title, which is the thing a reader would want read to them. Wrapping the date, the
 * title and the standfirst in three separate anchors would announce the same issue three
 * times, and a click handler would put the first line of JavaScript on a site that has
 * none.
 *
 * The cost is real and it is the known one: a transparent layer over the text means the
 * standfirst cannot be selected with a mouse from the index. It can be selected on the
 * issue's own page, which is one click away and is where that text is the point. */
.issues__item h2 a::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
}

.issues__item { cursor: pointer; }

/* The title alone carries the hover color, and the entry answers with its texture, so a
   pointer anywhere in the entry produces a visible change rather than a silent hit area. */
.issues__item:hover h2 a { color: var(--ocean); }

/* A keyboard lands on the anchor but the target is the entry, so the ring goes round the
   entry. Browsers without :has() still draw their own ring on the title, which is correct
   and merely smaller. */
@supports selector(:has(*)) {
  .issues__item:has(a:focus-visible) {
    outline: 2px solid var(--marine);
    outline-offset: 2px;
  }
  .issues__item h2 a:focus-visible { outline: none; }
}

/* --text-body, not --text-muted, and this is the line that pays for the picture.
   Muted on foam is 4.65:1, seven hundredths over the AA floor, so every photograph in
   the library capped at 0.01 opacity behind it - a texture in structure and not in
   appearance. Body on foam is 13.37:1, and the same photographs then hold the floor at
   0.41 to 0.75. One token is the difference between an image nobody can see and an
   image behind every entry. The standfirst also reads better for it. */
.issues__standfirst { margin: 0; color: var(--text-body); max-width: 60ch; }

/* ── colophon ─────────────────────────────────────────────────────────── */

.colophon {
  max-width: var(--container-read);
  margin: var(--s-16) auto 0;
  padding: var(--s-6) var(--gutter) var(--s-16);
  border-top: 1px solid var(--line);
  font-size: var(--t-xs);
  color: var(--text-muted);
}

.colophon p { margin: 0 0 var(--s-2); }
.colophon a { color: var(--text-muted); }
.colophon a:hover { color: var(--ocean); }

/* A reader who has asked for more contrast has asked for the text and not for the water.
   The frontispiece at the head of an issue stays: it is the image, not the background. */
@media (prefers-contrast: more) {
  .issues__item::before { display: none; }
}

@media (prefers-color-scheme: dark) {
  :root {
    --foam: #001324;
    --text-body: #dde8f0;
    --text-strong: #f4f8fb;
    --text-muted: #97cbdc;
    --line: rgba(151, 203, 220, 0.22);
    --ocean: #97cbdc;

    /* Dark mode affords far more of the image, and the reason is arithmetic rather than
       taste: the muted text runs at 10.8:1 here against 4.57:1 on the light page, so
       there is room to give away before the floor. The image lightens this page instead
       of darkening it - the other direction, the same budget, the same script. Measured
       ceiling here is 0.48. */
    --texture: 0.26;
  }
  .issues__item h2 a { color: var(--text-strong); }
}
