/* ==========================================================================
   THE TOME — 3D book shell (TRIAL)
   ==========================================================================
 * A physical book as the whole interface: leather cover, page block with real
 * thickness, ribbon markers down the fore-edge for navigation, and a page that
 * turns when you move between sections.
 *
 * Built on CSS 3D (`preserve-3d` + `rotateY`) rather than a WebGL library —
 * the whole thing is a handful of transforms, it stays on the compositor, and
 * the project keeps its zero-dependency property.
 *
 * Self-contained: delete this file, /js/book3d.js, and the two lines that load
 * them from the four list pages, and everything is exactly as it was.
 */

.tome-page {
  --leather:     #3a2617;
  --leather-hi:  #5c4028;
  --leather-lo:  #24170d;
  --page:        #f6efd8;
  --page-edge:   #ded2ae;
  --page-ink:    #2a2318;
}

/* The toolbar and the pill nav move inside the book, so their originals go. */
.tome-page .toolbar,
.tome-page .appbar__nav { display: none !important; }

/* The nav held the middle column; book3d.js moves the search there instead, so
   the bar keeps its three columns and the field sits dead centre regardless of
   how wide the brand or the right-hand cluster get. The outer 1fr columns are
   equal, so the centre stays centred on the viewport, not on the leftovers. */
.tome-page .appbar__inner { grid-template-columns: 1fr minmax(200px, 460px) 1fr; }
.tome-page .search { justify-self: center; width: 100%; max-width: 460px; }
.tome-page .appbar__right { gap: 6px; }

/* The face is the one personal thing in the bar, so it carries a little more
   weight than a 30px chip gave it. */
.tome-page .account__btn { width: 42px; height: 42px; }
.tome-page .account__avatar { width: 38px; height: 38px; }
.tome-page .account__avatar--letter { font-size: 16px; }
.tome-page .account__btn:hover { box-shadow: 0 0 0 2px rgba(214, 168, 84, .75); }

/* The bar stands on the book's ground, so its controls warm up on hover instead
   of going slate — the amber picks up the gilt on the cover and the ribbons. */
.tome-page .appbar__right .icon-btn:hover,
.tome-page .appbar__right .icon-btn:focus-visible,
.tome-page .theme-dock .icon-btn:hover,
.tome-page .theme-dock .icon-btn:focus-visible {
  background: rgba(214, 168, 84, .2);
  color: #f2cd83;
}

/* Docked bottom-right, on the ground the book sits on. Fixed rather than in
   flow so it stays reachable while a long spread scrolls, and it sits at rest
   as a faint disc so it reads as a lamp switch on the wall rather than another
   button on the page. */
/* Same 24px inset as the app bar's right edge, and the same 42px box as the
   avatar, so the two sit on one vertical axis rather than 3px apart. */
.theme-dock {
  position: fixed;
  right: 24px; bottom: 24px;
  z-index: 60;
  display: grid; place-items: center;
}
.tome-page .theme-dock .icon-btn {
  width: 42px; height: 42px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .05);
  box-shadow: inset 0 0 0 1px rgba(255, 226, 178, .12);
  /* Measured: .62 put the icon at 2.68:1 on the light ground, under the 3:1
     minimum for a UI glyph. .78 reads as recessive and still clears it — 3.7:1
     on white, 5.2:1 on the dark ground. */
  opacity: .78;
  transition: opacity .18s var(--ease), background .18s var(--ease),
              color .18s var(--ease), box-shadow .18s var(--ease);
}
.tome-page .theme-dock .icon-btn:hover,
.tome-page .theme-dock .icon-btn:focus-visible {
  opacity: 1;
  box-shadow: inset 0 0 0 1px rgba(214, 168, 84, .5);
}

/* On the light ground a white disc and a cream ring are both invisible, so the
   control loses its affordance entirely. Invert the tint there. */
[data-theme="light"] .tome-page .theme-dock .icon-btn {
  background: rgba(42, 35, 24, .05);
  box-shadow: inset 0 0 0 1px rgba(42, 35, 24, .14);
}
[data-theme="light"] .tome-page .theme-dock .icon-btn:hover,
[data-theme="light"] .tome-page .theme-dock .icon-btn:focus-visible {
  background: rgba(168, 122, 32, .14);
  color: #7a5410;
  box-shadow: inset 0 0 0 1px rgba(168, 122, 32, .45);
}

@media (max-width: 620px) {
  .theme-dock { right: 12px; bottom: 12px; }
}

/* Below 1040 app.css switches the bar to named areas ("brand right" / "nav nav")
   so the nav can drop to its own row. The nav is gone here and the search has no
   area of its own, so it auto-placed straight on top of the brand. Redeclare the
   areas with the search in them rather than only overriding the columns. */
@media (max-width: 1040px) {
  .tome-page .appbar__inner {
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas: "brand search right";
    padding-bottom: 0; row-gap: 0;
  }
  .tome-page .search { grid-area: search; max-width: none; }
}

/* The brand is the first thing to go when the field needs the room. It used to
   leave the guitar chip behind as a marker; with that gone there is nothing to
   keep, so the whole thing goes and the search takes the space. The ribbon tabs
   name the current section anyway. */
@media (max-width: 620px) {
  .tome-page .appbar__inner { gap: 10px; padding: 0 12px; }
  .tome-page .brand { display: none; }
}

/* .page caps at 1440, but .tome sizes itself against 100vw. Above ~1840 the
   book outgrew its container and shoved the ribbons past the right edge of the
   viewport. The book is its own centred object here, so it does not need the
   text-column cap the ordinary pages want. */
.tome-page .page { max-width: none; }

/* Same reason, and it keeps the avatar on the same vertical axis as the theme
   switch below it: the switch is fixed to the viewport, so a bar that caps at
   1440 would let the avatar drift inward on anything wider. */
.tome-page .appbar__inner { max-width: none; }

/* ---- The page is the book: one screen, no scroll ----
 *
 * The book used to be sized by 84vh, which ignores the bar above it and the
 * credit below, so on a 13" screen the whole thing ran past the fold. Here the
 * body becomes a fixed-height column — bar, book, credit — and the book takes
 * whatever is left. Nothing outside the book scrolls; the spread still scrolls
 * inside .tome__scroll, which is where reading belongs.
 *
 * Gated on height as well as width: below ~620px tall there is not enough room
 * to divide, so short windows fall back to an ordinary scrolling page rather
 * than being squeezed into an unusable sliver.
 */
@media (min-width: 901px) and (min-height: 620px) {
  html { height: 100%; }
  .tome-page {
    height: 100%;
    overflow: hidden;
    display: flex; flex-direction: column;
  }
  .tome-page .appbar { flex: none; }
  /* min-height:0 on both: a flex item defaults to min-content, which would let
     the book push the column taller than the viewport and reintroduce scroll. */
  .tome-page .page {
    flex: 1 1 auto; min-height: 0;
    display: flex; padding-bottom: 0;
  }
  .tome-page .tome-stage {
    flex: 1 1 auto; min-height: 0;
    padding: 22px 20px 54px;
  }
  /* min-height:0 is load-bearing. .tome is a grid item, so it defaults to
     min-height:auto — its content floor — and that floor beat the computed
     100%: the height resolved to 679px and the book still rendered 1384px
     tall. Same trap the leaf columns hit. */
  .tome-page .tome,
  .tome-page .tome__block { height: 100%; min-height: 0; }

  /* Sits with the book rather than a screen below it. */
  .tome-page .credit { flex: none; padding: 0 76px 20px; }
}

.tome-stage {
  perspective: 2600px;
  perspective-origin: 50% 40%;
  padding: 26px 20px 60px;
  display: grid; justify-items: center;
}

/* The book itself. A few degrees of tilt is all it takes to stop reading as a
   rectangle and start reading as an object on a table. */
.tome {
  position: relative;
  /* The ribbons hang past the fore-edge, so the board must leave room for them
     or they push the whole page sideways. Measured worst case is the My Book
     marker while it is current — longest root (--pad-root 72) and pushed out
     another 14 — which protrudes 183px at 1024. 220 a side leaves ~33px. */
  width: min(1480px, calc(100vw - 440px));
  transform-style: preserve-3d;
  transform: rotateX(6deg);
  transition: transform .5s var(--ease);
}
.tome-stage:hover .tome { transform: rotateX(4.4deg); }

/* Leather boards, proud of the page block on every side. */
.tome__cover {
  position: absolute;
  inset: -20px -46px -26px -46px;
  border-radius: 8px 16px 16px 8px;
  background:
    radial-gradient(120% 100% at 12% 0%, var(--leather-hi), transparent 46%),
    linear-gradient(155deg, var(--leather) 0%, var(--leather-lo) 100%);
  box-shadow:
    inset 0 0 0 1px rgba(255, 226, 178, .13),
    inset 0 2px 0 rgba(255, 226, 178, .10),
    0 44px 80px -30px rgba(0, 0, 0, .95),
    0 12px 26px -12px rgba(0, 0, 0, .7);
  transform: translateZ(-14px);
}
/* Blind-tooled rule, the way a hardback is stamped. */
.tome__cover::after {
  content: "";
  position: absolute; inset: 12px;
  border: 1px solid rgba(255, 226, 178, .16);
  border-radius: 5px 10px 10px 5px;
  pointer-events: none;
}

/* The page block: one spread — head across the top, entries running over
   both leaves, foot along the bottom. The fold is an overlay, so the columns
   straddle it exactly as type does in a real spread. */
.tome__block {
  position: relative;
  display: flex; flex-direction: column;
  height: clamp(560px, 84vh, 940px);
  background: var(--page);
  border-radius: 3px;
  overflow: hidden;
  box-shadow: 0 2px 10px rgba(0, 0, 0, .35);
  color: var(--page-ink);
}

/* Paper thickness — a real stack of leaves, one element per sheet, spread
   through the block's depth from just above the page (z +5) down to just
   short of the cover (z -14). The ribbons are pinned at +8 / -4 / -8 / -12, so
   each one lands *between* leaves and gets paper both in front of and behind
   it. That is the whole reason these are sixteen elements and not one gradient:
   no amount of striping on a single sliver can occlude a ribbon from one side
   and be occluded by it on the other.

   The fan goes OUTWARD — each deeper leaf reaches a little further past the
   one above it. Fanning inward instead tucks the lower half of the stack behind
   the opaque page block, so half the leaves you paid for never appear and the
   edge stays as thin as it was. Outward also means a ribbon buried under more
   leaves is hidden further out, so the stack itself steps the markers. */
/* Each leaf is one sheet: flat paper with a single fine line at its own edge,
   darkening as it goes down the stack. The striped gradient this replaced ran
   horizontally, which is the wrong axis — a fore-edge seen from here shows one
   line per sheet running with the page height, and the *stack* is what makes
   the striation. Painting stripes on every leaf as well turned the whole band
   into corrugation. */
.tome__edge {
  position: absolute;
  top:    calc(3px + var(--i) * 0.35px);
  bottom: calc(3px + var(--i) * 0.35px);
  width: 13px;
  background:
    linear-gradient(rgba(52, 38, 16, calc(var(--i) * 0.013)) 0 100%),
    var(--page);
  pointer-events: none;
  transform: translateZ(calc(5px - var(--i) * 1.15px));
}
.tome__edge--right {
  right: calc(-13px - var(--i) * 1.6px);
  border-right: 1px solid rgba(122, 100, 58, .4);
  border-radius: 0 2px 2px 0;
}
.tome__edge--left {
  left: calc(-13px - var(--i) * 1.6px);
  border-left: 1px solid rgba(122, 100, 58, .4);
  border-radius: 2px 0 0 2px;
}

/* The fold: paper curving away into the gutter on both sides. */
.tome__fold {
  position: absolute; top: 0; bottom: 0; left: 50%;
  width: 128px; transform: translateX(-50%);
  pointer-events: none; z-index: 3;
  background: linear-gradient(90deg,
    rgba(90, 70, 38, 0)    0%,
    rgba(90, 70, 38, .05) 26%,
    rgba(84, 64, 34, .13) 44%,
    rgba(60, 44, 22, .26) 50%,
    rgba(84, 64, 34, .13) 56%,
    rgba(90, 70, 38, .05) 74%,
    rgba(90, 70, 38, 0)  100%);
}

/* ---- Head ---- */

/* The head must outrank .tome__scroll, not tie with it. Both sat at 4, and on a
   tie the later sibling wins — which put the scrolling page over the head. That
   is invisible until the "Arranged by" menu opens: the menu's own z-index is
   scoped inside this stacking context, so it can never climb over the scroll,
   and because song rows are transparent the menu still *looks* on top while the
   rows underneath swallow the clicks. */
.tome__head { padding: 30px 52px 0; flex: none; position: relative; z-index: 6; }
.tome__headline {
  display: flex; align-items: flex-end; justify-content: space-between; gap: 24px;
}
.tome__headline-text { min-width: 0; }
.tome__title {
  font-family: var(--hand);
  font-size: clamp(30px, 3.1vw, 48px);
  line-height: 1.04;
}
.tome__subtitle {
  font-family: var(--serif);
  font-size: 11.5px; letter-spacing: .2em; text-transform: uppercase;
  color: #7c6a48;
}
.tome__rule {
  margin: 14px 0 10px;
  border-top: 2px solid #2a2318;
  border-bottom: 1px solid #2a2318;
  height: 3px;
}

/* ---- The two controls, in the corner ---- */

.tome__controls {
  display: flex; align-items: center; gap: 8px;
  flex: none; padding-bottom: 4px;
}
.tome__ctl-label {
  font-family: var(--serif);
  font-size: 10px; font-weight: 600;
  letter-spacing: .19em; text-transform: uppercase;
  color: #a08c62; white-space: nowrap;
}

/* Both controls are one button, so they cannot drift apart. */
.book-btn, .book-filters-btn {
  display: inline-flex; align-items: center; gap: 7px;
  height: 30px; padding: 0 12px;
  border: 1px solid #b6a37a; border-radius: 3px;
  background: rgba(255, 255, 255, .5);
  font-family: var(--serif);
  font-size: 13px; color: #2a2318;
  cursor: pointer;
  transition: background .15s var(--ease), border-color .15s var(--ease);
}
.book-btn:hover, .book-filters-btn:hover { background: rgba(255, 255, 255, .9); border-color: #8a7855; }

#axis-switch { position: relative; }
.book-btn__caret {
  width: 0; height: 0; margin-left: 1px;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid #6b5c3e;
}

.book-menu {
  position: absolute; top: calc(100% + 5px); right: 0;
  z-index: 20; min-width: 100%;
  padding: 4px;
  border: 1px solid #2a2318; border-radius: 3px;
  background: var(--page);
  box-shadow: 3px 3px 0 rgba(42, 35, 24, .35);
}
.book-menu[hidden] { display: none; }
.book-menu__item {
  display: block; width: 100%;
  padding: 6px 12px 6px 22px;
  border: 0; border-radius: 2px; background: none;
  font-family: var(--serif); font-size: 13px; color: #4a3f2c;
  text-align: left; white-space: nowrap; cursor: pointer;
  position: relative;
}
.book-menu__item:hover { background: rgba(42, 35, 24, .08); color: #2a2318; }
.book-menu__item.is-on { color: #2a2318; font-weight: 600; }
.book-menu__item.is-on::before {
  content: "☞";
  position: absolute; left: 4px; top: 50%; transform: translateY(-52%);
  font-size: 12px; color: #a8321f;
}

.book-filters-btn {
  display: inline-flex; align-items: center; gap: 7px;
  height: 30px; padding: 0 12px;
  border: 1px solid #b6a37a; border-radius: 3px;
  background: rgba(255, 255, 255, .5);
  font-family: var(--serif);
  font-size: 13px; color: #2a2318;
  cursor: pointer;
  transition: background .15s var(--ease), border-color .15s var(--ease);
}
.book-filters-btn svg { color: #6b5c3e; }
.book-filters-btn:hover { background: rgba(255, 255, 255, .85); }
.book-filters-btn.has-active { border-color: #a8321f; }
.book-filters-btn.has-active svg { color: #a8321f; }
.book-filters-btn.is-open { background: #2a2318; border-color: #2a2318; color: var(--page); }
.book-filters-btn.is-open svg { color: var(--page); }
.book-filters-btn__count {
  min-width: 17px; height: 17px; padding: 0 4px;
  display: grid; place-items: center;
  border-radius: 9px; background: #a8321f; color: #f7e9cf;
  font-size: 10.5px; font-weight: 700;
}

/* ---- The filter leaf ---- */

.filter-page[hidden] { display: none; }
.filter-page__head {
  display: flex; align-items: center; gap: 12px;
  padding-bottom: 14px; margin-bottom: 6px;
  border-bottom: 1px solid rgba(42, 35, 24, .18);
}
.filter-page__title {
  font-family: var(--hand); font-size: 26px; font-weight: 400; line-height: 1;
}
.filter-page__spacer { flex: 1; }
.filter-page__count {
  font-family: var(--serif); font-size: 11px;
  letter-spacing: .14em; text-transform: uppercase; color: #8a7855;
}
.filter-page__clear, .filter-page__done {
  height: 28px; padding: 0 13px;
  border: 1px solid #8a7855; border-radius: 3px;
  background: none; color: #4a3f2c;
  font-family: var(--serif); font-size: 11.5px; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase;
  cursor: pointer;
}
.filter-page__clear:hover { background: rgba(168, 50, 31, .1); border-color: #a8321f; color: #a8321f; }
.filter-page__done {
  background: #2a2318; border-color: #2a2318; color: var(--page);
  box-shadow: 2px 2px 0 rgba(42, 35, 24, .35);
}
.filter-page__done:hover { transform: translate(-1px, -1px); box-shadow: 3px 3px 0 rgba(42, 35, 24, .35); }

/* Facet groups flow across both leaves, like an index. */
.filter-page__grid { column-count: 2; column-gap: 104px; }
.facet { break-inside: avoid; margin-bottom: 22px; }
.facet__title {
  font-family: var(--serif);
  font-size: 10px; font-weight: 700;
  letter-spacing: .19em; text-transform: uppercase;
  color: #a08c62;
  margin-bottom: 6px;
}
.facet__options { display: flex; flex-direction: column; }
.facet__opt {
  display: flex; align-items: center; gap: 9px;
  width: 100%; padding: 5px 6px 5px 4px;
  border: 0; border-radius: 3px; background: none;
  font-family: var(--serif); font-size: 13.5px; color: #4a3f2c;
  text-align: left; cursor: pointer;
}
.facet__opt:hover { background: rgba(42, 35, 24, .07); color: #2a2318; }
.facet__tick {
  width: 13px; height: 13px; flex: none;
  border: 1.3px solid #a08c62; border-radius: 2px;
  position: relative;
}
.facet__opt.is-on { color: #2a2318; font-weight: 600; }
.facet__opt.is-on .facet__tick { border-color: #a8321f; }
.facet__opt.is-on .facet__tick::after {
  content: "✓";
  position: absolute; inset: -3px 0 0 0;
  font-size: 13px; font-weight: 700; color: #a8321f; text-align: center;
}
.facet__label { flex: 1; min-width: 0; }
.facet__count {
  font-size: 11px; color: #a08c62;
  font-variant-numeric: tabular-nums;
}

/* ---- Body: entries across both leaves ---- */

.tome__scroll {
  flex: 1; min-height: 0;
  overflow-y: auto;
  padding: 4px 52px 8px;
  position: relative; z-index: 4;
  scrollbar-width: thin;
  scrollbar-color: #c3b189 transparent;
}
.tome__scroll::-webkit-scrollbar { width: 7px; }
.tome__scroll::-webkit-scrollbar-thumb { background: #c3b189; border-radius: 4px; }
.tome__scroll::-webkit-scrollbar-track { background: transparent; }

/* Two columns straddling the fold — the spread's own layout, restored. */
.tome__scroll .book__spread { column-count: 2; column-gap: 104px; }
.tome__scroll .empty { column-span: all; }

.tome__foot {
  flex: none;
  margin: 0 52px; padding: 12px 0 18px;
  border-top: 1px solid rgba(42, 35, 24, .18);
  font-family: var(--serif);
  font-size: 10.5px; letter-spacing: .16em; text-transform: uppercase;
  color: #8a7855;
  display: flex; justify-content: space-between; gap: 12px;
  position: relative; z-index: 4;
}

/* ---- Ribbon markers down the fore-edge ---- */

/* preserve-3d, and deliberately no z-index. Without preserve-3d the ribbons'
   translateZ is flattened into this strip's own plane, so they can only ever
   paint as one flat layer above or below the block — never interleaved with it.
   A z-index here would reimpose that flat order on top of the 3D sort, which is
   exactly the depth we are trying to express. */
.tome__ribbons {
  position: absolute; top: 46px; left: 100%; margin-left: -18px;
  display: flex; flex-direction: column; align-items: flex-start; gap: 10px;
  transform-style: preserve-3d;
  /* The strip is a layout box with nothing to draw, and its children now sit at
     negative Z — which puts them behind their own parent's plane, so the parent
     swallowed every click. Only the one ribbon at positive Z stayed reachable.
     Skip the container in hit-testing and let the ribbons take their own. */
  pointer-events: none;
}
.tome__ribbon { pointer-events: auto; }
/* A real ribbon marker is cut, woven and sewn, and none of that came through
   when these were plain rounded rectangles. Three cues do most of the work:
   the swallowtail notch on the tail (clip-path), a woven grain running with the
   weft, and a dark root where the ribbon disappears into the gutter. */
.tome__ribbon {
  position: relative;
  display: flex; align-items: center; gap: 8px;
  height: 42px;
  /* The tail eats ~13px, so the right padding has to clear the notch. The left
     padding is the ROOT: the length of ribbon that disappears into the stack.
     A marker buried under more leaves needs a longer root, not a shove to the
     right — shifting the whole ribbon moved its cut end out past the paper and
     left it floating in mid-air instead of entering the book. */
  padding: 0 28px 0 var(--pad-root);
  border: 0; cursor: pointer;
  border-radius: 0 2px 2px 0;
  color: #f7e9cf;
  font-family: var(--serif);
  font-size: 11.5px; font-weight: 600;
  letter-spacing: .11em; text-transform: uppercase;
  white-space: nowrap;

  /* Depth, root length and state slide are separate so a state rule can move a
     ribbon without restating how deep it is pinned or how far it is buried.
     --x-depth is kept at 0: translating a buried ribbon rightward carries its
     cut end out past the paper, which is what made these float. Depth is paid
     for in --pad-root instead. */
  --x-depth: 0px;
  --x-state: 0px;
  --pad-root: 15px;
  --z: 8px;
  --shade: 0;
  transform: translateX(calc(var(--x-depth) + var(--x-state))) translateZ(var(--z));

  clip-path: polygon(0 0, 100% 0, calc(100% - 13px) 50%, 100% 100%, 0 100%);
  filter: drop-shadow(2px 3px 5px rgba(0, 0, 0, .5));
  transition: transform .22s var(--ease), filter .22s var(--ease);

  /* Grain first, then the dye. The 2px weft is fine enough to read as cloth
     rather than as stripes at this size. */
  background-image:
    repeating-linear-gradient(180deg,
      rgba(255, 255, 255, .07) 0 1px, rgba(0, 0, 0, .07) 1px 2px),
    linear-gradient(100deg, rgba(0, 0, 0, .34) 0 6px, rgba(0, 0, 0, 0) 16px),
    linear-gradient(180deg, #a8321f, #7e2214);
}
.tome__ribbon:nth-child(2) { background-image:
  repeating-linear-gradient(180deg, rgba(255,255,255,.07) 0 1px, rgba(0,0,0,.07) 1px 2px),
  linear-gradient(100deg, rgba(0,0,0,.34) 0 6px, rgba(0,0,0,0) 16px),
  linear-gradient(180deg, #b8862c, #8a5f18); }
.tome__ribbon:nth-child(3) { background-image:
  repeating-linear-gradient(180deg, rgba(255,255,255,.07) 0 1px, rgba(0,0,0,.07) 1px 2px),
  linear-gradient(100deg, rgba(0,0,0,.34) 0 6px, rgba(0,0,0,0) 16px),
  linear-gradient(180deg, #2f6a4f, #1d4735); }
.tome__ribbon:nth-child(4) { background-image:
  repeating-linear-gradient(180deg, rgba(255,255,255,.07) 0 1px, rgba(0,0,0,.07) 1px 2px),
  linear-gradient(100deg, rgba(0,0,0,.34) 0 6px, rgba(0,0,0,0) 16px),
  linear-gradient(180deg, #3c5a86, #26405f); }

/* Stitching, a thread in from the edge. The parent's clip-path trims it along
   the notch, which is exactly where a real seam would stop. */
.tome__ribbon::before {
  content: "";
  position: absolute; inset: 4px 10px 4px 5px;
  border: 1px dashed rgba(255, 240, 214, .3);
  border-radius: 1px;
  pointer-events: none;
}
/* Top fold catching the light, bottom edge falling into shadow — and, for the
   ribbons tucked further down the stack, the shadow the leaves above throw
   across the root, strongest where it emerges and gone by mid-ribbon. */
.tome__ribbon::after {
  content: "";
  position: absolute; inset: 0;
  background:
    linear-gradient(180deg,
      rgba(255, 255, 255, .22) 0 1px,
      rgba(255, 255, 255, 0) 3px,
      rgba(0, 0, 0, 0) 88%,
      rgba(0, 0, 0, .3) 100%),
    linear-gradient(90deg,
      rgba(6, 4, 2, var(--shade)) 0,
      rgba(6, 4, 2, calc(var(--shade) * .55)) var(--pad-root),
      rgba(6, 4, 2, 0) calc(var(--pad-root) + 46px));
  pointer-events: none;
}

/* Each marker is pinned to a leaf one deeper than the last: Blues Book on the
   top page, then a leaf down for every section after it. The block sits at z 0
   and the fore-edge stack at -3, so only the first clears them and lies on the
   open page; the rest are genuinely occluded by the paper they are tucked
   behind, which is the effect.

   Every depth stays in front of the cover at -14. Past it the leather swallows
   the ribbon whole — "GROOVES" came back as "ROOVES" and My Book lost its face
   — which reads as behind the *book*, not behind a page.

   --x-depth buys back exactly what the paper hides, so the label protrudes by
   the same amount at every depth however much of the root is swallowed. */
.tome__ribbon:nth-child(1) { --z:   8px; --pad-root: 15px; --shade: 0;   }
.tome__ribbon:nth-child(2) { --z:  -4px; --pad-root: 63px; --shade: .14; }
.tome__ribbon:nth-child(3) { --z:  -8px; --pad-root: 68px; --shade: .24; }
.tome__ribbon:nth-child(4) { --z: -12px; --pad-root: 72px; --shade: .32; }

.tome__ribbon:hover { --x-state: 10px; filter: drop-shadow(3px 4px 7px rgba(0, 0, 0, .55)) brightness(1.1); }
.tome__ribbon.is-current {
  --x-state: 14px;
  filter: drop-shadow(4px 5px 9px rgba(0, 0, 0, .6)) brightness(1.14);
}
/* The page it marks: a bright thread down the root, as if the ribbon is pulled
   taut against the leaf it is holding. */
.tome__ribbon.is-current::before {
  border-color: rgba(255, 245, 224, .55);
  box-shadow: inset 2px 0 0 -1px rgba(255, 255, 255, .5);
}

/* The member's own tab wears their face, the way you would tape a photo to the
   marker for your own section. */
.tome__ribbon-avatar {
  width: 21px; height: 21px; flex: none;
  border-radius: 50%; object-fit: cover;
  box-shadow: 0 0 0 1.5px rgba(255, 245, 224, .65), 0 1px 2px rgba(0, 0, 0, .5);
}

/* ---- The page turn ---- */

.tome__turn {
  position: absolute; top: 0; bottom: 0; left: 50%; right: 0;
  transform-origin: left center;
  transform-style: preserve-3d;
  background: var(--page);
  border-radius: 0 3px 3px 0;
  z-index: 8;
  pointer-events: none;
  opacity: 0;
  backface-visibility: hidden;
  box-shadow: -8px 0 24px rgba(0, 0, 0, .3);
}
.tome__turn.is-turning {
  animation: tome-turn 620ms cubic-bezier(.42, 0, .26, 1) forwards;
}
@keyframes tome-turn {
  0%   { opacity: 1; transform: rotateY(0deg);    box-shadow: -4px 0 14px rgba(0,0,0,.18); }
  55%  { opacity: 1;                              box-shadow: -30px 0 60px rgba(0,0,0,.45); }
  100% { opacity: 1; transform: rotateY(-172deg); box-shadow: 20px 0 50px rgba(0,0,0,.3); }
}
/* The incoming spread settles in rather than snapping. */
.tome__block { animation: tome-settle 420ms var(--ease) both; }
@keyframes tome-settle {
  from { opacity: 0; transform: translateX(10px) rotateY(2.5deg); }
  to   { opacity: 1; transform: none; }
}

/* ---- Rows, reset onto paper ---- */

/* Entries are set tight, the way a printed index is.
 *
 * The spread splits into two columns, so a row's text column is only ~240px at
 * 1440 and ~160px at 1280 — narrow enough that a title wraps, and then the
 * emoji and the "2 arrangements" badge each claimed a line of their own on top
 * of it. Four lines for one song. Block flow lets all three run together, which
 * is where most of the height came back; the rest is padding and type size. */
.tome-page .song-row__title { display: block; }
.tome-page .song-row__emoji { margin-left: 6px; }
.tome-page .song-row__title .badge { margin-left: 6px; vertical-align: 1px; }
.tome-page .song-row {
  padding: 8px 10px;
  gap: 10px;
  align-items: center;
}
.tome-page .song-row + .song-row::before { left: 10px; right: 10px; }
.tome-page .song-row__name { font-size: 17.5px; line-height: 1.18; }
.tome-page .song-row__meta { margin-top: 1px; font-size: 12px; gap: 6px; row-gap: 0; }
.tome-page .song-row__right { gap: 7px; }
.tome-page .bpm { font-size: 12px; }
.tome-page .keycap { min-width: 30px; padding: 3px 7px; font-size: 12.5px; }
.tome-page .bookmark { width: 28px; height: 28px; }
.tome-page .song-row .chevron { width: 16px; height: 16px; }

/* Below this the spread's columns get narrow enough that the generous inner
   margin costs more than it gives — 104px of the book's width was padding. */
@media (max-width: 1400px) {
  .tome-page .tome__scroll { padding-left: 30px; padding-right: 30px; }
  .tome-page .tome__head { padding-left: 30px; padding-right: 30px; }
  .tome-page .tome__foot { margin-left: 30px; margin-right: 30px; }
}

.tome-page .song-row:hover { background: rgba(42, 35, 24, .07); }
.tome-page .song-row + .song-row::before { background: rgba(42, 35, 24, .16); }
.tome-page .keycap { background: rgba(42, 35, 24, .10); color: #2a2318; }
.tome-page .badge  { background: rgba(42, 35, 24, .11); color: #4a3f2c; }
.tome-page .song-row__meta, .tome-page .bpm { color: #6b5c3e; }
.tome-page .bpm b { color: #2a2318; }
.tome-page .chevron, .tome-page .dot { color: #a08c62; }
.tome-page .section-head__title { color: #4a3f2c; }
.tome-page .section-head__rule { background: rgba(42, 35, 24, .2); }
.tome-page .section-head { padding: 22px 0 10px; }
.tome-page .section-head:first-child { padding-top: 0; }

/* ---- Form controls, reset onto paper ---- */

/* The app's surface tokens are tuned for the dark chrome around the book. Left
   alone, every select and input inside the tome renders as a navy pill stamped
   on cream. These match .book-btn instead, so the Grooves controls read as the
   same family as "Arranged by" and "Filters".

   Scoped to .tome, NOT .tome-page: the app bar lives outside the book on the
   dark ground, and paper colours there turned the theme toggle brown-on-navy
   — invisible on hover. */
.tome {
  --select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='%236b5c3e' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
}
.tome .select,
.tome .input {
  border-color: #b6a37a;
  border-radius: 3px;
  background-color: rgba(255, 255, 255, .5);
  color: #2a2318;
}
.tome .select:hover,
.tome .input:hover { background-color: rgba(255, 255, 255, .9); border-color: #8a7855; }
.tome .select:focus,
.tome .input:focus { outline: none; border-color: #2a2318; background-color: #fff; }
.tome .input::placeholder { color: #9a8a68; }

/* The groove title is an input that only looks like one on hover — its
   transparent border is the whole point, so it stays out of the rules above. */
.tome .groove-row__name { color: #2a2318; }
.tome .groove-row__name:hover { background: rgba(255, 255, 255, .55); }
.tome .groove-row__name:focus {
  background: #fff; border-color: #2a2318;
}

/* Primary actions become ink on paper rather than Material blue. Bare .btn is
   the filled variant, so the modifiers have to be restored below it — the same
   trap the dark-theme button fix hit. */
.tome .btn {
  background: #2a2318; color: var(--page);
  border-radius: 3px; font-family: var(--serif);
  box-shadow: 2px 2px 0 rgba(42, 35, 24, .3);
}
.tome .btn:hover { background: #443a26; box-shadow: 3px 3px 0 rgba(42, 35, 24, .32); }
.tome .btn--text {
  background: transparent; color: #4a3f2c; box-shadow: none;
}
.tome .btn--text:hover { background: rgba(42, 35, 24, .09); box-shadow: none; }
.tome .btn--outline {
  background: rgba(255, 255, 255, .5); color: #2a2318;
  border: 1px solid #b6a37a; box-shadow: none;
}
.tome .btn--outline:hover { background: rgba(255, 255, 255, .9); border-color: #8a7855; box-shadow: none; }
.tome .btn--tonal {
  background: rgba(42, 35, 24, .1); color: #2a2318; box-shadow: none;
}
.tome .btn--tonal:hover { background: rgba(42, 35, 24, .16); box-shadow: none; }

.tome .icon-btn { color: #8a7855; }
.tome .icon-btn:hover { background: rgba(42, 35, 24, .1); color: #2a2318; }
.tome .icon-btn--danger:hover { background: rgba(168, 50, 31, .12); color: #a8321f; }
.tome-page .bookmark { color: #a08c62; }
.tome-page .bookmark:hover { background: rgba(42, 35, 24, .12); color: #2a2318; }
.tome-page .bookmark.is-on { color: #a8321f; }
.tome-page .empty { color: #6b5c3e; }
.tome-page .empty__title { color: #2a2318; }

/* Grooves rows land on paper too. */
.tome-page .groove-row:hover { background: rgba(42, 35, 24, .07); }
.tome-page .groove-row + .groove-row::before { background: rgba(42, 35, 24, .16); }
.tome .groove-row__name { color: #2a2318; }

/* ---- Narrow: the book lies flat and becomes a single page ---- */

@media (max-width: 900px) {
  /* No tilt, no perspective: at this size the 3D reads as a rendering fault
     rather than an object, and it costs legibility. */
  .tome-stage { perspective: none; padding: 12px 10px 32px; }
  .tome, .tome-stage:hover .tome {
    transform: none;
    width: 100%;
  }

  /* One page: no fold, no fore-edge stack, a slimmer board. */
  .tome__cover { inset: -8px -10px -12px -10px; border-radius: 6px; }
  .tome__cover::after { inset: 6px; border-radius: 3px; }
  .tome__fold, .tome__edge, .tome__turn { display: none; }

  .tome__block {
    height: auto;                 /* the page grows; the browser scrolls */
    border-radius: 2px;
  }
  .tome__scroll { overflow: visible; padding: 4px 18px 8px; }
  .tome__scroll .book__spread,
  .filter-page__grid { column-count: 1; }

  .tome__head { padding: 20px 18px 0; }
  .tome__headline { flex-direction: column; align-items: stretch; gap: 12px; }
  .tome__title { font-size: clamp(26px, 8vw, 34px); }
  .tome__controls { justify-content: flex-start; flex-wrap: wrap; }
  .tome__rule { margin: 12px 0 8px; }
  .tome__foot { margin: 0 18px; font-size: 9.5px; }

  /* Ribbons become a row of tabs above the book — a fore-edge needs an edge.
     They sit after the block in the DOM, so ordering has to put them back on
     top once they rejoin normal flow. The cover and fold are absolute, so only
     the block and the ribbons take part. */
  .tome { display: flex; flex-direction: column; }
  .tome__ribbons { order: -1; }

  .tome__ribbons {
    position: static; margin: 0 0 10px;
    flex-direction: row; flex-wrap: wrap; align-items: stretch; gap: 6px;
  }
  /* The swallowtail is a fore-edge cue — in a horizontal row it just reads as a
     chipped corner, so the tabs go square here. */
  .tome__ribbon,
  .tome__ribbon:hover,
  .tome__ribbon.is-current {
    /* Flat book, flat tabs: there is no leaf stack to be tucked into here. */
    --z: 0px; --x-depth: 0px; --x-state: 0px; --shade: 0; --pad-root: 12px;
    transform: none;
    height: 32px; padding: 0 12px;
    border-radius: 4px;
    clip-path: none;
    font-size: 10.5px; letter-spacing: .08em;
    filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .4));
  }
  .tome__ribbon::before { inset: 3px 5px; }
  .tome__ribbon.is-current { box-shadow: 0 0 0 2px rgba(255, 255, 255, .35); }
  .tome__ribbon-avatar { width: 17px; height: 17px; }

  /* Rows stack rather than trailing a column of metadata off the edge.
     The trailing cluster is the real cost here: at full desktop sizing it eats
     ~100px of a 390px screen, which is enough to wrap every title onto two
     lines and push each entry past 200px tall. Tightening the cluster and the
     hand a few points buys back a whole line per row. */
  .tome-page .song-row { grid-template-columns: 1fr auto; gap: 8px; padding: 9px 6px; }
  .tome-page .song-row + .song-row::before { left: 6px; right: 6px; }
  .tome-page .song-row__name { font-size: 17px; }
  .tome-page .song-row__meta { font-size: 11.5px; }
  .tome-page .song-row__right { gap: 4px; }
  .tome-page .keycap { min-width: 30px; padding: 3px 6px; font-size: 12px; }
  .tome-page .filter-page__head { flex-wrap: wrap; gap: 8px; }
  .tome-page .filter-page__spacer { display: none; }
}

@media (max-width: 560px) {
  .tome__ribbons { gap: 4px; }
  /* Size to content, never grow: with four tabs wrapping onto two rows, a
     growing flex item left the lone tab on the last row stretched full-width. */
  .tome__ribbon, .tome__ribbon:hover, .tome__ribbon.is-current {
    flex: 0 1 auto; justify-content: center; padding: 0 9px; font-size: 10px;
  }
  .tome__ribbons { justify-content: center; }
  .tome-page .keycap { min-width: 32px; }

  /* Groove rows: the thumb spans both columns here, so auto-placement pushes
     the action column down onto a third row and stacks its two icons one per
     line. Placing all three explicitly puts the actions back beside the
     player, laid out across rather than down. */
  .tome-page .groove-row { align-items: start; row-gap: 8px; }
  .tome-page .groove-row__main { grid-column: 1 / -1; grid-row: 1; }
  .tome-page .groove-row__thumb { grid-column: 1; grid-row: 2; }
  .tome-page .groove-row__actions {
    grid-column: 2; grid-row: 2;
    flex-direction: row; align-self: end; gap: 4px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .tome__turn.is-turning { animation: none; opacity: 0; }
  .tome__block { animation: none; }
}

/* Short labels — keys, tempo bands — read better as a wrapping row than a
   stacked list, and the inked fill carries selection so the tick box goes. */
.facet--inline .facet__options { flex-direction: row; flex-wrap: wrap; gap: 5px; }
.facet--inline .facet__opt {
  width: auto; gap: 6px;
  padding: 4px 9px;
  border: 1px solid #c3b189; border-radius: 3px;
  background: rgba(255, 255, 255, .45);
  font-size: 13px;
}
.facet--inline .facet__opt:hover { background: rgba(255, 255, 255, .9); border-color: #8a7855; }
.facet--inline .facet__label { flex: none; }
.facet--inline .facet__opt.is-on {
  background: #2a2318; border-color: #2a2318; color: var(--page);
}
.facet--inline .facet__opt.is-on .facet__count { color: rgba(247, 233, 207, .7); }
