/* 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. */

:root {
  --measure: 34rem;
  --serif: Iowan Old Style, Palatino Linotype, Palatino, Georgia, serif;
  --sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-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);
}
@media (prefers-color-scheme: dark) {
  :root {
    --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; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: 1.0625rem/1.65 var(--serif);
}
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, footer.site {
  display: flex; flex-wrap: wrap; gap: 1rem; align-items: center;
  padding: 1rem clamp(1rem, 4vw, 3rem);
  border-bottom: 1px solid var(--edge);
  font-family: var(--sans); font-size: .9375rem;
}
header.site { position: sticky; top: 0; z-index: 20; background: var(--bg); }
footer.site { border-bottom: 0; border-top: 1px solid var(--edge); margin-top: 4rem;
  align-items: baseline; position: static; }
/* Padded to a real tap target. Setting line-height on it shrank the box to 19px
   tall, which the mobile harness caught: the text was the right size and the
   thing you press was not. */
.brand { font-weight: 600; text-decoration: none; letter-spacing: .01em; line-height: 1.25;
  display: inline-block; padding: .4rem 0; }
.brand-full { display: none; }
.brand-short { display: inline; }
@media (min-width: 46rem) {
  .brand-full { display: inline; }
  .brand-short { display: none; }
}

/* --- 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: 600;
}
.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); }

/* On a phone the drawer already holds these, so the bar keeps only the search,
   full width on its own row and aligned with everything else. `margin-left:auto`
   is for the wide layout, where the nav sits opposite the brand; left on it
   below that, the wrapped search row floated off to the right on its own. */
.site-nav { display: flex; gap: 1rem; align-items: center; flex-wrap: wrap; width: 100%; }
.site-nav > a { display: none; }
.site-nav .search { flex: 1; }
.site-nav .search input { width: 100%; }
@media (min-width: 46rem) {
  .site-nav { margin-left: auto; width: auto; }
  .site-nav > a { display: inline; }
  .site-nav .search { flex: 0 0 auto; }
  .site-nav .search input { width: 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: 9rem;
}
#results {
  position: absolute; right: 0; top: 2.2rem; z-index: 5; margin: 0; padding: .25rem;
  list-style: none; background: var(--bg); border: 1px solid var(--edge);
  border-radius: .5rem; max-width: 22rem; box-shadow: 0 6px 24px rgba(0,0,0,.12);
}
#results li a { display: block; padding: .35rem .6rem; text-decoration: none; }
#results li a:hover { background: var(--card); }

main { padding: clamp(1.5rem, 5vw, 4rem) clamp(1rem, 4vw, 3rem); }
h1, h2, h3 { line-height: 1.2; font-weight: 600; }
.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-weight: 600; }
.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;
  background: var(--accent); color: 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);
}
img.cover { object-fit: cover; padding: 0; }
.cover-title { font-size: 1.35rem; line-height: 1.15; }
.cover-year { font-family: var(--sans); 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; }
.hub-head h1 { margin: 0; }
.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(--sans); 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(--sans); 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(--sans); 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 img.cover { height: auto; aspect-ratio: 2 / 3; }
/* 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(--sans); 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(--sans); 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; }

.footnotes { margin-top: 3rem; border-top: 1px solid var(--edge); font-size: .9375rem; }
.footnotes-head { font-family: var(--sans); 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(--sans); font-size: 1rem;
  text-transform: uppercase; letter-spacing: .08em; margin-top: 0; }
.colophon-block { margin-top: 4rem; font-size: .875rem; color: var(--muted); max-width: var(--measure); }

.tags { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: 2.5rem; }
.tag { font-family: var(--sans); 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(--sans); 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 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"; }
.pager-title {
  font-size: .9375rem; color: var(--muted); line-height: 1.3;
  overflow-wrap: anywhere;
}
.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(--sans); 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(--sans); 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; }
}
