/* The fairy-book template's design system.
 *
 * Structure lives here; a book's palette recolours only the identity layer, and
 * arrives as custom properties on :root (light) with the same tokens' dark
 * variants swapped in under prefers-color-scheme. Every colour below is a token,
 * so a book that declares a new palette needs no CSS at all.
 *
 * Dark mode is a first-class mode, not an inversion: period line art sits on a
 * paper-tone card in BOTH modes, so dark mode dims the frame and never the
 * engraving.
 *
 * THE GROUNDS ARE THE BOOK'S OWN, and that is new. --paper and --night resolve
 * to the style guide's Light Ink and Dark Ink for the book being read, so the
 * Red book's page is a warm blush and the Blue book's a cool one, in both
 * modes. Nothing in this file changes for that: the grounds were always tokens,
 * and book_palette() decides what they hold. A series with no style guide keeps
 * the neutral pair, and so does every page that belongs to no single book.
 *
 * Every token is emitted TWICE by book_palette_css(), hex then oklch(), because
 * the guide masters its colours in OKLCH and several of them are outside sRGB.
 * Later declaration wins where it parses, so this file never mentions either. */

/* --- the three faces, and the hierarchy is the guide's, not a preference
 *
 * STYLE-GUIDE.md "Typographic hierarchy" names three families and one job each:
 * Cinzel Decorative is the fairy-book identity, Forum is editorial structure and
 * credits, EB Garamond is sustained reading. The tokens below are named after
 * the JOBS rather than after the faces, so a series that swaps a family changes
 * three declarations and nothing else, and so a rule that reaches for the wrong
 * one is visible as a wrong word rather than as a wrong font name.
 *
 * The old names were a small lie waiting to happen: `--sans` now resolves to
 * Forum, which is an inscriptional serif.
 *
 * SELF-HOSTED, and served from this site. All three are Google families; loaded
 * from Google they are a third-party request on every page of a reading site,
 * which is a privacy question this house does not need to have and a round trip
 * the reader pays for before the first word. The latin woff2 subsets are 121KB
 * for five faces and ship in the template beside this file. The paths are
 * relative, so they resolve beside the stylesheet whatever its hashed name is.
 *
 * `swap` on every face: the fallback stacks below are real period-appropriate
 * serifs, so text is readable from the first paint and reflows once. `block`
 * would hold the words back for a face nobody is waiting for.
 *
 * Cinzel Decorative Bold is asked for by exactly one rule, .cover-title on the
 * COMPOSED cover, which is what a book wears until its artwork arrives. Both
 * live volumes have art now, so nothing on langfairybooks.com paints the Bold
 * today and it is still correct to ship: a face is fetched only by a page that
 * uses one, and the next volume lands without a cover. The fixture keeps the
 * path exercised. Do not remove it for being unused.
 *
 * EB Garamond's roman is a VARIABLE font, wght 400-800 (checked in its fvar,
 * not assumed from the family's documentation), so the range is declared and a
 * weight in it is a real drawing rather than a synthetic smear. Its italic is a
 * separate static face at 400, which is the whole of what the corpus asks for:
 * the 74 tales contain 92 <em> and no <strong>. */
@font-face {
  font-family: "Cinzel Decorative";
  font-style: normal; font-weight: 400; font-display: swap;
  src: url("fonts/cinzel-decorative-400-normal.woff2") format("woff2");
}
@font-face {
  font-family: "Cinzel Decorative";
  font-style: normal; font-weight: 700; font-display: swap;
  src: url("fonts/cinzel-decorative-700-normal.woff2") format("woff2");
}
@font-face {
  font-family: "Forum";
  font-style: normal; font-weight: 400; font-display: swap;
  src: url("fonts/forum-400-normal.woff2") format("woff2");
}
@font-face {
  font-family: "EB Garamond";
  font-style: normal; font-weight: 400 800; font-display: swap;
  src: url("fonts/eb-garamond-400-normal.woff2") format("woff2");
}
@font-face {
  font-family: "EB Garamond";
  font-style: italic; font-weight: 400; font-display: swap;
  src: url("fonts/eb-garamond-400-italic.woff2") format("woff2");
}

:root {
  color-scheme: light;
  --measure: 34rem;
  /* THE CONTAINER EVERY BAND SHARES. The header, the main content and the
     footer all sit in a box of this width, centred, so the masthead, the menu,
     the text and the copyright line stand on one axis instead of each hugging
     the left edge of whatever width the window happens to be. */
  --page: 76rem;
  /* The one size the reader's control moves. Used by .text and by nothing else,
     which is what keeps a size change out of the headings and the chrome. The
     measure stays in rem on purpose: growing the column with the type would keep
     the line at the same character count, and shortening the line is part of
     what makes larger text easier rather than merely bigger. */
  --prose: calc(1.25rem * var(--reader-scale, 1));
  /* Cinzel Decorative: the series and the books, and nothing else. A titling
     face used on a paragraph stops being an identity and becomes decoration. */
  --display: "Cinzel Decorative", Palatino Linotype, Palatino, Georgia, serif;
  /* Forum: contents, story titles, labels, credits, the chrome. */
  --structural: "Forum", Optima, Palatino Linotype, Palatino, Georgia, serif;
  /* EB Garamond: the tales. */
  --reading: "EB Garamond", Iowan Old Style, Palatino Linotype, Palatino, Georgia, serif;
  --bg: var(--paper);
  --fg: var(--ink);
  --muted: color-mix(in srgb, var(--ink) 62%, var(--paper));

  /* THE CHROME IS NEUTRAL, ON EVERY PAGE, WHATEVER BOOK IT IS.
   *
   * Every other edition of these books falls into the same trap: the volume's
   * colour spreads from the cover onto the header, the links, the rules, and in
   * the worst cases the page ground, and the result is a red website rather than
   * a website showing a red book. This template was doing the first half of it.
   * Used once, the colour means something.
   *
   * So navigation, borders and hover states resolve to the --ui-* scale, which
   * is the same for every page. --tinted still carries the book's ink and now
   * has exactly one job: the drop cap that opens the text. */
  --edge: var(--ui-rule);
  --card: var(--ui-wash);
  --nav-ink: var(--ui-tint);
  --tinted: var(--tint);
}
/* DARK IS A CHOICE AS WELL AS A SETTING, so the same tokens are reachable three
 * ways: the system says dark and the reader has not overridden it, or the reader
 * asked for dark outright. The `:not([data-theme="light"])` is what makes a
 * reader on a dark system able to choose light and be obeyed; without it the
 * media query would win back on the next paint.
 *
 * The two blocks are duplicated because CSS has no way to name one set of
 * declarations and apply it under two different conditions, and that duplication
 * is exactly the "when a model changes, the consumers change with it" hazard: a
 * token added to one block and not the other gives a reader who CHOSE dark a
 * different page from one whose system chose it, and nothing would say so. It is
 * held mechanically instead. `theme-parity` in _tools/book-gate.php compares the
 * two declaration lists and fails on any difference, so the pair cannot drift.
 *
 * `color-scheme` is in here rather than assumed: it is what tells the browser to
 * paint its OWN surfaces dark, and the search box is a native control on every
 * page. Without it a dark page carries a white input with white-on-white text. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg: var(--night);
    --fg: var(--moon);
    --muted: color-mix(in srgb, var(--moon) 62%, var(--night));
    --edge: var(--ui-rule-dark);
    --card: var(--ui-wash-dark);
    --nav-ink: var(--ui-tint-dark);
    --tinted: var(--tint-dark);
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: var(--night);
  --fg: var(--moon);
  --muted: color-mix(in srgb, var(--moon) 62%, var(--night));
  --edge: var(--ui-rule-dark);
  --card: var(--ui-wash-dark);
  --nav-ink: var(--ui-tint-dark);
  --tinted: var(--tint-dark);
}

* { box-sizing: border-box; }

/* THE READER'S SIZE CONTROL MOVES THE STORY, AND NOTHING ELSE.
 *
 * It used to sit here, on <html>, and since every size in this file is in rem
 * that scaled the entire page: the masthead, the search box, the story title,
 * the reading measure. Turning the text up meant a 40px heading on a phone
 * wrapping to four lines, and a reader who wanted a more comfortable tale got a
 * louder website. The two are different requests and only one of them was made.
 *
 * So the root is left alone. The chrome and the headings are anchored to the
 * reader's own browser default, and the scale multiplies ONE thing: --prose,
 * which only the story's text uses. Everything else on the page is exactly where
 * the design put it at every setting.
 *
 * A PERCENTAGE on the root, never a pixel count. 100% is whatever the reader's
 * browser setting says, so somebody who has already told their browser they want
 * large text keeps it. Pinning `16px` here would silently throw that away.
 *
 * THE DEFAULT PROSE SIZE IS 1.25rem, and it is measured rather than judged: EB
 * Garamond's x-height is 0.412 of its em, taken off a real render rather than
 * from the family's documentation. Georgia's is about 0.48 and a UI face is
 * nearer 0.55, so the 17px this set originally read like roughly 14.6px of an
 * ordinary text face. 20px lands its x-height where a comfortable 17px Georgia
 * would be. The fallback in the var() is what a reader with no stored choice and
 * no JavaScript at all gets, so the larger default never depends on a script. */
html {
  font-size: 100%;
  -webkit-text-size-adjust: 100%;
}
body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: 1.0625rem/1.5 var(--reading);
}
/* CENTRED, EVERYWHERE, and this is the one rule that does it. Each band is
   full-bleed (the header's rule and the page ground run edge to edge) and its
   CONTENTS are held to one centred container. Before this every view filled the
   window and its children were measure-wide and left-ranged, so a wide screen
   put the reading column against the left edge with half the page empty. */
header.site > *, footer.site > * { max-width: var(--page); margin-inline: auto; }
/* THE READING MEASURE IS THE DEFAULT AND WIDTH IS OPTED INTO, which is the way
   round that keeps a page on one axis. The first attempt did the opposite: every
   view got the wide container and its narrow children were centred inside it one
   by one, so a heading nobody had listed sat at the container's left edge while
   its own list sat in the middle. Three pages had that, and each would have been
   a separate fix. `main > *` is one type selector, so any class below overrides
   it without !important or ordering games. */
main > * { max-width: var(--measure); margin-inline: auto; }
/* The one thing that wants room for a grid. The shelf's rows go straight into
   <main> with no wrapper of their own, which is worth knowing before reaching
   for a container class that is not there: an earlier read of this said the
   shelf was a .hub and it is not.
   THE HUB IS NOT ON THIS LIST ANY MORE. It was, and that is what made the book
   page look unfinished beside the story page: a page-wide container holding
   measure-wide children, so the title, the contents and the cover all sat at
   the left edge of a wide window. It takes the reading measure from `main > *`
   like everything else and puts the room that frees into the margin. */
.shelf { max-width: var(--page); }
/* And ONE page takes the whole window: a single tag stands every tale that
   carries it side by side, and holding that to a column turns a rainbow into a
   list. */
.tag-page { max-width: none; }
/* Its head still reads as a head: centred over the wall rather than adrift at
   the left of it. */
.tag-page h1, .tag-page .standfirst {
  max-width: var(--measure); margin-inline: auto; text-align: center;
}
/* The hub's own section heading, on the axis its list is on. */
.hub > h2 { max-width: var(--measure); margin-inline: auto; }

a { color: var(--nav-ink); text-underline-offset: 0.15em; }
a:focus-visible, button:focus-visible, input:focus-visible {
  outline: 3px solid var(--nav-ink); outline-offset: 2px;
}
.skip { position: absolute; left: -9999px; }
.skip:focus { left: 1rem; top: 1rem; background: var(--bg); padding: .5rem 1rem; z-index: 9; }
.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

header.site {
  position: relative;
  padding: 1rem clamp(1rem, 4vw, 3rem);
  border-bottom: 1px solid var(--edge);
  font-family: var(--structural); font-size: .9375rem;
  position: sticky; top: 0; z-index: 20; background: var(--bg);
}
/* Row one, and it never wraps: the name shrinks and the search gives way first,
   in that order (see .site-nav below). The menu row is a sibling rather than a
   member, because one flex container cannot be told both "never wrap" and
   "always break here". */
.site-bar {
  display: flex; flex-wrap: nowrap; gap: 1rem; align-items: center;
}
/* Not a footer so much as a copyright line: the small print a book carries on
   its copyright page, set once at the foot of the page. No rule above it, no
   columns and no heading, because there is nothing here to navigate. The one
   link is to the publisher, and About when the series has written one. */
footer.site {
  padding: 0 clamp(1rem, 4vw, 3rem) clamp(2rem, 5vw, 3rem);
  margin-top: clamp(3rem, 8vw, 5rem);
  font-family: var(--structural); font-size: .8125rem; color: var(--muted);
  /* Centred as a line, not just as a box. A copyright line is the small print at
     the foot of a title page and reads as one set in the middle; a
     measure-wide box with left-ranged text inside starts it left of centre and
     looks like a paragraph that lost its article. */
  text-align: center;
}
.colophon, .colophon-more { margin: 0; max-width: var(--measure); margin-inline: auto; }
.colophon-more { margin-top: .35rem; }
footer.site a { color: inherit; text-decoration-color: var(--edge); }
footer.site a:hover { color: var(--fg); text-decoration-color: currentColor; }
/* The name shrinks; it never wraps the bar. `nowrap` on the row plus a
   shrinkable brand is what makes it one line at EVERY width instead of at the
   widths this series' title happens to fit: an earlier version set the
   breakpoint from a measurement of "Andrew Lang: The Coloured Fairy Book
   Series" and would have gone back to a two-row bar the day a longer title
   arrived. The ellipsis is the last resort and does not fire here at any width.

   `clamp` on the size for the same reason. The name is the heading of the site
   and wants to read like one, but a fixed 1.5rem is 630px of title on a 736px
   bar, so it grows with the room it has.

   REGULAR 400, NOT BOLD, and the guide allows exactly this: "Regular 400 when
   lighter display is preferable". A masthead this long is where it is
   preferable, and the reason is measurable rather than aesthetic. At the
   breakpoint the brand's share of the bar is 469px (736 less the 4vw padding,
   the gap, and the search box's 12rem floor). The name was "Andrew Lang: The
   Coloured Fairy Book Series", 42 characters, which Cinzel Decorative Bold set
   at 471.5px there: an ellipsis at the one width the whole shrink order exists
   to protect. Regular was 443.2px.

   It is now "Andrew Lang's Coloured Fairy Books", and the measurement was taken
   again rather than assumed to still hold: 352px at the breakpoint, 497px at its
   1.5rem ceiling, and not clipped at any width from 736 to 1920. The budget has
   more room than it did, and Regular stays for the reason the guide gives rather
   than because it was forced. */
.brand { font-family: var(--display); font-weight: 400;
  text-decoration: none; letter-spacing: 0; line-height: 1.25;
  display: none; padding: .4rem 0;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (min-width: 46rem) {
  .brand { display: inline-block; font-size: clamp(1.0625rem, 2.2vw, 1.5rem); }
}

/* --- reading settings: text size and light or dark
 *
 * A <details> like the drawer, and beside it in the bar, because they are the
 * same kind of thing: a control that opens onto a small panel. It sits at the
 * END of row one so the reading controls are together on the right at every
 * width and the navigation stays on the left.
 *
 * HIDDEN WITHOUT JAVASCRIPT, and this is the one place in this file where that
 * is the right answer rather than a shortcut. Everything else here works with
 * scripting off because it resolves to a real link or a native <details>. These
 * buttons cannot: a preference has to be stored and re-applied on the next page,
 * and there is nowhere to store it. A panel that opened onto four buttons that
 * did nothing would be worse than no panel, so `.js` is added to <html> by the
 * head script and only then does this exist. The default size is the larger one
 * either way, which is the half that actually matters. */
.prefs { position: relative; margin-left: auto; display: none; }
html.js .prefs { display: block; }
.prefs-toggle {
  list-style: none; cursor: pointer; display: grid; place-items: center;
  width: 2.75rem; height: 2.75rem; margin: -.5rem -.6rem -.5rem 0;
  border-radius: 6px; font-family: var(--structural); font-size: 1.0625rem;
  line-height: 1; letter-spacing: .02em;
}
.prefs-toggle::-webkit-details-marker { display: none; }
.prefs-toggle:hover { background: var(--card); }
.prefs-toggle:focus-visible { outline: 3px solid var(--nav-ink); outline-offset: 2px; }
/* Anchored to the right edge of the bar rather than to the toggle, so it cannot
   hang off the side of a narrow screen. */
.prefs-panel {
  position: absolute; top: calc(100% + .55rem); right: 0; z-index: 30;
  width: max-content; max-width: min(19rem, calc(100vw - 2rem));
  background: var(--bg); border: 1px solid var(--edge); border-radius: 8px;
  box-shadow: 0 8px 30px rgba(0,0,0,.18);
  padding: .85rem .9rem 1rem;
}
.prefs-group { border: 0; margin: 0; padding: 0; }
.prefs-group + .prefs-group { margin-top: .9rem; }
.prefs-group legend {
  padding: 0; margin-bottom: .45rem;
  font-family: var(--structural); font-size: .75rem; color: var(--muted);
  text-transform: uppercase; letter-spacing: .1em;
}
/* WRAPS, because this row grows with the very setting it sets. Four buttons that
   cannot shrink below their own words are wider than a 320px screen once the
   reader picks one of the top two sizes, and the panel pushed the page sideways
   at exactly the setting somebody with poor eyesight would choose. Two rows of
   two is the right answer rather than smaller buttons: a reader who asked for
   large text should not be handed small controls. */
.prefs-row { display: flex; flex-wrap: wrap; gap: .3rem; }
.prefs-row button {
  flex: 1 1 auto; cursor: pointer; white-space: nowrap;
  font-family: var(--structural); font-size: .8125rem; line-height: 1;
  padding: .5rem .6rem; border-radius: 5px;
  border: 1px solid var(--edge); background: transparent; color: inherit;
}
.prefs-row button:hover { background: var(--card); }
/* THE PRESSED STATE IS THE ARIA STATE, not a class the script has to keep in
   step with it. One source for "which one is on", so a button cannot look
   chosen while telling a screen reader it is not. */
.prefs-row button[aria-pressed="true"] {
  border-color: var(--nav-ink); color: var(--nav-ink);
  background: var(--card); font-weight: 600;
}
/* The size buttons show their own effect: each is set at the size it selects,
   scaled down to fit the row, so the choice is legible as a difference rather
   than as four words. */
.prefs-row .size-s { font-size: .6875rem; }
.prefs-row .size-m { font-size: .8125rem; }
.prefs-row .size-l { font-size: .9375rem; }
.prefs-row .size-xl { font-size: 1.0625rem; }

/* --- the drawer: the whole shelf, from anywhere
 *
 * A <details>, not a scripted menu. It opens, closes and answers the keyboard
 * with no JavaScript, which is the line this engine already holds elsewhere:
 * the random link resolves to a real page with scripting off, and a drawer that
 * needed a bundle to open would be the one control that did not. reader.js adds
 * Escape and click-outside on top; neither is needed to use it. */
.drawer { position: relative; }
.drawer-toggle {
  list-style: none; cursor: pointer; display: grid; place-items: center;
  width: 2.75rem; height: 2.75rem; margin: -.5rem 0 -.5rem -.6rem;
  border-radius: 6px;
}
.drawer-toggle::-webkit-details-marker { display: none; }
.drawer-toggle:hover { background: var(--card); }
.drawer-toggle:focus-visible { outline: 3px solid var(--nav-ink); outline-offset: 2px; }
/* Three bars from one element: the box is the middle, the pseudo-elements are
   the outer two. */
.bars, .bars::before, .bars::after {
  display: block; width: 1.25rem; height: 2px; background: currentColor;
  border-radius: 2px; content: "";
}
.bars { position: relative; }
.bars::before { position: absolute; top: -6px; }
.bars::after  { position: absolute; top: 6px; }
.drawer[open] .bars { background: transparent; }
.drawer[open] .bars::before { top: 0; transform: rotate(45deg); }
.drawer[open] .bars::after  { top: 0; transform: rotate(-45deg); }

.drawer-panel {
  position: fixed; inset: 0 auto 0 0; width: min(20rem, 86vw); z-index: 30;
  background: var(--bg); border-right: 1px solid var(--edge);
  box-shadow: 0 0 40px rgba(0,0,0,.35);
  overflow-y: auto; overscroll-behavior: contain;
  /* Top padding clears the toggle, which is lifted over the panel below so it
     can still be pressed. Without both, the panel covered its own close button
     and the drawer could only be shut by Escape or a click outside: two things
     the script adds, so with scripting off it could not be closed at all. */
  padding: 4.25rem 1rem 3rem;
}
.drawer[open] .drawer-toggle { position: relative; z-index: 31; }
.drawer-nav { display: block; }
.drawer-top {
  display: block; padding: .6rem .5rem; text-decoration: none; color: inherit;
  border-radius: 4px; font-weight: 400; letter-spacing: .02em;
}
.drawer-top:hover { background: var(--card); }
.drawer-book { border-top: 1px solid var(--edge); }
.drawer-book > summary {
  cursor: pointer; padding: .7rem .5rem; list-style: none;
  display: flex; align-items: center; justify-content: space-between; gap: .5rem;
}
.drawer-book > summary::-webkit-details-marker { display: none; }
.drawer-book > summary::after {
  content: "\203a"; transform: rotate(90deg); color: var(--muted);
  transition: transform .12s ease;
}
.drawer-book[open] > summary::after { transform: rotate(-90deg); }
.drawer-book > summary:hover { background: var(--card); }
.drawer-book ul { list-style: none; margin: 0 0 .5rem; padding: 0 0 0 .5rem; }
.drawer-book li a {
  display: block; padding: .5rem .5rem; text-decoration: none; color: var(--muted);
  border-radius: 4px; font-size: .9375rem;
}
.drawer-book li a:hover { background: var(--card); color: var(--fg); }
.drawer-hub a { color: var(--nav-ink); }

/* THE MENU BAR, and the hamburger it replaces.
   On a phone the bar is two things on one line, the hamburger and the search,
   and the drawer holds the four destinations. Above the breakpoint they get a
   row of their own under the name and the search, and the hamburger goes: with
   the same four links already on the screen it opened onto a list of things the
   reader could see. The drawer's markup still ships, because it is what holds
   every book and every story, and it is only hidden. */
.site-nav { display: none; }
@media (min-width: 46rem) {
  .drawer { display: none; }
  .site-nav {
    display: flex; flex-wrap: wrap; gap: 0 1.75rem; align-items: baseline;
    margin-top: .55rem;
    /* CENTRED, which is what frees the band's outer edges. Prev and next go
       there on a reading page, outside the container everything else sits in. */
    justify-content: center;
  }
  .site-nav > a {
    flex: none; white-space: nowrap; text-decoration: none; color: var(--muted);
    padding: .35rem 0; font-weight: 400; letter-spacing: .02em;
    border-bottom: 2px solid transparent;
  }
  .site-nav > a:hover { color: var(--fg); border-bottom-color: var(--accent); }
}

/* The search box: full width beside the hamburger on a phone, and on a desktop
   it takes the row's spare width up to a sane measure rather than sitting at
   the placeholder's length. A typed search returns stories, books, motifs and
   concepts, so its results want room.

   WHAT GIVES WAY FIRST, stated rather than left to flexbox's proportional
   default: there are widths where the name and the box do not both fit. The
   search shrinks, because it is a hint and degrades to "Search Lang's Fairy B",
   and it has a floor. The name shrinks only after that floor is reached,
   because a site title cut mid-word reads as broken in a way a shortened
   placeholder does not. `flex-shrink: 100` against the brand's 1 orders it, and
   THE FLOOR GOES ON THE FORM: an input's automatic minimum is its `size` width,
   so a `min-width: auto` form floored the search at its full 263px and it never
   gave up a pixel while the name beside it was being cut. */
.site-bar .search { flex: 1 1 auto; min-width: 0; }
/* `min-width: 0` HERE, not only in the desktop block below. The input carries a
   12rem floor of its own, which is a floor in rem and therefore grows with the
   reader's chosen text size: at the largest step 12rem is 273px, and a 320px bar
   also holding a 44px hamburger and a 44px settings button cannot give it that.
   The bar overflowed sideways on every page at the top two sizes. On a phone the
   search takes the room that is left; the floor is re-established below the
   breakpoint's other side, where there is room for it. */
.site-bar .search input { width: 100%; min-width: 0; }
@media (min-width: 46rem) {
  .site-bar .search { flex: 1 100 auto; min-width: 12rem; max-width: 32rem; margin-left: auto; }
  .site-bar .search input { width: 100%; min-width: 0; }
  .brand { flex: 0 1 auto; }
}
.search { position: relative; }
.search input {
  font: inherit; padding: .3rem .6rem; border: 1px solid var(--edge);
  border-radius: 999px; background: var(--bg); color: var(--fg); min-width: 12rem;
}
/* The dropdown is as wide as the box it hangs from, and no wider: it used to be
   capped at 22rem while the box beside it grew, which put a narrow panel under a
   wide field. `left: 0; right: 0` rather than a width, so it follows the input
   at every size. */
#results {
  position: absolute; left: 0; right: 0; top: 2.4rem; z-index: 5; margin: 0; padding: .3rem;
  list-style: none; background: var(--bg); border: 1px solid var(--edge);
  border-radius: .5rem; box-shadow: 0 6px 24px rgba(0,0,0,.12);
  max-height: min(70vh, 32rem); overflow-y: auto; overscroll-behavior: contain;
}
#results li a {
  display: flex; gap: .75rem; justify-content: space-between; align-items: baseline;
  padding: .4rem .6rem; text-decoration: none; border-radius: 4px;
}
#results li a:hover { background: var(--card); }
/* THE GROUP HEADING IS THE POINT OF THE WHOLE THING. A reader typing
   "curiosity" gets back "Concept: Curiosity punished", and the word Concept is
   what tells them the site holds an idea by that name and not just a story
   title. Set as a label rather than as a row: it is not clickable and must not
   read as though it were. */
.res-head {
  font-size: .75rem; text-transform: uppercase; letter-spacing: .1em;
  color: var(--muted); padding: .6rem .6rem .2rem;
}
.res-head:first-child { padding-top: .2rem; }
.res-note { color: var(--muted); font-size: .8125rem; white-space: nowrap; flex: none; }
/* On a phone the book's name beside the title took half the row and pushed "The
   Tale of a Youth Who Set Out to Learn What Fear Was" onto four lines. Under it
   instead, where it is the same information at none of the cost. */
@media (max-width: 30rem) {
  #results li a, .res-section li a { flex-direction: column; gap: .1rem; align-items: flex-start; }
  .res-note { white-space: normal; }
}
.res-more a { color: var(--nav-ink); font-size: .8125rem; }
.res-none { padding: .5rem .6rem; color: var(--muted); }

/* --- the results page */
.search-page { max-width: var(--measure); margin-inline: auto; }
.search-big { margin: 1.25rem 0 2rem; }
.search-big input {
  font: inherit; font-size: 1.125rem; width: 100%; padding: .6rem 1rem;
  border: 1px solid var(--edge); border-radius: 999px;
  background: var(--bg); color: var(--fg);
}
.res-section { margin-bottom: 2.5rem; }
.res-section h2 {
  font-family: var(--structural); font-size: .8125rem; text-transform: uppercase;
  letter-spacing: .1em; color: var(--muted); margin: 0 0 .5rem;
  padding-bottom: .4rem; border-bottom: 1px solid var(--edge);
}
.res-section ul { list-style: none; margin: 0; padding: 0; }
.res-section li a {
  display: flex; gap: .75rem; justify-content: space-between; align-items: baseline;
  padding: .5rem 0; text-decoration: none; border-bottom: 1px solid var(--edge);
}
.res-section li:last-child a { border-bottom: 0; }
.search-none { color: var(--muted); }

main { padding: clamp(1.5rem, 5vw, 4rem) clamp(1rem, 4vw, 3rem); }
/* A story's title, a contents heading and a tag's name are editorial structure,
   which is Forum's job in the guide's hierarchy. Weight 400 because that is the
   only weight Forum has: leaving the old 600 here would have asked the browser
   to smear one, and a synthesised bold on an inscriptional face is exactly the
   effect the cover rules forbid. The hierarchy is carried by size, tracking and
   the rule under the head, which is how a printed page does it anyway. */
/* SIZED IN rem, EXPLICITLY, and this is not tidying. None of these carried a
   font-size at all, so every one of them was the browser's default `2em`, `1.5em`
   and `1.17em`: a multiple of whatever the PARENT was set to. That is why the
   reader's size control used to move the headings with the prose, and why a
   story title reached 40px on a 390px phone and wrapped to four lines. In rem
   they answer to the page, not to the paragraph they sit above.

   h1 is a clamp because a title is the one thing here whose length is not the
   designer's to choose: "The Goose-girl" and "Prince Hyacinth and the Dear
   Little Princess" want the same treatment and cannot have the same measure. */
h1, h2, h3 { font-family: var(--structural); line-height: 1.2; font-weight: 400; }
h1 { font-size: clamp(1.75rem, 1.15rem + 2.6vw, 2.375rem); }
h2 { font-size: 1.375rem; }
h3 { font-size: 1.125rem; }
.hub h1, .unit h1, .page h1 { max-width: var(--measure); margin-inline: auto; }
.shelf-lede { color: var(--muted); max-width: var(--measure); font-size: 1.0625rem;
  margin: 0 0 1.5rem; margin-inline: auto; }
.standfirst { color: var(--muted); max-width: var(--measure); margin-inline: auto; }

/* --- the shelf: the rainbow
 *
 * TWO COLUMNS from the narrowest screen up, not one. Plain
 * `minmax(13rem, 1fr)` needs 26rem plus the gap before a second card fits, so
 * every phone got one book per screen: a 2:3 cover at full width, then a
 * scroll, then the next one. A shelf that shows one book is not a shelf, and
 * the whole point of this series is seeing the colours side by side.
 *
 * ONE RULE, NO BREAKPOINT, and the breakpoint is why. The first version was an
 * explicit 2 up to 30rem and auto-fill above it, which opened a dead zone at
 * exactly the handover: at 480px `minmax(13rem, 1fr)` fits one track and the
 * explicit rule has just switched off, so the shelf dropped back to a single
 * column at the one width the fix was supposed to cover. Measured across nine
 * widths; 480px was the only one wrong, which is how a spot check at 390px
 * passed it.
 *
 * `min(13rem, calc(50% - .5rem))` says: a track is 13rem, but never more than
 * half a row. Below 26rem the half wins and there are exactly two; above it the
 * 13rem wins and auto-fill packs as many as fit. Two is the floor at every
 * width and there is no handover to get wrong. */
/* CENTRED TRACKS, and it took two goes because `justify-content: center` alone
   does nothing here. Two separate things were stopping it: `1fr` lets every
   track absorb the free space, so there is none left to distribute, and
   `auto-fill` keeps the empty columns, so three books in a four-column grid
   still pack against the left with the fourth track holding air. `auto-fit`
   collapses the empty track and a fixed track max leaves real free space, and
   only then does centring mean anything.
   The narrow rule keeps `1fr`, because on a phone the two columns SHOULD fill
   the width, and it keeps the `calc(50% - .5rem)` floor that makes it two
   columns at 320px rather than one. */
.shelf {
  justify-content: center;
  list-style: none; padding: 0; display: grid; gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(min(13rem, calc(50% - .5rem)), 1fr));
}
@media (min-width: 46rem) {
  /* 18rem is the measured card width the cover's own `sizes` already claims. */
  .shelf { grid-template-columns: repeat(auto-fit, minmax(13rem, 18rem)); }
}
@media (min-width: 40rem) { .shelf { gap: 2rem; } }
.shelf-card a { text-decoration: none; color: inherit; display: block; }
.shelf-name { display: block; margin-top: .75rem;
  font-family: var(--structural); font-weight: 400; font-size: 1.125rem; }
.shelf-blurb { color: var(--muted); font-size: .9375rem; }
.cover {
  display: grid; align-content: center; gap: .75rem; aspect-ratio: 2 / 3;
  width: 100%; border-radius: 3px; padding: 1.25rem; text-align: center;
  /* The style guide's own Main, where it declares one, falling back to the
     derived accent for a series that has no guide. A full-bleed field carries
     no body text, so this is the one place the raw identity colour belongs; the
     ink on it is picked at build time from the book's own two inks by whichever
     reads. */
  background: var(--cover-field, var(--accent)); color: var(--cover-ink, var(--on-accent));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--deep) 55%, transparent),
              0 2px 10px rgba(0,0,0,.18);
}
/* A <picture> IS A WRAPPER AND NOTHING ELSE, so it does not occupy a box.
   Every rule in this file targets the <img>; the <picture> now around one is
   `display: inline` by default, and an inline wrapper is where a stray baseline
   gap under an image comes from.

   MEASURED, AND IT CHANGES NOTHING TODAY, which is worth saying rather than
   leaving as an implied fix: with it and without it the hub's cover-to-title
   distance is 12px and the head is 305px tall, because every image here is
   already block-level (.cover is a grid, .plate img is a block) and block-level
   children raise no baseline. A first reading of that hub called a 4px
   difference between the wrapper's box and the image's a descender; it was the
   image's own margin-bottom sitting inside the wrapper, and the h1 never moved.

   It stays because it is what makes the paragraph above true by construction
   rather than by coincidence: the day a picture is placed in an inline flow,
   this is the rule that keeps the gap from appearing. A browser too old to know
   the value falls back to `inline`, which is today's behaviour either way. */
picture { display: contents; }

/* HEIGHT: AUTO, AND IT IS THE WHOLE FIX. A cover is a 2:3 field whatever shape
   the file is, and .cover says so, but an <img> arrived at its own full height
   anyway: 285x600 for a square scan, 285x1200 for a tall one, on a card sized
   for 285x428.

   The `height` attribute is why. The build writes width and height on every
   <img> so the page reserves the right box before the bytes arrive, and HTML
   maps both to PRESENTATIONAL HINTS for the CSS properties of the same name.
   `width: 100%` above overrides the width hint; nothing here overrode the
   height one, so the used height was the file's own, and `aspect-ratio` was
   ignored, because a ratio only computes a dimension that is missing and
   neither was.

   So the ratio governs and the crop is what absorbs the difference. Removing
   the attributes instead would fix the shape and reintroduce the layout shift
   they are there to prevent. .unit-cover carried this rule for its thumbnail
   alone and so hid the bug at the one size anybody looked at. */
img.cover { object-fit: cover; padding: 0; height: auto; }
/* THE TITLE ON THE COVER, which is the one place the guide asks for Bold. The
   size came down from 1.35rem with the face: Cinzel Decorative sets a word half
   again as wide as the old stack, and the narrowest card on a 320px phone gives
   the title 96px to work in. At 1.25rem the longest word in this series ("Fairy",
   68px) has room to spare; a longer one hyphenates rather than chopping, for the
   reason the pager gives at the foot of this file. */
.cover-title { font-family: var(--display); font-weight: 700;
  font-size: 1.25rem; line-height: 1.15; hyphens: auto; }
.cover-year { font-family: var(--structural); font-size: .8125rem; letter-spacing: .12em; }
.cover-rule { height: 1px; background: currentColor; opacity: .55; }

/* --- a book hub
 *
 * The head is a TIGHT stack. It was a 1rem grid gap around a 12rem cover with an
 * empty standfirst paragraph inside it, which read as a cover marooned in the
 * middle of the page. The renderer stopped emitting the empty paragraph; this
 * closes the rest, and pulls the cover in so the title sits with it rather than
 * a screen below it. */
/* POSITIONED, so the cover and the margin list can step out of the column at the
   reading breakpoint. Same mechanism as .unit, and the same reason. */
.hub { position: relative; }
.hub-head { display: grid; gap: .5rem; max-width: var(--measure); justify-items: start;
  margin-inline: auto; }
/* The cover is its own block now rather than the first cell of the head's grid,
   which is what lets it move into the margin. Stacked, it keeps the size and the
   spacing it had. */
.hub-plate { max-width: var(--measure); margin: 0 auto .25rem; }
.hub-plate .cover { max-width: 9rem; }
/* The BOOK'S OWN NAME, which is a major identity moment in the guide's sense
   and the only h1 on the site that gets the display face. A story's title does
   not: there are 74 of them and they are structure, not identity. */
.hub-head h1 { font-family: var(--display); margin: 0; hyphens: auto; }
.hub-head .standfirst, .hub-head .title-page { margin: 0; }
.title-page { font-style: italic; color: var(--muted); }

/* The contents list.
 *
 * Was a browser-numbered <ol> of bare links, each with the story's full
 * bibliographic source underneath: thirty-seven underlined lines interleaved
 * with thirty-seven citations, which is a bibliography, not a table of contents.
 * The credit moved to the story page (where it already was) and the number
 * became an authored element so it can hold its own gutter.
 *
 * Rows rather than list items: a hairline between entries, the number set in the
 * book's own colour, and no underline until hover or focus, so the eye reads a
 * column of TITLES instead of a column of links. */
.toc { max-width: var(--measure); padding-left: 0; list-style: none; margin: 1rem 0 0;
  margin-inline: auto; }
.toc li { margin: 0; border-top: 1px solid var(--edge); }
.toc li:last-child { border-bottom: 1px solid var(--edge); }
.toc li a {
  display: grid; grid-template-columns: 2.25rem 1fr; align-items: baseline;
  gap: .5rem; padding: .7rem .25rem; text-decoration: none; color: inherit;
}
.toc-n {
  font-family: var(--structural); font-size: .8125rem; font-variant-numeric: tabular-nums;
  color: var(--nav-ink); text-align: right; padding-top: .15em;
}
.toc-t { text-decoration: underline; text-decoration-color: transparent;
  text-underline-offset: .15em; transition: text-decoration-color .12s ease; }
.toc li a:hover .toc-t, .toc li a:focus-visible .toc-t { text-decoration-color: currentColor; }
.toc li a:hover { background: var(--card); }

.in-book, .matter-role {
  display: block; font-family: var(--structural); font-size: .8125rem; color: var(--muted);
}
/* EVERYTHING THAT IS NOT A STORY, as one stacked list. Stacked rather than a
   paragraph each, because in the margin these are a column of destinations under
   the book, and the same rule has to hold on a phone or the two layouts are two
   designs. */
.matter-nav {
  display: flex; flex-direction: column; gap: .25rem;
  max-width: var(--measure); margin: 1.25rem auto 0;
}
/* The whole row is the target. It was a link with a caption underneath it that
   was not one, which is a row that reads as one thing and behaves as two. */
.matter {
  display: block; padding: .45rem .25rem; text-decoration: none; color: inherit;
  border-radius: 5px;
}
.matter-title { text-decoration: underline; text-decoration-color: transparent;
  text-underline-offset: .15em; transition: text-decoration-color .12s ease; }
.matter:hover { background: var(--card); }
.matter:hover .matter-title, .matter:focus-visible .matter-title {
  text-decoration-color: currentColor;
}
.matter-role { text-transform: uppercase; letter-spacing: .1em; }

/* THE WAY BACK, at the top of a page that is not a story.
   A pill rather than a bare link: it is a control, it sits alone above a title
   with nothing to group it with, and underlined text at the top of a page reads
   as part of the heading. */
/* `flex` with `fit-content`, NOT `inline-flex`. Inline it was, and below the
   reading breakpoint the plate is still in the flow and also inline, so the two
   set on one line: the button hung off the right-hand end of the book's name
   instead of starting the column. Only visible under 1056px, where the plate has
   not yet been taken out of the flow by `position: absolute`. */
.back {
  display: flex; width: fit-content; align-items: center; gap: .4rem;
  margin: 0 0 1.25rem; padding: .3rem .7rem .3rem .55rem;
  border: 1px solid var(--edge); border-radius: 999px;
  font-family: var(--structural); font-size: .8125rem; line-height: 1.4;
  text-decoration: none; color: var(--nav-ink);
}
.back:hover { background: var(--card); }
.back-arrow { font-size: 1em; line-height: 1; }

/* --- the spine: the book's colour, down the page
 *
 * Only on a page that belongs to ONE book (body.has-accent). The shelf and the
 * indexes belong to all of them and get none.
 *
 * Fixed rather than a border on the article, so it runs the whole height of the
 * viewport and stays there through a long tale, which is what "all the way down
 * the page" means for a story that is nine screens long. At left: 0 with no
 * width contribution it cannot cause horizontal overflow, and the page's own
 * padding (min 1rem) keeps text well clear of it. */
body.has-accent::before {
  content: ""; position: fixed; left: 0; top: 0; bottom: 0; width: 7px;
  /* --accent: the book's OFFICIAL colour, the one on its cover, not a derived
     one. The version before this used --tinted, the per-mode readable
     derivative, because at 5px the real navy was hard to see on the night
     ground. That solved the wrong problem. A stripe that is too quiet wants
     more WIDTH; changing its colour instead trades away the only thing the
     stripe is for, which is being that book's red or that book's blue. */
  background: var(--accent); z-index: 10; pointer-events: none;
}

/* --- a unit
 *
 * CENTRED, and the margin that centring creates is then used. Left-aligned, the
 * column sat against the left edge of a wide screen with the whole right half
 * empty, which reads as a page that failed to lay out rather than as a
 * deliberate measure. Centred, the two margins are equal and the left one is
 * somewhere the book can live. */
.unit { max-width: var(--measure); margin-inline: auto; position: relative; }

/* The desktop arrangement lives at the END of this section, after every rule it
   overrides. It did not, and that cost a render: a media query adds no
   specificity, so the plate's own later `margin` and `border-bottom` won and the
   book sat on top of the accent rule with its label lapping into the text. */
/* .breadcrumb is gone with the markup that used it: the book plate above does
   that job now, and a rule matching nothing in the tree is a rule the next
   session has to check before they can change anything near it. */
.unit .credit { color: var(--muted); font-style: italic; margin: .35rem 0 0; }

/* The book plate: the cover and the book's name as ONE object, above the title.
   See book_view_unit() for why it is not a cover beside the heading any more.
   `inline-flex` so the plate is exactly as wide as its content and the hairline
   under it stops where the words stop, which is what makes it read as a plate
   rather than as a bar across the page. */
.unit-book {
  display: inline-flex; align-items: center; gap: .8rem;
  text-decoration: none; color: inherit;
  margin: 0 0 1.4rem; padding: 0 0 .9rem;
  border-bottom: 1px solid var(--edge);
}
.unit-book-text { display: grid; gap: .15rem; min-width: 0; }
.unit-book-label {
  font-family: var(--structural); font-size: .6875rem; line-height: 1;
  letter-spacing: .14em; text-transform: uppercase; color: var(--muted);
}
.unit-book-title { font-family: var(--structural); font-size: 1.0625rem; line-height: 1.2; }
a.unit-book:hover .unit-book-title { text-decoration: underline; text-underline-offset: .15em; }
.unit-head h1 { margin: 0; }
/* The gap the old title grid used to carry on its own margin. Without it the
   credit line sat directly on the drop cap and the tale appeared to begin inside
   the heading. */
.unit-head { margin-bottom: 1.75rem; }
/* NO FRAME, and it was two things rather than one. The base .cover carries an
   inset hairline AND a padded field in the book's own colour, which together
   drew a mat and a ruled edge around the artwork. Both are right on the shelf,
   where a cover is a card among cards and the field is what a book with no art
   yet is made of. Here it is one object in a margin and the art has its own
   printed border, so the ring goes and the padding with it: `padding: 0` is what
   removes the coloured mat, because the field is the element's background and
   the padding is the only place it shows. */
.unit-cover .cover { width: 3.5rem; padding: 0; gap: .2rem; box-shadow: none; }
/* The composed cover's lettering does not survive being shrunk to a thumbnail:
   at this size it is a coloured spine, so the type comes out and the rules
   stay. */
.unit-cover .cover-title, .unit-cover .cover-year { display: none; }
.unit-cover .cover-rule { opacity: .8; }

/* --- the wide arrangement: the column centred, the book in the margin beside it
 *
 * LAST IN THIS SECTION ON PURPOSE, after every rule it overrides. A media query
 * adds no specificity, so when this block sat earlier the plate's own later
 * `margin` and `border-bottom` won it: the book landed on top of the accent rule
 * with its label lapping into the first paragraph. Rendered, seen, and moved.
 *
 * 66rem IS THE MEASUREMENT, and it has now been wrong twice. The margin either
 * side is (container - measure) / 2. What has to fit in it is the plate plus the
 * space between the plate and the stripe, the stripe itself, and the space
 * between the stripe and the text: 144 + 52 = 196px at the narrow end. That is
 * available from about 1020px, so 66rem (1056px) clears it with 18px of slack.
 *
 * The first attempt said 64rem against a plate needing 204px in a 199px margin,
 * five pixels short, which would have clipped the book for every reader on a
 * 1024 to 1035px screen. Adding the stripe made the requirement bigger again.
 * The lesson is not "be more careful": it is that this number is derived from
 * four others, so `plate-fits-its-margin` in the gate now derives it too and
 * fails when the four stop adding up.
 *
 * Below it there is no margin to hold a book without crowding the text, so the
 * plate stays stacked above the title, where it already works. */
@media (min-width: 66rem) {
  /* THE BOOK GROWS WITH THE ROOM. Fixed at 7rem it was a postage stamp on a
     1920px screen with 640px of margin beside it.
     THE CEILING IS SET BY THE STORED PICTURE, not by taste: a cover's derivative
     ladder tops out at 320 real pixels, so 14rem (224px) is still 1.43x on a
     retina screen and anything wider starts serving a soft image. Going past it
     is a real option and a different job: derive a wider rendition and upload
     it, which needs the bucket and therefore the box. */
  /* ONE ARRANGEMENT, TWO PAGE TYPES. The book page and the story page put the
     same book in the same margin beside the same column, so they share the
     numbers rather than each keeping a copy. Two copies of five measurements is
     two chances to drift, and `plate-fits-its-margin` derives the breakpoint
     from these declarations: split them and it checks one layout while the other
     goes unmeasured. */
  .unit, .hub { --book: clamp(7rem, 12vw, 14rem); }
  .unit-book, .hub-plate {
    /* `right: 100%` puts it wholly outside the column, so no number here has to
       be kept in step with the measure. */
    position: absolute; top: 0; right: 100%;
    /* Wider than the book it holds at the narrow end, so the name sets on one
       line under it rather than breaking after "The Blue Fairy". */
    width: max(var(--book), 9rem);
    margin: 0 3.25rem 0 0; padding: 0; border-bottom: 0;
    /* Stacked and right-ranged, so the book and its label lean toward the text
       they belong to rather than away from it. */
    flex-direction: column; align-items: flex-end; gap: .5rem; text-align: right;
  }
  .unit-book-text { justify-items: end; }
  .unit-book-title { font-size: .9375rem; }
  .unit-cover .cover, .hub-plate .cover { width: var(--book); max-width: none; }
  /* The hub's cover carries no label under it, so it is right-ranged on its own
     rather than by the flex column the story page's plate uses. */
  .hub-plate { display: block; text-align: right; }
  /* ONE STRIPE, NEVER TWO. This is the page-edge stripe, moved: at this width it
     stops being a band down the side of the window and becomes the edge of the
     reading column itself, the way the boards of a book meet its pages. Same
     colour, same 7px, so it reads as the same object in a better place, and the
     one at the edge is switched off below rather than left to sit beside it. */
  .unit::before, .hub::before {
    content: ""; position: absolute; top: 0; bottom: 0; left: -1.5rem; width: 7px;
    background: var(--accent); border-radius: 4px;
  }
  /* The edge one goes. `column-page` is set on <body> by the build, because CSS
     cannot ask whether a page paints the accent beside its column, and only the
     layouts that do may switch the window-edge stripe off.
     It was `unit-page` until the book page took the same arrangement, at which
     point the name would have been false on half the pages that need it. A class
     that names the page TYPE has to be renamed every time a second type wants
     the behaviour; one that names the BEHAVIOUR does not. */
  body.column-page.has-accent::before { display: none; }

  /* THE TAGS COME UP INTO THE MARGIN, under the book they belong to, so a wide
     screen ends the tale with the tale and nothing after it.
     `top` is DERIVED from the book's own size rather than guessed: the cover is
     2:3, so its height is --book * 1.5, and the label block under it is two
     rem-sized lines plus their gap. The slack allows a book title that wraps to
     two lines, which the narrow end of the clamp does. */
  /* ONE TAG PER ROW in the margin. Wrapped, they came out as an uneven raft
     (one on the first line, two on the second) that read as a paragraph of
     buttons rather than a list; stacked and right-ranged they line up under the
     book on the same edge the plate and the stripe use. */
  .unit > .tags {
    position: absolute; right: 100%; top: calc(var(--book) * 1.5 + 5rem);
    width: max(var(--book), 9rem); margin: 0 3.25rem 0 0;
    flex-direction: column; align-items: flex-end; gap: .35rem;
  }
  /* NOTHING AFTER THE STORY. The pager the article carries is the mobile one;
     up here the same links live in the band. Exactly one of the two is ever
     displayed, and `display: none` takes the other out of the accessibility
     tree as well, so the links are never met twice. */
  .unit > .unit-nav { display: none; }

  /* THE BOOK'S OWN PAGES COME UP INTO THE MARGIN, under the cover, the same way
     a story's tags do. `top` is derived from the book's size rather than
     guessed: the cover is 2:3, so its height is --book * 1.5, and 1.25rem is the
     gap under it. The story page adds 5rem instead, because its plate carries
     two lines of label between the cover and whatever follows. */
  .hub > .matter-nav {
    position: absolute; right: 100%; top: calc(var(--book) * 1.5 + 1.25rem);
    width: max(var(--book), 9rem); margin: 0 3.25rem 0 0;
    align-items: flex-end; text-align: right;
  }
  /* Its rows sit against the same edge the cover and the stripe use, so the
     margin reads as one column rather than three things that happen to be left
     of the text. */
  .hub > .matter-nav .matter { padding-right: .25rem; }
}

/* --- prev and next in the band, on a wide screen
 *
 * `display: contents` on both wrappers so the three cells become children of the
 * header for positioning: the header is the positioned ancestor, and the cells
 * pin to the band's own outer edges, OUTSIDE the centred container the masthead
 * and the menu sit in. That is the room centring the menu freed up.
 *
 * The random cell goes: "Pick Random Story" is already a row in the menu two
 * centimetres away, and the same act offered twice in one band is clutter. */
.bar-pager { display: none; }
@media (min-width: 66rem) {
  .bar-pager, .bar-pager .unit-nav { display: contents; }
  .bar-pager .pager.random, .bar-pager .pager.empty { display: none; }
  /* ALIGNED TO THE BOTTOM OF THE BAND, on the menu's row. Centred vertically
     they straddled both rows: "PREVIOUS" cut across the masthead's line and
     "NEXT" sat directly under the Aa button with nothing between them. The
     band's own bottom padding is 1rem, so this puts their feet where the menu's
     are and the two rows read as one. */
  .bar-pager .pager.prev, .bar-pager .pager.next {
    position: absolute; bottom: 1rem;
    max-width: 11rem; padding: .3rem .5rem; border-radius: 5px;
  }
  .bar-pager .pager.prev { left: clamp(1rem, 4vw, 3rem); }
  .bar-pager .pager.next { right: clamp(1rem, 4vw, 3rem); }
  .bar-pager .pager:hover { background: var(--card); }
  /* Smaller than at the foot of a tale. Up here the title is a confirmation of
     a direction already named, not the thing being read, and a story name set
     as large as the menu competes with it. */
  .bar-pager .pager-role { font-size: .6875rem; }
  .bar-pager .pager-title {
    font-size: .8125rem; line-height: 1.2;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
  }
}
/* .unit-cover's own copy of the height/ratio pair is gone: it is the general
   rule now, and a duplicate that can drift is worse than no duplicate. */
/* Not on a poem: a drop cap belongs to the opening of prose, and :first-of-type
   would otherwise hand one to a tale that opens on a rhyme. */
.text > p:not(.verse):first-of-type::first-letter {
  float: left; font-size: 3.1em; line-height: .82; padding: .06em .08em 0 0;
  color: var(--tinted);
}
/* AND NOT ON MATTER, which is what `.text` carries besides tales. A drop cap
   opens a STORY. On the dedication's "TO" it is a three-line initial on a
   two-letter word, and on the colophon's "First published by Longmans, Green,
   and Co." it is one on a bibliographic note.
   Both were live, and only the colophon was noticed first because it was the new
   page: the dedication had been carrying it since the day matter pages started
   rendering through the story view, which is what a rule written for one page
   kind does when a second kind quietly qualifies. */
.matter-page .text > p:first-of-type::first-letter {
  float: none; font-size: inherit; line-height: inherit; padding: 0; color: inherit;
}
/* THE STORY, and the only thing the reader's size control reaches.
 *
 * 1.5 rather than the 1.65 this had. The leading was set before the type grew,
 * and a measure this narrow at 20px does not need that much air: at 1.65 the
 * lines drifted apart far enough that the eye lost the return sweep, which reads
 * as a page that is hard to follow rather than as a line-height that is wrong. */
.text { font-size: var(--prose); line-height: 1.5; }
.text p { margin: 0 0 1.15em; }

/* --- verse. The line is the unit, and the indent is meaning: a ballad's
   alternating indent is how the printed page says it is a ballad. */
.text p.verse { margin: 1.6em 0; font-style: italic; }
/* The <br>s are the no-CSS fallback; with the stylesheet the spans are already
   blocks and the breaks would double. */
.verse br { display: none; }
.verse .vl { display: block; text-indent: -1.4em; padding-left: 1.4em; }
.verse .vs { display: block; height: .8em; }
.verse .vl-1 { padding-left: 2.8em; }
.verse .vl-2 { padding-left: 4.2em; }
.verse .vl-3 { padding-left: 5.6em; }
.verse .vl-4 { padding-left: 7em; }
.editions { display: flex; gap: .5rem; font-family: var(--structural); font-size: .875rem; margin: 1rem 0 2rem; }
.edition { padding: .25rem .7rem; border: 1px solid var(--edge); border-radius: 999px; text-decoration: none; }
.edition.current { background: var(--ui-accent); color: var(--ui-on-accent); border-color: transparent; }

.plate { margin: 2rem 0; padding: .75rem; background: var(--paper); border-radius: 2px;
  box-shadow: 0 1px 6px rgba(0,0,0,.2); }
/* A vignette is the small decorative cut, not a full-page illustration, and it
   sits inside the measure rather than filling it. A hero opens the page. */
.plate-vignette { max-width: 16rem; margin-inline: auto; }
.plate-hero { margin-top: 0; }
.plate img { width: 100%; height: auto; display: block; }
.plate figcaption { font-family: var(--structural); font-size: .8125rem; color: #55504a; margin-top: .5rem; }
.plate .credit { display: block; opacity: .8; }

/* A DECLARED PLATE WITH NO IMAGE YET. The text knows where every illustration
   goes and what it shows; the files arrive separately and later. So this is the
   normal state of a freshly ingested volume, not a broken one, and it reads as
   a place held open: the printed caption, on the paper, in the plate's own
   slot. It gets no drop shadow, because nothing is sitting on the page yet. */
.plate-stub { box-shadow: none; border: 1px dashed var(--edge); background: none;
  display: flex; align-items: center; justify-content: center; min-height: 5rem;
  padding: 1.25rem; }
.plate-stub figcaption { margin-top: 0; text-align: center; font-style: italic;
  max-width: 28rem; }

/* ALSO TOLD AS: the same tale, in another country and another volume. This is
   what a tale-type number is for, and the number itself stays a quiet footnote
   under the list rather than a heading, because a reader wants the tales. */
.also { margin-top: 3rem; border-top: 1px solid var(--edge); max-width: var(--measure);
  margin-inline: auto; }
.also h2 { font-family: var(--structural); font-size: .8125rem; text-transform: uppercase;
  letter-spacing: .1em; color: var(--muted); font-weight: 400; }
.also-list { list-style: none; padding: 0; margin: .5rem 0 0; }
.also-list li { border-top: 1px solid var(--edge); }
.also-list li:last-child { border-bottom: 1px solid var(--edge); }
.also-list a { display: block; padding: .7rem .25rem; text-decoration: none; color: inherit; }
.also-list a:hover { background: var(--card); }
.also-type { font-family: var(--structural); font-size: .8125rem; color: var(--muted); }

.footnotes { margin-top: 3rem; border-top: 1px solid var(--edge); font-size: .9375rem; }
.footnotes-head { font-family: var(--structural); font-size: .8125rem; text-transform: uppercase; letter-spacing: .1em; }
.fn-ref a { text-decoration: none; padding: 0 .15em; }
.fn-back { text-decoration: none; }

.commentary {
  margin-top: 3rem; padding: 1.25rem 1.5rem; background: var(--card);
  border-left: 3px solid var(--accent); border-radius: 0 4px 4px 0;
}
.commentary h1, .commentary h2 { font-family: var(--structural); font-size: 1rem;
  text-transform: uppercase; letter-spacing: .08em; margin-top: 0; }
/* `overflow-wrap: anywhere` because this block carries a source URL, and a URL
   has no break opportunities in it: the Wikisource address sets as one 348px run
   at the largest text size and pushed the whole hub sideways, which is a
   horizontal scrollbar on a phone at exactly the setting a reader with poor
   eyesight would pick. It was always too wide to break, and was simply narrower
   than the screen until the text could grow. Scoped here rather than set
   globally, because breaking words mid-letter is right for an address and wrong
   for a tale. */
/* `.colophon-block` is gone with the markup that used it: the colophon is a page
   now, not a footer on the book page, so its rows are ordinary `.text`. What had
   to survive the move is the word-breaking, which is not decoration: the source
   URL is a single unbreakable token wider than a phone, and without this it
   pushed the whole column sideways at the largest reader size. Scoped to the
   colophon page rather than to `.text`, because breaking words mid-letter is
   right for an address and wrong for a tale. */
.colophon-page .text { overflow-wrap: anywhere; }

.tags { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: 2.5rem;
  justify-content: center; }
.tag { font-family: var(--structural); font-size: .8125rem; padding: .2rem .6rem;
  border: 1px solid var(--edge); border-radius: 999px; text-decoration: none; }
/* The pager: three fixed cells, so backwards is always on the left and forwards
 * is always on the right whether or not either exists. A flex row that dropped
 * an absent cell used to slide the remaining ones sideways, which is how "next"
 * ended up wherever there was room. */
.unit-nav {
  display: grid; grid-template-columns: 1fr auto 1fr; gap: .75rem;
  margin-top: 3rem; padding-top: 1.25rem; border-top: 1px solid var(--edge);
  font-family: var(--structural); align-items: start;
}
.pager { display: flex; flex-direction: column; gap: .15rem; text-decoration: none;
  color: inherit; padding: .5rem .25rem; border-radius: 4px; min-width: 0; }
.pager.empty { padding: 0; }
.pager.prev { text-align: left; }
.pager.random { text-align: center; align-items: center; }
.pager.next { text-align: right; align-items: flex-end; }
.pager-role {
  font-size: .8125rem; letter-spacing: .08em; text-transform: uppercase;
  color: var(--nav-ink); white-space: nowrap;
}
/* The labels are `nowrap`, so on the narrowest phones they do not wrap, they
   OVERFLOW: at 320px the three columns come to exactly the content width and
   "PREVIOUS" ran under "PICK RANDOM" with about three pixels between them.
   Set smaller and tighter there rather than letting them wrap, because "PICK /
   RANDOM" over two lines is a worse row than a slightly smaller one. */
@media (max-width: 23.5rem) {
  .pager-role { font-size: .75rem; letter-spacing: .03em; }
  .unit-nav { gap: .5rem; }
}
/* The chevrons live in CSS, not in the text, so a screen reader reads
   "Previous" and not "left angle bracket Previous". */
.pager.prev .pager-role::before { content: "\2039\00a0"; }
.pager.next .pager-role::after { content: "\00a0\203a"; }
/* HYPHENATE, NEVER CHOP. The cell is 103px on a 390px phone and
   "Weatherbeard" wants 107, so `overflow-wrap: anywhere` broke it after the r
   and the foot of The Nettle Spinner offered the reader "Farmer Weatherbear /
   d". `break-word` is no better: at 320px it made "Rapunzel" into "Rapunze /
   l". A story's title is the one string on this page a reader is meant to
   recognise, and four pixels is not worth making it unrecognisable.

   So the titles hyphenate where English allows it and otherwise overflow their
   cell by those few pixels. That is safe HERE and not in general: the titles
   sit on the grid's second row, where the middle cell is empty, so a title
   wider than its column runs into nothing. */
.pager-title {
  font-size: .9375rem; color: var(--muted); line-height: 1.3;
  hyphens: auto;
}
.pager:hover { background: var(--card); }
.pager:hover .pager-title { color: var(--fg); }
/* No box on the shuffle. It was drawn as a bordered control on the reasoning
   that it is an action rather than a place, which made one of three sibling
   links look like a different species and pushed its label off the line the
   other two share. Three cells, one kind, one row. */

/* --- indexes */
.a-to-z, .tag-list, .tag-cards { list-style: none; padding: 0; }
.a-to-z li { margin: .3rem 0; }
.a-to-z .letter { font-family: var(--structural); font-size: .8125rem; letter-spacing: .12em;
  color: var(--muted); margin-top: 1.5rem; }
.tag-list { display: flex; flex-wrap: wrap; gap: .5rem; }
.tag-list a { display: inline-flex; gap: .4rem; align-items: baseline;
  border: 1px solid var(--edge); border-radius: 999px; padding: .25rem .75rem;
  text-decoration: none; font-family: var(--structural); font-size: .875rem; }
.tag-list .count { color: var(--muted); font-size: .75rem; }
.tag-cards { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)); }
.tag-card { border-left: 4px solid var(--accent); padding-left: .9rem; }
.tag-card a { text-decoration: none; color: inherit; }

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* --- touch targets
 *
 * A tale's title in the contents list renders as a 19px inline link, and a book
 * hub is 37 of them stacked. On a phone that is a column of neighbours a thumb
 * cannot separate, and mis-tapping into the wrong story is the one navigation
 * failure a reader cannot work around: they do not know which story they meant
 * to open until they have lost it. Measured at 390px before this existed: 35
 * targets under 24px on the hub, 36 on the A-to-Z.
 *
 * Widened only where the pointer is COARSE. A mouse is precise at 19px and a
 * dense list is easier to scan, so paying for touch on every device would make
 * the desktop worse to fix something desktop does not have.
 */
@media (pointer: coarse) {
  /* .toc is deliberately absent. Its rows are already a full-width grid with
   * .7rem of padding, and the `display: block` this block used to apply to them
   * flattened that grid back into a stack, collapsing the number gutter on
   * exactly the devices the rule exists to serve. A rule that only fires on
   * touch is a rule nobody sees break. */
  .a-to-z li a,
  .matter a { display: block; padding: .5rem 0; }
  .a-to-z li { margin: 0; }
}
