/* wavie.me — the scroll. Tokens first, then type, then the page. */

:root {
  --paper: #f7f6f2;
  --paper-2: #efede7;
  --ink: #1c1c1a;
  --ink-2: #6f6e6a;
  --ink-3: #c9c7c1;
  --indigo: #2f4f7f;
  --indigo-2: #a9b8cc;
  --cinnabar: #b8312f;
  /* Selecting text is the one place cinnabar is allowed to spread. It is the seal's
     colour and everywhere else it means "press this", so the wash is mixed down until
     it reads as tinted paper rather than as a mark: 18% on paper, and 30% in the dark,
     where the same 18% would have disappeared into the ground. Both are mixed from the
     tokens, so the toggle and the OS both carry it without a second literal. */
  --select: color-mix(in srgb, var(--cinnabar) 18%, var(--paper));

  /* ⚠ The two script faces sit in every stack, right behind the Latin one. Each is a
     `text=` subset with a unicode-range naming its nineteen codepoints and nothing
     else, so neither can ever be picked for a character it was not fetched for. They
     are here because nothing else in this stylesheet covers Hangul or Devanagari, and
     those lines were coming out in the machine's own sans in the middle of a serif
     page. Character lists and rebuild: tools/fonts.py (TEXT_FACES, --audit). */
  --serif: "Cormorant Garamond", "Noto Serif KR", "Noto Serif Devanagari", "Iowan Old Style", "Palatino", Georgia, serif;
  /* Latin first in every CJK stack. A browser resolves a font stack per glyph, so
     English inside a Chinese line comes from Cormorant and only the han characters
     fall through to the CJK face. Without this, a credit like
     「緬甸仰光一景。（Photo by Wavie Hu on Unsplash）」 sets its English in Noto Serif's
     Latin, which is not the site's English face. No local fonts anywhere in these
     stacks: that is Parote's lesson, so mixed 简/繁 never lands on two metrics. */
  --kai: "Cormorant Garamond", "Noto Serif KR", "Noto Serif Devanagari", "WenKai Site", "LXGW WenKai TC", "Noto Serif SC", "Noto Serif TC", serif;
  --zh: "Cormorant Garamond", "Noto Serif KR", "Noto Serif Devanagari", "Noto Serif TC", "Noto Serif SC", serif;
  --zh-hans: "Cormorant Garamond", "Noto Serif KR", "Noto Serif Devanagari", "Noto Serif SC", "Noto Serif TC", serif;

  --gutter: clamp(20px, 5vw, 96px);
  /* A scroll has an edge. Past this the river would be a thread in an empty field and
     the hero would drift away from it, so the page stops widening and centres instead.
     The rail keeps its full-bleed background; only what it holds is bounded. */
  --page-max: 1680px;
  --rim: max(var(--gutter), calc((100% - var(--page-max)) / 2));
  /* The title's size, named so everything under it can be nudged IN PROPORTION to it.
     ⚠ On :root, not on .hero. The river's hint is .hero's SIBLING and takes the same
     nudge, and a custom property inherits down the tree, not sideways — declared on
     .hero, the calc() out there resolves to nothing and silently does nothing at all. */
  --title: clamp(64px, 15.5vw, 228px);
  /* The vertical Chinese column's type size, named because the horizontal lines beside it
     have to keep out of its way and only this file knows how wide it is. Measured: the
     column's box is exactly 1.5× this at every size (27px at 18, 33.8 at 22.5, 45 at 30). */
  --zh-col: clamp(18px, 2.2vw, 30px);
  --measure: 40rem;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper: #15140f;
    --paper-2: #1d1c17;
    --ink: #ebe8e0;
    --ink-2: #a39f95;
    --ink-3: #3a3830;
    --indigo: #8fa6c9;
    --indigo-2: #3d4d66;
    --cinnabar: #d24d48;
    --select: color-mix(in srgb, var(--cinnabar) 30%, var(--paper));
  }
}
:root[data-theme="dark"] {
  --paper: #15140f;
  --paper-2: #1d1c17;
  --ink: #ebe8e0;
  --ink-2: #a39f95;
  --ink-3: #3a3830;
  --indigo: #8fa6c9;
  --indigo-2: #3d4d66;
  --cinnabar: #d24d48;
  --select: color-mix(in srgb, var(--cinnabar) 30%, var(--paper));
}

html { color-scheme: light dark; }
/* ⚠ The text colour is stated too, not left alone. Selected prose is mostly --ink and
   would be fine either way, but a cinnabar link selected inside a cinnabar wash falls
   to about 2.6:1 in dark mode and stops being readable at the moment someone is trying
   to copy it. Selection is a transient state; making every run of it read the same is
   worth flattening the link colour for as long as it lasts. */
::selection { background: var(--select); color: var(--ink); }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--serif);
  font-size: 19px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
:lang(zh-Hant) { font-family: var(--zh); }
:lang(zh-Hans) { font-family: var(--zh-hans); }   /* her own essays keep the script she wrote them in */
/* Chinese has no italic. Wherever Chinese sits inside an italic line, it stays upright:
   an invariant, so it is allowed to win over whatever local rule set the italic.
   ⚠ Korean and Devanagari are here for the same reason and were missed at first. A
   Korean article title sits inside `.use .what`, which is italic, so the browser was
   synthesising an oblique on a face that has no italic to synthesise from. js/read.js
   tags them; without the tag this rule cannot see them. */
:lang(zh-Hans), :lang(zh-Hant), :lang(ko), :lang(hi) {
  font-style: normal !important; font-synthesis-style: none;
}
.kai, :lang(zh-Hant).kai { font-family: var(--kai); }

a { color: var(--ink); text-decoration: none; }
a:hover { color: var(--cinnabar); }
a:focus-visible, button:focus-visible { outline: 2px solid var(--cinnabar); outline-offset: 3px; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }

.small-caps {
  font-size: 15px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-2);
}

/* rail */
.rail {
  /* ⚠ One line tall, whatever is in it. Following swaps the five line buttons (19.5px)
     for `Following ✕` (22.5px), and because the rail is sticky it occupies flow — so
     that 3px moved the seal down and pushed the whole page with it every time a line
     was lit or let go. Stated in em so it follows the font rather than a magic pixel. */
  min-height: 1.5em;
  position: sticky;
  top: 0;
  z-index: 5;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  padding: 22px var(--rim);
}
/* The rail's paper backing, and why it is not in the rule above.
   macOS Safari 26+ draws the strip under its address bar as glass — the page showing
   through it — unless it decides the page has a bar of its own up there, in which case
   the strip goes flat and opaque. Eighteen test pages (tools/lab.py) settled the rule:

     pinned to the very top edge AND painting anything there  ->  solid
     break either half of that                                ->  glass

   Either half alone is enough: the 88% paper does it, the blur alone does it, and 30%
   paper is as solid as 88% — it is not a threshold.
   ⚠ Handing the rail its backing 50ms or 300ms after load both came out SOLID; handing
   it over on the first scroll event came out GLASS. So the rail carries no backing
   until the page first moves. It costs nothing to look at: at scroll 0 the 88% paper
   sits on the page's own paper, so backed and bare are the same picture. The backing
   only means anything once something is under it, and by then it is there.
   ⚠⚠ IT MUST BE ABSENT FROM THE FIRST PAINT, not taken away afterwards. The first
   version of this shipped the backing on .rail and had read.js remove it at
   DOMContentLoaded — computed styles identical to the tested page, and it did not
   work: solid on a fresh private window. The only configuration with evidence behind
   it is the one the lab actually passed, so this matches it exactly. Do not "simplify"
   this back into a `background: none` override.
   ⚠ Which is why the no-JavaScript fallback has to be its own stylesheet, pulled in
   from a <noscript> by tools/headtags.py: CSS can override a property but it cannot
   un-declare one, and un-declared is the state that matters here. */
.rail.backed {
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: blur(6px);
}
.rail .mark { display: inline-flex; }
.rail nav { display: flex; align-items: center; gap: 22px; flex-wrap: wrap; }
.rail nav .lead { margin-right: 4px; }
/* ⚠ The five swatches are grouped in the markup, but the grouping is INVISIBLE at every
   width except the one band below: `display: contents` hands the buttons straight back to
   the nav, so they are its own flex items and lay out exactly as they did before the
   spans existed. Do not give `.ways` or `.row` a box outside that band. */
.rail nav.lines .ways, .rail nav.lines .row { display: contents; }
.rail nav a, .rail nav button { display: inline-flex; align-items: center; gap: 8px; }
.rail .lang { color: var(--ink); }
/* The two things the rail can say, stacked in one grid cell so one can fade into the
   other. ⚠ They cannot simply be toggled with `display`: that cannot be transitioned,
   and while one is fading the other would be shoving it sideways. Sharing a cell also
   means the rail's width and height stop depending on which of them is showing. */
.rail-swap { display: grid; align-items: center; }
.rail-swap > * {
  grid-area: 1 / 1;
  transition: opacity 0.28s ease, visibility 0.28s;
}
.rail .following { display: inline-flex; align-items: center; gap: 10px; color: var(--ink); }
/* ⚠ `visibility`, not just opacity: a transparent nav would still take the focus and
   still swallow presses. It changes discretely — at the end of the fade out, at the
   start of the fade in — which is exactly the behaviour wanted here. */
body:not([data-follow]) .rail .following,
body[data-follow] .rail nav.lines { opacity: 0; visibility: hidden; }
.rail .following .stop { color: var(--ink-2); margin-left: 4px; }
/* The rail cannot hold one line here, and what it did instead was wrap the LABEL along
   with the swatches: `Follow a line` sat on the first row and the leftovers began under
   it, so the second row started further left than the first and ended well short of it.
   She asked for the label to stay whole as an extension of the seal, with the swatches
   forming a block beside it, and for the second row to line up at BOTH ends (2026-09-06).
   ⚠ So the block is a column of two authored rows, `width: max-content` — which is the
   wider row, the first — and both rows are `space-between`. The first row IS that width,
   so nothing moves in it; the second (260.8 against 297.6) spreads its one gap to reach
   the same right edge. This is the only way to hug the widest wrapped line: CSS has no
   "shrink to the longest line" for a wrapping flex box, which is why the rows are
   authored rather than left to wrap.
   ⚠ The band, measured 2026-09-06 with the fonts loaded: 960px still holds one line, 950
   no longer does. If the labels or `Lagoon` ever get wider, re-measure — too high a
   ceiling wastes a line on screens that could hold one, too low brings the ragged wrap
   back. Below 721px the phone rules take over: the label is hidden and the row scrolls. */
@media (min-width: 721px) and (max-width: 959px) {
  /* ⚠ Left, not spread. The rail is `space-between` and that is right while the legend is
     a LINE — three things across a band. Two rows make it a BLOCK, and a block has a real
     left edge; an edge floating mid-rail with nothing above or below to meet is what made
     it look unmoored, and it left the seal stranded alone behind 155px of nothing. Pulled
     left, the seal and the label read as one masthead lockup at the rail's own 16px gap,
     the block follows, and the whole left cluster starts where the h1 does. The 295px that
     opens before Lagoon is not a hole: the hero's right side is empty too, carrying only
     the vertical line, so the rail now agrees with the page instead of being the one
     centred thing on it. Her call, 2026-09-06; A/B'd at 955 before taking it.
     ⚠ `margin-right: auto` on the swap, so `space-between` keeps holding Lagoon at the
     rim — margins eat the free space before justify-content ever sees it. */
  .rail-swap { margin-right: auto; }
  .rail nav.lines .ways {
    display: flex; flex-direction: column; gap: 22px; width: max-content;
  }
  .rail nav.lines .row {
    display: flex; align-items: center; gap: 22px; justify-content: space-between;
  }
  /* ⚠ Where the second row's slack goes. Justified on its own, its ONE gap swallowed all
     36.8px of it and opened a hole in the middle of the row while the row above kept 22px
     gaps — her read, 2026-09-06: give it to the swatches instead and take the hole back.
     Both marks on this row are drawn LONG in the markup and it is this width that decides
     how much of the drawing you see: those two <svg> carry no viewBox, so their user units
     ARE CSS pixels and a wider box reveals more of the same line rather than scaling it
     (a viewBox would have stretched the stroke with it). At 30 the clip lands exactly
     where the old 30-wide drawings ended, which is why every other width is untouched.
     ⚠ 50.3, not 48.4 — the number the arithmetic wants. It is the length at which the
     dotted line ends on a whole dot (1.3 + 7 x 6.8 + 0.1, then its cap) instead of on a
     half one, and it carries the dash-dot two full repeats plus the head of a third, so
     both marks still reach their right edge. It overshoots by 3.8px, which simply makes
     THIS row the wider one: the block hugs it and the row above spreads its two gaps by
     1.9px each. Both rows stay flush at both ends and every gap is within 2px of 22. */
  .rail nav.lines .row:last-child svg { width: 51.3px; }
  /* ⚠ 51.3, and the drawings still stop at 50.3: a whole pixel of headroom, because ink
     that ends flush with the box edge gets shaved. Measured 2026-09-06 at dpr 2: the used
     width snaps to 50.296875 (1/64) and the right edge lands on 375.25 — half a device
     pixel — so the last dot lost its cap and read as a half dot, the same fault as the
     first dot before it, at the other end. Ink must be cut by the PATH, never by the clip.
     ⚠ The dash-dot ran off the end mid-pattern and the 2.3px stub that was left read as a
     third DOT, not a line (her word, 2026-09-06). `stroke-dashoffset` slides the pattern
     along the path so both ends break inside a long dash instead: 50.3 is 2.3 past two
     whole 24-unit repeats, so an offset of 5.85 leaves 14 - 5.85 at the head and
     5.85 + 2.3 at the tail — 8.15 each, matched, and four times the dot's 2 so neither
     can be mistaken for one. The row now reads dash · dot · dash · dot · dash.
     ⚠ Both are scoped to this band, so at every other width the two marks are still the
     30-wide drawings, pixel for pixel. */
  .rail nav.lines [data-follow-line="photographs"] svg path { stroke-dashoffset: 5.85px; }
}
@media (max-width: 720px) {
  .rail { padding: 14px 20px; gap: 12px; }
  .rail nav.lines { flex-wrap: nowrap; overflow-x: auto; gap: 16px; scrollbar-width: none; -webkit-overflow-scrolling: touch; min-width: 0; }
  .rail nav.lines::-webkit-scrollbar { display: none; }
  .rail nav.lines .lead { display: none; }
  .rail nav.lines button { flex: 0 0 auto; font-size: 13px; }
}

/* hero */
.hero {
  position: relative;
  padding: clamp(40px, 8vh, 96px) var(--rim) 0 var(--rim);
  /* ⚠ The floor has to track the same thing the content does. Everything in here is
     sized off the WIDTH — h1 is clamp(64px, 15.5vw, 228px), and the two lines under it
     follow — while 62vh alone tracks the HEIGHT. On a landscape screen those roughly
     agree; on a tall narrow one they pull opposite ways, and the taller and narrower
     the device the emptier the hero gets. On a portrait iPad mini (744x1050) the box
     came out 735px tall around 227px of type: 424px of nothing between the title and
     the river. She saw it there, 2026-09-05.
     ⚠ min(), so the height rule still wins wherever it is the smaller of the two —
     every landscape viewport measured keeps exactly the hero it had. */
  min-height: min(62vh, 50vw);
}
.hero h1 {
  margin: 0;
  font-weight: 500;
  font-size: var(--title);
  line-height: 0.9;
  letter-spacing: -0.02em;
  white-space: nowrap;
}
/* ⚠⚠ These two are nudged right, and the reason is the W. I removed the nudge once, on
   the strength of measuring the INK edge — canvas's actualBoundingBoxLeft says Cormorant's
   W overhangs the margin by 0.41px, so flush looked provably right. She looked at it and
   said the small lines now sat too far left, and she was right: an ink box is an extreme,
   and a W touches that extreme only at its two apexes. Measured properly — the leftmost
   ink of every raster row across the cap height, averaged — the mass of the letters sits:

       W  0.188em right of the origin        H (italic)  0.132em
       F  (small caps) 0.122em               T  (small caps) 0.214em

   So a W set flush reads as inset, and the lines under it read as pushed out. ⚠ Aligning
   those averages outright would be absurd — at a 223px title the W's is 42px in, and the
   standfirst would have to move 37px. The eye wants the extreme with a small allowance,
   which is a judgement, not a measurement. The one honest anchor is the pair she has
   looked at for weeks and accepted: 4px and 6px under a 223px title. Solving both for the
   same allowance gives 0.20 and 0.17 — near enough the same number from two independent
   lines, which is why those two literals in design/gen.py, arbitrary as their origin was,
   were doing real work.
   ⚠ So: keep 4px and 6px where she approved them, and make them a FRACTION of the title,
   because the thing being compensated is the title. 1.8% and 2.7% — 4.0px and 6.0px at
   223px, to the pixel, and the same relation at every other width instead of the same
   pixels. `.thesis` takes less than `.how` because its own type is larger, so more of the
   correction is already in its own first letter. */
/* ⚠ `min()`, because two things bound these lines and the SMALLER has to win: the reading
   measure, and the vertical Chinese standing at the right margin. Without the second one
   `.how` simply ran underneath the column — 27px of it at 768, 45px at 1440, at every
   width from 561 up, which is where the two of them coexist. She saw it on a tablet,
   2026-09-06. The column's box is 1.5× its own type size, measured, and 28px is the air
   between the two. */
.hero .thesis {
  margin: 28px 0 0 calc(var(--title) * 0.018);
  max-width: min(26ch, calc(100% - var(--zh-col) * 1.5 - 28px));
  font-size: clamp(22px, 2.6vw, 36px);
  line-height: 1.15;
  font-style: italic;
}
.hero .how {
  margin: 22px 0 0 calc(var(--title) * 0.027);
  max-width: calc(100% - var(--zh-col) * 1.5 - 28px);
}
/* ⚠ Clause by clause, and `inline-block` rather than `inline`: it lets a line break
   BETWEEN the clauses and never inside one, so wherever this wraps it wraps at a full stop
   or a comma. On a wide window all three sit on one line and it is indistinguishable from
   plain text; below 560px they are forced one to a line. */
.hero .how span { display: inline-block; }
/* ⚠ Above the river, and carrying its own paper. On a short window — 1366×768, 1440×700,
   both ordinary — this column hangs some 55px below the hero and lands exactly on the year
   rail, which is drawn at the same margin. Growing the hero to contain it is not the fix:
   that is the empty gap under the hero she had me close on 2026-09-05. So it crosses, and
   when it crosses it wins — it is the sentence, the years are a scale. z-index 1 clears
   `.river` (positioned, auto) and stays under the note at 2 and the rail at 5.
   ⚠ The halo is what makes crossing acceptable rather than merely ordered: the river gives
   every one of its own labels a paper stroke for exactly this, and text-shadow is the same
   idea for an element that is not SVG. Stacked three deep because one pass of it is too
   thin to cut a hairline tick. On a page with nothing behind it, paper on paper costs
   nothing. */
.hero .zh-vertical {
  position: absolute;
  z-index: 1;
  text-shadow: 0 0 4px var(--paper), 0 0 4px var(--paper), 0 0 4px var(--paper);
  right: var(--rim);
  top: clamp(40px, 8vh, 96px);
  margin: 0;
  writing-mode: vertical-rl;
  font-size: var(--zh-col);
  letter-spacing: 0.18em;
  color: var(--ink);
}
/* A vertical line must never break into a second column: that reads as a mistake, not
   as a second line. If it cannot fit in one, it lies down instead. */
.hero .zh-vertical { white-space: nowrap; }
@media (max-width: 720px) {
  /* ⚠ No `top` here. It used to carry `top: 200px`, which started the column level with
     the standfirst instead of with the title — the one width band on the site where it
     did not begin where the writing begins, and she caught it at 700px on 2026-09-06.
     Nothing needed it: at 700 the title ends around x=450 and the column stands at 652,
     so there was never anything to duck under. Dropping it lets the base `top` through
     and the column starts at the title, the way it does at every other width.
     ⚠ The size goes through `--zh-col` rather than `font-size` directly, because the two
     horizontal lines reserve their right margin from that same variable. Set one and not
     the other and the reservation is measured against a column that is not there. */
  :root { --zh-col: 17px; }
  .hero .zh-vertical { right: 22px; }
  .hero .thesis { max-width: min(22ch, calc(100% - var(--zh-col) * 1.5 - 28px)); }
}
/* On a phone there is no room beside the writing for a vertical column, and a line set
   vertically in a 375px window lands on top of the standfirst. A short window is the
   same problem turned on its side. Either way it lies down and joins the flow, which
   is what Chinese does perfectly well. */
@media (max-width: 560px), (max-height: 660px) {
  .hero { min-height: auto; padding-top: clamp(28px, 5vh, 56px); }
  /* ⚠ Order: the Chinese sits with the standfirst, not after the instructions. They are
     the same sentence in two scripts and belong together; `.how` and the river's hint
     below it are both small-caps directions and belong together too. Two voices, in two
     pairs, instead of the four alternating lines this used to be. Done by DOM order in
     index.html rather than by `order:` — on a wide screen this line is out of the flow
     entirely, so where it sits in the source costs nothing there. She asked for it,
     2026-09-06. The 16px above it is tighter than the 34px under it: that gap is the
     seam between the two voices. */
  .hero .zh-vertical {
    position: static; writing-mode: horizontal-tb; margin: 16px 0 0 0;
    font-size: 19px; letter-spacing: 0.12em; max-width: 22ch;
  }
  .hero .thesis { max-width: none; font-size: 21px; }
  /* ⚠ `max-width: 30ch` was 186px of a 350px column, and it is why this broke into four
     ragged lines on a phone — never at a full stop, twice mid-clause. Let it have the
     column and break at its own punctuation instead: 173 / 243 / 115px, three clauses,
     three lines, every break a place the sentence already stops. Her observation. */
  .hero .how { max-width: none; font-size: 13px; letter-spacing: 0.08em; margin-top: 34px; }
  .hero .how span { display: block; }
  :root { --title: clamp(48px, 17vw, 96px); }

  /* ⚠ The Chinese takes the standfirst's share of the title correction and then gives
     back its own. A full-width glyph carries side bearing INSIDE the em box — 同's is
     0.1425em, and for a CJK glyph the ink edge and the optical one are nearly the same
     place (0.1425 against 0.1631), unlike the W above. So it is pulled left by its own
     bearing and pushed right by the same 1.8% the standfirst above it takes.
     ⚠ 0.144em is 同's, not the font's: 一 is 0.059, 個 0.033, 探 0.031. It is right only
     while the line still begins with 同, which tools/check.py holds — a reworded tagline
     would otherwise walk off with a correction measured for a character it no longer has.
     ⚠ The river's hint sits directly under `.how` at this width, same size and style, so
     it takes the same nudge; two adjacent small-caps lines starting 2px apart is exactly
     the step she has caught three times now. */
  .hero .zh-vertical { margin-left: calc(var(--title) * 0.018 - 0.144em); }

  /* The rail runs out of room: on the home page the mark, five line buttons and Lagoon
     leave 12px between them at 375px. Lagoon goes everywhere at this width, not just
     there — it is in effect the tail that walks you back to the home page, and every
     other page already has its own way back in the rail (← All the photographs, and so
     on). The home page keeps it in its own section and in the links under the river. */
  .rail .lang { display: none; }
}

/* Below a laptop's width the water cannot hold twenty-seven names without them
   touching, and a drawing where some marks are named and others are not reads as an
   oversight rather than a choice. So the names come off, and come back one line at a
   time: follow a line and its own names return. A mark still opens when pressed.
   Measured, not guessed: at 1280 nothing has to be dropped, at 1024 three do. */
.river-hint { display: none; margin: 0 auto; padding: 0 var(--rim) 8px; color: var(--ink-3); }
/* ⚠ deconflict() measures every label with getBBox(), and a `display: none` element
   measures zero — which silently switched the whole pass off below 1024px. It turns
   this on for the length of that measurement and off again. */
.river.measuring text.label { display: inline !important; }

@media (max-width: 1024px) {
  /* ⚠ Opacity, not display. `.river text` already carries a 0.4s opacity transition,
     so this fades for free — and getBBox() reports a real box either way, which is the
     measurement deconflict() needs (a `display: none` element measures zero). */
  body:not([data-follow]) .river text.label { opacity: 0; }
  body[data-follow] .river text.label.dim { opacity: 0; }
  /* ⚠ Shown whether or not a line is being followed. Hiding it on follow took its box
     out of the flow, and the whole page jumped up by a line — a state change that
     moves the thing you are reading is worse than a sentence you have already read.
     It stays true either way: a mark still opens, another line still takes over, and
     the same line again lets go. */
  /* ⚠ Shown here, so nudged here — and as PADDING, because that is how this element
     already holds its gutter (`padding: 0 var(--rim)`, with `margin: 0 auto` centring a
     full-width box). A `margin-left` written in the narrower query above was silently
     beaten by that `margin` shorthand, which is declared further down the file. It takes
     `.how`'s share because on a phone it is the line directly under it, same size, same
     small caps: two of those starting 2px apart is the step she has now caught three
     times. */
  .river-hint { display: block; padding-left: calc(var(--rim) + var(--title) * 0.027); }
}
/* ⚠ Only where the two of them are actually adjacent. Below 560px the hero has no floor
   left, so this lands immediately under the last line of `.how` and the two run together
   as one grey paragraph. 14px binds them as a pair without closing the 34px seam above
   `.how`, which is what separates the directions from the two lines that say something:
   28 / 16 · 34 · 14. */
@media (max-width: 560px), (max-height: 660px) {
  .river-hint { margin-top: 14px; }
}

/* the river */
.river {
  position: relative;
  width: 100%;
  padding-inline: max(0px, calc((100% - var(--page-max)) / 2));
  box-sizing: border-box;
}
.river svg { display: block; width: 100%; height: auto; overflow: visible; }
.river .line { fill: none; transition: opacity 0.4s ease; }
.river .line.ink { stroke: var(--ink); }
.river .line.indigo { stroke: var(--indigo); }
.river .spring { fill: var(--paper); stroke: var(--ink); stroke-width: 1.4;
  transition: opacity 0.4s ease, fill 0.4s ease, stroke 0.4s ease; }
/* ⚠ Two springs — photographs 2020, causeries 2022 — are the whole mark: their line
   was not born with a work, so there is no seal above them and no dot to light. `.bare`
   is only ever on such a group. They answer the way a dot does, filling in when pointed
   at, because here the ring IS the mark. Fast on the way in, slow on the way out: the
   transition is read from the state being moved INTO, same as .dot above. */
.river .seal.bare:hover .spring, .river .seal.bare:focus .spring,
.river .seal.bare:focus-visible .spring,
.river .seal.bare.dim:hover .spring, .river .seal.bare.dim:focus .spring {
  fill: var(--cinnabar); stroke: var(--cinnabar);
  transition: fill 0.15s ease, stroke 0.15s ease; }
.river .seal { cursor: pointer; transition: opacity 0.4s ease; }
/* The browser draws its own focus box on a focusable <g>, and it frames the invisible
   hit area, so it is both blue and far too big. Keyboard focus says so by filling the
   ring instead, which is the same thing hovering does and needs no box at all. */
.river .seal:focus { outline: none; }
.river .seal:focus-visible { outline: none; }
.river .seal:focus-visible .dot { fill: var(--cinnabar); stroke-width: 2.6; }
.river .seal:focus-visible .stone { fill: var(--ink); }
.river .seal:focus-visible .dun, .river .seal:focus-visible .knot { stroke: var(--cinnabar); }
.river .seal .stone { fill: var(--cinnabar); stroke: none;
  transition: fill 0.4s ease, stroke 0.4s ease, stroke-width 0.4s ease; }
.river .seal:hover .stone, .river .seal:focus .stone { fill: var(--ink); }

/* Where the photographs went. A hairline world on paper: countries stay empty until
   one of her pictures surfaced there, and then they take ink. No colour scale — with
   counts of one to seven a scale would be a decoration pretending to be information;
   the number is in the line under the map. Hong Kong and Singapore have no outline of
   their own at this resolution, so they are drawn as points rather than being folded
   into a neighbour. */
.atlas { max-width: 1120px; margin: 26px auto 6px; padding: 0 var(--gutter); }
.atlas-map svg { display: block; width: 100%; height: auto; }
.atlas .land { fill: none; stroke: var(--ink-3); stroke-width: 0.6; vector-effect: non-scaling-stroke; }
/* ⚠ A lit country keeps the same hairline as every other. It used to draw its own
   border in --ink-2, two steps darker, and the result was not "lit countries are
   outlined" but a mess: every border is drawn TWICE, once by each side, and the whole
   map goes down in one loop in data order — so a lit country's coastline stayed dark
   (nothing is drawn over open sea) while its land borders were painted back to the
   pale line by whichever neighbour happened to come later. Dark coasts, pale borders,
   no rule behind it. She asked for the gentle line everywhere (2026-09-05), and it
   also makes the draw order stop mattering: every stroke on the map is now identical,
   so it cannot matter who paints last. Being lit is said by the fill. */
.atlas .lit { fill: var(--ink); fill-opacity: 0.14; cursor: pointer; }
.atlas .lit:hover, .atlas .lit:focus, .atlas .lit.on {
  fill: var(--cinnabar); fill-opacity: 0.85; stroke: var(--cinnabar); outline: none;
}
.atlas .spot { fill: var(--ink); fill-opacity: 0.55; stroke: var(--paper); stroke-width: 1.4; cursor: pointer; }
.atlas .spot:hover, .atlas .spot:focus, .atlas .spot.on { fill: var(--cinnabar); fill-opacity: 1; outline: none; }
/* ⚠ The note holds two stacked layers while it changes, so photographs.js sets its height
   in pixels and it is animated from here. `min-height` still floors it, which is why most
   swaps move nothing below at all. `overflow: hidden` is what makes a growing box reveal
   its new last line rather than let it hang over the figures — and the box is measured
   again on resize and after the webfonts land, so it cannot end up cutting one off.
   ⚠ The height settles a shade before the words finish arriving (0.24 against 0.26), so
   the box is never still moving under text that is already fully there. */
.atlas-note {
  position: relative; overflow: hidden;
  min-height: 4.4em; margin: 14px 0 0; font-size: 15px; line-height: 1.55; color: var(--ink-2);
  max-width: var(--measure);
  transition: height 0.24s ease;
}
.atlas-note .layer { position: absolute; left: 0; top: 0; width: 100%; transition: opacity 0.26s ease; }
.atlas-note b { color: var(--ink); font-weight: 400; }
.atlas-note .n { font-variant-numeric: tabular-nums; }
/* ⚠ Only ever drawn on a coarse pointer — photographs.js grows it into the note when the
   map has no hover to carry the reading. On a mouse the note is transient and the cursor
   cannot reach this without leaving the map and wiping it, so there the country is the
   link instead. The note reserves 4.4em, which is three lines and this one. */
.atlas-note .to-list { display: block; margin-top: 6px; color: var(--cinnabar); }

/* The record itself, gathered under the place it happened rather than run out flat. */
.usages .group { margin-top: 30px; scroll-margin-top: 90px; }
.usages .group > h3 {
  margin: 0 0 4px 0; font-size: 15px; font-weight: 400; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-2);
}
.usages .group > h3 .n { color: var(--ink-3); margin-left: 8px; }

/* The marker: a small cinnabar ring, the same red as the seal at the foot of every
   page. Its middle is paper, not transparent, so the line stops at the ring instead
   of running through it; nothing is drawn along the line, so no number of them can
   tangle the drawing. Geometry in scroll.js, colour here. */
/* ⚠ Dimming happens two different ways on this drawing and they have to take the
   same time, or letting go of a line looks like two things happening at once.
   A line, a label and the seal as a whole fade by OPACITY over 0.4s. A dot, a
   spring and the stone cannot fade — a translucent mark lets the water show
   through it — so they dim by COLOUR instead, and colour has to be in the
   transition list or it snaps. `.dot` listed only `fill`, so its ring changed
   instantly while the line under it was still fading. She caught it as
   「极细微区别」, 2026-09-05. */
.river .dot { fill: var(--paper); stroke: var(--cinnabar); stroke-width: 1.6;
  transition: fill 0.15s ease, stroke 0.4s ease; }
/* Pointing at one fills it in, the way a seal goes from cut stone to a stamped mark. */
/* ⚠ Pointing at one must answer at once, so the hovered state carries its own fast
   stroke transition — a transition is read from the state being moved INTO. Coming
   back out falls to the rule above and takes the dimming's 0.4s, which is right. */
.river .seal:hover .dot, .river .seal:focus .dot { fill: var(--cinnabar);
  transition: fill 0.15s ease, stroke 0.15s ease; }
.river .seal.dim .dot { stroke: var(--ink-3); fill: var(--paper); }
.river .seal.dim:hover .dot, .river .seal.dim:focus .dot { stroke: var(--cinnabar); fill: var(--cinnabar); }

/* 頓筆: the line's own stroke, briefly heavier where it pauses. No new shape, so it
   cannot clutter; it takes the line's colour and turns with the water. Cinnabar only
   on hover, which keeps red meaning the same thing everywhere on the site. */
.river .dun { fill: none; stroke-linecap: round; transition: stroke 0.4s ease; }
.river .dun.ink { stroke: var(--ink); }
.river .dun.indigo { stroke: var(--indigo); }
.river .seal:hover .dun, .river .seal:focus .dun { stroke: var(--cinnabar); }
.river .seal.dim .dun { stroke: var(--ink-3); }
.river .seal.dim:hover .dun, .river .seal.dim:focus .dun { stroke: var(--cinnabar); }

/* The knot marker. It is the line's own rope, so it takes the line's colour and never
   its dash: a rope is continuous where it is tied. Cinnabar arrives only on hover, to
   say the knot can be pressed, which keeps red meaning the same thing it means elsewhere. */
.river .knot { fill: none; stroke-linecap: round; stroke-linejoin: round;
  transition: stroke 0.4s ease; }
.river .knot.ink { stroke: var(--ink); }
.river .knot.indigo { stroke: var(--indigo); }
.river .grab { fill: transparent; stroke: none; }
.river .seal:hover .knot, .river .seal:focus .knot { stroke: var(--cinnabar); }
.river .seal.dim .knot { stroke: var(--ink-3); }
.river .seal.dim:hover .knot, .river .seal.dim:focus .knot { stroke: var(--cinnabar); }

/* While another line is being followed, a seal off that line must not go translucent:
   the water would show through it and the two drawings would smear together. It keeps
   its full opacity and turns into an empty stone instead, so it still covers the line
   it sits on and still reads as quieter than the line you are following. */
.river .seal.dim { opacity: 1; }
.river .seal.dim .stone { fill: var(--paper); stroke: var(--ink-3); stroke-width: 1; }
.river .seal.dim:hover .stone, .river .seal.dim:focus .stone { fill: var(--ink-3); stroke: none; }
.river .silt { fill: var(--paper); stroke: var(--ink-3); stroke-width: 1; }
/* ⚠ The names carry no stroke of their own. The paper each one is cut out of lives in
   a single layer in front of everything else in `marks` (scroll.js: haloLayer), so all
   the paper is laid down before any letter is — otherwise a name drawn later puts its
   paper over a name drawn earlier, which at phone width ate whole letters off the bank
   names. `paint-order` cannot do this: it is per element.
   ⚠ stroke-width stays here, on the shared rule, so the halo clones inherit the right
   weight for their kind (a year uses 4, everything else 5) without stating it twice. */
.river text {
  font-family: var(--serif);
  fill: var(--ink);
  stroke: none;
  stroke-width: 5px;
  stroke-linejoin: round;
  paint-order: stroke;
  transition: opacity 0.4s ease;
}
.river g.halo { pointer-events: none; }
/* ⚠ `!important` earns its place here: this layer must NEVER paint a letter, whatever
   any other rule says. Without it `.river text.year.now` (0,3,1) outranked this
   selector (0,2,2) and the `now` marker had its ink painted in the halo layer as well
   as its own — one glyph drawn twice, the lower one liable to be buried by the next
   name's paper. Any future `.river text.a.b` rule would have found the same hole. */
.river g.halo text, .river g.halo tspan { fill: none !important; stroke: var(--paper); }
.river text.label { font-size: 15px; font-style: italic; pointer-events: none; }
.river text .zh { font-family: var(--kai); font-style: normal; font-size: 0.94em; letter-spacing: 0.03em; }
.river text.bank { font-size: 14px; letter-spacing: 0.1em; text-transform: uppercase; fill: var(--ink-2); }
.river text.year { font-size: 13px; letter-spacing: 0.1em; fill: var(--ink-2); stroke-width: 4px; }
.river text.year.now { fill: var(--ink); }
/* The year inside a merged `now · 2027` keeps the colour of the other years.
   ⚠ Scoped to `g.marks > text`, i.e. NOT the halo clone, and the halo rule above now
   names `tspan` too. `.river g.halo text` stops a <text> from painting, but a <tspan>
   with a fill rule of its own is a different element and walks straight through —
   the year would have been inked a second time in the paper layer, the same hole the
   note above describes for `now`. */
.river g.marks > text.year .yr { fill: var(--ink-2); }
.river .tick { stroke: var(--ink-3); stroke-width: 1; }
.river .tick.now { stroke: var(--ink); stroke-width: 1.2; }
.river .lagoon { fill: var(--indigo-2); opacity: 0.45; }
.river .ripple { stroke: var(--indigo-2); stroke-width: 1; fill: none; }

.river .dim { opacity: 0.16; }
.river text.dim { opacity: 0.35; }
/* ⚠ A spring is exempt for exactly the reason the seal above is, and it was missed when
   the seal got its exemption. At 0.16 the disc's paper fill is 84% transparent, so the
   water it is standing on shows straight through it and the line reads as running over
   the mark rather than under it. She saw it while following a line and reported it as
   「非focus的河流线盖过点」 (2026-09-05) — which is what it looks like, though nothing
   is out of order: gLines is drawn before gMarks and always was. Full opacity, quieted
   by colour instead, the same way .seal.dim quiets its dot. */
.river .spring.dim { opacity: 1; stroke: var(--ink-3); }

@media (max-width: 720px) {
  .river text.label { font-size: 12px; }
  .river text.bank { font-size: 10px; }
  .river text.year { font-size: 10px; }
}

/* colophon: the note a seal opens */
.colophon {
  position: absolute;
  width: min(340px, calc(100vw - 48px));
  box-sizing: border-box;
  padding: 20px 22px 18px 22px;
  background: var(--paper);
  border: 1px solid var(--ink);
  display: flex;
  flex-direction: column;
  gap: 10px;
  z-index: 2;
  opacity: 0;
  visibility: hidden;
  transform: translateY(5px);
  transition: opacity 0.34s ease, transform 0.34s ease, visibility 0s linear 0.34s;
}
.colophon.is-open {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: opacity 0.34s ease, transform 0.34s ease, visibility 0s;
}
.colophon .kicker { padding-right: 30px; }
.colophon .title { margin: 0; font-size: 27px; line-height: 1.05; font-style: italic; font-weight: 400; }
.colophon .title-zh { font-family: var(--kai); font-size: 19px; line-height: 1.3; color: var(--ink-2); margin-top: -6px; }
.colophon .meta { font-size: 14px; letter-spacing: 0.04em; color: var(--ink-2); }
.colophon p { margin: 0; font-size: 15px; line-height: 1.5; }
.colophon .zh-original { font-family: var(--kai); font-size: 17px; }
.colophon .note { font-size: 13px; line-height: 1.4; color: var(--ink-2); }
.colophon .read { color: var(--cinnabar); }
.colophon .close { position: absolute; top: 10px; right: 12px; line-height: 1; color: var(--ink-2); }
.colophon .close:hover { color: var(--ink); }

/* A set page: a handful of photographs, each at its own width. The wall next door is
   for choosing among forty-eight; this is for looking. */
.set { display: flex; flex-direction: column; gap: clamp(40px, 6vh, 80px);
       margin: 34px auto 0; padding: 0 var(--gutter); max-width: 1100px; }
.set figure { margin: 0; }
.set img { display: block; width: 100%; height: auto; background: var(--paper-2); }
.set figure.tall { max-width: 680px; margin-inline: auto; }
.set figcaption { margin-top: 10px; font-size: 15px; line-height: 1.5; color: var(--ink-2); font-style: italic; }
.set figcaption:lang(zh-Hans), .set figcaption:lang(zh-Hant) { font-style: normal; }
.set .set-place {
  margin: 10px 0 -14px; font-size: 14px; font-weight: 400; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-2);
}
.set-more { margin-top: clamp(40px, 6vh, 72px); }

/* The chosen photographs, as a wall. Forty-eight at full width is a page nobody
   reaches the bottom of, and the record of where they travelled lives below it.
   Columns rather than a square grid, so no photograph is cropped to fit the wall.
   `.plates` next door is the documentary's grid of stills and is a different thing. */
.chosen { columns: 172px; column-gap: 8px; margin: 30px auto 0;
          padding: 0 var(--gutter); max-width: 1180px; }
.chosen button { display: block; width: 100%; margin: 0 0 8px; padding: 0; border: 0;
                 background: none; cursor: zoom-in; line-height: 0; }
.chosen img { display: block; width: 100%; height: auto; background: var(--paper-2);
              transition: opacity 0.25s ease; }
.chosen button:hover img, .chosen button:focus-visible img { opacity: 0.72; }

/* One photograph, whole. Same grammar as a colophon: it fades, and the paper closes it. */
.viewer {
  position: fixed; inset: 0; z-index: 60; background: var(--paper);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 14px; padding: clamp(16px, 4vw, 40px);
  opacity: 0; visibility: hidden; transition: opacity 0.3s ease, visibility 0.3s;
}
.viewer.is-open { opacity: 1; visibility: visible; }
.viewer img { max-width: 100%; max-height: 76vh; width: auto; height: auto; object-fit: contain; }
.viewer .cap { margin: 0; font-size: 15px; color: var(--ink-2); font-style: italic; text-align: center; }
.viewer .cap:lang(zh-Hans), .viewer .cap:lang(zh-Hant) { font-style: normal; }
.viewer .shut, .viewer .step {
  position: absolute; background: none; border: 0; color: var(--ink-2);
  font-size: 22px; line-height: 1; cursor: pointer; padding: 10px;
}
.viewer .shut { top: 14px; right: 16px; }
.viewer .step { top: 50%; transform: translateY(-50%); font-size: 26px; }
.viewer .prev { left: 8px; }
.viewer .next { right: 8px; }
.viewer .shut:hover, .viewer .step:hover { color: var(--ink); }

/* The seal wherever it appears outside the river. 五筆, cut 白文: the five strokes
   are masked out of the cinnabar, not painted on top of it, so the knockout shows
   whatever is behind and no dark-mode rule is needed. Geometry: tools/seal.py. */
.seal { display: block; color: var(--cinnabar); flex: none; }
.seal-defs { position: absolute; width: 0; height: 0; overflow: hidden; }

/* lagoon */
.lagoon-section {
  padding: 40px var(--gutter) 60px var(--gutter);
  max-width: 880px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.lagoon-section .heading { display: flex; align-items: baseline; gap: 22px; }
.lagoon-section .heading .en { font-size: clamp(40px, 5vw, 60px); line-height: 1; font-weight: 500; }
.lagoon-section .heading .zh { font-family: var(--kai); font-size: clamp(24px, 3vw, 34px); color: var(--ink-2); }
.lagoon-section p { margin: 0; max-width: 620px; font-size: 20px; line-height: 1.5; font-style: italic; }
.lagoon-section form { display: flex; gap: 12px; max-width: 560px; }
.lagoon-section input {
  flex: 1; min-width: 0; padding: 12px 16px; border: 1px solid var(--ink-3); background: var(--paper);
  font: inherit; font-size: 17px; font-style: italic; color: var(--ink);
}
.lagoon-section input::placeholder { color: var(--ink-2); }
.lagoon-section .btn { padding: 12px 22px; border: 1px solid var(--ink-3); color: var(--ink); }
.lagoon-section .btn:hover { border-color: var(--ink); }
.lagoon-section .links { display: flex; flex-wrap: wrap; gap: 12px 40px; margin-top: 18px; font-size: 16px; letter-spacing: 0.06em; }
.lagoon-section footer {
  display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px 24px;
  margin-top: 70px; padding-top: 16px; border-top: 1px solid var(--ink-3);
}

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

@media (prefers-reduced-motion: reduce) {
  .river .line, .river .spring, .river .seal, .river text,
  .river .dot, .river .dun, .river .knot, .river .stone,
  .rail-swap > * { transition: none; }
  .viewer, .viewer.is-open, .chosen img { transition: none; }
  .colophon, .colophon.is-open { transition: none; }
  /* photographs.js also skips the crossfade itself here — it swaps the layer outright,
     so this only covers the height. Both, because either alone still moves. */
  .atlas-note, .atlas-note .layer { transition: none; }
}

/* ─────────────────────────────────────────────────────────────
   Reading pages. Same paper, same hand; the river becomes a margin.
   ───────────────────────────────────────────────────────────── */

.rail .back { display: inline-flex; align-items: center; gap: 10px; color: var(--ink); }
/* The page you are on, and a way back to the top of it. It is a link now, so it has
   to answer to the pointer — `a:hover` (0,1,1) loses to this rule (0,2,0), so the
   cinnabar has to be said again here or the name would look inert. */
.rail .here { color: var(--ink-2); }
.rail .here:hover, .rail .here:focus-visible { color: var(--cinnabar); }

.page {
  max-width: 68rem;
  margin: 0 auto;
  padding: 0 var(--gutter) 80px var(--gutter);
}
.page-head {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: clamp(30px, 6vh, 70px) 0 clamp(26px, 5vh, 54px) 0;
}
.page-head h1 {
  margin: 0;
  font-weight: 500;
  font-size: clamp(44px, 8vw, 104px);
  line-height: 0.94;
  letter-spacing: -0.02em;
}
.page-head .zh {
  font-family: var(--kai);
  font-size: clamp(20px, 3vw, 30px);
  color: var(--ink-2);
  letter-spacing: 0.08em;
}
.page-head .lede {
  margin: 6px 0 0 0;
  max-width: 34ch;
  font-size: clamp(19px, 2.2vw, 25px);
  line-height: 1.35;
  font-style: italic;
}
.page-head .facts { display: flex; flex-wrap: wrap; gap: 6px 26px; margin-top: 8px; }

.prose { max-width: var(--measure); }
.prose p { margin: 0 0 1.1em 0; font-size: 19px; line-height: 1.72; }
.prose p:lang(zh-Hant) { font-family: var(--zh); }
.prose p:lang(zh-Hans) { font-family: var(--zh-hans); }
.prose p:lang(zh-Hant), .prose p:lang(zh-Hans) { font-size: 17.5px; line-height: 1.95; }
.prose h2 { font-weight: 500; font-size: 30px; line-height: 1.1; margin: 2em 0 0.6em 0; }
.prose blockquote {
  margin: 1.6em 0; padding-left: 20px; border-left: 1px solid var(--ink-3);
  font-style: italic; color: var(--ink-2);
}
.prose figure { margin: 1.8em 0; }
.prose img { display: block; width: 100%; height: auto; background: var(--paper-2); }
.prose figcaption { margin-top: 8px; font-size: 14px; color: var(--ink-2); }

/* an entry on a long page */
.entry { padding: 0 0 clamp(48px, 7vh, 84px) 0; scroll-margin-top: 90px; }
.entry-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px 18px; margin-bottom: 22px; }
/* The date reads the same here as it does on a Lagoon issue: quiet, unshouted,
   the same size and tracking. The wording itself is hers and is left alone. */
.entry-head .when { font-size: 14px; letter-spacing: 0.08em; text-transform: none; color: var(--ink-2); }
.entry-head .what { font-family: var(--kai); font-size: 25px; line-height: 1.2; }
.entry .stamp {
  display: inline-flex; align-items: center; gap: 10px;
  margin-top: 6px; font-size: 14px; letter-spacing: 0.09em; text-transform: uppercase; color: var(--ink-2);
}
.entry .stamp::before { content: ""; width: 26px; height: 1px; background: var(--ink-3); }
/* ⚠ Room after the stamp when something follows it. The stamp is inline-flex, so it sat
   in a line box with no margin of its own and the body text came in 7px under it, while
   the title above it was 20px away — the label read as part of the paragraph rather than
   a caption of the heading. She caught it on 2026-10-23, for every stamp of this kind.
   The gap after (≈25px) is now a little MORE than the gap before (20px), so the stamp
   groups with the title it annotates.
   ⚠ On the element that FOLLOWS, not on the stamp: a stamp that ends an entry (the place
   name at the foot of a causerie) has nothing after it and must not add to the entry's
   own bottom padding. `.issue` is covered too, so a Lagoon issue that grows one behaves
   the same. */
.entry .stamp + *, .issue .stamp + * { margin-top: 18px; }
.entry .images { display: flex; flex-direction: column; gap: 12px; margin: 1.6em 0; }
.entry .images.pair { display: grid; grid-template-columns: 1fr 1fr; }
@media (max-width: 620px) { .entry .images.pair { grid-template-columns: 1fr; } }

/* the year rail, echoing the ticks on the river */
.with-years { display: grid; grid-template-columns: minmax(0, 1fr) 7rem; gap: 0 32px; }
.years {
  position: sticky; top: 88px; align-self: start;
  display: flex; flex-direction: column; gap: 10px;
  font-size: 13px; letter-spacing: 0.1em; color: var(--ink-2);
}
.years a { display: flex; align-items: center; justify-content: flex-end; gap: 10px; color: inherit; }
.years a::after { content: ""; width: 14px; height: 1px; background: var(--ink-3); }
.years a[aria-current="true"] { color: var(--ink); }
.years a[aria-current="true"]::after { background: var(--ink); width: 22px; }
@media (max-width: 900px) { .with-years { grid-template-columns: minmax(0, 1fr); } .years { display: none; } }

/* question and answer */
.qa { padding: 0 0 40px 0; scroll-margin-top: 90px; }
.qa .q { margin: 0 0 14px 0; font-size: 24px; line-height: 1.3; font-style: italic; font-weight: 400; }
.qa .q:lang(zh-Hans), .qa .q:lang(zh-Hant) { font-family: var(--kai); font-size: 23px; line-height: 1.5; }
.qa .a p { margin: 0 0 0.9em 0; }
.qa .when { font-size: 13px; letter-spacing: 0.08em; color: var(--ink-2); font-variant-numeric: tabular-nums; }
.month-rule {
  display: flex; align-items: center; gap: 18px; margin: 28px 0 34px 0;
  font-size: 13px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-2);
}
.month-rule::after { content: ""; flex: 1; height: 1px; background: var(--ink-3); }

/* a form on paper */
.ask-form { display: flex; flex-direction: column; gap: 12px; max-width: 34rem; margin: 0 0 20px 0; }
.ask-form textarea, .lagoon-section input, .ask-form input {
  width: 100%; box-sizing: border-box; padding: 12px 16px;
  border: 1px solid var(--ink-3); background: var(--paper);
  font: inherit; font-size: 17px; color: var(--ink); resize: vertical;
}
.ask-form textarea { min-height: 7em; line-height: 1.6; }
/* ⚠ The browser's own focus ring, off. On a text field it is drawn for `:focus`, which
   fires on a CLICK and not only on tab, so simply typing in the subscribe box put a heavy
   blue slab around it — she saw it there, 2026-09-06, and asked for it everywhere.
   ⚠ Off, but not replaced with nothing: a field that gives no sign it has focus loses a
   keyboard reader their place, and the ask page has two of them. Instead of adding a ring,
   the field's OWN border darkens — no new geometry, nothing moves. `:focus`, not
   `:focus-visible`, because a text field should also say which of the two you are typing
   into when you got there by clicking.
   ⚠ `--ink`, not `--cinnabar`. The button standing beside these fields already says "you
   are addressing me" with `border-color: var(--ink)` on hover — so the form has a gesture
   for this and it is the darker border, not the red one. Cinnabar on the field would be a
   second vocabulary inside one object; red is what the river's marks and the links speak.
   ⚠ The three period mirrors are not here, and cannot be: these selectors are scoped to
   `.ask-form` and `.lagoon-section`, which are ours. Their controls are WordPress's and
   those sites are migrated, not redesigned (her call again, 2026-09-06). */
.ask-form textarea:focus, .ask-form input:focus, .lagoon-section input:focus {
  outline: none;
  border-color: var(--ink);
}
.ask-form .row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.ask-form .btn { padding: 12px 22px; border: 1px solid var(--ink-3); color: var(--ink); }
.ask-form .btn:hover { border-color: var(--ink); }
.fine { font-size: 14px; color: var(--ink-2); max-width: 34rem; }

.page footer.colophon-foot {
  display: flex; justify-content: space-between; flex-wrap: wrap; gap: 10px 24px;
  margin-top: 70px; padding-top: 16px; border-top: 1px solid var(--ink-3);
}
/* An address is written the way it is typed. The small caps around it may shout;
   the mailbox does not. */
/* Wherever small caps run, an address stays as it is typed. The rule has to hang
   off .small-caps, not off one footer: the scroll's own footer is not a .colophon-foot. */
.small-caps a[href^="mailto:"] { text-transform: none; letter-spacing: 0.06em; }

/* ─────────────────────────────────────────────────────────────
   Riverive: a walk you listen to. Water at the top, paper below.
   ───────────────────────────────────────────────────────────── */

.hero-full {
  position: relative;
  min-height: min(78vh, 720px);
  display: flex;
  align-items: flex-end;
  padding: 0 var(--gutter) clamp(34px, 6vh, 64px) var(--gutter);
  background: #10171c;
  overflow: hidden;
}
.hero-full img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; opacity: 0.62;
}
.hero-full .inner { position: relative; color: #f2f1ee; display: flex; flex-direction: column; gap: 12px; }
.hero-full h1 {
  margin: 0; font-weight: 500; letter-spacing: -0.02em;
  font-size: clamp(44px, 8.4vw, 118px); line-height: 0.92;
}
.hero-full .sub { font-size: clamp(19px, 2.4vw, 28px); font-style: italic; opacity: 0.92; }
.hero-full .zh { font-family: var(--kai); font-size: clamp(20px, 2.6vw, 30px); letter-spacing: 0.16em; opacity: 0.9; }
.hero-full .stamp {
  font-size: 14px; letter-spacing: 0.1em; text-transform: uppercase; opacity: 0.78;
  display: flex; flex-wrap: wrap; gap: 4px 22px; margin-top: 6px;
}

.pull {
  margin: clamp(40px, 7vh, 80px) 0;
  font-size: clamp(24px, 3.6vw, 42px);
  line-height: 1.2; font-style: italic; text-align: center; color: var(--ink);
}
.map-figure { margin: clamp(30px, 5vh, 56px) 0; }
.map-figure img { display: block; width: 100%; height: auto; }
.map-figure figcaption { margin-top: 12px; font-size: 15px; color: var(--ink-2); }

/* the five parts, strung on one line like the river */
.walk { position: relative; padding-left: 42px; }
.walk::before {
  content: ""; position: absolute; left: 8px; top: 8px; bottom: 8px;
  width: 1px; background: var(--ink-3);
}
.part { position: relative; padding: 0 0 clamp(44px, 6vh, 70px) 0; scroll-margin-top: 90px; }
.part::before {
  content: ""; position: absolute; left: -38px; top: 12px;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--paper); border: 1px solid var(--ink);
}
.part.playing::before { background: var(--cinnabar); border-color: var(--cinnabar); }
.part h2 {
  margin: 0; font-weight: 500; font-size: clamp(30px, 4.4vw, 46px); line-height: 1;
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px 16px;
}
.part h2 .sub { font-size: 0.5em; font-style: italic; color: var(--ink-2); }
.part .where { margin: 10px 0 14px 0; font-size: 14px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-2); }
.part p { margin: 0 0 0.8em 0; font-size: 18px; line-height: 1.65; max-width: var(--measure); }

.player { display: flex; align-items: center; gap: 16px; margin: 18px 0 0 0; max-width: 30rem; }
.player button {
  flex: 0 0 auto; width: 34px; height: 34px; border-radius: 2px;
  background: var(--cinnabar); display: grid; place-items: center;
}
.player button:hover { background: var(--ink); }
.player button svg { display: block; fill: var(--paper); }
.player .track {
  position: relative; flex: 1; height: 18px; cursor: pointer; touch-action: none;
  display: flex; align-items: center;
}
.player .track::before { content: ""; position: absolute; left: 0; right: 0; height: 1px; background: var(--ink-3); }
.player .fill { position: absolute; left: 0; height: 1px; background: var(--ink); width: 0; }
.player .head {
  position: absolute; width: 7px; height: 7px; border-radius: 50%;
  background: var(--ink); left: 0; margin-left: -3px;
}
.player .time { flex: 0 0 auto; font-size: 14px; color: var(--ink-2); font-variant-numeric: tabular-nums; }

.close-lines {
  margin: clamp(40px, 7vh, 78px) 0 0 0; font-size: clamp(22px, 3vw, 34px);
  line-height: 1.3; font-style: italic;
}

/* ─────────────────────────────────────────────────────────────
   A long read, in more than one language.
   ───────────────────────────────────────────────────────────── */

.read-head { max-width: var(--measure); }
.read-head .kicker { font-size: 15px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-2); }
.read-head h1 {
  margin: 14px 0 0 0; font-weight: 500; letter-spacing: -0.02em;
  font-size: clamp(38px, 6.4vw, 76px); line-height: 1.0;
}
.read-head .standfirst { margin: 16px 0 0 0; font-size: clamp(19px, 2.2vw, 25px); line-height: 1.35; font-style: italic; }

.versions { display: flex; gap: 0; margin: clamp(26px, 4vh, 44px) 0 0 0; border-bottom: 1px solid var(--ink-3); }
.versions button {
  padding: 10px 20px 12px 0; margin-right: 22px; position: relative;
  font-size: 16px; letter-spacing: 0.06em; color: var(--ink-2);
}
.versions button[aria-selected="true"] { color: var(--ink); }
.versions button[aria-selected="true"]::after {
  content: ""; position: absolute; left: 0; right: 22px; bottom: -1px; height: 2px; background: var(--cinnabar);
}
.version-note {
  max-width: var(--measure); margin: 20px 0 0 0;
  font-size: 14px; line-height: 1.5; color: var(--ink-2);
  display: flex; flex-wrap: wrap; gap: 4px 18px;
}
.version-note a { color: var(--cinnabar); }
.long-read { max-width: var(--measure); margin-top: clamp(22px, 3.5vh, 38px); position: relative; }
/* ⚠ The essay body is the tab PANEL of the two version tabs, so it carries `tabindex=0`
   (ARIA: a panel holding nothing focusable has to be reachable itself, or the keyboard
   cannot get into the prose). The browser answers that with its own ring — an orange box
   around a whole essay, which is the thing she asked me to take off the fields, 2026-09-06.
   ⚠ Off on `:focus`, so clicking into the text never draws anything; a mark of our own on
   `:focus-visible`, so tabbing in still says where you landed.
   ⚠ NOT the site's 2px ring from line 110. That ring is right for a link or a button; this
   panel is ~4,000px tall, so a ring becomes two red rules running down both sides of the
   column for as long as you read. A focus mark should be the size of the thing it marks —
   so it is a rule across the head of the essay, drawn where the eye already is when focus
   arrives (the browser scrolls the panel's top edge into view), in the same hairline
   vocabulary as the colophon and the rail. Absolutely positioned: it sits in the margin
   above and moves nothing. */
.long-read:focus { outline: none; }
.long-read:focus-visible::before {
  content: ''; position: absolute; left: 0; right: 0; top: -12px; height: 2px;
  background: var(--cinnabar);
}
.long-read h2 {
  font-family: var(--kai); font-weight: 400; font-size: 30px; margin: 0 0 0.7em 0;
}
.long-read p { font-size: 19px; line-height: 1.78; margin: 0 0 1.15em 0; }
.long-read p:lang(zh-Hans), .long-read p:lang(zh-Hant) { font-size: 17.5px; line-height: 2.0; }
.long-read p:first-of-type::first-letter {
  float: left; font-size: 3.3em; line-height: 0.86; padding: 0.06em 0.09em 0 0; font-weight: 500;
}
.long-read p:lang(zh-Hans):first-of-type::first-letter,
.long-read p:lang(zh-Hant):first-of-type::first-letter { float: none; font-size: inherit; padding: 0; }

/* ─────────────────────────────────────────────────────────────
   The lagoon: a letter, and the issues that have gone out.
   ───────────────────────────────────────────────────────────── */

.issue { padding: 0 0 clamp(52px, 8vh, 96px) 0; scroll-margin-top: 90px; }
.issue-head { margin: 0 0 6px 0; font-weight: 400; display: flex; flex-direction: column; gap: 8px; }
.issue-head .no { font-size: 15px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-2); }
.issue-head .name { font-family: var(--kai); font-size: clamp(24px, 3vw, 34px); line-height: 1.2; }
.issue .when { font-size: 14px; letter-spacing: 0.08em; color: var(--ink-2); margin-bottom: 26px; }
.issue-head .no { order: -1; }
.piece { max-width: var(--measure); padding: 0 0 30px 0; }
.piece > h3 {
  margin: 0 0 4px 0; font-weight: 400; font-size: 24px; line-height: 1.3;
}
.piece > h3:lang(zh-Hans), .piece > h3:lang(zh-Hant) { font-family: var(--kai); font-size: 26px; }
.piece .alt { font-size: 17px; color: var(--ink-2); margin-bottom: 8px; }
.piece .sub { font-size: 16px; color: var(--ink-2); font-style: italic; margin-bottom: 14px; }
.piece.note { border-left: 1px solid var(--ink-3); padding-left: 20px; }
.piece.note > h3 { font-size: 15px; letter-spacing: 0.1em; color: var(--ink-2); font-family: var(--kai); }
.piece .to {
  display: inline-flex; align-items: baseline; gap: 8px;
  margin-top: 6px; color: var(--cinnabar); font-size: 16px;
}
.issue .stamp {
  display: inline-flex; align-items: center; gap: 10px; margin-top: 4px;
  font-size: 14px; color: var(--ink-2);
}
.issue .stamp::before { content: ""; width: 26px; height: 1px; background: var(--ink-3); }

/* ─────────────────────────────────────────────────────────────
   A film page: the work first, the credits after.
   ───────────────────────────────────────────────────────────── */

.words { max-width: 30ch; font-size: clamp(24px, 3.4vw, 40px); line-height: 1.25; margin: 0; }
.words .w { font-style: italic; }
.words .w::after { content: ", "; font-style: normal; color: var(--ink-2); }
.words .w:nth-last-child(2)::after { content: " and "; font-style: normal; color: var(--ink-2); }
.words .w:last-child::after { content: ""; }

.film { margin: clamp(30px, 5vh, 56px) 0 0 0; }
.film video { display: block; width: 100%; height: auto; background: #10171c; }
.film .under {
  display: flex; flex-wrap: wrap; justify-content: space-between; gap: 8px 24px;
  margin-top: 12px; font-size: 15px; color: var(--ink-2);
}
.film .under a { color: var(--cinnabar); }

.credits { display: flex; flex-wrap: wrap; gap: 10px 34px; margin: 26px 0 0 0; padding: 0; }
.credits li { list-style: none; font-size: 17px; }
.credits li::before { content: "· "; color: var(--ink-3); }

.plates { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 18px; margin: clamp(30px, 5vh, 56px) 0 0 0; }
.plates img { display: block; width: 100%; height: auto; background: var(--paper-2); }
.strip { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 10px; margin: 18px 0 0 0; }
.strip img { display: block; width: 100%; height: auto; aspect-ratio: 3 / 2; object-fit: cover; background: var(--paper-2); }

/* the signature that closes every page, beside the seal */
.signature {
  display: inline-flex; align-items: center; gap: 10px;
  font-size: 17px; font-style: italic; letter-spacing: 0; text-transform: none; color: var(--ink);
}

/* ─────────────────────────────────────────────────────────────
   Where the photographs went.
   ───────────────────────────────────────────────────────────── */

.figures { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 20px 32px; margin: 0 0 clamp(30px, 5vh, 54px) 0; }
.figures .fig b { display: block; font-weight: 500; font-size: clamp(28px, 3.4vw, 40px); line-height: 1; font-variant-numeric: tabular-nums; }
.figures .fig span { display: block; margin-top: 6px; font-size: 14px; line-height: 1.4; color: var(--ink-2); }

.usages { border-top: 1px solid var(--ink-3); }
.use {
  display: grid; grid-template-columns: 9rem minmax(0, 1fr) 9rem; gap: 4px 24px;
  padding: 14px 0; border-bottom: 1px solid var(--ink-3); align-items: baseline;
}
.use .when { font-size: 14px; letter-spacing: 0.06em; color: var(--ink-2); font-variant-numeric: tabular-nums; }
.use .who { font-size: 18px; }
.use .what { display: block; font-size: 15px; font-style: italic; color: var(--ink-2); margin-top: 3px; }
.use .subj { font-size: 14px; color: var(--ink-2); text-align: right; }
.use .early { font-style: normal; }
.use .what a.out {
  color: inherit; text-decoration: underline; text-decoration-color: var(--ink-3);
  text-decoration-thickness: 1px; text-underline-offset: 3px;
}
.use .what a.out:hover { color: var(--cinnabar); text-decoration-color: currentColor; }
@media (max-width: 720px) {
  .use { grid-template-columns: minmax(0, 1fr); }
  .use .subj { text-align: left; }
}

.work-title { font-weight: 500; font-size: clamp(24px, 3vw, 34px); line-height: 1.12; margin: 0 0 10px 0; max-width: var(--measure); }
.entry .to { display: inline-flex; gap: 8px; margin-top: 12px; color: var(--cinnabar); font-size: 16px; }

/* an issue's footnotes: small, and referenced by a superscript mark */
.piece sup { font-size: 0.62em; vertical-align: super; line-height: 0; letter-spacing: 0.02em; }
.footnotes { margin-top: 26px; padding-top: 14px; border-top: 1px solid var(--ink-3); display: flex; flex-direction: column; gap: 6px; }
.footnotes p { margin: 0; font-size: 14px; line-height: 1.6; color: var(--ink-2); }
.footnotes p:lang(zh-Hans), .footnotes p:lang(zh-Hant) { font-size: 14px; line-height: 1.75; }
.piece .sign { margin-top: 22px; display: flex; flex-direction: column; gap: 2px; }
.piece .sign p { margin: 0; font-size: 16px; line-height: 1.7; }
.piece .sign p:lang(zh-Hans), .piece .sign p:lang(zh-Hant) { font-size: 15.5px; }
.piece .sign p:first-child { font-family: var(--kai); font-size: 19px; }

/* The 404 page. Its own small block, because the page has no markup of its own to
   hang styles on and a `style` attribute would be refused by the CSP. */
.gone { max-width: 640px; margin: 0 auto; padding: 18vh var(--gutter) 0; }
.gone h1 { font-size: clamp(30px, 5vw, 46px); font-weight: 400; margin: 0 0 0.4em; }
.gone p { font-size: 18px; line-height: 1.6; }

/* Three rules that used to live in `style` attributes. The strict CSP refuses those,
   and an attribute refused by the CSP fails silently — the rule is simply not there. */
.lagoon-form-email { flex: 1; min-width: 16rem; }
.where-head { scroll-margin-top: 90px; }
.where-more { margin-top: 30px; }

/* A set page's own share of the record. Same rows as the photographs page, in a
   narrower column, because a set has a handful of uses rather than sixty-one. */
.set-went { max-width: 900px; margin: clamp(48px, 8vh, 96px) auto 0; }
.set-went h2 { font-size: clamp(24px, 3vw, 32px); font-weight: 400; margin: 0 0 6px; }
.set-went .lede { margin: 0 0 18px; }
.set-went .uses { border-top: 1px solid var(--ink-3); }
.set-went .fine { margin-top: 16px; }

/* The two arrows (js/read.js): real outlines out of Noto Serif TC, which this site
   already ships. Filled glyphs, not stroked shapes — the taper is the difference.
   ⚠ Cormorant carries no arrows at all, so before this both came from whatever
   fallback face the device had; the horizontal one sat far too low on Android. */
.go { fill: currentColor; overflow: visible; }
.go-ne { width: 0.78em; height: 0.78em; vertical-align: -0.02em; }
/* ⚠ These three numbers are not taste, they are the glyph's own metrics: Noto Serif's
   → has a bounding box of 926×480 units on a 1000-unit em, sitting 140 units above the
   baseline. Drawn at those figures it is set exactly as the typeface would set it.
   The ↗ is deliberately a little larger than its own 669 — it follows small capitals,
   which are short, and at true size it read as undersized beside them. */
.go-right { width: 0.926em; height: 0.48em; vertical-align: 0.14em; }

/* The photograph that opens A borderlander's reckoning. Set to the reading measure's
   width rather than the page's, so it belongs to the essay and not to the furniture. */
.opening { margin: 26px 0 34px; max-width: 860px; }
.opening img { display: block; width: 100%; height: auto; }
.opening figcaption {
  margin-top: 10px; font-size: 15px; line-height: 1.5; color: var(--ink-2); max-width: var(--measure);
}
.opening figcaption:lang(zh-Hans) { font-family: var(--zh-hans); }

/* ── What can be selected ───────────────────────────────────────────────────
   Selection is for prose. Controls and drawn furniture are not: on a phone a press
   that lingers paints a button blue instead of pressing it, and a drag across the
   river highlights its labels rather than moving anything. Everything a reader might
   reasonably want to copy — an essay, an answer, a caption, a title, a date inside the
   text — stays selectable, and so does anything typed into a form.
   ⚠ The address is the exception, restored below: it is a mailto link and on a phone
   it is tapped, but on a desktop plenty of people copy it rather than hand it to
   whatever mail program the system nominates. */
.rail, .rail *,
.river, .river *,
.river-hint,
/* The photographs page, 2026-09-06. Three surfaces there are drawn or pressed, not read:
   the wall of thumbnails and the gaps between them; the full-screen viewer, which is
   steered by pointerdown/up, so a drag beginning on its caption would select instead of
   turning the page; and the atlas — where the line under the map rewrites itself on every
   hover, so a drag across the countries used to leave that line highlighted behind you.
   ⚠ `.usages` is deliberately NOT here and must not be added. That list is the record —
   outlet names, titles, dates — and her reason for keeping it selectable is that people
   copy a line out of it to go and search for it. Same for the prose either side of the map. */
.chosen, .chosen *,
.viewer, .viewer *,
.atlas, .atlas *,
.versions, .versions *,
button, .btn,
.colophon .close, .colophon .kicker, .colophon .read,
.links, .links *,
.set-more, .set-more *,
.colophon-foot, .colophon-foot *,
.lagoon-section footer.small-caps, .lagoon-section footer.small-caps * {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  /* ⚠ Android Chrome paints its tap highlight over the element's BOUNDING BOX, not the
     shape you pressed. On a mark that is a small square; on the 22px line target it is
     the whole meander's box — a teal slab across half the drawing, every time a line
     was pressed. Nothing here needs that feedback: a mark opens its note and a line
     lights up, which is feedback enough. */
  -webkit-tap-highlight-color: transparent;
}

/* The rest of the places where the tap highlight lies, kept apart from the rule above
   because they are a different question: that one is about what may be SELECTED, this
   one only about the flash Android Chrome paints on a press.
   ⚠ It paints the element's BOUNDING BOX, so it is wrong exactly where the box is not
   the shape. Found by walking every focusable element on every page and reading the
   computed value rather than guessing from the stylesheet (2026-09-05):

     a country on the map   the box of a coastline — half a continent, for Australia
     a point on the map     a 2px circle wearing a slab
     a year in the rail     navigational furniture, not prose
     the long read          253 x 9686px: one tap flashes the whole essay

   ⚠ Highlight only. `.long-read` is prose and has to stay selectable, so this rule must
   never grow a `user-select` — that is what the list above is for, and `.long-read` is
   deliberately not in it.
   ⚠ Inline links in running text are NOT here, and should not be. There the box IS the
   words, and a reader tapping a link in a paragraph should see that it took. */
.atlas .lit, .atlas .spot, .years a, .long-read {
  -webkit-tap-highlight-color: transparent;
}

/* ⚠ Never the form fields. A rule above could otherwise reach a control inside one,
   and an ask box you cannot select inside is a broken ask box. */
input, textarea, [contenteditable] {
  -webkit-user-select: text;
  user-select: text;
  -webkit-touch-callout: default;
}

/* ⚠ And never the address. It sits inside a footer that is otherwise furniture, so it
   has to be said explicitly — and said at each footer's own weight. The bare
   `a[href^=]` (0,1,1) beats `.colophon-foot *` (0,1,0) on a set page, but loses to the
   home page's `.lagoon-section footer.small-caps *` (0,2,1), which is exactly how this
   went out half-working the first time: correct everywhere except the home page. */
a[href^="mailto:"],
.lagoon-section footer.small-caps a[href^="mailto:"],
.colophon-foot a[href^="mailto:"] {
  -webkit-user-select: text;
  user-select: text;
  -webkit-touch-callout: default;
}

/* The press target for a line. Invisible, the same shape as the water, wide enough to
   aim at with a thumb. ⚠ `pointer-events: stroke` and not `all`: with a fill it would
   claim the whole area the curve encloses, which on a meandering line is most of the
   page. The drawn line itself takes no events, so the two can never disagree. */
.river .line-hit {
  fill: none;
  stroke: transparent;
  stroke-width: 22;
  pointer-events: stroke;
  cursor: pointer;
}
.river .line { pointer-events: none; }
