/* dibbler.co.uk — v2 skeleton theme
   Kidby-sketch direction: black/white/grey pencil aesthetic. Monochrome
   throughout; hierarchy from weight, hatching and hand-drawn borders.
   v2 (Steven's feedback): header wash-out art slot, varied sketch borders,
   shaded + inverse block styles, cover hover cross-fade, hero story art
   breaking its frame, editions carousel, 2-col intro. */

:root {
  --ink: #1c1b19;
  --ink-soft: #4a4741;
  --ink-faint: #8d897f;
  --paper: #fbfaf6;
  --paper-shade: #e9e6dc;   /* darker than v1 — Steven wanted more pop */
  --line: #b9b4a8;
  --accent: #9d3b2b;        /* red ochre — the "one wash" over the pencil */
  /* Pencil tone ramp for The Reading Bar (assets/timeline.js): eight steps for
     the eight biggest settings, plus one for everything past them. These live
     here rather than in the block's own CSS because segments carry
     `--tone: var(--gN)` inline — if --gN is undefined the custom property is
     invalid and the segment paints TRANSPARENT, which is a blank bar with
     perfectly correct geometry. That is exactly how it shipped: the widths
     measured right and nothing was visible. */
  --g1: #2b2924; --g2: #3f3c36; --g3: #55514a; --g4: #6b665e;
  --g5: #847e74; --g6: #9d968b; --g7: #b5aea3; --g8: #cdc6ba;
  --go: #e2dccf;            /* the tail beyond the top eight */
  /* A scene with no recorded setting: painted as the empty lane track, so it
     reads as ABSENCE rather than as another place. Ranking it by weight gave it
     the darkest ink in Witches Abroad and let missing data dominate the block. */
  --gu: #eceadf;
}
/* dev toggle: accent off = pure monochrome for comparison */
body.noaccent { --accent: var(--ink); }
/* dev toggle: hide the ⟨generated⟩ block markers to judge real spacing */
body.hideblocks .gen { outline: none; }
body.hideblocks .gen::before { display: none; }

* { box-sizing: border-box; }
/* THE SHEET: the site itself carries NB's paper (grain extracted from the
   artwork by tools/make_paper.py, tiled). The drawings are stripped to pure
   ink by prep_art.py and multiplied onto it — so the texture Steven liked is
   everywhere and the hard image edge exists nowhere. */
body {
  margin: 0;
  font: 17px/1.65 Georgia, 'Times New Roman', serif;
  color: var(--ink);
  background-color: var(--paper);
  background-image: url('paper.png');
  background-blend-mode: multiply;
}
a { color: var(--ink); text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { text-decoration-style: wavy; }

/* ---------- hand-drawn boxes: FOUR border variants to break repetition.
   Assign .sb-a/.sb-b/.sb-c/.sb-d, or let grids auto-vary via nth-child. */
.sketchbox {
  border: 2px solid var(--ink);
  border-radius: 255px 15px 225px 15px / 15px 225px 15px 255px; /* variant a */
  padding: 18px 22px;
  background: transparent;   /* let the page's paper grain run through */
}
.sb-b { border-radius: 15px 255px 15px 225px / 225px 15px 255px 15px; }
/* c + d toned down — v2's were too curvy (Steven) */
.sb-c { border-radius: 20px 170px 18px 160px / 160px 18px 170px 20px; border-width: 1.5px; }
.sb-d { border-radius: 170px 22px 180px 16px / 16px 180px 22px 170px; border-style: solid; border-width: 2.5px 1.5px 1.5px 2.5px; }
.tilegrid > :nth-child(4n+2) .sketchbox { border-radius: 15px 255px 15px 225px / 225px 15px 255px 15px; }
.tilegrid > :nth-child(4n+3) .sketchbox { border-radius: 20px 170px 18px 160px / 160px 18px 170px 20px; border-width: 1.5px; }
.tilegrid > :nth-child(4n) .sketchbox   { border-radius: 170px 22px 180px 16px / 16px 180px 22px 170px; }

/* shaded panel — the "Tray look", kept as a reusable break-up style.
   Carries the grain too, or it reads as a clean rectangle on a textured page */
.shaded {
  background-color: var(--paper-shade);
  background-image: url('paper.png');
  background-blend-mode: multiply;
}
/* full inverse — dark paper, light ink; use sparingly for punch.
   v4: softened from near-black #232120 — too harsh against the page (Steven) */
.inverse { background: #45413a; color: var(--paper); border-color: #45413a; }
.inverse a, .inverse .handnote { color: var(--paper); }
.inverse .hatchbar { border-color: var(--paper); }
.inverse .hatchbar > div { background: repeating-linear-gradient(45deg, var(--paper) 0 2px, transparent 2px 5px); }

.wrap { max-width: 1040px; margin: 0 auto; padding: 0 20px 80px; }

/* ---------- header: content band on clean paper, then the sketch skyline
   as a full-width frieze below it, washing out into the page. The art is
   linear/full-width (NB skyline, approved 16 July 2026) so text sits ABOVE
   it rather than on it. multiply-blend melts the art's paper into ours. */
header.site { margin-bottom: 30px; }
/* art locked to theme width, flush top, edge-to-edge (no side padding);
   anchored to the image TOP so tower tops survive; strong bottom wash so
   the masthead can ride up over the faded zone */
.artwrap { max-width: 1040px; margin: 0 auto; padding: 0; position: relative; }
.headerart {
  /* fixed height on desktop (aspect-ratio + max-height shrinks the WIDTH —
     learned the hard way); mobile swaps to ratio-hugging in the 700px query.
     -flat = paper flat-fielded to pure white by tools/prep_art.py, so
     multiply leaves ONLY the pencil: no rectangle, no edge, no tint. */
  height: 225px;
  background: url('header-art-flat.png') center top / cover no-repeat;
  mix-blend-mode: multiply;
  -webkit-mask-image: linear-gradient(to bottom, #000 0, #000 42%, transparent 94%);
  mask-image: linear-gradient(to bottom, #000 0, #000 42%, transparent 94%);
  pointer-events: none;
}
/* deeper wash for banners whose drawing runs to the bottom edge (the witches
   moorland does; the skyline doesn't). Part of the theme config: each themed
   banner declares its fade depth. */
.headerart.fade-deep {
  -webkit-mask-image: linear-gradient(to bottom, #000 0, #000 26%, transparent 86%);
  mask-image: linear-gradient(to bottom, #000 0, #000 26%, transparent 86%);
}
.strap {
  position: absolute; top: 6px; left: 44px; margin: 0;
  color: var(--ink-soft); font-family: 'Caveat', cursive; font-size: 1.35em;
  text-shadow: 0 0 10px var(--paper), 0 0 4px var(--paper);
}
/* masthead rides up into the wash; paper halo keeps the text clean */
header.site .wrap { margin-top: -42px; position: relative; }
header.site .wrap a { text-shadow: 0 0 10px var(--paper), 0 0 5px var(--paper); }
header.site .wrap {
  padding-top: 6px; padding-bottom: 12px;
  display: flex; align-items: baseline; gap: 26px; flex-wrap: wrap;
}
.brand {
  font-size: 2.1em; font-weight: bold; letter-spacing: 0.5px;
  font-variant: small-caps; text-decoration: none;
  text-shadow: 0 0 14px var(--paper), 0 0 6px var(--paper);
}
.brand .tld { color: var(--ink-faint); font-variant: normal; font-size: 0.6em; }
nav.main { display: flex; gap: 20px; flex-wrap: wrap; font-variant: small-caps; }
nav.main a { text-decoration: none; border-bottom: 2px solid transparent; }
nav.main a:hover, nav.main a.here { border-bottom: 2px solid var(--accent); }

/* ---------- headings & prose ---------- */
h1, h2, h3 { font-weight: bold; line-height: 1.2; }
h1 { font-size: 2.1em; font-variant: small-caps; margin: 0 0 4px; }
h2 {
  font-size: 1.35em; font-variant: small-caps; margin: 44px 0 14px;
  border-bottom: 1px solid var(--line); padding-bottom: 4px;
}
.handnote { font-family: 'Caveat', cursive; color: var(--ink-soft); font-size: 1.15em; }
/* interstitial prose band — content between blocks to breathe.
   v3: larger + darker; v2's felt too small for the space (Steven) */
.between {
  max-width: 720px; margin: 44px auto; text-align: center;
  color: var(--ink); font-style: italic; font-size: 1.28em; line-height: 1.5;
}
.between::before, .between::after {
  content: "· · ·"; display: block; letter-spacing: 8px; color: var(--ink-faint);
  font-style: normal; margin: 8px 0;
}

/* ---------- generated-block markers (SKELETON ONLY) ---------- */
.gen { position: relative; outline: 2px dashed var(--ink-faint); outline-offset: 4px; margin: 26px 0; }
.gen::before {
  content: "⟨generated⟩ " attr(data-block);
  position: absolute; top: -1.7em; right: 0; z-index: 5;
  font: 12px/1 monospace; color: var(--paper);
  background: var(--ink-soft); padding: 2px 8px; border-radius: 3px;
}

/* ---------- layout helpers ---------- */
.cols { display: flex; gap: 34px; align-items: flex-start; }
.cols .main { flex: 1; min-width: 0; }
.cols .side { width: 280px; flex-shrink: 0; }
.stack > * + * { margin-top: 18px; }
@media (max-width: 820px) { .cols { flex-direction: column; } .cols .side { width: auto; } }

/* A column pair with a FULL-WIDTH block beneath it (book.html's editions).
   Opt-in via .has-full, because four other pages use .cols as a plain
   two-column flex row and must not change.

   Grid rather than moving the block out to a sibling of .cols: grid keeps it
   inside the same source order, and lets the 820px breakpoint place it BEFORE
   the sidebar — editions is primary content, the sidebar is secondary, and a
   sibling <div> after .cols would always stack after it on mobile.

   Row 2 starts below whichever column is taller, so no overlap is possible.
   Measured across all 41 books the two columns end within -37 to +115px of
   each other, so the leftover whitespace is at most about a line and a half. */
.cols.has-full {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 280px;
  column-gap: 34px;
  align-items: start;
}
.cols.has-full > .main { grid-column: 1; grid-row: 1; }
.cols.has-full > .side { grid-column: 2; grid-row: 1; width: auto; }
.cols.has-full > .fullrow { grid-column: 1 / -1; grid-row: 2; min-width: 0; }
@media (max-width: 820px) {
  .cols.has-full { grid-template-columns: minmax(0, 1fr); }
  .cols.has-full > .main    { grid-column: 1; grid-row: 1; }
  .cols.has-full > .fullrow { grid-column: 1; grid-row: 2; }
  .cols.has-full > .side    { grid-column: 1; grid-row: 3; }
}

.tilegrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 18px; }
.tile { text-decoration: none; display: block; }
.tile .sketchbox { height: 100%; }
.tile:hover .sketchbox { background: var(--paper-shade); }
.tile h3 { margin: 0 0 6px; font-variant: small-caps; }
.tile p { margin: 0; color: var(--ink-soft); font-size: 0.92em; }

/* ---------- collection cards (characters / places grids) ---------- */
.cardgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(148px, 1fr)); gap: 16px; }
.card { text-decoration: none; display: block; position: relative; }
.card .sketchbox { height: 100%; text-align: center; padding: 12px 12px 14px; }
.card:hover .sketchbox { background: var(--paper-shade); }
.card .portrait {
  width: 100%; aspect-ratio: 1 / 1; background-size: cover; background-position: center 12%;
  border: 1.5px solid var(--ink); border-radius: 14px 200px 16px 190px / 190px 16px 200px 14px;
  margin-bottom: 9px; display: flex; align-items: center; justify-content: center;
  background-color: var(--paper-shade);
}
.cardgrid > :nth-child(3n+2) .portrait { border-radius: 190px 16px 200px 14px / 14px 200px 16px 190px; }
.cardgrid > :nth-child(3n) .portrait   { border-radius: 18px 160px 20px 170px / 170px 20px 160px 18px; }
.card .portrait .letter { font-family: 'Caveat', cursive; font-size: 3.2em; color: var(--accent); }
.card h3 { margin: 2px 0 2px; font-variant: small-caps; font-size: 1.02em; line-height: 1.15; overflow-wrap: anywhere; }
.card .meta { margin: 0; color: var(--ink-soft); font-size: 0.82em; }
/* ---------- books collection: large cards, hero + description ---------- */
.filterbar {
  display: flex; flex-wrap: wrap; gap: 12px 18px; align-items: center;
  margin: 6px 0 26px; padding: 14px 18px; border: 1.5px solid var(--line);
  border-radius: 18px 160px 20px 150px / 150px 20px 160px 18px;
  background: rgba(255, 255, 255, 0.45);
}
.filterbar label { font-variant: small-caps; font-size: 0.9em; color: var(--ink-soft); display: flex; gap: 6px; align-items: center; }
.filterbar select {
  font-family: inherit; font-size: 0.92em; padding: 4px 6px; max-width: 210px;
  border: 1px solid var(--line); border-radius: 6px; background: var(--paper); color: var(--ink);
}
.filterbar .fcount { margin-left: auto; font-size: 0.9em; }
.filterbar .fclear { cursor: pointer; }
.booklist { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 24px; }
.bookcard { text-decoration: none; display: block; position: relative; }
.bookcard .sketchbox { height: 100%; padding: 0; overflow: hidden; display: flex; flex-direction: column; }
/* dev-only per-card framing gizmo button — shows only when block mode is visible */
.framebtn { display: none; position: absolute; top: 9px; right: 9px; z-index: 5;
  border: 1.5px solid var(--ink); background: var(--paper); border-radius: 7px;
  font-size: 0.95em; line-height: 1; padding: 4px 7px; cursor: pointer; }
.framebtn:hover { background: var(--paper-shade); }
body:not(.hideblocks) .bookcard .framebtn,
body:not(.hideblocks) .card .framebtn { display: block; }
.bookcard:hover .sketchbox { background: var(--paper-shade); }
.bookcard:nth-child(3n+2) .sketchbox { border-radius: 15px 235px 15px 225px / 225px 15px 235px 15px; }
.bookcard:nth-child(3n) .sketchbox   { border-radius: 200px 20px 210px 16px / 16px 210px 20px 200px; }
.bc-hero {
  aspect-ratio: 3 / 2; background-size: cover; background-position: center top;
  background-color: var(--paper-shade); border-bottom: 1.5px solid var(--ink);
  display: flex; align-items: center; justify-content: center;
}
.bc-hero .letter { font-family: 'Caveat', cursive; font-size: 4em; color: var(--accent); }
.bc-body { padding: 12px 17px 17px; }
.bookcard h3 { margin: 0 0 4px; font-variant: small-caps; font-size: 1.18em; }
.bc-meta { margin: 0 0 9px; font-size: 0.82em; color: var(--ink-soft); }
.bc-meta .st.done { color: var(--accent); font-weight: bold; }
.bc-meta .st.now { color: var(--accent); font-style: italic; }
.bc-desc { margin: 0; font-size: 0.9em; color: var(--ink-soft); line-height: 1.5; }
.collsub { font-variant: small-caps; font-size: 1.2em; color: var(--ink-soft); margin: 30px 0 12px; border-bottom: 1px solid var(--line); padding-bottom: 4px; }

/* mini stat boxes (headline numbers, sidebar vitals) */
.mini { text-align: center; padding: 14px 16px; }
.mini .big { font-size: 1.9em; font-weight: bold; line-height: 1.1; }

/* ---------- book page ---------- */
/* Header row: the hero art and the side box FLOAT, and the title block and the
   introduction run in normal flow beside them. Flex was the obvious choice and
   was wrong: it puts the intro in a row of its own BELOW the tallest column, so
   a 400px hero sitting next to a 150px title block left a dead gap the width of
   the page (Steven, 29 Aug 2026). Floats let the prose fill that gap and then
   widen to the full measure once it clears the art. */
.bookhead { display: block; }
.bookhead::after { content: ''; display: block; clear: both; }
.bookhead > .headside { float: right; width: 225px; margin: 0 0 18px 30px; }
.bookhead > .headtext { min-width: 220px; }
.bookhead > .blurb { margin-top: 16px; }

/* hero story art: NOT a cover — an AI illustration of the story that
   escapes its frame: no border, edges dissolving into the paper. The art is
   drawn on its own off-white paper; multiply melts that onto ours. */
.heroart {
  width: 300px; aspect-ratio: 3 / 4; flex-shrink: 0;
  float: left;
  margin: -10px 30px 6px -20px;
  transform: rotate(-1.5deg);
  background: url('hero-equal-rites-flat.png') center / contain no-repeat;
  mix-blend-mode: multiply;
  /* no mask: prep_art.py made the paper pure white, so the drawing already
     dissolves into the page on its own — masking would only eat the art */
}

/* cover with hover cross-fade: sketch layer on top, colour beneath revealed
   on hover. Both layers are build-time images; here: placeholders. */
.coverflip {
  /* display:block matters — this is an <a>, and inline elements ignore
     width/height. Inside .carousel its flex parent blockified it; inside the
     wrapping .covergrid nothing did, and the card collapsed to a few pixels. */
  display: block; position: relative;
  width: var(--cover-w, 190px); height: calc(var(--cover-w, 190px) / 0.635);
  flex-shrink: 0;
  border: 2px solid var(--ink);
  border-radius: 105px 8px 95px 8px / 8px 95px 8px 105px;
  overflow: hidden;
}
.coverflip .layer {
  position: absolute; inset: 0; display: flex; align-items: center;
  justify-content: center; text-align: center; font-size: 0.8em; padding: 8px;
  background-size: cover; background-position: center;
}
.coverflip .sketch {
  background-color: var(--paper);
  color: var(--ink-soft); font-style: italic;
  transition: opacity 0.45s ease;
}
/* no drawing yet: the hatch stands in, with the title set over it */
.coverflip .sketch.placeholder {
  background:
    repeating-linear-gradient(45deg, rgba(28,27,25,0.13) 0 2px, transparent 2px 6px),
    var(--paper-shade);
}
.coverflip .sketch.placeholder span {
  font-family: Georgia, 'Times New Roman', serif; font-style: normal;
  font-variant: small-caps; font-size: 1.15em; line-height: 1.25;
  color: var(--ink); text-shadow: 0 0 6px var(--paper), 0 0 6px var(--paper);
}
.coverflip .colour { background-color: var(--paper-shade); }
/* a 60x90 thumbnail blown up to card size is mush — soften rather than pixelate */
.coverflip .colour.lowres { image-rendering: auto; filter: blur(0.3px); }
.coverflip .colour.nocover span { color: var(--ink-soft); font-style: italic; }
.coverflip:hover .sketch, .coverflip:focus-visible .sketch { opacity: 0; }
.coverflip:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* editions gallery: a wrapping shelf of covers (the carousel is for the single
   strip on a book page; this is the whole collection) */
.covergrid { display: flex; flex-wrap: wrap; gap: 20px 18px; margin: 4px 0 30px; }
.covergrid figure { margin: 0; text-align: center; width: var(--cover-w, 190px); }
.covergrid figcaption { font-size: 0.8em; color: var(--ink-soft); margin-top: 8px; }
.collsub .shelflink { font-size: 0.72em; font-variant: normal; margin-left: 10px; }

/* edition popover — the full publication record for one printing */
.edpop {
  display: none; position: absolute; z-index: 60; width: 290px;
  background: var(--paper); border: 1.5px solid var(--ink);
  border-radius: 16px 90px 18px 84px / 84px 18px 90px 16px;
  padding: 16px 18px 14px; box-shadow: 3px 4px 0 rgba(28,27,25,0.13);
}
/* with the cover plate in it the panel can outgrow a laptop viewport */
.edpop { width: 320px; max-height: 82vh; overflow-y: auto; }
.edpop h4 { margin: 0 0 10px; font-size: 1.05em; padding-right: 18px; }
/* the cover at a size you can actually read; click through for full size */
.edpop .pop-plate { display: block; margin: 0 auto 12px; width: 172px; }
.edpop .pop-plate img {
  display: block; width: 100%; height: auto; border: 1.5px solid var(--ink);
  box-shadow: 2px 3px 0 rgba(28,27,25,0.13);
}
.edpop .pop-plate.lowres img { image-rendering: auto; }
.edpop .erow {
  display: flex; gap: 10px; justify-content: space-between; align-items: baseline;
  padding: 4px 0; border-bottom: 1px solid var(--line); font-size: 0.9em;
}
.edpop .erow:last-of-type { border-bottom: none; }
.edpop .erow span { font-variant: small-caps; color: var(--ink-soft); white-space: nowrap; }
.edpop .erow b { text-align: right; font-weight: normal; }
.edpop .isbn { font-family: monospace; font-size: 0.92em; }
.edpop .pop-note { margin: 8px 0 0; font-size: 0.84em; }
.edpop .pop-buy { margin-top: 12px; display: flex; flex-wrap: wrap; gap: 6px; }
.edpop .pop-buy .chip { margin-right: 0; }
.edpop .pop-close {
  position: absolute; top: 8px; right: 14px; border: none; background: none;
  font-size: 1.3em; line-height: 1; cursor: pointer; color: var(--ink-soft);
  font-family: inherit; padding: 2px 4px;
}
.edpop .pop-close:hover { color: var(--ink); }

.chips { margin: 10px 0; }
.chip {
  display: inline-block; border: 1.5px solid var(--ink); border-radius: 20px;
  padding: 1px 12px; font-size: 0.82em; font-variant: small-caps;
  margin-right: 8px; text-decoration: none;
}
.prevnext { color: var(--ink-soft); font-size: 0.92em; }
.blurb { font-size: 1.12em; }  /* full width — v2's 640px cap looked adrift */
.blurb p:first-child::first-letter {
  /* spans ~2 lines and sits ON the text top, not floating above it
     (Steven: the old cap floated too high) */
  font-size: 3.4em; float: left; line-height: 0.72;
  padding: 0.06em 0.07em 0 0; margin-top: 0.06em;
}

/* editions table */
table.editions { border-collapse: collapse; width: 100%; font-size: 0.94em; }
.editions th {
  text-align: left; font-variant: small-caps; border-bottom: 2px solid var(--ink);
  padding: 6px 10px;
}
.editions td { padding: 7px 10px; border-bottom: 1px solid var(--line); vertical-align: top; }
.editions .yr { font-weight: bold; white-space: nowrap; }
.editions .isbn { font-family: monospace; font-size: 0.9em; color: var(--ink-soft); }
.buy { white-space: nowrap; font-size: 0.9em; }

/* cover carousel: horizontal scroll-snap strip of every publication */
.carousel {
  display: flex; gap: 16px; overflow-x: auto; padding: 10px 4px 16px;
  scroll-snap-type: x mandatory;
}
.carousel .coverflip { scroll-snap-align: center; }
.carousel figure { margin: 0; flex-shrink: 0; text-align: center; width: var(--cover-w, 190px); }
.carousel figcaption { font-size: 0.8em; color: var(--ink-soft); margin-top: 8px; }
.carousel::-webkit-scrollbar { height: 8px; }
.carousel::-webkit-scrollbar-thumb { background: var(--line); border-radius: 4px; }

/* ---------- stats widget ---------- */
.widget .controls { display: flex; gap: 12px; margin-bottom: 14px; flex-wrap: wrap; }
.widget select, .widget input {
  font: inherit; font-size: 0.92em; background: var(--paper);
  border: 1.5px solid var(--ink); border-radius: 8px 3px 8px 3px; padding: 4px 10px;
}
.statrow {
  display: grid; grid-template-columns: 200px 1fr 90px; gap: 12px;
  align-items: center; padding: 5px 0; border-bottom: 1px dotted var(--line);
}
.statrow .who { font-variant: small-caps; }
.statrow .num { text-align: right; font-variant-numeric: tabular-nums; color: var(--ink-soft); font-size: 0.9em; }
.hatchbar { height: 14px; border: 1.5px solid var(--ink); border-radius: 7px 2px 7px 2px; overflow: hidden; }
.hatchbar > div {
  height: 100%;
  background: repeating-linear-gradient(45deg, var(--accent) 0 2px, transparent 2px 5px);
}
/* compact list variant for sidebar mini-boards */
.minirow { display: flex; justify-content: space-between; gap: 10px;
  padding: 4px 0; border-bottom: 1px dotted var(--line); font-size: 0.93em; }
.minirow:last-child { border-bottom: 0; }
.minirow .who { font-variant: small-caps; }
.minirow .num { color: var(--ink-soft); font-variant-numeric: tabular-nums; }

/* ---------- in-page artwork slots (all art prep_art-flattened, multiply) ---
   spotart: square margin vignette for the sidebar — a single object, tucked
   between the boxes like marginalia. stripart: short wide scene strip used
   as a section divider in the main column. Both carry placeholder hatching
   until real NB art is wired: swap background to url('<name>-flat.png'). */
.spotart {
  width: 190px; aspect-ratio: 420 / 554;   /* the image's own trimmed ratio */
  margin: 4px auto;
  transform: rotate(2deg);
  background: url('witches-spot-flat.png') center / contain no-repeat;
  mix-blend-mode: multiply;
}
.stripart {
  aspect-ratio: 1400 / 346; height: auto; margin: 38px 0 6px;
  transform: rotate(-0.6deg);
  background: url('witches-divider-flat.png') center / contain no-repeat;
  mix-blend-mode: multiply;
}
/* themed art: the generator swaps these urls per page theme
   (witches/wizards/watch/... — see docs/site-artwork-prompts.md) */

/* quotes */
blockquote.dwquote {
  margin: 14px 0; padding: 4px 0 4px 20px; border-left: 3px solid var(--ink);
  font-style: italic; font-size: 1.08em;
}
blockquote.dwquote footer { font-style: normal; font-size: 0.85em; color: var(--ink-soft); }
.requote { font-size: 0.82em; }

/* ---------- footer ---------- */
footer.site {
  border-top: 3px double var(--ink); margin-top: 60px;
  color: var(--ink-soft); font-size: 0.86em;
}
footer.site .wrap { padding-top: 18px; padding-bottom: 30px; }
footer.site .cmot { font-family: 'Caveat', cursive; font-size: 1.25em; }

/* compact all-books list (grouped; tiles don't scale to 41 books) */
.bookcols { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 8px 34px; }
.bookrow {
  display: flex; gap: 10px; align-items: baseline; padding: 5px 2px;
  border-bottom: 1px dotted var(--line); text-decoration: none;
}
.bookrow:hover { background: var(--paper-shade); }
.bookrow .n { color: var(--ink-faint); font-variant-numeric: tabular-nums; width: 22px; text-align: right; }
.bookrow .t { font-variant: small-caps; flex: 1; }
.bookrow .st { font-size: 0.8em; color: var(--ink-soft); white-space: nowrap; }
.bookrow .st.done { color: var(--accent); font-weight: bold; }
.bookrow .st.now { color: var(--accent); font-style: italic; }

/* ---------- responsive (mobile squash) ---------- */
.navtoggle {
  display: none; font: inherit; font-variant: small-caps; background: none;
  border: 1.5px solid var(--ink); border-radius: 8px 3px 8px 3px;
  padding: 3px 14px; cursor: pointer; color: var(--ink);
}
@media (max-width: 700px) {
  /* centred masthead, strapline as a modest centred line, hamburger nav.
     Negative margin pulls the strapline up over the art's faded blank zone
     (the image's own bottom third is empty paper — without this there's a
     dead gap between picture and text) */
  .headerart { height: auto; aspect-ratio: 1920 / 634; margin-bottom: -9%; }
  .strap { position: static; text-align: center; padding: 2px 12px 0; font-size: 1.12em; }
  header.site .wrap {
    margin-top: 0; padding-top: 4px;
    flex-direction: column; align-items: center; gap: 8px;
  }
  .navtoggle { display: inline-block; }
  nav.main { display: none; flex-direction: column; align-items: center; gap: 10px; width: 100%; }
  body.navopen nav.main { display: flex; }
}
@media (max-width: 820px) {
  /* THE stacking fix: in column mode children must stretch to the viewport,
     not size to their content (this was the 822px phantom width) */
  .cols { align-items: stretch; }

  /* homepage headline boxes: side by side, scaled down */
  .cols .side .stack { display: flex; gap: 10px; }
  .cols .side .stack > * + * { margin-top: 0; }
  .cols .side .stack .sketchbox { flex: 1; padding: 10px 8px; }
  .cols .side .stack .mini .big { font-size: 1.2em; }
  .cols .side .stack .handnote { font-size: 0.9em; }

  /* book hero: title first, art second, cast third, introduction last, all at
     full width. Floats are dropped here — at this width there is no gap to
     fill, and a 300px float would leave a column of text two words wide. */
  .bookhead { display: flex; flex-direction: column; }
  .bookhead > .heroart { float: none; order: 2; width: 100%; max-width: 420px; margin: 0 auto; }
  .bookhead > .headtext { order: 1; }
  .bookhead > .headside { float: none; order: 3; width: auto; margin: 0; }
  .bookhead > .blurb { order: 4; }

  /* stat rows: name gets its own line, bar + number below */
  .statrow { grid-template-columns: 1fr 84px; }
  .statrow .who { grid-column: 1 / -1; padding-top: 3px; }

  /* editions table collapses to stacked cards (header row hidden) */
  table.editions, .editions tbody, .editions tr, .editions td { display: block; }
  .editions tr:first-child { display: none; }
  .editions tr { border-bottom: 1px solid var(--line); padding: 8px 0; }
  .editions td { border-bottom: 0; padding: 1px 6px; }
}

/* ---------- dev bar (skeleton only — never ships) ---------- */
.devbar {
  margin-top: 26px; padding: 10px 14px; border: 1px dashed var(--ink-faint);
  border-radius: 6px; font-size: 0.85em; display: flex; gap: 18px; flex-wrap: wrap;
  align-items: center;
}
.devbar b { font-variant: small-caps; color: var(--ink-faint); }
.devbar a { cursor: pointer; }
.subhead { font-variant: small-caps; font-weight: bold; margin: 18px 0 6px; color: var(--ink-soft); }

/* ---------- data-driven stats blocks (assets/stats.js) ---------- */
.statlead { font-size: 1.05em; margin: 4px 0 14px; }
.statlead b { font-variant: small-caps; font-size: 1.1em; }
.statrow.pairrow { grid-template-columns: 1fr auto; }
.statrow.hi { background: var(--paper-shade); border-radius: 4px; }
.statrow.hi .who { font-weight: bold; }
/* dev annotation: shows with the block labels, hidden in the clean/real view */
.payload { font-size: 0.75em; color: var(--ink-faint); font-family: monospace;
  margin: 10px 0 0; border-top: 1px dotted var(--line); padding-top: 6px; }
body.hideblocks .payload { display: none; }
.rankcards { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 18px; }
.rankcard { flex: 1; min-width: 130px; display: flex; align-items: center; gap: 10px;
  border: 1.5px solid var(--ink); border-radius: 45px 12px 45px 12px / 12px 45px 12px 45px;
  padding: 8px 14px; background: transparent; }
.rankcard .rk { font-size: 1.9em; font-weight: bold; color: var(--accent); line-height: 1; }
.rankcard .rl { font-variant: small-caps; font-size: 0.9em; }
.rankcard .rl span { font-variant: normal; color: var(--ink-soft); font-size: 0.82em; }

/* stat-block tabs — one board on show at a time (keeps blocks compact) */
.tabbar { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 14px; }
.tabbar button { font: inherit; font-variant: small-caps; font-size: 0.9em;
  background: transparent; border: 1.5px solid var(--line);
  border-radius: 8px 3px 8px 3px; padding: 3px 13px; cursor: pointer; color: var(--ink-soft); }
.tabbar button:hover { border-color: var(--ink-soft); }
.tabbar button.on { border-color: var(--ink); color: var(--ink); background: var(--paper-shade); font-weight: bold; }
.tabpanel .stat { margin: 0; }

/* pie chart (assets/stats.js) — hatched ochre slices in the pencil palette */
.piewrap { text-align: center; }
.pie { width: 200px; max-width: 100%; height: auto; display: inline-block; }
.pielegend { text-align: left; margin-top: 10px; }
.piekey { display: grid; grid-template-columns: 16px 1fr auto; gap: 8px;
  align-items: center; padding: 2px 0; font-size: 0.9em; }
.piekey .swatch { width: 14px; height: 14px; }
.piekey .lbl { font-variant: small-caps; }
.piekey .pc { color: var(--ink-soft); font-variant-numeric: tabular-nums; }

/* rank-card summary sitting beside the hero: compact 2-up */
.herorank .rankcards { margin: 0; }
.herorank .rankcard { min-width: 0; flex: 1 1 44%; padding: 6px 10px; }
.herorank .rankcard .rk { font-size: 1.5em; }
.herorank .rankcard .rl { font-size: 0.8em; }
.herorank .rankcard .rl span { font-size: 0.76em; }

/* spot placeholder (skeleton only — real art drops into .spotart) */
.spotart.ph {
  background: repeating-linear-gradient(41deg, rgba(28,27,25,0.10) 0 2px, transparent 2px 7px),
             repeating-linear-gradient(-49deg, rgba(28,27,25,0.07) 0 2px, transparent 2px 9px);
  mix-blend-mode: normal; display: flex; align-items: center; justify-content: center;
  text-align: center; color: var(--ink-faint); font-style: italic; font-size: 0.82em; padding: 18px;
}

/* hero motto caption (place pages) */
.herowrap { flex: none; }
.heromotto { text-align: center; margin: 0 0 0 -14px; font-family: 'Caveat', cursive;
  font-size: 0.95em; color: var(--ink); line-height: 1.25; }
.heromotto i { font-style: normal; font-size: 1.15em; }
.heromotto span { display: block; color: var(--ink-soft); }

/* faint hero image behind a link-panel tile. Per-tile knobs (Steven's point —
   every image needs its own focal point + scale):
     --bgimg   url('…-flat.png')
     --bgx --bgy   focal point (default centre)
     --bgscale  background-size (default 150%)  */
/* the image sits as the box's own background under a translucent-paper wash
   that fades it — reliable everywhere (no blend-mode/z-index/isolation quirks;
   content paints above the box background natively). --bgx/--bgy/--bgscale are
   the focal point + zoom from the framing gizmo. */
.tile.bg .sketchbox { overflow: hidden;
  background-image:
    linear-gradient(var(--wash, rgba(251,250,246,0.55)), var(--wash, rgba(251,250,246,0.55))),
    var(--bgimg);
  background-position: center, var(--bgx, 50%) var(--bgy, 50%);
  background-size: cover, var(--bgscale, 150%);
  background-repeat: no-repeat, no-repeat;
}
.tile.bg:hover .sketchbox { --wash: rgba(251,250,246,0.38); }   /* lift on hover */

/* generated companions chip-list (character page) */
.companions { display: flex; flex-wrap: wrap; gap: 8px; margin: 6px 0 4px; }
.companions .chip { text-decoration: none; }
.companions .chip:hover { background: var(--paper-shade); }

/* ---------- reading-order strands ---------- */
.strand { margin: 40px 0; }
/* each strand is its own reading order — divide consecutive ones with the
   site's · · · interstitial so they read as separate paths (Steven, 23 Jul) */
.strand + .strand::before {
  content: "· · ·"; display: block; text-align: center; letter-spacing: 8px;
  color: var(--ink-faint); font-size: 1.28em; margin: 0 0 40px;
}
/* full banner shown uncropped (its own 3:1 ratio), with the title on a
   hand-drawn dark cross-hatched plaque overlapping it (Steven, 22 Jul) */
.strandhead { position: relative; }
.strandbanner { width: 100%; aspect-ratio: 1920 / 634;
  background-position: center; background-size: cover; background-repeat: no-repeat;
  mix-blend-mode: multiply;
  /* strong fade: figures dissolve to mist through the bottom third so the
     description can rise into it consistently on every banner (Steven, opt 3) */
  -webkit-mask-image: linear-gradient(to bottom, #000 0, #000 56%, transparent 92%);
  mask-image: linear-gradient(to bottom, #000 0, #000 56%, transparent 92%); }
/* description rises up into the misty faded zone */
.strand > p { margin-top: -7%; position: relative; }
.strandtitle { position: absolute; left: 18px; bottom: 24%; margin: 0; max-width: 80%;
  padding: 5px 20px; color: var(--paper); font-size: 1.4em;
  font-variant: small-caps; font-weight: bold; line-height: 1.25;
  background-color: #26221d;
  background-image:
    repeating-linear-gradient(45deg, rgba(0,0,0,0.45) 0 1.5px, transparent 1.5px 7px),
    repeating-linear-gradient(-45deg, rgba(0,0,0,0.45) 0 1.5px, transparent 1.5px 7px);
  border: 2px solid rgba(251,250,246,0.5);
  border-radius: 18px 160px 16px 150px / 150px 16px 160px 18px;
  text-shadow: 0 1px 3px rgba(0,0,0,0.75); }
.strandtitle .handnote { color: rgba(251,250,246,0.75); }
/* mobile: the overlapping plaque covers a short banner and long titles wrap
   badly — drop it below the banner as a normal label, banner fully visible */
@media (max-width: 640px) {
  /* lift the title up into the banner's faded bottom (~a title-block height),
     closing the gap; the description follows it up */
  .strandtitle { position: static; bottom: auto; left: auto; max-width: none;
    display: inline-block; margin: -9% 0 0; font-size: 1.05em; position: relative; }
  .strand > p { margin-top: 10px; }
}
.readpath { list-style: none; padding: 0; margin: 10px 0 0; counter-reset: rp;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 0 26px; }
.readpath li { counter-increment: rp; display: flex; gap: 9px; align-items: baseline;
  padding: 4px 0; border-bottom: 1px dotted var(--line); }
.readpath li::before { content: counter(rp); color: var(--ink-faint);
  font-variant-numeric: tabular-nums; width: 1.3em; text-align: right; flex: none; }
.readpath .t, .readpath a { flex: 1; text-decoration: none; }
/* list links match the book-index style: NO wavy underline (that's for inline
   prose links) — the whole row highlights on hover instead */
.readpath li:hover { background: var(--paper-shade); border-radius: 4px; }
.readpath a:hover { text-decoration: none; }
.readpath .st { margin-left: auto; font-size: 0.8em; color: var(--ink-soft); white-space: nowrap; }
.readpath .st.done { color: var(--accent); font-weight: bold; }

/* --- editions: cover-line metadata and market tags (Aug 2026) --- */
.covergrid figcaption .mkt, .carousel figcaption .mkt {
  display: inline-block; margin-top: 3px; padding: 0 6px;
  border: 1px solid var(--line); border-radius: 8px;
  font-size: 0.86em; font-variant: small-caps; color: var(--ink-soft);
}
.collsub .linemeta { font-size: 0.72em; font-variant: normal; color: var(--ink-soft); }
.edpop .pop-conf b { font-variant: small-caps; }
/* the research notes are long — let the panel scroll rather than run off-screen */
.edpop .pop-note { max-height: 12em; overflow-y: auto; }

/* Researched data can always surprise a fixed layout, so the editions table
   scrolls inside its own box rather than pushing the page sideways. */
.editionswrap { overflow-x: auto; }
.editions .yr { white-space: nowrap; }
.editions td { overflow-wrap: break-word; }

/* ---- The Reading Bar (assets/timeline.js) --------------------------------
   Steven's pick, 28 Aug 2026: the dual track. Every row hangs off ONE grid so
   story time, setting and cast line through instead of running to two scales.
   The bar is pencil grey like the rest of the site; ochre is reserved for the
   thread being pointed at, which is the only colour in the block. */
.readingbar { margin: 26px 0 30px; }
.readingbar h2 { margin: 0 0 2px; }
.tl-dual { display: grid; gap: 6px; }
.tl-row { display: grid; grid-template-columns: var(--tl-gutter, 124px) 1fr;
  gap: 10px; align-items: center; }
.tl-nm { font-size: 0.78em; text-align: right; color: var(--ink-soft);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tl-band { display: flex; height: 46px; border: 1.5px solid var(--ink);
  border-radius: 2px; overflow: hidden; }
.tl-band i { display: block; height: 100%; cursor: pointer;
  background: var(--tone); transition: background-color .18s, opacity .18s; }
.tl-lanes { display: grid; gap: 3px; }
.tl-tr { position: relative; height: 13px; background: var(--paper-shade);
  border-radius: 2px; cursor: pointer; }
.tl-tr b { position: absolute; top: 0; bottom: 0; border-radius: 1px;
  background: var(--tone); transition: background-color .18s, opacity .18s; }
.tl-band i[data-hit="1"], .tl-tr b[data-hit="1"] { background: var(--accent); }
.tl-band i[data-off="1"], .tl-tr b[data-off="1"] { opacity: .34; }
.tl-ticks { position: relative; height: 20px; border-bottom: 1px solid var(--line); }
.tl-ticks u { position: absolute; bottom: 0; width: 1px; height: 7px;
  background: var(--ink-faint); text-decoration: none; }
.tl-ticks u.date { height: 13px; background: var(--accent); }
.tl-ticks u:hover::after { content: attr(data-l); position: absolute; bottom: 100%;
  left: 0; transform: translateX(-50%); white-space: nowrap; font-size: 11px;
  background: var(--paper); border: 1px solid var(--line); border-radius: 2px;
  padding: 1px 5px; z-index: 5; }
.tl-axis { display: flex; justify-content: space-between; font-size: 0.7em;
  letter-spacing: .07em; color: var(--ink-faint);
  margin: 5px 0 0 calc(var(--tl-gutter, 124px) + 10px); }
.tl-legend { display: flex; flex-wrap: wrap; gap: 5px 7px; margin-top: 10px; }
.tl-legend button { display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 9px 3px 5px; font: inherit; font-size: 0.8em; cursor: pointer;
  border: 1px solid var(--line); border-radius: 11px; background: transparent;
  color: inherit; }
.tl-legend s { width: 10px; height: 10px; border-radius: 2px; display: block;
  text-decoration: none; background: var(--tone); transition: background-color .18s; }
.tl-legend button[data-hit="1"] { border-color: var(--accent); color: var(--accent); }
.tl-legend button[data-hit="1"] s { background: var(--accent); }
.tl-legend button[data-off="1"] { opacity: .45; }
.tl-more { font-size: 0.8em; color: var(--ink-faint); align-self: center; }
.tl-clear { border-style: dashed !important; }
.tl-readout { margin: 7px 0 0; min-height: 1.4em; font-size: 0.85em; }

/* Portrait phones: the label gutter is the first thing to give. Below 560px the
   cast lanes go too - five 13px tracks under a 46px bar is unreadable at that
   width, and the setting bar is the part that carries the meaning. */
@media (max-width: 720px) {
  .readingbar { --tl-gutter: 74px; }
  .tl-nm { font-size: 0.7em; }
  .tl-band { height: 38px; }
}
@media (max-width: 560px) {
  .readingbar { --tl-gutter: 0px; }
  .tl-row { grid-template-columns: 1fr; gap: 2px; }
  .tl-nm { text-align: left; font-size: 0.68em; letter-spacing: .04em; }
  .tl-axis { margin-left: 0; }
  .tl-lanes { display: none; }
  .tl-band { height: 34px; }
}

/* ---------- production overrides (deploy_site.py) ---------- */
.gen { outline: none; }
.gen::before { display: none; }
.devbar { display: none; }
