/* 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;
  /* 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 OWN SIZE, and the default is deliberately larger than it was.
 *
 * 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 before read like
 * roughly 14.6px of an ordinary text face. That is why it looked small: not a
 * preference, a property of the face, and it is why the fix is a bigger number
 * here rather than a different opinion.
 *
 * A PERCENTAGE, never a pixel count. 100% is whatever the reader's own browser
 * setting says, so somebody who has already told their browser they want large
 * text keeps that and gets this on top of it. Pinning `16px` here would silently
 * throw their setting away, which is the accessibility bug that has to be
 * avoided rather than the one that has to be added.
 *
 * Everything in this file is in rem, so this one declaration carries the whole
 * page: prose, chrome, the reading measure, and the spacing between them all
 * grow together and the design keeps its proportions instead of stretching.
 *
 * The scale is a custom property so the reader's choice is one inline value on
 * <html>, set before first paint by the small script in the head. The fallback
 * in the var() is the default, so a reader with no stored choice and no
 * JavaScript at all still gets the larger setting. */
html {
  font-size: calc(100% * var(--reader-scale, 1.125));
  -webkit-text-size-adjust: 100%;
}
body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: 1.125rem/1.65 var(--reading);
}
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 {
  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);
}
.colophon, .colophon-more { margin: 0; max-width: var(--measure); }
.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 42-character masthead 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). Cinzel Decorative Bold sets
   "Andrew Lang: The Coloured Fairy Book Series" at 471.5px there and the name
   would ship with an ellipsis on it at the one width the whole shrink order
   exists to protect. Regular is 443.2px. The tracking goes for the same reason:
   .01em over 42 characters is another 7px of a budget with 26 to spare. */
.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;
  }
  .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); }
.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. */
h1, h2, h3 { font-family: var(--structural); line-height: 1.2; font-weight: 400; }
.hub h1, .unit h1, .page h1 { max-width: var(--measure); }
.shelf-lede { color: var(--muted); max-width: var(--measure); font-size: 1.0625rem;
  margin: 0 0 1.5rem; }
.standfirst { color: var(--muted); max-width: var(--measure); }

/* --- 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. */
.shelf {
  list-style: none; padding: 0; display: grid; gap: 1rem;
  grid-template-columns: repeat(auto-fill, minmax(min(13rem, calc(50% - .5rem)), 1fr));
}
@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. */
.hub-head { display: grid; gap: .5rem; max-width: 46rem; justify-items: start; }
.hub-head .cover { max-width: 9rem; margin-bottom: .25rem; }
/* 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; }
.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);
}
.matter { max-width: var(--measure); }
.matter-role { text-transform: uppercase; letter-spacing: .1em; }

/* --- 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 */
.unit { max-width: var(--measure); }
.breadcrumb { font-family: var(--structural); font-size: .875rem; margin: 0 0 .5rem; }
.unit .credit { color: var(--muted); font-style: italic; margin: .35rem 0 0; }

/* The title block: a small cover, then the title. The cover is what tells a
   reader which of the coloured books they are in without making them read the
   breadcrumb. */
/* The gap below matters as much as the one beside. The cover is taller than the
   title and credit stacked next to it, so its bottom edge sat almost against the
   drop cap and the story appeared to start inside the masthead. */
.unit-title { display: grid; grid-template-columns: 3.25rem 1fr; gap: .9rem;
  align-items: start; margin-bottom: 2rem; }
.unit-title h1 { margin: 0; }
.unit-cover .cover {
  width: 3.25rem; padding: .3rem; gap: .2rem; border-radius: 2px;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--deep) 55%, transparent),
              0 1px 4px rgba(0,0,0,.22);
}
/* 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; }
/* .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);
}
.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); }
.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 { margin-top: 4rem; font-size: .875rem; color: var(--muted);
  max-width: var(--measure); overflow-wrap: anywhere; }

.tags { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: 2.5rem; }
.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; }
}
