/* The bookcase, close up.
 *
 * /fun is a room you stand in. Clicking the bookcase should not drop you into a
 * list, so this page is the same room seen from a step closer: shelves, a board
 * under each one, and Shaun's five-out-of-five books standing on them in the year
 * he finished each one. It loads fun.css for the tokens and the typefaces and
 * then builds one component, the book.
 *
 * WHY THE BOOK IS BUILT IN CSS AND NOT DRAWN AS A PLATE
 *
 * Every other object in the room is a generated graphite plate composited at a
 * percentage coordinate, which is right for four objects and impossible for 140.
 * A book is a box, CSS draws a box at any size in any weight of paper, and the
 * title stays real selectable text that a screen reader can say and a search
 * engine can index. Adding a book stays one row of data.
 *
 * WHY IT IS DRAWN IN GRAPHITE RATHER THAN FILLED WITH FLAT COLOUR
 *
 * Shaun, 2026-08-25: "why have we not designed the books like graphite pencil?"
 * He was right and the first version was wrong. Standing a flat CSS rectangle
 * with a hairline border next to a hand drawn bookcase gives you a CHART of a
 * bookcase, and the room next door is a pencil drawing on cartridge paper.
 *
 * So each face is built the way a pencil builds one. A flat tone underneath,
 * paper grain multiplied through it, a band of hatching laid across at an angle,
 * and a contour that is a drawn SVG path with a wobble in it rather than a
 * border. All of it is CSS and two data URIs, which matters because this page
 * still has to work with JavaScript switched off.
 *
 * WHY THE COVERS ARE REAL AND REDRAWN
 *
 * Shaun, same day: "i think where possible we should use the actual cover art."
 * That reverses an earlier call in the same session for drawn covers, and it
 * pulls against the request above it, since a full colour dust jacket dropped
 * onto a pencil drawing is a photograph lying on a drawing.
 *
 * Both are satisfied by passing the real cover through the room's own duotone,
 * in scripts/build-book-covers.py, which is the same treatment every plate in
 * the room gets. A duotone keeps composition and typography and throws away only
 * hue, so the cover stays completely recognisable and is unmistakably part of
 * this room. The BOOK is drawn; the COVER is the printed thing the drawn book
 * carries, which is what a real book is.
 *
 * 113 of the 140 have a cover that could be verified as the right book in
 * English. The other 27 keep the drawn face carrying the title and the author,
 * and that fallback is not a stopgap: it is also what a browser with no WebP
 * sees, because the drawn face is in the markup underneath the image rather
 * than replaced by it.
 *
 * THE TRAP IN THIS FILE
 *
 * The three faces of the box share a base rule and are then placed individually.
 * `.bk3 > span` scores 0-1-1 and `.bk-cover` scores 0-1-0, so anything the base
 * rule sets silently beats the face that needs it different. A `left` there
 * collapses the page block onto the spine; a `display` there leaves the cover as
 * a block with a flex-direction that does nothing, so the author's name runs into
 * the title. Both render, both look nearly right, and no assertion catches
 * either: only reading the pixels back does.
 *
 * So the base rule carries only what no face overrides, which today is position
 * and top. Adding anything else to it will break a face at a distance. Put it on
 * the three faces instead, even at the cost of repeating it.
 */

/* ---- the page ---- */

/* The two textures every drawn face shares, declared once so 140 books cost one
   copy of each string rather than 140. On .reading-body rather than :root so
   they cannot leak into a page that has not asked to be drawn on. */
.reading-body {
  /* Fractal noise at the scale of cartridge paper tooth. numOctaves 4 rather
     than 1, because a single octave is television static and four is a surface. */
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='150' height='150'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' seed='11'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='0.5'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='150' height='150' filter='url(%23g)'/%3E%3C/svg%3E");
  /* Pencil hatching, laid at the angle a right hand naturally rules at. */
  --hatch: repeating-linear-gradient(62deg, rgba(20, 18, 15, .10) 0 1px, transparent 1px 5px);
}

.reading-body main { max-width: 78rem; padding: 3.25rem 1.5rem 5rem; }

.rl-top {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .8rem 1.2rem;
  margin: 0 0 1.6rem;
}
.rl-top .btn-drawn { flex: none; font-size: .95rem; padding: .7rem 1.35rem; }
.rl-top .btn-drawn i { font-style: normal; font-size: 1.05em; line-height: 1; margin-right: .45em; }

.rl-head { max-width: 40rem; }
.rl-head h1 { margin: 0 0 .7rem; }
.rl-head .lede { margin: 0; }

/* The count, sized like data rather than like prose, because it is the one fact
   on the page that a reader wants before anything else. */
.rl-count {
  font-family: var(--font-mono); font-size: .72rem; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-muted);
  margin: 2.6rem 0 0; padding: 0 0 .3rem;
}

/* ---- a shelf ---- */

.shelf-row { margin: 0 0 .3rem; }

.shelf-row h2 {
  display: flex; align-items: baseline; gap: .85rem;
  margin: 2.1rem 0 .5rem;
  font-family: var(--font-mono); font-size: .72rem; font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-muted);
}
.shelf-row h2 em { font-style: normal; font-size: .66rem; color: var(--rule); letter-spacing: .1em; }

/* A bookcase too wide for the screen is scanned sideways, which is also what you
   do to a real one. The board scrolls with the books so they never come apart. */
.shelf-scroll { overflow-x: auto; overflow-y: hidden; padding: 0 0 .4rem; }
.shelf-inner { min-width: max-content; }

.books {
  list-style: none; margin: 0;
  /* Declared, not a fallback on the one line that reads it. Every book's height
     AND the width of its boards are computed from this, so it has to be a real
     custom property that a media query can change and have the whole shelf
     follow. --shelf-h was a var(..., 186px) fallback and nothing ever set it. */
  --shelf-h: 186px;
  display: flex; align-items: flex-end; gap: 2px;
  height: var(--shelf-h);
  padding: 0 0 0 12px;
  /* One vanishing point per board, so every book on a shelf recedes together
     the way a real shelf does rather than each book having its own camera. */
  perspective: 1500px;
  perspective-origin: 50% 40%;
}

.plank {
  height: 10px; border-radius: 1px;
  background: linear-gradient(180deg, #C6B99C, #A89B80 42%, #8A8069);
  box-shadow: 0 5px 9px -4px rgba(20, 20, 20, .42);
}

/* ---- the book ---- */

/* A book's slot on the board is only as wide as its spine, so a shelf packs edge
   to edge exactly as a real one does. The 3D box overflows the slot, which is
   what lets a book swing out over its neighbours when someone reaches for it.

   The link sits inside the slot and wraps the box rather than sitting beside it.
   A transformed child is still part of its anchor for hit testing, so the turned
   out cover is clickable even though it is nowhere near the slot it came from,
   which is the half of this that an overlay hit area would get wrong. */
/* A TALLER BOOK IS ALSO A WIDER BOOK, which is why --cw is computed here rather
   than being one fixed number for all 140.
 *
 * It used to be a flat 124px against heights running 160 to 186, so the face
 * came out anywhere from 0.67 to 0.78 wide over tall depending on the book. That
 * did not matter while the face was a rectangle with type on it, and it matters
 * a lot now that it carries artwork: every cover is cut to exactly 0.68 by
 * scripts/build-book-covers.py, and object-fit then quietly ate up to 15% of the
 * widest ones. Steal Like an Artist read USTIN KLEON.
 *
 * So the board is 0.68 of the book's own height, the number the builder cuts to,
 * and both come off --shelf-h. Change the shape here and it has to change there
 * too; nothing fails if they disagree, the covers just start getting cropped
 * again. --tallf is unitless for exactly this reason: a percentage could give
 * the height but could not be multiplied to give the width. */
.bk {
  --cw: calc(var(--shelf-h) * var(--tallf) * .68);
  position: relative; flex: 0 0 auto;
  width: var(--thick); height: calc(var(--shelf-h) * var(--tallf));
}
.bk-a {
  position: absolute; inset: 0; display: block;
  text-decoration: none; color: inherit; outline: none;
}

.bk3 {
  position: absolute; inset: 0;
  width: var(--cw, 124px); height: 100%;
  transform-style: preserve-3d;
  transform-origin: 0 50%;
  transform: rotateY(88deg);
  transition: transform .42s cubic-bezier(.22, .7, .24, 1);
}
.bk:hover, .bk:focus-within { z-index: 6; }
.bk:hover .bk3, .bk:focus-within .bk3 { transform: rotateY(26deg) translateZ(26px); }

/* Only what no face overrides. See THE TRAP at the top of this file. */
.bk3 > span { position: absolute; top: 0; }
.bk-cover, .bk-spine { left: 0; }

/* HOW A FACE IS DRAWN IN GRAPHITE
 *
 * Four layers, in the order a hand would lay them: the tone of the cloth, the
 * grain of the paper multiplied through it so the tone is never flat, a band of
 * hatching at 62 degrees for the sheen down one side, and last the contour.
 *
 * The grain is one feTurbulence tile as a data URI, shared by every face on the
 * page, so 140 books cost one 300 byte string. It has to be multiplied rather
 * than laid over, or a light book gets a grey wash and a dark one gets nothing.
 *
 * The contour is the part that actually reads as drawn. A border is four
 * straight lines of one width; this is an SVG path with the corners missed and
 * the edges bowed, stretched over the face with preserveAspectRatio="none" and
 * held to an even weight by vector-effect. Stretching is deliberate: a spine is
 * a fifth the width of a cover, so the same wobble compresses into a tighter
 * jitter there, and no two books are the same size, so no two contours land
 * identically. */
.bk-cover,
.bk-spine {
  background-color: var(--tone);
  background-image: var(--grain), var(--hatch);
  background-blend-mode: multiply, multiply;
  background-size: 150px 150px, auto;
  color: var(--fg);
}
.bk-cover::after,
.bk-spine::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: var(--contour) 0 0 / 100% 100% no-repeat;
}

/* Back cover at z=0, front cover at z=thickness, and the two narrow faces
   pivoted about their own left edge to span the gap between them. */
.bk-cover {
  display: flex; flex-direction: column; justify-content: space-between;
  width: var(--cw); height: 100%;
  transform: translateZ(var(--thick));
  border-radius: 1px 3px 3px 1px;
  padding: 13px 11px;
  /* The shadow the boards cast into the hinge, which is the one piece of this
     that a drawn contour cannot say. */
  box-shadow: inset 3px 0 0 rgba(20, 20, 20, .14);
  --contour: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 124 186' preserveAspectRatio='none'%3E%3Cpath d='M2.6 3.4C34 1.9 74 4.6 121.3 2.4M121.3 2.4C122.6 62 120.4 124 121.6 183.4M121.6 183.4C82 185.1 42 182.2 2.2 184.4M2.2 184.4C3.6 124 1.4 62 2.6 3.4' fill='none' stroke='%232B2823' stroke-width='1.5' stroke-linecap='round' vector-effect='non-scaling-stroke' opacity='.66'/%3E%3Cpath d='M9.4 4.2C8.8 62 10.6 124 9.1 183.4' fill='none' stroke='%232B2823' stroke-width='1' stroke-linecap='round' vector-effect='non-scaling-stroke' opacity='.34'/%3E%3C/svg%3E");
}
.bk-cover b { font-family: var(--font-display); font-weight: 600; font-size: .84rem; line-height: 1.2; text-wrap: balance; }
.bk-cover i {
  font-family: var(--font-mono); font-style: normal; font-size: .54rem;
  letter-spacing: .06em; text-transform: uppercase; opacity: .74;
}

/* ---- the real cover, redrawn ---- */

/* The artwork sits ON the drawn board rather than instead of it. The title and
   the author are still in the markup underneath, which is what a browser with
   no WebP is left looking at, and the contour is lifted above the image so the
   drawn edge survives on a book that carries one. */
/* .bk3 is here for weight, not for reach, and it is THE TRAP at the top of this
   file wearing a different hat. An absolutely positioned child is laid against
   its ancestor's PADDING box, so the face's padding insets the artwork, and
   `.bk-cover.has-art` and `.book-hero .bk-cover` both score 0-2-0. Whichever is
   written second wins, which is a rule about the order of lines in a file
   rather than about what the page means. The extra class settles it. */
.bk3 .bk-cover.has-art { padding: 0; }
.bk-cover.has-art img {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%; object-fit: cover;
  border-radius: 1px 3px 3px 1px;
}
/* The same paper the rest of the room is drawn on, laid back over the printed
   cover. Without it the artwork is the one perfectly smooth surface on a page
   of pencil, and a smooth surface is what gives away a photograph. */
.bk-cover.has-art::before {
  content: ""; position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background: var(--grain) 0 0 / 150px 150px;
  mix-blend-mode: multiply; opacity: .5;
  border-radius: 1px 3px 3px 1px;
}
.bk-cover.has-art::after { z-index: 3; }

.bk-edge {
  display: block;
  width: var(--thick); height: 100%; left: var(--cw);
  transform-origin: 0 50%; transform: rotateY(-90deg);
  /* A page block is a stack of leaves, so it is hatched rather than filled. The
     ruling is a degree off square, because a hand ruling a hundred leaves does
     not get them parallel and a stack of pages that IS parallel reads as a
     printed swatch. */
  background:
    repeating-linear-gradient(91deg, #EFE7D6 0 1px, #D6CBB2 1px 2px),
    linear-gradient(180deg, #F0E9DC, #CFC4AA);
  border-radius: 0 2px 2px 0;
}

/* The face a reader actually sees, 139 times out of 140. Everything the cover
   gets, plus the two raised bands a bound spine carries at head and tail, which
   here are drawn lines in the contour rather than inset shadows. */
.bk-spine {
  display: flex; align-items: center; justify-content: center;
  width: var(--thick); height: 100%;
  transform-origin: 0 50%; transform: rotateY(-90deg);
  border-radius: 2px 0 0 2px;
  padding: 11px 0;
  --contour: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 186' preserveAspectRatio='none'%3E%3Cpath d='M2.4 3C11 1.8 21 4 27.6 2.6M27.6 2.6C28.7 62 26.5 124 27.8 183.6M27.8 183.6C20 185.2 10 182.4 2 184.6M2 184.6C3.2 124 1.2 62 2.4 3' fill='none' stroke='%232B2823' stroke-width='1.5' stroke-linecap='round' vector-effect='non-scaling-stroke' opacity='.7'/%3E%3Cpath d='M2.5 13.6C11 12.2 21 14.5 27.6 13M2.5 172.4C11 171 21 173.3 27.6 171.8' fill='none' stroke='%232B2823' stroke-width='1.1' stroke-linecap='round' vector-effect='non-scaling-stroke' opacity='.5'/%3E%3C/svg%3E");
}
.bk-spine b {
  writing-mode: vertical-rl;
  font-family: var(--font-display); font-weight: 600;
  font-size: var(--sfs, .72rem); line-height: 1.12;
  /* A long title wraps into a second column, which is what a real spine does.
     Truncating to an ellipsis is what a database does. */
  white-space: normal; overflow: hidden;
  max-height: 100%; text-align: center;
}

/* A book Shaun rated five out of five but has not written about yet still stands
   on the shelf, because he read it and rated it. It is simply not a link, the
   same way the cupboard in the room is furniture rather than a promise. When he
   writes the notes in Notion, the next fetch turns it into one on its own. */
.bk-unread .bk-spine { opacity: .8; }

.bk-a:focus-visible .bk-spine { box-shadow: inset 0 0 0 2px var(--orange); }
.bk:hover .bk-cover {
  box-shadow: inset 3px 0 0 rgba(20, 20, 20, .14),
    0 16px 30px -14px rgba(20, 20, 20, .5);
}

/* ---- one book's page ---- */

.book-page main { max-width: 52rem; }

.book-hero {
  display: flex; flex-wrap: wrap; align-items: flex-start; gap: 2.4rem 3rem;
  margin: 0 0 2.6rem; padding: 0 0 2.2rem;
  border-bottom: 1px solid var(--rule);
}

/* The same component, standing still and turned toward the reader, because on
   this page the book is the subject rather than one of 140.

   The height is computed from the width by the same 0.68 the shelf uses and the
   builder cuts to, rather than being a second hand measured number. It was 268
   against a width of 188, which is 0.701, and that three hundredths was enough
   to crop a strip off every cover on every book page. */
.book-hero .bk {
  --cw: 188px; --thick: 40px;
  width: 188px; height: calc(188px / .68); flex: none;
  margin: .3rem 3.2rem .3rem .4rem;
  perspective: 1400px;
}
.book-hero .bk3 { transform: rotateY(-26deg); }
.book-hero .bk:hover .bk3 { transform: rotateY(-16deg); }
.book-hero .bk-cover { padding: 20px 17px; }
.book-hero .bk-cover b { font-size: 1.12rem; }
.book-hero .bk-cover i { font-size: .62rem; }
.book-hero .bk-spine b { font-size: .8rem; }

.book-meta { flex: 1 1 20rem; min-width: 0; }
.book-meta h1 { margin: 0 0 .35rem; font-size: 2.1rem; }
.book-meta .by { margin: 0 0 1.1rem; font-size: 1.05rem; color: var(--ink-muted); font-family: var(--font-display); }
.book-facts { display: flex; flex-wrap: wrap; gap: .45rem; margin: 0; padding: 0; list-style: none; }
.book-facts li {
  font-family: var(--font-mono); font-size: .66rem; letter-spacing: .09em; text-transform: uppercase;
  color: var(--ink-muted); border: 1px solid var(--rule); border-radius: 2px; padding: .38em .6em;
}
.book-facts li.rated { color: var(--orange-deep); border-color: var(--orange); }

/* The notes are Shaun's own, straight out of Notion, so they get ordinary
   reading measure and nothing else. */
.notes { max-width: 36rem; }
.notes p { margin: 0 0 1.1rem; }
.notes ul, .notes ol { margin: 0 0 1.2rem; padding-left: 1.35rem; }
.notes li { margin: .38rem 0; }
.notes li > ul, .notes li > ol { margin: .38rem 0 .1rem; }
.notes h3 { font-family: var(--font-display); font-size: 1.18rem; margin: 1.9rem 0 .6rem; }
.notes code { font-family: var(--font-mono); font-size: .88em; background: var(--paper-2); padding: .1em .3em; border-radius: 2px; }

.book-nav {
  display: flex; flex-wrap: wrap; justify-content: space-between; gap: 1rem;
  margin: 3rem 0 0; padding: 1.3rem 0 0; border-top: 1px solid var(--rule);
  font-size: .95rem;
}
.book-nav a { text-decoration: none; }
.book-nav a:hover { text-decoration: underline; }
.book-nav .nxt { text-align: right; margin-left: auto; }

@media (max-width: 34rem) {
  .book-hero .bk { --cw: 148px; --thick: 32px; width: 148px; height: calc(148px / .68); margin-right: 2.4rem; }
  .book-meta h1 { font-size: 1.7rem; }
}

@media (prefers-reduced-motion: reduce) {
  .bk3 { transition: none; }
}
