/* Written by tools/fetch-fonts.mjs. Do not edit by hand; re-run the script. */
@font-face {
  font-family: "Bodoni Moda";
  font-style: normal;
  font-weight: 900;
  font-display: swap;
  src: url("bodoni-moda-900-latin-ext.b1131a40.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Bodoni Moda";
  font-style: normal;
  font-weight: 900;
  font-display: swap;
  src: url("bodoni-moda-900-latin.90076614.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: "Cinzel";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("cinzel-600-latin-ext.40e2a5e0.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Cinzel";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("cinzel-600-latin.37543dc7.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: "Courier Prime";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("courier-prime-400-latin-ext.18c8e32e.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Courier Prime";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("courier-prime-400-latin.21bf7ff8.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: "Courier Prime";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("courier-prime-700-latin-ext.078c7809.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Courier Prime";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("courier-prime-700-latin.06622415.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: "Public Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("public-sans-400-latin-ext.1c8a26c0.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Public Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("public-sans-400-latin.c1b6da51.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: "Public Sans";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("public-sans-400-latin-ext.1c8a26c0.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Public Sans";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("public-sans-400-latin.c1b6da51.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: "Public Sans";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("public-sans-400-latin-ext.1c8a26c0.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Public Sans";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("public-sans-400-latin.c1b6da51.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;
}

/* ─────────────────────────────────────────────────────────────────────────────
   Illiterati — the chart sheet.

   The society's engraving, printed on newsprint rather than struck in green. The
   inversion is functional, not decorative: a cover is how a listener tells one
   book from another, and covers die on a dark saturated ground with ornament
   behind them. Here the paper is the field, the green is the ink, and nothing
   is laid behind a row of books.

   Type has four jobs and four faces:
     Bodoni Moda   the masthead and chart positions — engraved didone
     Cinzel        mottos and section heads, anything inscribed rather than set
     Public Sans   titles, authors, everything read at length
     Courier Prime measured data — runtimes, ratings, serials, dates
   ───────────────────────────────────────────────────────────────────────────── */

:root {
  --paper:      #F2EFE6;
  --paper-2:    #EAE5D9;
  --paper-3:    #E1DBCC;
  --ink:        #14120E;
  --ink-2:      #4A453B;
  /* 4.46:1 on the paper before this, which is under the 4.5 that body text is held to — the number
     the note on .entry__spec had already measured and then worked around one rule at a time.
     Darkened along its own hue and saturation until it clears on every ground it is actually set
     on, not just the lightest one: 4.95:1 on the paper, 4.53:1 on --paper-2, which is a hovered
     chart row and the ground of the signup box. The tier keeps its job of being the quiet one
     rather than being replaced by --ink-2 everywhere it was too quiet to read. */
  --ink-3:      #6D6658;
  --green:      #123A2A;
  --green-2:    #1D5540;
  --gold:       #7A5F14;
  --gold-lift:  #C9A227;
  /* A gold for a thin line on the paper. The lift is a fill colour: at two pixels wide and dashed it
     is a pale scratch on cream, so a traced line takes the darker gold here and the lift on dark
     paper, where the darker gold is the one that disappears. */
  --gold-trace: #7A5F14;
  --down:       #8C3A22;

  --rule:        rgba(20, 18, 14, .82);
  --rule-mid:    rgba(20, 18, 14, .28);
  --rule-faint:  rgba(20, 18, 14, .14);

  /* The one surface whiter than the paper: a box you type into, or a storefront's own tile. */
  --field:       #fff;
  /* Tints — a coloured wash behind a notice. Kept as tokens because a wash tuned for paper is
     invisible on ink, and the dark scheme below sets its own. */
  --tint-gold:   rgba(201, 162, 39, .12);
  --tint-green:  rgba(18, 58, 42, .06);
  --tint-down:   rgba(140, 58, 34, .07);
  --shadow-ink:  20, 18, 14;

  /* The app's six bookmark tags, each with an ink of its own. */
  --tag-quote:     #6B4E9B;
  --tag-favourite: #A8571C;
  --tag-character: #1F6F5C;
  --tag-plot:      #2C5C8A;
  --tag-lookup:    #7A5F14;
  --tag-confusing: #8C3A22;

  /* Declared here as well as in the shell's meta tag, so form controls and scrollbars are drawn for
     the scheme the page is actually in. */
  color-scheme: light;

  --page:   1220px;
  --pad:    clamp(16px, 2.4vw, 32px);

  /* The bottom two steps were 11px and 13px. A chart row is dense by design and small type is part
     of how it stays dense, but 11px is where "compact" turns into "squint" — and the rows carry
     real sentences at that size now, not just labels: a narrator's name, a runtime, which chart a
     title moved on and what it moved from. Both steps go up one pixel. Nothing above them moves,
     so the relationship between a heading and the text under it is unchanged. */
  --step--2: .75rem;
  --step--1: .875rem;
  --step-0:  1rem;
  --step-1:  1.125rem;

  --sans:    "Public Sans", ui-sans-serif, system-ui, -apple-system, sans-serif;
  --display: "Bodoni Moda", Georgia, "Times New Roman", serif;
  --incise:  "Cinzel", Georgia, serif;
  --data:    "Courier Prime", ui-monospace, "SF Mono", Menlo, monospace;
  --wordmark: "Illiterati Display", Didot, "Bodoni Moda", Georgia, serif;

  --star: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cpath d='M50 3 61.5 34.2 94.7 35.5 68.5 56 77.6 88 50 69.5 22.4 88 31.5 56 5.3 35.5 38.5 34.2Z'/%3E%3C/svg%3E");
}

/* ── the dark scheme ────────────────────────────────────────────────────────
   The same plate, printed as a negative. The ink becomes the field and the paper the ink, the gold
   is lifted until it reads on it, and nothing else about the drawing changes. The ground stays a
   warm near-black rather than the deep green the site once tried as a field — PRODUCT.md records
   why: a saturated ground under a row of covers made the covers unreadable, and a cover is how a
   listener tells one book from another.
   The accent does not simply lift: on paper the society's ink is deep green, and at night it is an
   apricot orange. The --green tokens keep their name because every rule on the sheet reaches for
   them by it, and what they mean is "the accent", whichever colour that is on this ground. Because
   the accent is now warm, the "down" red is pulled toward pink so a fall and a climb on the movers
   page stay two colours rather than two oranges. Every pair below was measured: the quiet ink tier
   clears 6:1 on the field, the gold 9.7:1, the orange 9.8:1, the down 8.3:1. */
@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;
    --paper:      #16140F;
    --paper-2:    #1F1C15;
    --paper-3:    #2A261E;
    --ink:        #EFE9DD;
    --ink-2:      #CBC3B3;
    --ink-3:      #9C9384;
    --green:      #F5AE74;
    --green-2:    #EA9A62;
    --gold:       #DDB94D;
    --gold-lift:  #EACB63;
    --gold-trace: #EACB63;
    --down:       #F0959C;
    --rule:        rgba(239, 233, 221, .72);
    --rule-mid:    rgba(239, 233, 221, .26);
    --rule-faint:  rgba(239, 233, 221, .12);
    --field:       #0E0D09;
    --tint-gold:   rgba(221, 185, 77, .12);
    --tint-green:  rgba(245, 174, 116, .09);
    --tint-down:   rgba(240, 149, 156, .10);
    --shadow-ink:  0, 0, 0;
    --tag-quote:     #B9A3E0;
    --tag-favourite: #E9A56E;
    --tag-character: #8ED0B6;
    --tag-plot:      #9CC0E6;
    --tag-lookup:    #DDB94D;
    --tag-confusing: #F0959C;
  }
  /* On paper the hovered button is gold with ink on it; as a negative the "ink" is cream, which is
     not a colour to set on gold. The field colour is the one that reads on both golds. */
  .buy-link:hover { color: var(--paper); }
  /* The guilloche is cut in cream. On paper that is a whisper; on the night field it is a lamp, so it
     is turned down until it reads as engraving again rather than as a stain on the plate. */
  .rosette { opacity: .3; }
}

/* The wordmark's own face: Illiterati Display, a renamed cut of Libre Bodoni under the OFL, whose
   licence ships beside it. Only the SemiBold is loaded — it is the weight the logo is drawn at and
   the static file is 15KB lighter than the variable one, which would buy an axis nothing here uses.
   The URL is stamped in at build time so the file can be cached forever under a content hash. */
@font-face {
  font-family: "Illiterati Display";
  src: url("illiterati-display.53ce7f10.woff2") format("woff2");
  font-style: normal;
  font-weight: 600;
  font-display: swap;
}

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

html { -webkit-text-size-adjust: 100%; scrollbar-color: var(--green) var(--paper-3); }
body {
  background: var(--paper);
  color: var(--ink);
  font: 400 var(--step-0)/1.6 var(--sans);
  -webkit-font-smoothing: antialiased;
}

::selection { background: var(--green); color: var(--paper); }
:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; }
::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-track { background: var(--paper-3); }
::-webkit-scrollbar-thumb { background: var(--green); border: 3px solid var(--paper-3); border-radius: 8px; }

a { color: inherit; }
img { max-width: 100%; }
input, button, select { font: inherit; color: inherit; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}
.skip {
  position: absolute; left: -9999px; top: 0; z-index: 99;
  background: var(--green); color: var(--paper); padding: 12px 18px;
  font: 700 var(--step--1)/1 var(--sans); letter-spacing: .12em; text-transform: uppercase;
}
.skip:focus { left: 12px; top: 12px; }

/* ── the sheet ─────────────────────────────────────────────────────────────
   One printed page. The engraved ground lives in the masthead only; below the
   first rule the paper stays clean so the covers carry the page.            */

.sheet { max-width: var(--page); margin: 0 auto; padding: clamp(10px, 1.6vw, 20px); }
/* `clip` rather than `hidden`, and both are declared. They clip identically — the rosettes and the
   security ground still stop at the plate's edge — but `hidden` makes a scroll container, and a
   scroll container anywhere above a `position: sticky` element stops it sticking. `clip` is not one.
   The old value stays above it so a browser that does not understand `clip` keeps the clipping and
   simply does not get the sticky selector on /movers/, which is an enhancement rather than the
   feature. */
.plate { position: relative; overflow: hidden; overflow: clip; padding: 0 var(--pad) clamp(24px, 3vw, 36px); }
.plate > * { position: relative; z-index: 2; }
/* One above the rest of the plate, so the phone's open menu hangs over the page under it rather
   than being painted under the search box. The masthead scrolls away with the page, so nothing
   below it is ever covered by anything but that open panel. */
.plate > .masthead { z-index: 3; }

.ground { position: absolute; inset: 0 0 auto; height: 210px; overflow: hidden; pointer-events: none; color: var(--green); z-index: 0;
  -webkit-mask-image: linear-gradient(#000 62%, transparent); mask-image: linear-gradient(#000 62%, transparent); }
.ground__mark { position: absolute; display: block; line-height: 0; }
.rosette { position: absolute; pointer-events: none; z-index: 1; color: var(--green); opacity: .5; }
.rosette--tl { top: -150px; left: -140px; }
@media (max-width: 760px) { .rosette { display: none; } }

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

/* The bar every page wears. What changes is under the rule, never above it. */
.masthead { border-bottom: 2px solid var(--rule); padding-bottom: clamp(10px, 1.4vw, 16px); }
.head { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; padding-top: 12px; }
.head__nav { margin-left: auto; display: flex; gap: clamp(12px, 2vw, 24px); flex-wrap: wrap; }
.head__nav a {
  font: 600 var(--step--2)/1 var(--sans); letter-spacing: .15em; text-transform: uppercase;
  text-decoration: none; color: var(--ink-2); padding-bottom: 3px; border-bottom: 2px solid transparent;
}
.head__nav a:hover { color: var(--ink); }
.head__nav a[aria-current] { color: var(--green); border-bottom-color: var(--green); }
/* The phone's half of the bar. Not drawn at all on a desk — see the comment in lib/html.mjs on why
   the links are in the page twice — and on a phone it replaces the row of links entirely. */
.head__search, .head__menu { display: none; }
.head__search, .head__menu > summary {
  font: 600 var(--step--2)/1 var(--sans); letter-spacing: .15em; text-transform: uppercase;
  text-decoration: none; color: var(--ink-2); cursor: pointer; line-height: 1.4; padding: 14px 10px; margin: -14px 0;
}
.head__search[aria-current] { color: var(--green); }
.head__menu { position: relative; }
.head__menu > summary { list-style: none; display: flex; align-items: center; gap: 6px; }
.head__menu > summary::-webkit-details-marker { display: none; }
/* A small chevron drawn in border rather than a glyph, so it comes from no font and turns when open. */
.head__menu > summary::after {
  content: ""; width: 6px; height: 6px; border: solid currentColor; border-width: 0 1.5px 1.5px 0;
  transform: translateY(-2px) rotate(45deg); transition: transform .15s ease;
}
.head__menu[open] > summary { color: var(--green); }
.head__menu[open] > summary::after { transform: translateY(1px) rotate(-135deg); }
/* The panel hangs off the bar rather than pushing the wordmark down: opening the menu should not
   move the thing the reader is looking at. Each link is a full row, tall enough for a thumb. */
.head__menu > nav {
  position: absolute; right: 0; top: calc(100% + 10px); z-index: 8; min-width: 220px;
  display: flex; flex-direction: column; padding: 6px 0;
  background: var(--paper); border: 2px solid var(--rule);
  box-shadow: 0 12px 30px -10px rgba(var(--shadow-ink), .5);
}
.head__menu > nav a {
  padding: 12px 18px; text-decoration: none; color: var(--ink);
  font: 600 var(--step--1)/1.3 var(--sans); letter-spacing: .12em; text-transform: uppercase;
  border-bottom: 1px solid var(--rule-faint);
}
.head__menu > nav a:last-child { border-bottom: 0; }
.head__menu > nav a:hover { background: var(--green); color: var(--paper); }
.head__menu > nav a[aria-current] { color: var(--green); }
.head__menu > nav a[aria-current]:hover { color: var(--paper); }

/* Name at the left margin, society line at the right, and nothing between them: the two ends of the
   row are what hold the width open. The seal that once sat dead centre is the app's mark and lives
   on as the favicon and the touch icon; the masthead is set in type alone. */
.crest {
  display: grid; grid-template-columns: 1fr auto; align-items: center;
  gap: clamp(14px, 3vw, 40px); margin-top: 10px;
}
/* Set the way the family's own notes prescribe: SemiBold, tight negative tracking, title case. The
   caps-and-wide-tracking setting was a stand-in for a face the brand did not have yet. */
.crest__word {
  font: 600 clamp(34px, 5.4vw, 62px)/.9 var(--wordmark);
  letter-spacing: -.035em; color: var(--green); text-decoration: none;
}
a.crest__word:hover { color: var(--green-2); }
.crest__strap {
  justify-self: end; text-align: right; text-wrap: balance;
  font: 600 var(--step--1)/1.8 var(--incise); letter-spacing: .26em; text-transform: uppercase; color: var(--gold);
  /* Tracking hangs a gap off the last letter; pull it back so the line sits flush to the margin. */
  margin-right: -.3em;
}

/* Section pages still need a real title, but a modest one. */
.declare { text-align: center; text-transform: uppercase; text-wrap: balance; }
/* The same grotesque as a book's title, for the same reason: it is read, not recognised. Uppercase
   is kept, so it needs a little air between the letters where the title-case book title needed less. */
.declare--sub {
  margin: 10px auto 6px; max-width: 26ch; color: var(--green);
  font: 700 clamp(22px, 3.2vw, 36px)/1.08 var(--sans);
  letter-spacing: .02em;
}

/* ── the denomination strip ────────────────────────────────────────────── */

.denom { display: flex; flex-wrap: wrap; border-top: 2px solid var(--rule); border-bottom: 1px solid var(--rule); }
main > .denom:first-child { border-top: 0; }
.denom a {
  flex: 1 1 118px; text-align: center; padding: 8px 8px; text-decoration: none; color: var(--ink);
  font: 600 var(--step--2)/1.3 var(--sans); letter-spacing: .12em; text-transform: uppercase;
  border-right: 1px solid var(--rule-faint);
}
.denom a:last-child { border-right: 0; }
.denom a:hover { background: var(--green); color: var(--paper); }
.denom a[aria-current] { background: var(--green); color: var(--paper); }
/* The way to the other fourteen, at the end of the eight. Set in the green of a link out. */
.denom a.denom__all { flex-grow: 0; color: var(--green); }

/* ── the slot ──────────────────────────────────────────────────────────── */

.slot { display: flex; margin-top: 12px; border: 2px solid var(--green); background: var(--field); }
/* The box you type into has to say so when it has the keyboard. The input's own outline is off
   because it would draw inside the frame; the frame draws it instead, in the ring every other focused
   thing on the site wears, set off the green border so the two do not merge into one thick line. */
.slot:focus-within { outline: 2px solid var(--green); outline-offset: 3px; }
.slot input { flex: 1; min-width: 0; background: transparent; border: 0; font: 400 var(--step-1)/1 var(--data); padding: 13px 15px; outline: none; }
.slot input::placeholder { color: var(--ink-3); }
.slot button {
  padding: 13px clamp(14px, 2.4vw, 24px); background: var(--green); color: var(--paper); border: 0; cursor: pointer;
  font: 700 var(--step--2)/1 var(--sans); letter-spacing: .16em; text-transform: uppercase;
}
.slot button:hover { background: var(--green-2); }

/* ── section rules ─────────────────────────────────────────────────────── */

/* A section head is a heading, an optional link out, and an optional note about the shelf. The note
   used to be pushed to the right of the heading on the same line, where it had whatever width the
   heading left it — which on a wide plate was a single cramped line of mono set hard against the
   right margin, and on a narrow one a wrap that read as an accident. It gets its own line and the
   whole column now, so a sentence can be a sentence. The link out stays on the heading's line: it
   is two words and it belongs beside the thing it opens. */
.rule-head { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: baseline; column-gap: 14px; margin-top: clamp(20px, 2.4vw, 28px); padding-bottom: 6px; border-bottom: 2px solid var(--rule); }
.rule-head h2 { grid-column: 1; font: 600 var(--step-0)/1.3 var(--incise); letter-spacing: .22em; text-transform: uppercase; color: var(--green); }
/* The days a chart was read over, in the slot beside its heading. Struck in the gold this site keeps
   for a measurement, and in the data face, because that is what it is. */
.rule-head__when {
  grid-column: 2; justify-self: end; white-space: nowrap;
  font: 400 var(--step--2)/1.3 var(--data); letter-spacing: .06em; color: var(--gold);
}
@media (max-width: 560px) { .rule-head__when { font-size: var(--step--2); letter-spacing: .02em; } }
.rule-head a { grid-column: 2; justify-self: end; font: 700 var(--step--2)/1 var(--sans); letter-spacing: .1em; text-transform: uppercase; color: var(--green); }
.rule-head p { grid-column: 1 / -1; margin-top: 5px; max-width: 88ch; font: 400 var(--step--2)/1.5 var(--data); letter-spacing: .06em; color: var(--ink-3); }

/* ── the chart ─────────────────────────────────────────────────────────────
   Two picks to a line, and the cover carries each one. Aligned narrator/length/
   rating columns were the right answer at full width; at half width they cannot
   coexist with a cover this size, and the cover is what a listener buys by. The
   three facts fold into one spec line instead, still tabular-figured so numbers
   stack down a column.                                                       */

.chart { list-style: none; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: clamp(18px, 2.4vw, 34px); }
/* The whole row is the click target. Rather than wrapping everything in one anchor — which nests
   links and reads terribly to a screen reader — the title's own link is stretched across the row.
   One link, one accessible name, the entire row clickable. */
.pick {
  position: relative;
  display: grid; grid-template-columns: 30px auto minmax(0, 1fr) auto;
  gap: 0 clamp(10px, 1.2vw, 15px); align-items: start;
  padding: 10px 0; border-bottom: 1px solid var(--rule-faint); min-width: 0;
}
/* Only a row with somewhere to go lights up. On the combined chart most rows are books this site
   has no page for, and a row that changes colour under the pointer is a row promising a page. */
.pick:has(a.entry__title):hover { background: var(--paper-2); }
.pick a.entry__title::after { content: ""; position: absolute; inset: 0; z-index: 1; }
/* The cover and the buy control sit above the stretched link so they keep their own hit areas and
   their own hover states. Both lead to the same page, so nothing is lost if a click lands either. */
.pick__art, .pick .buy-link { position: relative; z-index: 2; }

.pick__pos {
  display: flex; flex-direction: column; align-items: flex-end; gap: 3px;
  font: 900 clamp(16px, 1.7vw, 21px)/1 var(--display); color: var(--green);
  font-variant-numeric: tabular-nums; align-self: start; padding-top: 4px;
}
/* ── the price cell ────────────────────────────────────────────────────────
   On /deals/ the leading column is not a chart position but what the shelf was
   sorted on, and it is drawn at the same weight a position is drawn at: this is
   the figure the eye runs down, and the whole reason that page exists. Struck
   in the gold the site reserves for a measurement rather than the green it uses
   for a ranking — a price is a fact about a thing, not a place in an order.    */
.pick__rate {
  display: flex; flex-direction: column; align-items: flex-end; gap: 2px;
  align-self: start; padding-top: 2px; text-align: right;
}
.pick__rate b {
  font: 900 clamp(19px, 2.1vw, 26px)/1 var(--display); color: var(--gold);
  font-variant-numeric: tabular-nums; letter-spacing: -.01em;
}
.pick__rate i {
  font: 400 var(--step--2)/1.2 var(--data); font-style: normal; color: var(--ink-3);
  letter-spacing: .04em; max-width: 8ch;
}
.chart[data-unnumbered] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
/* No position cell is emitted at all on an unnumbered chart, so the row is two columns. Hiding it
   with display:none instead took it out of the grid flow and auto-placed the cover into the
   zero-width first column, which silently removed the covers from every unnumbered chart. */
.chart[data-unnumbered] .pick { grid-template-columns: auto minmax(0, 1fr) auto; }
/* A row that leads with a figure is four columns again. Both attributes are named rather than only
   [data-lead]: such a chart is also an unnumbered one, and the rule above it here would otherwise
   win on source order and drop the row back to three columns — which does not fail loudly, it
   silently wraps the last cell onto a second line under the figure. */
/* Wide enough for "0.61¢" at the figure's largest size. Each row is its own grid, so a lead column
   sized to its own figure put the covers on a two-decimal row ten pixels to the right of the rest. */
.chart[data-unnumbered][data-lead] .pick { grid-template-columns: minmax(70px, auto) auto minmax(0, 1fr) auto; }

.pick__art { display: block; line-height: 0; }
.cover {
  width: clamp(72px, 7vw, 96px); height: clamp(72px, 7vw, 96px);
  object-fit: cover; display: block;
  border: 1px solid var(--rule-mid); background: var(--paper-3);
  box-shadow: 0 1px 2px rgba(var(--shadow-ink),.16), 0 8px 18px -8px rgba(var(--shadow-ink),.48);
  transition: box-shadow .15s ease, transform .15s ease;
}
.pick__art:hover .cover { transform: translateY(-2px); box-shadow: 0 2px 4px rgba(var(--shadow-ink),.18), 0 14px 26px -10px rgba(var(--shadow-ink),.55); }
.cover--blank { display: grid; place-items: center; color: var(--green); opacity: .4; }
.title-head__art.cover--blank { aspect-ratio: 1; }

/* The body is as tall as the cover, and the buy control now sits beside it rather than under it:
   the vertical the button used to occupy is what pays for the spec line being legible. */
.pick__body { display: flex; flex-direction: column; justify-content: center; gap: 1px; min-width: 0; min-height: clamp(72px, 7vw, 96px); }
.pick__buy { align-self: center; }
.entry__title { font: 600 var(--step-0)/1.3 var(--sans); text-decoration: none; color: var(--ink); }
a.entry__title:hover, .pick:has(a.entry__title):hover .entry__title { color: var(--green); text-decoration: underline; text-underline-offset: 3px; }
.pick:has(.entry__title:focus-visible) { outline: 2px solid var(--green); outline-offset: 2px; }
/* 1.35, not the 1.6 it inherited from body. That is a line-height for a paragraph you read down;
   this is a single name under a title, and at 14px the inherited value opened a 22px gap between
   the two lines that belong together most. */
.entry__by { font: 400 var(--step--1)/1.35 var(--sans); color: var(--ink-2); }
/* Which book of which series. The name gives way to an ellipsis when it is long; the number never
   does, because the number is the part that answers "have I already heard this one?". */
.entry__series { display: flex; align-items: baseline; gap: .5em; min-width: 0; margin-top: 1px; font-size: var(--step--2); color: var(--green-2); }
.entry__series > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.entry__series b { flex: none; font: 700 var(--step--2)/1 var(--data); color: var(--gold); letter-spacing: .04em; font-variant-numeric: tabular-nums; }
/* Narrator, runtime and rating. Set one step up and in the darker ink: at 11px in --ink-3 this line
   sat at about 4.4:1 on the paper, which is a coin-flip for the readers most likely to need it. */
.entry__spec { margin-top: 2px; font-size: var(--step--1); line-height: 1.4; color: var(--ink-2); }
.pick .entry__spec { display: flex; flex-wrap: wrap; column-gap: .55em; }
.entry__cast { font-family: var(--sans); }
/* The separator hangs off the end of the name, never off the front of the line below it. */
.entry__cast::after { content: "\00A0·"; color: var(--ink-3); }
.entry__meas { font-family: var(--data); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Every row ends in a rating cell, rated or not, so this pip always has something to separate from
   — and like the one after the narrator it hangs off the end of its own cell, never off a wrap. */
.pick .entry__meas::after { content: "\00A0·"; color: var(--ink-3); }
/* …except on a row with no rating cell to separate from — a show from a chart that carries no score. */
.pick .entry__meas:last-child::after { content: none; }
/* The wait at the library. Set in the data face like the runtime beside it, because it is the same
   kind of thing — a measurement of the book's availability rather than a judgement of the book —
   and it separates from its neighbours on the same rule the runtime does. */
.entry__queue { font-family: var(--data); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* A no-break space, not a space: on a phone the queue cell wraps inside itself (see below), and an
   ordinary space let the pip wrap onto a line of its own under "4 libraries sampled". */
.pick .entry__queue::after { content: "\00A0\00B7"; color: var(--ink-3); }
.pick .entry__queue:last-child::after { content: none; }
/* The one state a reader can act on this minute, and the only one worth colouring. */
.entry__queue--now { color: var(--gold); }

/* The rating, with the star that says it is one. Gold rather than ink so the eye finds it without
   reading it — the same gold the mottos are struck in, at the one place on a row it means "score".
   The star is a mask rather than a glyph or an inline SVG: neither Public Sans nor Courier Prime
   carries ★, so a text star would fall back to some other face and sit off the baseline, and an
   inline SVG would ship the same path on every one of twelve thousand rows. */
.rate { display: inline-flex; align-items: center; gap: .3em; font-family: var(--data); font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--gold); }
.rate::before {
  content: ""; flex: none; width: .95em; height: .95em; background: currentColor;
  transform: translateY(-.04em);
  -webkit-mask: var(--star) center / contain no-repeat; mask: var(--star) center / contain no-repeat;
}
.rate--none { color: var(--ink-3); }
.rate--none::before { content: none; }
/* The small print beside a library wait: how many systems it was measured across. Quiet ink, and
   allowed to wrap onto its own line, because the figure is the fact and this is its footnote. */
.entry__queue { white-space: normal; }
.entry__queue i { font-style: normal; color: var(--ink-3); white-space: nowrap; }
.entry__queue i::before { content: "· "; }
/* When a forthcoming book is out, where a rating would sit. Gold, because it is a measurement; the
   count of days beside it is its footnote and sits in the quiet ink the wait's does. */
.entry__date { font-family: var(--data); font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--gold); }
.entry__date i { font-style: normal; color: var(--ink-3); }
.entry__date i::before { content: " · "; }
.entry__date--tba { color: var(--ink-3); }

/* The rest of a folded chart. A control in the middle of a chart rather than a shorter chart: the
   rows behind it are on the page, numbered from where the first list stopped, and open in place. */
.chart__more > summary {
  cursor: pointer; list-style: none; display: flex; align-items: baseline; gap: 10px;
  padding: 13px 0; border-bottom: 1px solid var(--rule-faint);
  font: 700 var(--step--2)/1 var(--sans); letter-spacing: .13em; text-transform: uppercase; color: var(--green);
}
.chart__more > summary::-webkit-details-marker { display: none; }
.chart__more > summary::before { content: "+"; font: 400 var(--step-1)/0 var(--data); }
.chart__more[open] > summary::before { content: "\2212"; }
.chart__more > summary:hover { background: var(--paper-2); }
.chart__more > summary i { font: 400 var(--step--2)/1 var(--data); font-style: normal; letter-spacing: .04em; color: var(--ink-3); }

/* Why a recommendation is here. A row that cannot say why is indistinguishable from an ad. */
.entry__why { margin-top: 2px; font: 400 var(--step--2)/1.3 var(--data); color: var(--gold); letter-spacing: .02em; }

/* Movement, only ever rendered when a real prior standing exists. */
.move { font: 700 var(--step--2)/1 var(--data); letter-spacing: .02em; white-space: nowrap; }
.move--up { color: var(--green-2); }
.move--down { color: var(--down); }
.move--same { color: var(--ink-3); }
.move--new { color: var(--gold); text-transform: uppercase; letter-spacing: .06em; }

.buy-link {
  display: inline-block; text-decoration: none; white-space: nowrap;
  font: 700 var(--step--2)/1 var(--sans); letter-spacing: .13em; text-transform: uppercase;
  background: var(--green); color: var(--paper); padding: 8px 11px; border: 1px solid var(--green);
}
.buy-link:hover { background: var(--gold-lift); border-color: var(--gold-lift); color: var(--ink); }
.buy-link--quiet { background: transparent; color: var(--green); }

/* ── advertising ────────────────────────────────────────────────────────────
   A unit sits between sections in a slot of its own, labelled as the network requires and as
   honesty does, and is hidden until consent.js has read a yes — so a page nobody has answered on
   carries no empty box. Never inside a chart. */
.ad { margin-top: clamp(20px, 2.4vw, 28px); padding-top: 8px; border-top: 1px solid var(--rule-mid); }
.ad__label { display: block; margin-bottom: 6px; font: 400 var(--step--2)/1 var(--data); letter-spacing: .12em; text-transform: uppercase; color: var(--ink-3); }
.ad .adsbygoogle { min-height: 90px; }

/* The consent banner. Fixed to the foot of the window, in the paper and rules of the site rather
   than a black bar borrowed from elsewhere; it asks once, and the footer link asks again. */
.consent {
  position: fixed; inset: auto 0 0 0; z-index: 20;
  padding: 14px clamp(16px, 4vw, 40px); background: var(--paper); border-top: 3px solid var(--gold);
  box-shadow: 0 -8px 30px -12px rgba(var(--shadow-ink),.45);
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 12px 24px; align-items: center;
}
.consent p { margin: 0; max-width: 78ch; font: 400 var(--step--1)/1.5 var(--sans); color: var(--ink); }
.consent p b { color: var(--green); }
.consent a { color: var(--green); }
.consent__acts { display: flex; gap: 8px; flex-wrap: wrap; }
.consent__acts .buy-link { cursor: pointer; }
/* `display: grid` above would beat the browser's own `[hidden] { display: none }`, and a banner that
   cannot be dismissed is the one thing worse than a banner. */
.consent[hidden] { display: none; }
@media (max-width: 720px) { .consent { grid-template-columns: minmax(0, 1fr); } }

/* Two charts to a row leaves no width for narrator and measures on one line. Break in the same
   place on every row rather than wherever each narrator's name happens to run out, so the runtimes
   still read down a column — a chart that wraps differently on each line is not a chart. */
@media (min-width: 1081px) {
  .entry__cast { flex: 1 0 100%; }
  .entry__cast::after { content: none; }
}

/* One column before the two-up gets cramped, and the buy button drops below the text. */
@media (max-width: 1080px) {
  .chart, .chart[data-unnumbered] { grid-template-columns: minmax(0, 1fr); }
}
/* A search row for a book with pages. No page of its own to go to, so the title is not a link and the
   row does not light up; the two text links at the end are the editions row's, drawn the same way. */
.print-results { margin-top: clamp(22px, 3vw, 34px); }
.pick__editions { align-self: center; font: 400 var(--step--1)/1.4 var(--sans); white-space: nowrap; color: var(--ink-3); }
.pick__editions a { color: var(--ink); }

/* Beside a synthetic reader. The app's tag chip at two-thirds size, in the quiet ink: it should be
   seen without being read, and never look like a feature of the book. */
.ai-tag { display: inline-block; margin-left: .5em; font: 700 .66em/1 var(--sans); letter-spacing: .1em; text-transform: uppercase; padding: 2px 5px; border: 1px solid var(--ink-3); color: var(--ink-3); vertical-align: 1px; }

/* Too narrow for a fourth column: the buy control drops under the text it belongs to. */
@media (max-width: 560px) {
  .pick { grid-template-columns: 26px auto minmax(0, 1fr); }
  .chart[data-unnumbered] .pick { grid-template-columns: auto minmax(0, 1fr); }
  .pick__body { min-height: 0; gap: 3px; }
  .pick__buy, .pick__editions { grid-column: 3; justify-self: start; align-self: start; margin-top: 7px; }
  .chart[data-unnumbered] .pick__buy, .chart[data-unnumbered] .pick__editions { grid-column: 2; }
  /* A priced row keeps its figure at every width — it is the column the page is read down — so it
     is the buy control that drops, one column further right than on an unpriced row. */
  .chart[data-unnumbered][data-lead] .pick { grid-template-columns: auto auto minmax(0, 1fr); }
  .chart[data-unnumbered][data-lead] .pick__buy,
  .chart[data-unnumbered][data-lead] .pick__stat { grid-column: 3; }
}

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

/* Ruled, not filled. The lines between cells used to be the grid's own background showing through a
   1px gap, which also filled whatever the last row left empty with a slab of that grey — twenty-two
   categories in five columns leaves three cells of it. Each cell now draws its own hairline halo
   into the gap instead, and the empty end of a row is paper. */
.index-grid { display: grid; gap: 1px; padding: 1px; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); margin-top: 14px; }
.index-grid a { background: var(--paper); padding: 12px 14px; text-decoration: none; display: block; box-shadow: 0 0 0 1px var(--rule-mid); }
.index-grid a:hover { background: var(--green); color: var(--paper); }
.index-grid a:hover span { color: var(--paper); }
.index-grid b { display: block; font: 600 var(--step-0)/1.25 var(--sans); }
.index-grid span { display: block; margin-top: 3px; font: 400 var(--step--2)/1 var(--data); color: var(--gold); letter-spacing: .06em; }

.chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 14px; }
.chips a { text-decoration: none; border: 1px solid var(--rule-mid); padding: 7px 10px; font: 400 var(--step--2)/1 var(--data); letter-spacing: .04em; }
.chips a:hover { background: var(--green); color: var(--paper); border-color: var(--green); }
.chips--switch a[aria-current] { background: var(--green); color: var(--paper); border-color: var(--green); }

/* ── a single title ────────────────────────────────────────────────────── */

.title-head { display: grid; grid-template-columns: minmax(0, 250px) minmax(0, 1fr); gap: clamp(18px, 3vw, 38px); margin-top: clamp(16px, 2.4vw, 26px); align-items: start; }
/* Cover, buy row and DRM note as one column. The buy row was full-width under the facts, which made
   two large boxes of the two storefronts and left the paper under the cover empty; at rail width it
   stacks into two quiet lines and fills the space it used to leave. */
.title-head__rail { display: flex; flex-direction: column; gap: 12px; }
.title-head__rail .buy { margin-top: 0; grid-template-columns: minmax(0, 1fr); gap: 6px; }
.title-head__rail .drm { margin-top: 2px; }
/* Where to subscribe. Chips rather than a second column of buttons: four players and a feed under
   the one link that matters is furniture, and furniture is set small. The feed URL is printed in
   the data face and allowed to break anywhere, because a feed URL is a thing to be copied. */
.subscribe { margin-top: 4px; }
.subscribe__head { font: 700 var(--step--2)/1.3 var(--sans); letter-spacing: .09em; text-transform: uppercase; color: var(--ink-2); }
.subscribe__players { margin-top: 8px; }
.subscribe__feed { margin-top: 10px; font: 400 var(--step--2)/1.5 var(--data); color: var(--ink-3); }
.subscribe__feed a { color: var(--green); font-weight: 700; }
.subscribe__feed code { display: block; margin-top: 3px; overflow-wrap: anywhere; font: inherit; color: var(--ink-3); }
/* Audible's art is square and that is how it is shown — the cover is the thing, and nothing is
   cropped or padded to make it a different shape. A hairline and a little shadow lift it off the
   paper; the <img> carries width and height attributes, so height must be released for the
   aspect-ratio to hold. */
.title-head__art { width: 100%; height: auto; aspect-ratio: 1; object-fit: cover; border: 1px solid var(--rule-mid); display: block; box-shadow: 0 1px 2px rgba(var(--shadow-ink),.12), 0 8px 18px -10px rgba(var(--shadow-ink),.42); }
/* The grotesque, not the didone. A book or show title is the one heading on the site that is read
   as words rather than recognised as a shape, and at this size on the dark ground the didone's
   hairlines fall out — "Crime Junkie" was a row of thick strokes with the thin ones missing. The
   didone keeps the section headlines (uppercase, where it holds) and the chart positions. */
.title-head h1 { font: 700 clamp(26px, 4vw, 42px)/1.06 var(--sans); letter-spacing: -.022em; text-wrap: balance; color: var(--green); }
.title-head .sub { margin-top: 8px; font-size: var(--step-1); color: var(--ink-2); }
.title-head .by { margin-top: 10px; font: 600 var(--step-1)/1 var(--incise); letter-spacing: .08em; color: var(--gold); }
.title-head .by a { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 4px; }
.title-head .by a:hover { color: var(--green); }
.facts { display: flex; flex-wrap: wrap; margin-top: 16px; border: 1px solid var(--rule-mid); width: fit-content; max-width: 100%; }
.facts div { padding: 8px 13px; border-right: 1px solid var(--rule-faint); }
.facts div:last-child { border-right: 0; }
.facts dt { font: 400 var(--step--2)/1 var(--data); letter-spacing: .1em; text-transform: uppercase; color: var(--gold); }
.facts dd { margin-top: 4px; font: 600 var(--step-0)/1 var(--sans); font-variant-numeric: tabular-nums; }
/* A fact that can be checked somewhere else — the other shop's price — is a link, drawn as the
   prose links are so it reads as one and not as a fifth button. */
.facts dd a { color: var(--green); text-decoration: underline; text-underline-offset: 3px; }
.facts dd a:hover { color: var(--gold); }

.buy { display: grid; gap: 8px; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); margin-top: 18px; }
.buy__link { display: grid; gap: 3px; padding: 12px 14px; text-decoration: none; border: 1px solid var(--rule-mid); background: var(--field); }
/* Compact: the label alone. The note each storefront carries is worth a sentence on the store page
   and is furniture beside a cover. */
.buy--compact .buy__link { padding: 9px 12px; }
.buy--compact .buy__label { font-size: var(--step--1); letter-spacing: .04em; }
.buy__link:hover { border-color: var(--green); }
.buy__link--primary { background: var(--green); color: var(--paper); border-color: var(--green); }
.buy__link--primary:hover { background: var(--green-2); }
.buy__link--drm { border-color: var(--gold); border-width: 2px; }
.buy__label { font: 700 var(--step-0)/1.2 var(--sans); display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.buy__note { font-size: var(--step--1); color: var(--ink-2); }
.buy__link--primary .buy__note { color: var(--paper); opacity: .86; }
.tag-drm { font: 700 var(--step--2)/1 var(--sans); letter-spacing: .09em; text-transform: uppercase; border: 1px solid var(--gold); color: var(--gold); padding: 3px 6px; white-space: nowrap; }
.buy__link--primary .tag-drm { border-color: var(--paper); color: var(--paper); }
.tag-drm--solid { background: var(--gold); color: var(--paper); border-color: var(--gold); }

.drm { margin-top: 16px; padding: 14px 16px; border: 2px solid var(--gold); background: var(--tint-gold); }
.drm__head { font: 600 var(--step-1)/1.4 var(--sans); display: flex; gap: 9px; align-items: baseline; flex-wrap: wrap; }
.drm__body { margin-top: 6px; font-size: var(--step--1); color: var(--ink-2); }
.drm--compact { padding: 9px 11px; border-width: 1px; }
.drm--compact .drm__head { font-size: var(--step--1); gap: 7px; }
.drm--compact .drm__body { margin-top: 4px; font-size: var(--step--2); line-height: 1.5; }
.drm a { color: var(--green); font-weight: 700; }

/* The same book with its pages on. Quieter than everything above it and deliberately so: this is
   the row for a reader who has decided not to listen, and it must never compete with the three that
   sell what the site is for. A rule above it does the separating; nothing here is a button.       */
.editions { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--rule-faint); }
.editions__head { font: 700 var(--step--2)/1.3 var(--sans); letter-spacing: .1em; text-transform: uppercase; color: var(--ink-3); }
.editions__body { margin-top: 5px; font-size: var(--step--2); line-height: 1.5; color: var(--ink-2); }
.editions__links { margin-top: 7px; font-size: var(--step--1); color: var(--ink-3); }
.editions__links a { color: var(--green); font-weight: 600; text-decoration: underline; text-underline-offset: 2px; }
.editions__links a:hover { color: var(--gold); }

/* The price, and the two things that have to travel with it: whose price it is and when it was
   read. Set in the data face under the facts strip, where a number belongs.                       */
.facts__note { margin-top: 8px; font: 400 var(--step--2)/1.6 var(--data); color: var(--ink-3); max-width: 62ch; }
.facts__note a { color: var(--green); }

/* The membership offer. It is the highest-paying link on the page and the third most important
   thing in the rail, and it is drawn to say so: a dashed note with a text link, not a third filled
   button arguing with the two storefronts above it. */
.member { padding: 9px 11px; border: 1px dashed var(--rule-mid); }
.member__body { font-size: var(--step--2); line-height: 1.5; color: var(--ink-2); }
.member__link { display: inline-block; margin-top: 6px; font: 600 var(--step--1)/1.3 var(--sans); color: var(--green); text-decoration: underline; text-underline-offset: 2px; }
.member__link:hover { color: var(--green-2); }

/* ── the run of a series ───────────────────────────────────────────────────
   A contents page, not a chart: twenty-two rows of cover art would bury the book
   the reader is actually on. Numbers in the display face so the order reads down
   the column, and the instalment you are on is the only one not a link.        */

.run { list-style: none; margin-top: 14px; display: grid; gap: 1px 26px; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
.run__item { display: grid; grid-template-columns: 2.6rem minmax(0, 1fr); align-items: baseline; gap: 10px; border-bottom: 1px solid var(--rule-faint); }
.run__no { font: 900 var(--step--1)/1 var(--display); color: var(--green); text-align: right; font-variant-numeric: tabular-nums; }
.run__name { display: flex; align-items: baseline; gap: 9px; min-width: 0; padding: 7px 0; text-decoration: none; color: var(--ink); font-size: var(--step--1); }
a.run__name:hover .run__title { color: var(--green); text-decoration: underline; text-underline-offset: 3px; }
.run__title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.run__rate { flex: none; font: 400 var(--step--2)/1 var(--data); color: var(--gold); font-variant-numeric: tabular-nums; }
.run__rate::before {
  content: ""; display: inline-block; width: .9em; height: .9em; margin-right: .25em;
  vertical-align: -.06em; background: currentColor;
  -webkit-mask: var(--star) center / contain no-repeat; mask: var(--star) center / contain no-repeat;
}
/* Where you are. The only row without a link, and the only one that says so in words. */
.run__item.is-here { border-bottom-color: var(--green); }
.run__item.is-here .run__name { font-weight: 700; color: var(--green); }
.run__item.is-here .run__name::after { content: "You are here"; flex: none; font: 400 var(--step--2)/1 var(--data); letter-spacing: .1em; text-transform: uppercase; color: var(--ink-3); }
.run__item.is-here .run__no { color: var(--gold); }

/* ── the description ───────────────────────────────────────────────────────
   A checkbox and its label, no script. The whole text is in the page either way —
   folded is a viewing state, never a shorter document.                        */

.blurb { max-width: 66ch; margin-top: clamp(18px, 2.4vw, 26px); }
.blurb h2 { font: 600 var(--step-0)/1.3 var(--incise); letter-spacing: .22em; text-transform: uppercase; color: var(--green); margin-bottom: 10px; padding-bottom: 7px; border-bottom: 2px solid var(--rule); }
.blurb__body > p { margin-bottom: 12px; }
.blurb__body > p:last-child { margin-bottom: 0; }
.blurb--folds .blurb__body {
  max-height: 11.4em; overflow: hidden;
  -webkit-mask-image: linear-gradient(#000 62%, transparent); mask-image: linear-gradient(#000 62%, transparent);
}
.blurb__toggle:checked ~ .blurb__body { max-height: none; -webkit-mask-image: none; mask-image: none; }
.blurb__more {
  display: inline-block; margin-top: 10px; cursor: pointer; user-select: none;
  font: 700 var(--step--2)/1 var(--sans); letter-spacing: .13em; text-transform: uppercase;
  color: var(--green); border-bottom: 2px solid var(--gold-lift); padding-bottom: 3px;
}
.blurb__more:hover { color: var(--gold); }
.blurb__toggle:focus-visible ~ .blurb__more { outline: 2px solid var(--green); outline-offset: 3px; }
.blurb__shut, .blurb__toggle:checked ~ .blurb__more .blurb__open { display: none; }
.blurb__toggle:checked ~ .blurb__more .blurb__shut { display: inline; }

.prose { max-width: 68ch; margin-top: clamp(20px, 3vw, 30px); }
/* Under a centred title the prose runs the full width of the plate, ranged left, so its edges are the
   edges of everything else on the page: the rule under each h2 spans the plate as the rule-heads on
   a chart page do, and the paragraphs start where the footer's first column starts. A 68ch column
   was tried twice — at the left margin it read as a page missing its right-hand half, centred it read
   as a block that had slipped — and both had the same cause, an edge that agreed with nothing. */
.declare--sub + .prose, .declare--sub + .disclosure, .declare--sub + .disclosure + .prose { max-width: none; margin-left: 0; margin-right: 0; }
.prose--lede p:last-child { margin-bottom: 0; }

/* The shelf opening. It sits between a centred heading and a centred line, so the block is centred
   while the text stays ranged left: centred prose is read a line at a time with a hunt for the
   start of each one, and this is meant to be read. Tighter to the heading than .prose alone, because
   it belongs to it. */
.shelf-note { margin-inline: auto; margin-top: clamp(13px, 1.8vw, 18px); }
.shelf-note p { margin-bottom: 0; color: var(--ink-2); }

/* Podcasts.
   A show row is a book row with the two book-shaped cells removed and one added, so it inherits
   .pick wholesale and only the new line is described here. That line is whether the show is still
   being made — the one fact on these charts no storefront prints — so it gets its own row under the
   spec rather than a fourth cell inside it, where at two-up it would have wrapped alone and read as
   a stray fragment. */
.entry__aired { display: block; margin-top: 3px; font: 400 var(--step--2)/1.4 var(--data); letter-spacing: .02em; color: var(--ink-3); }
/* A live show and a finished one are the same field and opposite claims, so they are not the same
   colour. Green is the site's own affirmative; the default grey is left to mean "this stopped". */
.entry__aired--live { color: var(--green-2); }
.entry__aired--live::before { content: "● "; font-size: .7em; vertical-align: .12em; color: var(--green-2); }

.aired { margin-top: 12px; font: 400 var(--step--1)/1.4 var(--data); color: var(--ink-3); }
.aired--live { color: var(--green-2); }

/* The five newest episodes on a show page: a title and, at the right, the day and the length in the
   data face. One rule between rows and none under the last, so the list ends where it ends. */
.episodes { list-style: none; margin-top: 10px; }
.episode { display: flex; justify-content: space-between; align-items: baseline; gap: 18px; padding: 9px 0; border-bottom: 1px solid var(--rule-faint); }
.episode:last-child { border-bottom: 0; }
.episode__title { font: 600 var(--step-0)/1.35 var(--sans); color: var(--ink); text-decoration: none; }
a.episode__title:hover { color: var(--green); }
.episode__spec { flex: none; font: 400 var(--step--1)/1.4 var(--data); letter-spacing: .02em; color: var(--ink-3); white-space: nowrap; }
@media (max-width: 640px) {
  .episode { flex-direction: column; gap: 3px; }
}

/* Where a show stands on this site's own charts. Set apart from the facts table above it because it
   is the one line on the page that is this site's observation rather than Audible's record. */
.standing { margin-top: 14px; padding-left: 12px; border-left: 3px solid var(--gold);
  font: 600 var(--step--1)/1.45 var(--sans); color: var(--green); }

/* The link from a podcast shelf to the audiobook shelf of the same name. One line, given a rule of
   its own, because the whole point of it is that it is not part of the chart above it. */
.crossref { margin-top: clamp(22px, 3vw, 32px); padding: 13px 16px; border: 1px solid var(--rule-mid); background: var(--paper-2); }
.crossref p { margin: 0; font: 600 var(--step-0)/1.3 var(--sans); }
.crossref a { color: var(--green); text-decoration: none; }
.crossref a:hover { color: var(--green-2); text-decoration: underline; }
.prose h2 { font: 600 var(--step-0)/1.3 var(--incise); letter-spacing: .22em; text-transform: uppercase; color: var(--green); margin-bottom: 10px; padding-bottom: 7px; border-bottom: 2px solid var(--rule); }
.prose h3 { font: 600 var(--step-1)/1.3 var(--sans); margin: 20px 0 6px; }
.prose p { margin-bottom: 13px; }
.prose ul { margin: 0 0 13px 20px; }
.prose li { margin-bottom: 6px; }
.prose a { color: var(--green); }
.prose code { font: 400 .9em var(--data); background: var(--paper-3); padding: 1px 5px; }

main > :first-child { margin-top: clamp(14px, 2vw, 22px); }
.crumbs { font: 400 var(--step--2)/1.6 var(--data); letter-spacing: .06em; color: var(--ink-3); margin-top: 14px; }
/* Under a centred page title the crumb line is centred with it. Left against the margin it was the
   one ranged-left line in a header where the title and the lede were both set in the middle, and it
   read as a stray. On a book page the title is ranged left and the crumb stays with it. */
.crumbs:has(+ .declare) { text-align: center; }
.crumbs a { color: var(--green); text-decoration: none; }
.crumbs a:hover { text-decoration: underline; }

.disclosure { margin-top: 14px; font-size: var(--step--1); color: var(--ink-3); max-width: 66ch; padding-left: 12px; border-left: 1px solid var(--rule-mid); }
/* The line under a section title. A rule down its left edge marks an aside; this is not an aside,
   it is the heading finishing its sentence, so it sits centred under it with nothing drawn on it. */
.disclosure--lede { margin: 8px auto 0; padding-left: 0; border-left: 0; text-align: center; text-wrap: balance; }

.search-note { margin-top: 14px; font: 400 var(--step--1)/1.5 var(--data); color: var(--ink-2); }
.results:empty { display: none; }

/* ── device page ───────────────────────────────────────────────────────── */

.device { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 290px); gap: clamp(20px, 4vw, 48px); align-items: center; margin-top: clamp(18px, 2.6vw, 28px); }
.device__shot { width: 100%; border: 1px solid var(--rule-mid); display: block; box-shadow: 0 2px 4px rgba(var(--shadow-ink),.14), 0 20px 40px -20px rgba(var(--shadow-ink),.5); }
.feature { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 250px); gap: clamp(18px, 3vw, 40px); align-items: center; padding: clamp(20px, 3vw, 30px) 0; border-bottom: 1px solid var(--rule-faint); }
.feature:nth-child(even) > .feature__art { order: -1; }
.feature h3 { font: 900 clamp(20px, 2.6vw, 30px)/1.1 var(--display); letter-spacing: -.01em; color: var(--green); }
.feature p { margin-top: 10px; color: var(--ink-2); max-width: 56ch; }
.feature ul { margin: 12px 0 0 18px; color: var(--ink-2); font-size: var(--step--1); }
.placeholder { display: grid; place-items: center; aspect-ratio: 390/844; border: 1px dashed var(--rule-mid); color: var(--ink-3); font: 400 var(--step--1)/1.4 var(--data); text-align: center; padding: 20px; box-shadow: none; }
@media (max-width: 760px) { .device, .feature { grid-template-columns: 1fr; } .feature:nth-child(even) > .feature__art { order: 0; } }

/* ── foot ──────────────────────────────────────────────────────────────── */

/* Inset by exactly what the plate is inset by — the sheet's padding plus the plate's — so the rule
   over the columns starts and ends where the masthead's rule does, and the first column sits on the
   same left edge as the page title above it. The rule is on the columns rather than on the footer
   box, because a border on the box would run under the padding and come out wider than the page. */
.foot { max-width: var(--page); margin: 0 auto; padding: 0 calc(clamp(10px, 1.6vw, 20px) + var(--pad)) clamp(40px, 5vw, 58px); }
.foot__cols { display: grid; gap: clamp(18px, 3vw, 34px); grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); padding-top: clamp(20px, 3vw, 32px); border-top: 2px solid var(--rule); }
.foot h2 { font: 600 var(--step--2)/1 var(--incise); letter-spacing: .22em; text-transform: uppercase; color: var(--gold); margin-bottom: 9px; }
.foot a { display: block; text-decoration: none; font-size: var(--step--1); color: var(--ink-2); padding: 3px 0; }
.foot a:hover { color: var(--green); }
/* The rule runs the full width of the columns above it; only the text is held to a measure. */
.foot__fine { margin-top: clamp(18px, 3vw, 28px); padding-top: 14px; border-top: 1px solid var(--rule-faint); font-size: var(--step--2); color: var(--ink-3); display: grid; gap: 6px; }
.foot__fine p { max-width: 78ch; }
.foot__fine a { display: inline; color: var(--green); }

/* Under a thumb, or on a phone, the small controls get taller without getting louder: the type
   stays the size it is and the padding grows, so a link in the bar is 26px tall rather than 17 and
   a buy control 36 rather than 30. Keyed on the pointer as well as the width, because a touch laptop
   is wide and still tapped. */
@media (max-width: 640px), (pointer: coarse) {
  .head__nav { gap: 2px clamp(14px, 3vw, 22px); }
  .head__nav a { padding: 8px 0 6px; }
  .buy-link { padding: 11px 12px; }
  .denom a { padding: 12px 8px; }
  .chips a, .chips--facet span, .share__link { padding: 10px 12px; }
  .foot a { padding: 6px 0; }
  .save-btn { padding: 11px 13px; }
  .pick__stat a.pick__out { padding: 8px 0 6px; }
}

@media (max-width: 640px) {
  /* One row: the wordmark at the left, Search and the menu at the right, and the society line under
     them. The bar used to wrap its eight links into four rows above a centred wordmark, and the
     first book on the front plate began two thirds of the way down the screen. */
  .masthead { display: flex; flex-wrap: wrap; align-items: center; column-gap: 10px; padding-top: 10px; }
  .crest { display: contents; }
  .crest__word { order: 1; flex: 1 1 auto; min-width: 0; margin-top: 0; font-size: 34px; }
  .head { order: 2; flex: 0 0 auto; padding-top: 0; gap: 0 4px; margin-right: -10px; }
  .head__nav { display: none; }
  .head__search, .head__menu { display: block; }
  .crest__strap {
    order: 3; flex: 1 0 100%; justify-self: start; text-align: left; margin: 6px 0 0;
    font-size: var(--step--2); letter-spacing: .2em;
  }
  /* One row that scrolls, rather than four that do not. The strip is a thing to flick through on a
     phone; the last cell runs off the edge, which is what says there is more. */
  .denom { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
  .denom::-webkit-scrollbar { display: none; }
  .denom a { flex: 0 0 auto; padding: 13px 14px; }
  /* Stacked, the rail stops being a rail. Its parts become items of the head itself so the page
     reads in the order a phone should give it: what the book is, then where to buy it — rather than
     two storefronts and a DRM notice before the title has been said. */
  .title-head { display: flex; flex-direction: column; gap: 16px; }
  .title-head__rail { display: contents; }
  /* Anything in the rail not placed by name below goes after everything that is. The print
     editions and the podcast subscribe block had no place and so sorted first — above the cover,
     above the title — which is the one order the rail exists to prevent. */
  .title-head__rail > * { order: 6; max-width: 320px; }
  .title-head__art { max-width: 190px; order: 1; }
  .title-head__main { order: 2; }
  .title-head__rail .buy { order: 3; max-width: 320px; }
  .title-head__rail .drm { order: 4; max-width: 320px; margin-top: 0; }
  .title-head__rail .member { order: 5; max-width: 320px; }
}
/* The only motion on the site is the cover lifting under the pointer. Without motion the shadow
   still deepens, so the hover is still a hover; it just stops travelling. Named rather than a global
   kill, so a state change added later keeps whatever feedback it was drawn with. */
@media (prefers-reduced-motion: reduce) {
  .cover { transition: none; }
  .pick__art:hover .cover { transform: none; }
}

/* ── the account layer ─────────────────────────────────────────────────────
   Drawn by script, so it only ever appears where it can actually work.       */

.save-host { display: flex; margin-top: 12px; }
.save-btn {
  font: 700 var(--step--2)/1 var(--sans); letter-spacing: .13em; text-transform: uppercase;
  background: transparent; color: var(--green); border: 1px solid var(--green);
  padding: 9px 13px; cursor: pointer;
}
.save-btn:hover { background: var(--green); color: var(--paper); }
.save-btn.is-saved { background: var(--gold); border-color: var(--gold); color: var(--paper); }
.save-btn.is-saved::before { content: "✓ "; }
.save-btn[disabled] { opacity: .5; cursor: default; }

.signin { display: grid; gap: 14px; max-width: 460px; margin-top: 4px; }
.signin .buy-link { text-align: center; padding: 12px 16px; }
.signin .slot { margin-top: 0; }
.form-error {
  margin: 10px 0; padding: 10px 13px; border: 1px solid var(--down);
  background: var(--tint-down); color: var(--ink); font-size: var(--step--1); max-width: 60ch;
}
.linklike {
  background: none; border: 0; padding: 0; cursor: pointer; color: var(--green);
  font: 400 var(--step--2)/1 var(--data); letter-spacing: .04em; text-decoration: underline;
  text-underline-offset: 3px;
}
.note {
  margin-top: 6px; width: 100%; max-width: 40ch; background: transparent;
  border: 0; border-bottom: 1px dashed var(--rule-mid); padding: 3px 0;
  font: 400 var(--step--2)/1.5 var(--data); color: var(--ink-2);
}
.note:focus { outline: none; border-bottom-color: var(--green); color: var(--ink); }
.note::placeholder { color: var(--ink-3); }
.pick__actions { display: flex; gap: 12px; align-items: center; margin-top: 8px; }

/* ── bookmarks ─────────────────────────────────────────────────────────────
   Positions inside books somebody already owns, pushed up from the app. They
   are read here, not acted on, so they are set as a reading column rather than
   a chart row.                                                               */

.marks { margin-top: 18px; }
.marks__book { font: 900 var(--step-1)/1.25 var(--display); color: var(--green); letter-spacing: -.012em; }
.marks__list { list-style: none; margin-top: 6px; }
.marks__list li {
  display: grid; grid-template-columns: 74px minmax(0, 1fr) auto; gap: 12px;
  align-items: baseline; padding: 8px 0; border-bottom: 1px solid var(--rule-faint);
}
.marks__at { font: 700 var(--step--2)/1.5 var(--data); color: var(--gold); font-variant-numeric: tabular-nums; }
.marks__body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.marks__title { font: 600 var(--step-0)/1.35 var(--sans); }
.marks__note { font: 400 var(--step--1)/1.5 var(--sans); color: var(--ink-2); text-wrap: pretty; }

.tag {
  align-self: flex-start; font: 700 var(--step--2)/1 var(--sans); letter-spacing: .1em;
  text-transform: uppercase; padding: 3px 7px; border: 1px solid currentColor;
}
/* The app's six tags, each given its own ink rather than one accent repeated six times. */
.tag--quote      { color: var(--tag-quote); }
.tag--favourite  { color: var(--tag-favourite); }
.tag--character  { color: var(--tag-character); }
.tag--plot       { color: var(--tag-plot); }
.tag--lookup     { color: var(--tag-lookup); }
.tag--confusing  { color: var(--tag-confusing); }

@media (max-width: 560px) {
  .marks__list li { grid-template-columns: 62px minmax(0, 1fr); }
  .marks__list li .linklike { grid-column: 2; justify-self: start; }
}

/* Facets are statements about the book, not links to anywhere, so they are set apart from the
   shelf chips they sit beside. */
.chips--facet span {
  border: 1px solid var(--gold); color: var(--gold);
  padding: 7px 10px; font: 400 var(--step--2)/1 var(--data); letter-spacing: .04em;
}

/* ── the chart record ──────────────────────────────────────────────────────
   A book's own standing history. Positions and dates are measured data, so
   they are set in Courier like every other number on the site, and the table
   is ruled rather than boxed — a ledger line, not a card.                   */

/* The record runs the width of the column under its rule-head, as every other block under one does;
   a 72ch cap left the table and the run ending two-thirds of the way across. */
.chart-record { max-width: none; }
.chart-record__table {
  width: 100%; margin: 4px 0 13px; border-collapse: collapse;
  font: 400 var(--step--1)/1.5 var(--sans);
}
.chart-record__table th {
  text-align: left; padding: 6px 12px 6px 0;
  border-bottom: 1px solid var(--rule);
  font: 400 var(--step--2)/1 var(--data); letter-spacing: .1em;
  text-transform: uppercase; color: var(--gold); white-space: nowrap;
}
.chart-record__table td {
  padding: 8px 12px 8px 0; border-bottom: 1px solid var(--rule-faint);
  vertical-align: baseline;
}
.chart-record__table tr:last-child td { border-bottom: 0; }
.chart-record__table .num {
  font-family: var(--data); font-variant-numeric: tabular-nums;
  text-align: right; padding-right: 18px; white-space: nowrap;
}
.chart-record__table td.num { font-weight: 400; color: var(--green); }
/* A category heading inside the record: the same small capitals as the column heads, in ink rather
   than gold so the columns still read as the frame and the categories as the contents. */
.chart-record__group th {
  padding: 14px 0 4px; border-bottom: 0;
  font: 400 var(--step--2)/1 var(--data); letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-2); text-align: left;
}
.chart-record__group:first-child th { padding-top: 8px; }
.chart-record__note { font-size: var(--step--1); color: var(--ink-3); }

/* The run, plotted. Hairline rules, one solid data line, and two labelled points — the plate's own
   weights, not a dashboard's. The scale is inverted (1 at the top) so both ends carry their number. */
.chart-record__plot { margin: 16px 0 4px; }
.spark { display: block; width: 100%; height: auto; overflow: visible; }
.spark__rule { stroke: var(--rule-faint); stroke-width: 1; }
.spark__line { fill: none; stroke: var(--green); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.spark__dot { fill: var(--green); }
.spark__best { fill: var(--gold-lift); stroke: var(--paper); stroke-width: 1.5; }
.spark__now { fill: var(--green); stroke: var(--paper); stroke-width: 1.5; }
.spark__tick, .spark__foot { fill: var(--ink-3); font: 400 10px var(--data); letter-spacing: .04em; }
.spark__label { font: 700 10px var(--data); }
.spark__label--best { fill: var(--gold); }
/* Bigger than the marks, so a pointer finds a day without having to hit a pixel. */
.spark__hit { fill: transparent; }
.spark__hit:hover { fill: var(--green); fill-opacity: .12; }
.chart-record__plot figcaption { margin-top: 6px; font-size: var(--step--2); color: var(--ink-3); }

/* The table is the one place on a book page that can outgrow a phone. It scrolls
   inside itself rather than pushing the page sideways. */
@media (max-width: 560px) {
  .chart-record { overflow-x: auto; }
  .chart-record__table { min-width: 420px; }
}

/* The heading shares its row with the save control. The title block is set to the width it needs
   and the control takes the space at the top right that the title was never going to fill. */
.title-head__row { display: flex; align-items: flex-start; justify-content: space-between; gap: clamp(16px, 3vw, 32px); }
/* The title takes what it needs; the save control is the only thing allowed to compete for the row,
   and it never grows. */
.title-head__names { min-width: 0; flex: 1 1 auto; }
.title-head__row .save-host { flex: none; }
.title-head .share { margin-top: 14px; }
.title-head__names { min-width: 0; }
.title-head__row .save-host { flex: none; margin-top: 6px; }
/* Sized for the longer of its two labels so confirming a save does not shift the control
   out from under the cursor that just clicked it. */
.title-head__row .save-btn { min-width: 210px; }

/* Narrow enough and there is no space to the right to use, so the control returns to its own line. */
@media (max-width: 700px) {
  .title-head__row { flex-direction: column; gap: 12px; }
  .title-head__row .save-host { margin-top: 0; }
}

/* ── something worked ──────────────────────────────────────────────────────
   The counterpart to .form-error, and deliberately its equal in weight: a
   confirmation that whispers while a failure shouts teaches people to distrust
   the quiet one. Green rather than gold — this states a fact rather than
   offering something.                                                        */

.done { margin-top: 16px; padding: 16px 18px; border: 2px solid var(--green); background: var(--tint-green); max-width: 560px; }
.done__head { font: 700 var(--step-1)/1.3 var(--sans); color: var(--green); display: flex; gap: 10px; align-items: baseline; }
.done__mark { font-size: 1.2em; line-height: 1; }
.done__body { margin-top: 8px; font-size: var(--step--1); color: var(--ink-2); }
.done__act { margin-top: 14px; }
.done__act .buy-link { display: inline-block; }

/* ── telling someone ───────────────────────────────────────────────────────
   Set as text chips in the shelf's own idiom rather than as network logos.
   No third-party script and no brand mark: each of these is a plain link to a
   compose window, so nobody is watching who reads what from here.            */

.share { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 18px; }
.share__label {
  font: 400 var(--step--2)/1 var(--data); letter-spacing: .1em;
  text-transform: uppercase; color: var(--gold); margin-right: 2px;
}
.share__link {
  border: 1px solid var(--rule-mid); background: transparent; color: var(--ink);
  padding: 7px 11px; font: 400 var(--step--2)/1 var(--data); letter-spacing: .04em;
  text-decoration: none; cursor: pointer;
}
.share__link:hover { border-color: var(--green); color: var(--green); }
.share__link--native { border-color: var(--green); color: var(--green); font-weight: 700; }
.share__link.is-done { border-color: var(--green); background: var(--green); color: var(--paper); }
.share__url {
  border: 1px solid var(--green); background: var(--field); color: var(--ink);
  padding: 6px 10px; font: 400 var(--step--2)/1.4 var(--data); width: min(320px, 100%);
}

/* ── the weekly ledger ─────────────────────────────────────────────────────
   A subscription form on a page that is otherwise a chart, so it is boxed and
   ruled like a notice rather than floated like an advertisement. It reuses
   .slot — the search box — because on this site a box you type into looks one
   way, and a reader should not have to work out that this one is different.  */

.signup { margin-top: clamp(24px, 3vw, 36px); padding: clamp(16px, 2.4vw, 24px); border: 2px solid var(--rule); background: var(--paper-2); max-width: 62ch; }
.signup__head { font: 600 var(--step-1)/1.3 var(--incise); letter-spacing: .18em; text-transform: uppercase; color: var(--green); }
.signup__body { margin-top: 8px; font-size: var(--step--1); line-height: 1.6; color: var(--ink-2); }
.signup .slot { margin-top: 14px; }
.signup__fine { margin-top: 10px; font: 400 var(--step--2)/1.5 var(--data); color: var(--ink-3); }
.signup__fine a { color: var(--green); }
/* On /movers/ it sits under five chart sections and has to be the quieter of the two drawings. */
/* Between two shelves on the front plate: the full width of the column, with the form beside the
   words where there is room for that and under them where there is not. */
.signup--inline {
  max-width: none; display: grid; grid-template-columns: minmax(0, 1fr) minmax(260px, 360px);
  gap: 6px 32px; align-items: center; padding: clamp(14px, 2vw, 20px) clamp(16px, 2.4vw, 24px);
}
.signup--inline .signup__head { font-size: var(--step-0); }
.signup--inline .signup__body { margin-top: 4px; font-size: var(--step--2); }
.signup--inline .slot { grid-column: 2; grid-row: 1 / 3; margin-top: 0; }
.signup--inline .signup__fine { grid-column: 1 / -1; margin-top: 4px; }
@media (max-width: 760px) {
  .signup--inline { grid-template-columns: minmax(0, 1fr); }
  .signup--inline .slot { grid-column: 1; grid-row: auto; margin-top: 10px; }
}
.signup--compact { padding: 14px 16px; border-width: 1px; }
.signup--compact .signup__head { font-size: var(--step-0); }
.signup--compact .signup__body { font-size: var(--step--2); }

/* ── the shop ──────────────────────────────────────────────────────────────
   Priced things, laid out as a small catalogue rather than as a storefront:
   this site takes no payment and the link goes somewhere else, so anything
   that looked like a checkout would be a promise it cannot keep.            */

.goods { list-style: none; margin-top: clamp(18px, 2.4vw, 26px); display: grid; gap: clamp(16px, 2.4vw, 26px); grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
.goods__item { display: grid; gap: 10px; }
.goods__art img { width: 100%; height: auto; aspect-ratio: 1; object-fit: cover; border: 1px solid var(--rule-mid); display: block; background: var(--paper-2); }
.goods__name { font: 700 var(--step-0)/1.3 var(--sans); color: var(--green); text-decoration: none; }
.goods__name:hover { text-decoration: underline; text-underline-offset: 3px; }
.goods__note { margin-top: 4px; font-size: var(--step--1); color: var(--ink-2); }
.goods__price { margin-top: 6px; font: 400 var(--step--1)/1 var(--data); color: var(--gold); font-variant-numeric: tabular-nums; }

/* ── the headline figure ───────────────────────────────────────────────────
   One number, set at the size of the masthead, because the whole argument of
   /deals/ is a number and a page that buries it under three paragraphs of
   reasoning has argued itself out of being read. The rule under it is the one
   the section heads use, so it reads as the top of a page rather than as a
   pull-quote floated into one.                                              */

.headline { margin-top: clamp(16px, 2.4vw, 26px); padding-bottom: clamp(12px, 1.6vw, 18px); border-bottom: 2px solid var(--rule); }
.headline__figure {
  font: 900 clamp(56px, 13vw, 132px)/.86 var(--display); color: var(--green);
  letter-spacing: -.03em; font-variant-numeric: tabular-nums;
}
.headline__unit {
  margin-top: 6px; font: 600 var(--step-1)/1.3 var(--incise);
  letter-spacing: .12em; text-transform: uppercase; color: var(--gold); text-wrap: balance;
}
.headline__range { margin-top: 10px; max-width: 54ch; font-size: var(--step--1); line-height: 1.6; color: var(--ink-2); }
/* The second shop, under the figure it is measured against. The two rates are struck in the data face
   and the gold so they read as two more measurements rather than as prose. */
.headline__cross { margin-top: 10px; max-width: 60ch; font-size: var(--step--1); line-height: 1.6; color: var(--ink-2); }
.headline__cross b { font: 700 var(--step-0)/1 var(--data); color: var(--gold); font-variant-numeric: tabular-nums; }

/* ── where the catalogue sits ──────────────────────────────────────────────
   The argument on the left, the evidence for it on the right. One column as
   soon as the two would start fighting for the same words per line.        */

.costs { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: clamp(20px, 3.4vw, 48px); align-items: start; }
@media (max-width: 900px) { .costs { grid-template-columns: minmax(0, 1fr); } }

.costgram { margin: 0; }
.costgram .rule-head { margin-top: 0; }
.costgram__plot { display: block; width: 100%; height: auto; margin-top: 12px; overflow: visible; }
/* The bars are the ink of the page, so they are struck in it. Solid, hard-edged, no rounding: this
   is an engraving, and a rounded bar chart belongs to a different century of printing. */
.costgram__bar { fill: var(--green); }
.costgram__bar:hover { fill: var(--green-2); }
/* Everything past the last labelled step, in one bar that covers ten times the range of its
   neighbours. Hatched rather than solid so it cannot be read as one more equal column. */
.costgram__bar--over { fill: url(#costgram-hatch); }
.costgram__hatch { stroke: var(--gold); }
.costgram__base { stroke: var(--rule); stroke-width: 1.5; }
/* Two golds on one mark, and both are the right one: the line crosses the dark green bars and needs
   the lifted gold to be seen against them, the label sits on paper and needs the darker one to be
   read on it. */
.costgram__median { stroke: var(--gold-lift); stroke-width: 1.5; stroke-dasharray: 3 3; }
.costgram__median-label { font: 700 11px/1 var(--sans); letter-spacing: .1em; text-transform: uppercase; fill: var(--gold); }
/* Inside the bar it labels, so it is set in the paper the bar is printed on. */
.costgram__peak { font: 700 11px/1 var(--data); fill: var(--paper); font-variant-numeric: tabular-nums; }
.costgram__tick { font: 400 11px/1 var(--data); fill: var(--ink-3); font-variant-numeric: tabular-nums; }
.costgram__axis { font: 400 10px/1 var(--data); letter-spacing: .1em; text-transform: uppercase; fill: var(--gold); }
.costgram figcaption { margin-top: 10px; font-size: var(--step--2); line-height: 1.6; color: var(--ink-3); }

/* ── day by day ────────────────────────────────────────────────────────────
   The same marks as .spark, because it is the same kind of claim: this site's
   own readings, taken daily, drawn as one line per unbroken run so a morning
   the build did not run is a gap rather than a straight stretch across it.   */

.pricetrend { margin: clamp(20px, 3vw, 34px) 0 0; }
.pricetrend .rule-head { margin-top: 0; }
.pricetrend__plot { display: block; width: 100%; height: auto; margin-top: 12px; overflow: visible; }
.pricetrend__rule { stroke: var(--rule-faint); stroke-width: 1; }
.pricetrend__rule--base { stroke: var(--rule-mid); }
.pricetrend__mark { stroke: var(--rule-mid); stroke-width: 1; }
/* The unit over the axis, in the caption the distribution uses for its own. */
.pricetrend__axis { font: 400 11px/1 var(--data); letter-spacing: .1em; text-transform: uppercase; fill: var(--gold); }
/* Each line's name and standing figure at its own end, haloed in the paper so it stays legible
   where it crosses a rule or the other line. */
.pricetrend__name { font: 700 12px/1 var(--sans); fill: var(--green); paint-order: stroke; stroke: var(--paper); stroke-width: 4px; stroke-linejoin: round; }
.pricetrend__name--apple { fill: var(--gold-trace); }
.pricetrend__line { fill: none; stroke: var(--green); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.pricetrend__dot { fill: var(--green); }
/* The stretch across a morning nothing was read: dotted, thinner and fainter than the readings on
   either side, so the line connects without the bridge passing for data. */
.pricetrend__gap { stroke: var(--green); stroke-width: 1.5; stroke-dasharray: 2 4; stroke-linecap: round; opacity: .55; }
.pricetrend__gap--apple { stroke: var(--gold-trace); stroke-dasharray: 2 4; }
/* The second shop, in the gold this site keeps for a measurement and dashed so the two lines can never
   be confused where they cross — and they will cross. */
.pricetrend__line--apple { stroke: var(--gold-trace); stroke-dasharray: 5 4; }
.pricetrend__dot--apple { fill: var(--gold-trace); }
.pricetrend__now { fill: var(--gold-trace); stroke: var(--paper); stroke-width: 1.5; }
/* Two frames of the same drawing — see `pricetrend` in pages.mjs. The wide one spans the plate; at
   phone width it was a strip sixty pixels tall, so the narrow one takes over, taller than it is
   wide enough to read, with the type sized for the ~340px it actually lands in. */
.pricetrend__plot--narrow { display: none; }
@media (max-width: 600px) {
  .pricetrend__plot--wide { display: none; }
  .pricetrend__plot--narrow { display: block; }
  .pricetrend__plot--narrow .pricetrend__tick, .pricetrend__plot--narrow .pricetrend__foot { font-size: 12px; }
}
/* Sized against how big this actually renders, not against the number. The distribution's viewBox is
   460 wide and lands in a ~570px column, so 11px there is ~13.6px on screen; this one is 1100 wide
   and spans the whole plate, so 11px is 11px. It gets the two it was missing. */
.pricetrend__tick, .pricetrend__foot { fill: var(--ink-3); font: 400 13px var(--data); letter-spacing: .04em; }
/* Invisible targets, wider than the marks, so a pointer finds a morning without hitting a pixel. */
.pricetrend__hit { fill: transparent; }
.pricetrend__hit:hover { fill: var(--green); fill-opacity: .12; }
.pricetrend figcaption { margin-top: 10px; max-width: 78ch; font-size: var(--step--2); line-height: 1.6; color: var(--ink-3); }
.pricetrend__none { margin-top: 12px; font-size: var(--step--2); line-height: 1.6; color: var(--ink-3); }

/* Movement in the price, and deliberately not in the green-up/red-down of a chart position. On a
   chart, up is good and the palette says so; on a price, up is the reader paying more, and borrowing
   that palette here would have the page cheering for a rise. Both directions are set in the same
   neutral ink and the sentence carries the direction. */
.headline__move { margin-top: 6px; font: 400 var(--step--1)/1.5 var(--data); color: var(--ink-2); letter-spacing: .01em; }
.headline__move::before { content: "▲ "; font-size: .8em; color: var(--ink-3); }
.headline__move--down::before { content: "▼ "; }
.headline__move--flat::before { content: "— "; }

/* ── the cell where the buy button was ─────────────────────────────────────
   On /movers/ the row's last column carries the reading rather than a second
   link to the page the whole row already opens: where the title stands, on
   which chart, and what it is being compared against. Right-aligned so the
   standings read down a column of their own, and set in the data face because
   every line of it is a measurement.                                        */

/* Narrower than it was. Every step up in type size comes out of the title's column on a two-up
   chart, and a title that wraps to three lines is a worse readability problem than the one the
   bigger type solved. The dates inside it lost their year for the same reason — see `shortDate`. */
.pick__stat { align-self: start; padding-top: 2px; text-align: right; display: grid; gap: 2px; max-width: 19ch; justify-self: end; }
.pick__stat b { font: 700 var(--step--1)/1.2 var(--sans); color: var(--green); }
.pick__stat i { font: 400 var(--step--2)/1.45 var(--data); font-style: normal; color: var(--ink-2); letter-spacing: .01em; }
/* The shelf's name, marked as such rather than found by position — see `standing` in pages.mjs. */
.pick__stat i.is-chart { color: var(--gold); }
/* A chart the show is not on, in the combined shelf's cell. Lighter, never hidden: the absence is
   the fact the cell exists to print. */
.pick__stat i.is-absent { color: var(--ink-3); }
/* The way out of a row that has no page here. A link, drawn as one, in the control's own label
   type so it sits where the buy control would have. Above the stretched title link's layer, though
   there is none on such a row; the z-index is for the day there is. */
.pick__stat a.pick__out {
  position: relative; z-index: 2; justify-self: end; margin-top: 4px;
  font: 700 var(--step--2)/1 var(--sans); letter-spacing: .1em; text-transform: uppercase;
  color: var(--green); text-decoration: none; border-bottom: 2px solid var(--gold-lift); padding-bottom: 2px;
}
.pick__stat a.pick__out:hover { color: var(--gold); }
@media (max-width: 560px) { .pick__stat a.pick__out { justify-self: start; } }

/* The movement itself, in the leading cell — and it borrows the palette the
   movement column of a category chart already uses, so a rise means the same
   thing on this page as it does on that one. Gold stays the default, because
   everywhere else this cell holds a measurement rather than a direction. */
.chart[data-lead] .pick__rate b { font-size: clamp(17px, 1.9vw, 23px); }
.pick__rate--up b     { color: var(--green); }
.pick__rate--down b   { color: var(--down); }
.pick__rate--new b    { color: var(--gold); font: 600 clamp(15px, 1.6vw, 19px)/1 var(--incise); letter-spacing: .08em; }
.pick__rate--hold b   { color: var(--ink); }
.pick__rate--gone b   { color: var(--ink-3); font: 600 clamp(15px, 1.6vw, 19px)/1 var(--incise); letter-spacing: .08em; }
.pick__rate--gone i, .pick__rate--gone b { opacity: .85; }

@media (max-width: 560px) {
  /* Under the text with the buy button it replaces, not squeezed beside it. On a numbered chart —
     the combined podcast shelf is one — that is the third column, the same one the buy control
     drops into; left to auto-placement it landed under the position number. */
  .pick__stat { grid-column: 3; text-align: left; justify-self: start; margin-top: 7px; }
  .chart[data-unnumbered] .pick__stat { grid-column: 2; }
}

/* ── the quick selector ────────────────────────────────────────────────────
   Five sections and sixty rows is a page you scroll past rather than through.
   Built from the same parts as .denom, the strip that runs along the foot of
   a chart page, so a bar of links looks like a bar of links wherever it sits —
   and it rides the top of the plate on the way down.                         */

.jump {
  position: sticky; top: 0; z-index: 6;
  display: flex; flex-wrap: wrap; margin-top: clamp(16px, 2.4vw, 24px);
  background: var(--paper); border-top: 2px solid var(--rule); border-bottom: 2px solid var(--rule);
}
.jump a {
  flex: 1 1 96px; display: flex; align-items: baseline; justify-content: center; gap: 7px;
  padding: 9px 8px; text-decoration: none; color: var(--ink);
  font: 600 var(--step--2)/1.3 var(--sans); letter-spacing: .12em; text-transform: uppercase;
  border-right: 1px solid var(--rule-faint);
}
.jump a:last-child { border-right: 0; }
/* Seven cells with names of different lengths: each takes the width of its name, and none wraps. */
.jump--scroll a { flex: 1 1 auto; white-space: nowrap; }
.jump a:hover, .jump a:focus-visible { background: var(--green); color: var(--paper); }
/* The count is a measurement, so it is set in the data face and struck in the gold this site keeps
   for one — and it inverts with the cell rather than staying gold on green. */
.jump b { font: 400 var(--step--2)/1 var(--data); color: var(--gold); font-variant-numeric: tabular-nums; }
.jump a:hover b, .jump a:focus-visible b { color: var(--paper); }

/* A jump must not land the heading underneath the bar that did the jumping. The bar is about 38px;
   the margin is generous so the rule above a section clears it too. */
.chart-jump-target { scroll-margin-top: 58px; }

/* Five cells have to stay on one line. Wrapped onto two they would take a fifth of a phone screen
   and hold it there for the length of the page, which is a worse trade than smaller type. */
@media (max-width: 620px) {
  /* Equal fifths, the count under the word rather than beside it, and no tracking: side by side the
     five did not fit a 390px plate and "Gone" wrapped to a row of its own, which doubled the height
     of a bar that is stuck to the top of the screen. The type eases down a little on the narrowest
     phones so the longest word still fits its fifth. */
  .jump a {
    flex: 1 1 0; min-width: 0; flex-direction: column; align-items: center; gap: 3px;
    padding: 7px 2px 6px; letter-spacing: 0; font-size: clamp(10.5px, 3.1vw, 12px);
  }
  .jump b { font-size: var(--step--2); }
  .chart-jump-target { scroll-margin-top: 64px; }
  /* Seven sections do not fit a phone as equal sevenths, so the front plate's bar scrolls sideways
     instead: every cell keeps its whole name at a size a thumb can hit, and the cut-off last one
     says there is more. The bar is still stuck to the top. */
  .jump--scroll { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
  .jump--scroll::-webkit-scrollbar { display: none; }
  .jump--scroll a { flex: 0 0 auto; flex-direction: row; padding: 12px 14px; font-size: var(--step--2); letter-spacing: .08em; }
}
