/* GT America Light and Regular, only for the spines here: Regular for the
   titles, Light for the authors (fonts/README.md). */
@font-face {
  font-family: "GT America";
  src: url("/fonts/GT-America-Standard-Light.woff2") format("woff2");
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "GT America";
  src: url("/fonts/GT-America-Standard-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* ---- /read: every book Jason has finished, on one shelf ----
   Written by scripts/build-reading.mjs from content/reading.json, driven by
   read.js. The interaction follows Ben Tossell's bookshelf (a sideways
   shelf by year, drag, a timeline with a window, a book that lifts when
   picked); the look is the site's ledger: paper, ink, hairlines, the one
   red, mono labels. DESIGN.md, "Reading". */

.reading :is(h1, h2, h3) { text-wrap: balance; }
.reading :is(p, li) { text-wrap: pretty; }

/* ---- The head: the title and one line of instructions, with the toggle
   and the count at the right, level with the instructions' last line. On a
   phone they follow the instructions. ---- */

.reading-head { padding-bottom: var(--space-24); }
@media (width >= 700px) {
  .reading-head {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: var(--space-24);
    align-items: center;
  }
  .reading-head > .page-head__title { grid-area: 1 / 1; }
  .reading-intro { grid-area: 2 / 1; }
  /* Level with the foot of the instructions, the head's last line. */
  .reading-bar { grid-area: 1 / 2 / 3 / 3; align-self: end; margin-top: 0; }
  /* The count sits left of the toggle, so the toggle holds the corner. */
  .reading-stats { order: -1; }
}
.reading-intro {
  max-width: 520px;
  margin-top: var(--space-16);
  font: 400 19px / 25px var(--serif);
}

.reading-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-12) var(--space-24);
  margin-top: var(--space-24);
}

/* Two boxed options, the home page's filter box: the pressed one is ink. */
.reading-toggle { display: inline-flex; }
.reading-toggle__option {
  min-height: 44px;
  margin-block: calc(-1 * var(--hit-slop) + 8px);
  padding: 0 var(--space-12);
  border: 1px solid var(--ink);
  background: transparent;
  color: var(--ink);
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
  transition: background-color 120ms var(--ease-fade), color 120ms var(--ease-fade);
}
.reading-toggle__option + .reading-toggle__option { border-left: 0; }
.reading-toggle__option[aria-pressed="true"] { background: var(--ink); color: var(--paper); cursor: default; }
.reading-toggle__option:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
@media (hover: hover) and (pointer: fine) {
  .reading-toggle__option[aria-pressed="false"]:hover { background: color-mix(in oklch, var(--ink) 10%, transparent); }
}
html:not(.js) .reading-toggle { display: none; }

.reading-stats { color: var(--ink-soft); }
/* The scripted counts describe the shelf the toggle picks; without
   JavaScript every book shows, so the page counts them all instead. */
.js .reading-stats--all,
.js .shelf__count--all,
html:not(.js) [data-reading-stats],
html:not(.js) [data-shelf-count] { display: none; }

/* ---- The timeline, under the shelf: one bar a year, as wide as its shelf,
   and a window on the part of the shelf in view. It has no year labels, since
   each shelf names its own. Drag or click it to move the shelf. ---- */

.timeline {
  position: relative;
  height: 32px;
  margin-top: var(--space-16);
  cursor: pointer;
  touch-action: none;
  user-select: none;
}
html:not(.js) .timeline { display: none; }
.timeline__bars { position: absolute; inset: 0; display: flex; align-items: flex-end; gap: 2px; }
.timeline__bar { flex: 1 1 0; min-width: 1px; background: color-mix(in oklch, var(--ink) 22%, transparent); }
.timeline__window {
  position: absolute;
  top: -4px;
  bottom: -4px;
  left: 0;
  border: 1.5px solid var(--ink);
  pointer-events: none;
}

/* ---- The shelf ---- */

.shelf { outline: none; }

/* Full bleed, so the shelf runs to the window's edges; the gutter is padding
   inside the scroll, so the first and last shelves still start at it. The
   top room lets a picked book lift. */
.shelf__stage {
  margin-inline: calc(-1 * var(--gutter));
  padding: var(--space-40) var(--gutter) var(--space-4);
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}
.shelf__stage::-webkit-scrollbar { display: none; }
.js .shelf__stage { cursor: grab; }
.shelf__stage.is-dragging { cursor: grabbing; }
.shelf__stage.is-dragging .spine { pointer-events: none; }

.shelf__track { display: flex; align-items: flex-end; gap: var(--space-40); width: max-content; list-style: none; }
.shelf__year[hidden] { display: none; }

/* The plank is a 1.5px ink rule, the panel's own edge. */
.shelf__books {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  padding-inline: var(--space-8);
  border-bottom: 1.5px solid var(--ink);
  list-style: none;
}
.spine-slot { display: flex; }
.spine-slot[hidden] { display: none; }

.shelf__label {
  display: flex;
  align-items: baseline;
  gap: var(--space-10);
  padding: var(--space-12) var(--space-8) 0;
  font: 400 26px / 30px var(--display);
  letter-spacing: -0.02em;
  white-space: nowrap;
}
.shelf__count { color: var(--ink-soft); letter-spacing: 0.06em; }
/* A year's label stays at the window's left edge while its shelf passes,
   so the year in view always shows (the timeline has no labels). */
/* Sticky offsets count from inside the stage's side padding, already the
   gutter, so pulling back the label's own padding lines its year up with
   the page's text. */
.shelf__label { position: sticky; left: calc(-1 * var(--space-8)); width: max-content; }

/* ---- A spine, drawn to scale: --hin and --tin are its height and
   thickness in inches (build-reading.mjs, from the page count). A book
   thinner than --spine-min is drawn at that width, so every title has room.
   read.js sets the inch so the tallest book in view leaves the plank and the
   year labels above the fold; without it, the shortest stands half the
   window tall. Its colour is the cover's strongest colour, with the type in
   near-black or near-white, whichever reads.

   Every setting for the spine's type is a variable on .shelf, so read-dial.js
   (/reading/?dial) can tune them live; these are the chosen values. ---- */

.shelf {
  --in: calc(max(50svh, 320px) / 7.8);
  --spine-min: 0.95;
  /* The title: its size follows the spine's width, between a floor and a ceiling. */
  --title-scale: 0.32;
  --title-min: 12px;
  --title-max: 22px;
  --title-family: var(--body);
  --title-weight: 400;
  --title-track: -0.005em;
  --title-case: none;
  /* The author, sized against the title. */
  --author-size: 0.68;
  --author-family: var(--body);
  --author-weight: 300;
  --author-track: 0.01em;
  --author-case: none;
  --author-opacity: 0.85;
  --author-show: flex;
  /* How the two sit: stacked lines (column), one after the other down the
     spine (row), or the title at the top and the author at the foot (split). */
  --text-flow: column;
  --text-spread: normal;
  --text-fill: 0;
  --text-gap: 0.15em;
  --text-place: flex-start;
  --text-x: 0px;
  --text-y: 2px;
  --text-reads: vertical-rl;
  --text-turn: 0deg;
  --spine-pad-top: 12px;
  --spine-pad-foot: 28px;
  /* The least room either side of the title and author. */
  --spine-pad-side: 7px;
  --edge-mix: 80%;
  --lift: 12px;
}

.spine {
  --spine-bg: var(--fixed-paper);
  --spine-ink: var(--fixed-ink);
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: var(--text-place);
  width: calc(var(--in) * max(var(--spine-min), var(--tin)));
  /* Never narrower than its own title and author plus the side room, so a
     phone's larger text setting widens the book instead of overrunning it. */
  min-width: max-content;
  height: calc(var(--in) * var(--hin));
  padding: var(--spine-pad-top) var(--spine-pad-side) var(--spine-pad-foot);
  /* On a light page every edge is a little darker than its book. On a dark
     one each edge leans toward the book's own type colour, so a dark book
     gets a light edge against the page and a light book a dark one against
     its neighbours. */
  border: 1px solid color-mix(in oklch, var(--spine-bg) var(--edge-mix), light-dark(var(--fixed-ink), var(--spine-ink)));
  background: var(--spine-bg);
  color: var(--spine-ink);
  text-decoration: none;
  -webkit-user-drag: none;
  transition: translate 200ms var(--ease-out);
}
/* A book without a cover gets one of the page's light-mode tones, fixed, so
   it is the same book in dark mode; only covers' colours vary by book. */
.spine[data-tone="ink"] { --spine-bg: var(--fixed-ink); --spine-ink: var(--fixed-paper); }
.spine[data-tone="shade"] { --spine-bg: oklch(0.884 0.005 106.5); }
.spine[data-tone="tint"] { --spine-bg: oklch(0.917 0.007 106.5); }

/* The title and the author in GT America Light, reading top to bottom. The
   title grows with the spine; each line cuts off with an ellipsis rather
   than run off the book. */
.spine__text {
  display: flex;
  flex-direction: var(--text-flow);
  flex-grow: var(--text-fill);
  justify-content: var(--text-spread);
  gap: var(--text-gap);
  min-height: 0;
  max-height: 100%;
  /* --fit is read.js shrinking a long title's spine until the whole title fits. */
  font-size: calc(var(--fit, 1) * clamp(var(--title-min), var(--in) * max(var(--spine-min), var(--tin)) * var(--title-scale), var(--title-max)));
  writing-mode: var(--text-reads);
  rotate: var(--text-turn);
  translate: var(--text-x) var(--text-y);
}
.spine__title,
.spine__author {
  overflow: hidden;
  min-height: 0;
  white-space: nowrap;
  text-overflow: ellipsis;
}
/* A title too long for one line even at 9px takes two (read.js). */
.spine[data-wrap] .spine__title { white-space: normal; text-overflow: clip; text-wrap: balance; }

/* Short of room, the author gives way before the title. */
.spine__title {
  flex-shrink: 1;
  max-block-size: none;
  font: var(--title-weight) 1em / 1.1 var(--title-family);
  letter-spacing: var(--title-track);
  text-transform: var(--title-case);
}
.spine__author {
  display: var(--author-show);
  flex-shrink: 6;
  font: var(--author-weight) calc(var(--author-size) * 1em) / 1.15 var(--author-family);
  letter-spacing: var(--author-track);
  text-transform: var(--author-case);
  opacity: var(--author-opacity);
}

/* The marks, at the foot: a star for a favorite (5), always lowest, and
   above it a red square for a review. Nothing else is marked. The square keeps a thin edge
   in the spine's type colour, so it shows on a red book too. */
.spine__marks {
  position: absolute;
  bottom: var(--space-10);
  left: 50%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  translate: -50% 0;
}
.spine__star {
  width: 11px;
  height: 11px;
  background: currentColor;
  clip-path: polygon(50% 0, 61.8% 35.4%, 100% 38.2%, 69.1% 61.8%, 80.9% 100%, 50% 76.4%, 19.1% 100%, 30.9% 61.8%, 0 38.2%, 38.2% 35.4%);
}
.spine__review {
  width: 8px;
  height: 8px;
  background: var(--accent);
  box-shadow: 0 0 0 1px color-mix(in oklch, var(--spine-ink) 35%, transparent);
}

/* Every book peeks up under the pointer; only one with a review opens, so
   only it takes a pointer cursor. */
.spine:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.spine[aria-current="true"] { translate: 0 calc(-1.6 * var(--lift)); }
.js .spine[data-review] { cursor: pointer; }
@media (hover: hover) and (pointer: fine) {
  .spine:hover { translate: 0 calc(-1 * var(--lift)); }
  .spine[aria-current="true"]:hover { translate: 0 calc(-1.6 * var(--lift)); }
}
@media (prefers-reduced-motion: reduce) {
  .spine { transition: none; }
}

/* ---- The page doesn't scroll: the shelf fits above the fold (read.js) and
   the wheel moves it along. A window too short for the shelf's floor
   (.reading-short, read.js) and a page without JavaScript scroll as usual. ---- */

html.js:has(.reading):not(.reading-short) { overflow: hidden; }

/* ---- A review: a dialog over the dimmed page, the cover beside the words,
   coming and going as the lightbox does (style.css). ---- */

.reading-dialog {
  width: min(820px, 100vw - 2 * var(--gutter));
  max-width: none;
  max-height: calc(100dvh - 2 * var(--space-40));
  margin: auto;
  padding: var(--space-32);
  overflow: auto;
  overscroll-behavior: contain;
  border: 1.5px solid var(--ink);
  background: var(--paper);
  color: var(--ink);
}
.reading-dialog::backdrop { background: var(--ink-scrim); }
.reading-dialog__body { display: grid; grid-template-columns: 200px minmax(0, 1fr); gap: var(--space-32); align-items: start; }
.reading-dialog__cover { margin: 0; }
.reading-dialog__cover img {
  display: block;
  width: 100%;
  height: auto;
  box-shadow: 0 0 0 1px color-mix(in oklch, var(--ink) 14%, transparent);
}
/* No cover image: a plain one in the spine's colour, set in type. */
.reading-dialog__plain {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  aspect-ratio: 2 / 3;
  padding: var(--space-16);
  border: 1px solid color-mix(in oklch, var(--plain-bg, oklch(0.884 0.005 106.5)) 72%, var(--ink));
  background: var(--plain-bg, oklch(0.884 0.005 106.5));
  color: var(--plain-ink, var(--fixed-ink));
}
.reading-dialog__plain-title { font: 500 20px / 24px var(--body); text-wrap: balance; }
.reading-dialog__head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-16); }
.reading-dialog__meta { color: var(--ink-soft); }
.reading-dialog__close {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--space-6);
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-soft);
  white-space: nowrap;
  cursor: pointer;
}
/* A 44px target around a small label, as the panel's Close has. */
.reading-dialog__close::before {
  position: absolute;
  inset: 50% auto auto 50%;
  width: max(100%, 44px);
  height: 44px;
  content: "";
  transform: translate(-50%, -50%);
}
.reading-dialog__close:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
@media (hover: hover) and (pointer: fine) {
  .reading-dialog__close:hover { color: var(--ink); }
}
.reading-dialog__title { margin-top: var(--space-16); font: 400 32px / 36px var(--display); letter-spacing: -0.02em; text-wrap: balance; }
.reading-dialog__title:focus { outline: none; }
.reading-dialog__author { margin-top: var(--space-8); font: 400 21px / 28px var(--serif); }
.reading-dialog__review { margin-top: var(--space-24); font: 400 19px / 25px var(--serif); text-wrap: pretty; }
.reading-dialog__review p + p { margin-top: var(--paragraph-gap, 18px); }
@media (width < 600px) {
  .reading-dialog { padding: var(--space-24) var(--space-16); }
  .reading-dialog__body { grid-template-columns: 1fr; gap: var(--space-24); }
  .reading-dialog__cover { width: 140px; }
  .reading-dialog__title { font-size: 26px; line-height: 30px; }
}
@media (prefers-reduced-motion: no-preference) {
  .reading-dialog,
  .reading-dialog::backdrop {
    opacity: 0;
    transition: opacity 150ms var(--ease-fade), overlay 150ms allow-discrete, display 150ms allow-discrete;
  }
  .reading-dialog[open],
  .reading-dialog[open]::backdrop { opacity: 1; transition-duration: 220ms; }
  @starting-style {
    .reading-dialog[open],
    .reading-dialog[open]::backdrop { opacity: 0; }
  }
}

/* ---- Reviews as a list, for a reader without JavaScript; the dialog shows
   them otherwise. ---- */

.reading-reviews { margin-top: var(--space-48); }
.js .reading-reviews { display: none; }
.reading-review { max-width: 640px; margin-top: var(--space-24); font: 400 19px / 25px var(--serif); }
.reading-review h3 { font: 500 17px / 22px var(--body); }
.reading-review .micro { margin-bottom: var(--space-8); color: var(--ink-soft); }
