/* GENERATED by design/ship.py from design/library/library.css -- DO NOT EDIT. That file is the master; run `python3 design/export.py --shell` and this one is rewritten. */
/* THIS FILE IS THE DESIGN MASTER OF THE SHELF'S PICTURE (LIBRARY-DESIGN,
   wave 10, 26 Sep 2026) -- library/library.css, every rule of it, with the
   palette taken from design/reader/tokens.css (the page links it first) and
   THE SHELF, DRAWN as the last block. design/library/library.html dials the
   block's seven properties; on Osca's yes `export.py --shell` carries this
   file to shell/library/library.css byte for byte (design/ship.py, `library`
   in DESIGN_MODULES). Until then library/library.css is what ships. */
  :root {
    /* ONE PALETTE (POLISH rule 12): every value is tokens.css's, light and
       dark alike -- this sheet writes no colour of its own. The names the
       rules below spend are kept; they point at the tokens now. */
    --bg: var(--chrome-bg);
  /* ---- THE RADIUS SCALE (Osca, 30 Aug: "all boxes across all pages are just
     the wrong shape, wrong curve, not modern enough").
     Four files had grown ten different radii between them -- 3, 4, 6, 7, 8,
     10, 12, 14, 50% and 999px -- assigned per element as each was written, so
     no two boxes on a screen agreed and none of them agreed with the next
     screen. These four steps are the whole vocabulary now, and they are one
     step larger at every level than what they replace, which is most of what
     "modern" means here. A box picks the step for its ROLE, not its size:
       --r-0  a mark on running text (the word, the sentence highlight)
       --r-1  a control you press (button, input, select, small chip)
       --r-2  a surface that holds things (card, section, panel, header button)
       --r-3  a surface that floats over another (sheet, popup, menu)
     Anything outside these four is a deliberate exception and says why. ---- */
  --r-0: 5px;
  --r-1: 11px;
  --r-2: 15px;
  --r-3: 22px;
  --r-pill: 999px;
  /* The chrome's font, not the book's -- see reader.html's own note. */
  --ui-font: var(--chrome-ui-font);
    --fg: var(--chrome-fg); --fg-dim: var(--chrome-fg-dim); --accent: var(--chrome-accent);
    --border: var(--chrome-border); --panel-bg: var(--chrome-panel-bg); --link: var(--chrome-link);
    --control-bg: var(--chrome-control-bg); --ok: var(--chrome-ok); --bad: var(--chrome-bad);
    --warn: var(--chrome-warn);
  }
  /* dark is tokens.css's swap of the same names; nothing to repeat here */
  /* ---- THE PAPER'S WARMTH, the same one number the reading page takes
     (reader/reader.css's own block, and reader/settings.js writes it). The
     shelf is paper too: warming the book you are reading and leaving the room
     you chose it from cold would be the seam a person notices every time they
     switch tabs. Mixed from the real colour, so warmth 0 IS the shelf that was
     always there, exactly. The CHROME does not warm and should not -- chrome
     belongs to the window, paper to the book. */
  :root { --paper-warm: var(--chrome-paper-warm); --ink-warm: var(--chrome-ink-warm); }
  /* ---- AND THE WARMTH IS BEHIND @supports, which is not decoration
     (6 Sep, measured in a real WebKit).  A custom property takes ANY token
     stream: `--bg: color-mix(...)` parses in every engine ever written, so it
     overrides the plain `--bg: #fcfcfb` above whether or not the engine can
     compute it.  An engine without `color-mix` -- Safari before 16.2, which is
     the phone -- then fails at computed-value time instead, `background:
     var(--bg)` becomes `unset`, and the page paints on whatever is behind the
     webview with NO console error of any kind.  Measured, WebKitGTK 2.36
     (Safari 15) against this file before the guard: `body` background
     `rgba(0, 0, 0, 0)`, `--bg` still the literal `color-mix(...)` string; with
     the guard, `rgb(252, 252, 251)`, which is the plain value four lines up
     doing its job.  `@supports` is the only construct that stops the override
     from being seen at all, because the alternative -- a second declaration
     after it -- is dropped by the same "any tokens are valid" rule. ---- */
  /* ...AND SINCE ASKS #172 (30 Sep) THE WARMTH IS THE TOKENS' OWN, NOT THIS FILE'S. The mix that stood here spent
     `var(--warm) * 100%` -- but --warm is 0..18 (prefs.js writes warmth x 4.5; tokens.css is solved for 0..18), so
     every paper past the first came out as 100 % candle: Ivory and Candle were the same shelf. tokens.css now makes
     the chrome the paper (--chrome-bg / --chrome-fg ARE --ground / --ink, warmth and contrast in both themes), so
     the shelf takes Settings' warmth and contrast from the same two numbers as the page of a book, and a second mix
     here would warm it twice. */

  * { box-sizing: border-box; }
  html, body { margin: 0; padding: 0; background: var(--bg); color: var(--fg);
    font-family: var(--ui-font); height: 100%; }
  body { height: 100vh; display: flex; flex-direction: column; overflow: hidden; }
  header { padding: 10px 16px; border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 10px; flex: 0 0 auto; }
  /* THE STRIP IS THE HEADER, and only in the app (desktop/README.md
     `## Status`'s desktop-headless-chrome entry §6). Written out because the
     UA's own `[hidden] { display: none }` loses to the `display: flex` on the
     line above. In a plain browser and on the phone nothing sets it. */
  header[hidden] { display: none !important; }
  .headerspace { flex: 1; }
  /* A hairline box around every icon in the bar is the single most dated
     thing on these pages: five outlined squares in a row read as a toolbar
     from 2012. The button is the icon; the surface only appears under the
     pointer, which is also the only moment it means anything. */
  header button, header .headerLink {
    min-height: 40px; min-width: 40px;
    border-radius: var(--r-1); border: 1px solid transparent;
    background: transparent; color: var(--fg-dim);
    font-size: 1rem; cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center;
    text-decoration: none;
    transition: background 0.14s, color 0.14s;
  }
  header button:hover, header .headerLink:hover {
    background: var(--control-bg); color: var(--fg);
  }
  header button:active, header .headerLink:active { transform: scale(0.96); }
  header button svg { display: block; }
  header button:focus-visible, header .headerLink:focus-visible {
    outline: 2px solid var(--accent); outline-offset: 1px;
  }
  /* THE PHONE'S DOOR TO SETTINGS (library.html `#settingsDoor`): nothing on
     the Mac, where Settings is a window off Cmd-, and the menu (30 Aug, the
     gear left the header). THE PHONE WHOLE block at the foot of this file
     shows it under the phone's media query, and nowhere else. */
  #settingsDoor { display: none; font-size: 1.15rem; }
  /* The way out of the Library (Osca, 28 Aug: "Open library means I pretty much
     have to close the app"). Top-left in BOTH layouts, before the sources toggle. */

  /* ---- the shelf = the library rail. SHARED CONTRACT with studio/studio.html:
     every rule from .rail to .railrow-status below must stay byte-identical
     in both files (reader/tests/test_library_rail.py). The bench may only
     ADD (.railmore); this page adds only .ingest-* to a row still being
     ingested (further down). Spec: PROMPTS/library-sidebar.md.
     Moved here from reader.html on 28 Aug (READER_FIRST.md: the reader's
     sidebar is this book's chapters; the Library page holds the shelf). ---- */
  .rail {
    width:236px; flex:0 0 236px; border-right:1px solid var(--border);
    background:var(--panel-bg); overflow:auto; padding:14px 0;
  }
  .rail.hidden { display:none; }
  .rail h2 {
    margin:0 0 8px; padding:0 16px; font-size:0.72rem; font-weight:600;
    letter-spacing:0.09em; text-transform:uppercase; color:var(--fg-dim);
  }
  .railrow {
    display:block; width:100%; text-align:left; font:inherit; color:inherit;
    background:transparent; border:none; border-left:2px solid transparent;
    padding:9px 16px; cursor:pointer;
  }
  .railrow:hover { background:var(--control-bg); }
  .railrow[aria-selected="true"] { border-left-color:var(--accent); background:var(--control-bg); }
  .railrow-title { display:block; font-weight:600; font-size:0.9rem;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .railrow-by { display:block; color:var(--fg-dim); font-size:0.8rem;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .railrow-status { display:block; margin-top:3px; color:var(--fg-dim); font-size:0.76rem; }
  .railrow-status b { color:var(--ok); font-weight:600; }

  /* library-page layout: the shelf is the main pane, so it grows */
  .shell { flex: 1 1 auto; display: flex; min-height: 0; }
  #shelf.rail { container: shelf / inline-size;
    flex: 1 1 auto; width: auto; border-right: none; background: var(--bg);
    padding: 22px 34px; }
  /* a device row ("On this device", the phone's own books) is still a row:
     it is not in the picture, and nothing here turns it into a tile */
  #shelf .railrow:not(.tile) { max-width: 560px; }
  .empty { padding: 16px; color: var(--fg-dim); font-size: 0.9rem; }

  /* THE NO-PACK LINE (settings.js::tileLineHTML, G-LANG). Its own class since
     13 Sep -- it wore `.railrow-by` and so was placed in the byline's cell at
     the 1 ¶ rung and drawn on top of it. Same type, its own row. */
  .railrow-lang { display:block; color:var(--fg-dim); font-size:0.8rem;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .railrow-lang[hidden] { display:none; }
  /* ...MOVED OUT OF THE SHARED BLOCK, 14 Sep, and not one byte of it changed.
     It was written between `.railrow-by` and `.railrow-status`, which is
     INSIDE `.rail` .. `.railrow-status b` -- studio's half of a contract this
     page may not restyle, asserted byte for byte by
     `library/tests/test_library_rail.py::test_rail_css_byte_identical`. That
     test has been red since 13 Sep for this reason alone (measured: the whole
     of the drift is these six lines). Like the verdict below it, the no-pack
     line is drawn on TILES and on nothing else -- `tileLineHTML` is called
     from `tileHTML`, never from `railRows` -- so this is where it always
     belonged. Same rule, same order relative to everything that reads it. */

  /* THE VERDICT, ON THE TILE (P1; drawn 14 Sep). One word, the parser's own,
     and only where the book is not clean -- `library.html::verdictTileHTML`
     says why `clean` is silent here and spoken on the book page.

     ITS OWN ROW, LEARNED FROM THE LINE ABOVE IT. G-SMALL, 13 Sep: the no-pack
     line wore `.railrow-by` and was placed in the byline's cell at the 1 ¶
     rung and drawn on top of it. So this is its own class, and -- see the
     rungs below -- it is not drawn at 1 ¶ at all, where the pane says the same
     thing in full: no new grid row anywhere, and every rectangle G-SMALL
     measured stands unchanged.

     AND IT IS OUTSIDE THE SHARED BLOCK. `.rail` .. `.railrow-status b` is
     studio's half of a contract this page may not restyle
     (`library/tests/test_library_rail.py::test_rail_css_byte_identical`, which
     caught this rule sitting inside it). The word is drawn on TILES and on
     nothing else -- `verdictTileHTML` is called from `tileHTML`, never from
     `railRows` -- so it belongs here, with every other rule that turns a row
     into a tile. */
  .railrow-verdict { display:block; font: 600 0.66rem/1.4 var(--ui-font);
    letter-spacing:0.14em; color:var(--fg-dim);
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .railrow-verdict[data-verdict="damaged"],
  .railrow-verdict[data-verdict="unrecoverable"] { color:var(--warn, #b4622a); }

  /* ---- THE SHELF IS COVER TILES (PROMPTS/mock/library.html, 1 Sep).
     The shared `.rail` block above is studio's half of a contract this page
     may not restyle (reader/tests/test_library_rail.py), so every rule that
     turns a row into a tile is HERE, after it, and scoped to `.tile`. The
     row keeps its `.railrow` class and its `data-slug`, which is why
     selection, Enter, double-click, drag, the ⋯ menu and the ingest veil all
     go on working without a single handler being re-keyed. ---- */
  /* EVERY TILE THE SAME SIZE, EVERY PART ON THE SAME LINE (Osca, 1 Sep:
     "and all aligned, the same size"). The tile is a three-row grid --
     120px cover, a 34px title block that is exactly two 17px lines whether
     the title fills them or not, and an 18px line under it -- so a
     one-word title and a title that wraps twice put their covers, their
     captions and their grey lines on the same three baselines across the
     whole shelf. It is the mock's rule, row for row; the old `margin-bottom`
     under the cover is now the grid's `row-gap`. */
  /* ---- FOUR BOOKS ACROSS (Osca, 5 Sep: "go there get the new library ...
     that's the new library"). `reader/design/library.html`'s grid, value for
     value: four equal columns in a column 1120px wide or 88vw, whichever is
     smaller, its 2.6rem/1.6rem gaps and its two step-downs. A tile is no
     longer 120px of anything -- it is a quarter of that column, and the cover
     fills it, which is roughly three times the size it was.

     THREE THINGS OF THE BENCH'S DID NOT COME ACROSS, each for a reason
     already written down on this page:
       * its `.head` -- "THE LIBRARY", the count, a dark button. This page has
         no <h1> ON PURPOSE (see library.html): in the app the strip is the
         title bar and says Library. The bench needs a head because the bench
         has no strip. The theme lives in Settings.
       * its `<img src="../../books/<slug>/cover.jpg">`. THE TYPESET COVER IS
         THE COVER here (Osca, 1 Sep, and fifteen lines of why, above). The
         bench draws the jacket because the bench has no cover engine; this
         page has one hundred covers. §6 asks whether that should change.
       * its `location.href = "shell.html#book=" + slug`. Opening a book is a
         new crown, and a reader tab that navigates itself anywhere near the
         Library is closed by `track_url`. No click handler is touched here.

     THE THREE BASELINES SURVIVE (Osca, 1 Sep: "and all aligned, the same
     size"). The bench's tile is a flex column, so a two-line title there
     pushes its grey line down and the shelf loses its rows. This one stays a
     grid: the cover row is `auto` because `aspect-ratio` now drives it, the
     title block is exactly two lines of its own leading whether it fills them
     or not, and the line under it is one. Same rule as before, expressed in
     the type's own units instead of in 34px and 18px. ---- */
  /* TWO OF THE BENCH'S NUMBERS ARE VIEWPORT NUMBERS, AND THIS PAGE HAS
     SIDEBARS. `88vw` and `@media (max-width: 920px)` are right on a page whose
     only column is the window, which is what the bench is. Here Sources opens
     on the left and the phone shelf on the right, either or both, and the
     shelf is what is left over -- so a shelf 700px wide inside a 1400px window
     would take 88% of the WINDOW and hang off its own column, and would still
     be drawing four across because the WINDOW is over 920. Both are therefore
     measured against the shelf itself: `92%` of its column, and `@container`
     in place of `@media`. Same proportions, same two step-downs, asked of the
     right box.

     BIGGER, AND FURTHER APART (Osca, 5 Sep: "make the books slight bigger,
     more spaced", then "I want the books to be spaced more horizontally").
     1120 wide with 1.6rem/2.6rem gaps became 1410 with 4.8rem across and
     3.2rem down -- 77px between two books against 51px between two rows,
     half again as much air sideways as down. He has asked four times and
     every one of them was sideways ("more spaced", "spaced more
     horizontally", "slighter further apart left to right ... centred as they
     are but spread out a bit more sideways", "just a tad more"), so the row
     gap has not moved since the first and the column gap is the only dial
     that has been turned since. 1.6 -> 2.2 -> 3.4 -> 4.2 -> 4.8rem.

     THE FIXED COLUMN THAT CARRIED THOSE GAPS IS GONE (Osca, 6 Sep: "the
     bench wins"). It said: "the column is 1410 ... two columns want 665 and
     one wants 300", because more air between four books in a fixed box can
     only come out of the books. The bench answered that by making the BOOK
     the fixed thing and letting the shelf grow into the margin, which is the
     block below; the gaps and the step-downs above are unchanged by it, and
     294 is now `--bookw` rather than a quarter of 1410. */
  /* ---- THE BOOK IS THE FIXED THING AND THE SHELF GROWS AROUND IT
     (design/reader/library.html, 6 Sep; Osca that day: "the bench wins").

     Osca on the bench: "I increase spacing here, the books just get smaller --
     I don't want them smaller. There are huge margins in the page, I want to
     move them further apart. The margins are just made up, and it won't let
     me space past them."

     He is right, and this is the bench's fix, ported. The grid was
     `width:min(1410px,92%)` with `1fr` columns: a fixed box, so every rem of
     gap came OUT OF THE COVERS and the shelf could never cross the margin.
     Now the columns are `--bookw` wide and the grid is `fit-content`, centred,
     free to widen up to the shelf. Opening the gap moves the books apart and
     takes the room from the empty page either side, which is what the margin
     was. The covers only give way when the shelf actually reaches the page
     edge -- `minmax(0, --bookw)` lets the tracks shrink then, as before.

     4 AT ITS DEFAULT IS STILL THE APP TO THE PIXEL, which is the whole reason
     the model could change at all: 4 x 294 + 3 x 4.8rem = 1406, which is what
     `min(1410px, 92%)` with `1fr` columns was giving. The hand-set numbers of
     the 5th -- 294px and the step-downs at 1290 and 620 -- are unmoved; they
     are now written as `--bookw` and `--cols` instead of as a width and a
     repeat count. The GAP is not the 5th's any more: 3.2rem down and 4.8rem
     across became 4.30 and 6.45 on 10 Sep, Osca's own dial (THE SPACING,
     below), and this base rule carries 4's pair so the first paint is it.

     AND IT STAYS CENTRED ("centred as they are but spread out a bit more
     sideways"). `margin: 4px auto 0` is what centres it and it has not been
     touched; the column grows about its own middle. */
  /* THE TWO NUMBERS SIT ON THE GRID, NOT ON THE SHELF, and that is not a
     style choice -- IT IS THE ONLY PLACE THEY WORK. `#shelf` IS the container
     (`container: shelf / inline-size`, above), and an element cannot answer a
     container query about itself: a `@container shelf (...)` rule that names
     `#shelf` matches nothing, ever. The bench draws them on its `.sheet`,
     which is its container, so the bench's own two step-downs are dead and
     nobody could see it because the numbers only differ below 1290. Measured
     here at 1280 before the move: `--cols` computed 4 with the query in force
     (CLAUDE.md, "where prose and code disagree, the code is right" -- the
     rule the bench MEANT is the one ported, and library/STATUS.md §6 asks the
     bench for the same one-line fix). `.shelfgrid` is a descendant of the
     container, so on it they work. */
  .shelfgrid { --cols: 4; --bookw: 294px;
    display: grid;
    grid-template-columns: repeat(var(--cols), minmax(0, var(--bookw)));
    gap: 4.30rem 6.45rem; width: fit-content; max-width: 100%; margin: 4px auto 0;
    padding-bottom: 3.5rem; }
  /* A BOOK IS THE SAME SIZE AT EVERY STEP. Dropping from four columns to two
     must not make each book BIGGER as the window gets smaller -- that reads as
     the shelf lurching rather than reflowing. `--bookw` is the fixed thing and
     only `--cols` steps, which is that rule expressed once instead of three
     times. The two widths are the app's own, 1290 and 620, and they belong to
     the app's own zoom and to no other: at 8 up the whole point is that the
     columns do not step, and at 2 and 1 the tile is a different shape. */
  @container shelf (max-width: 1290px) { #shelf[data-zoom="4"] .shelfgrid { --cols: 2; } }
  @container shelf (max-width: 620px) { #shelf[data-zoom="4"] .shelfgrid { --cols: 1; } }

  /* ---- THE SORT BAR, AND THE BANDS IT SORTS (design/reader/bench-library.html).
     Osca: "I need a small drop down in library, to change how the books are
     ordered. Alphabetically by title, and by author and most recent. Put newly
     added in its own section too."

     THE BENCH'S THREE DIALS ARE NOT HERE, and that is the foot's own rule
     said again: the bench sets where the bar stands across the page, how big
     it is and whether it is at the top of the shelf or down in the foot, and
     the app SHIPS the numbers that came out of it -- at the top of the
     shelf, 12px, and AT THE RIGHT since 10 Sep (Osca's dial: the log's
     `.sortbar{ justify-content:flex-end; }`; it was left). A dial in the app
     would be a second place to set one number, which is the drift the loop
     exists to end. */
  .sortbar { --sortsize: 12px;
    display: flex; align-items: center; gap: 9px;
    width: 94%; margin: 0 auto; padding: 1.1rem 0 0.2rem;
    justify-content: flex-end; }
  .sortbar label { font: 600 calc(var(--sortsize) - 1px)/1 var(--ui-font);
    letter-spacing: 0.12em; text-transform: uppercase; color: var(--fg-dim); }
  .sortbar select { font: var(--sortsize)/1.2 var(--ui-font); color: var(--fg);
    background: var(--control-bg); border: 1px solid var(--border);
    border-radius: var(--r-pill); padding: 0.42em 0.8em; cursor: pointer;
    -webkit-appearance: none; appearance: none;
    background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
      linear-gradient(135deg, currentColor 50%, transparent 50%);
    background-position: calc(100% - 14px) 55%, calc(100% - 9px) 55%;
    background-size: 5px 5px, 5px 5px; background-repeat: no-repeat;
    padding-right: 2em; }
  .sortbar select:hover { border-color: var(--fg); }

  /* A BAND IS A HEADING AND A SHELF, sized to the shelf so the heading lines
     up with the first book's left edge instead of floating in the margin.
     `.shelfgrid` is `width: fit-content` centred on `margin: 4px auto`, so a
     band has to be the same or the two are centred on different boxes. */
  /* `.shelfband` since LIBRARY-DESIGN: `.band` is the doors' component (band.css) */
  .shelfband { width: fit-content; max-width: 100%; margin: 0 auto; }
  .shelfband[hidden] { display: none; }
  .bandhead { margin: 0; padding: 1.4rem 0 0; font: 600 12px/1.4 var(--ui-font);
    letter-spacing: 0.13em; text-transform: uppercase; color: var(--fg-dim); }

  /* ====================================================================
     THE LADDER -- SIX ZOOMS, AND ONE DIMENSION: how much of the pane a
     single book is given. Out to in (design/reader/bench-library.html):

      16   the whole library on one screen. Covers only, and they do not step
           down until the shelf is under 1100px, where they go to eight rather
           than shrink past reading (Osca, 6 Sep: "a new view in library on 16
           book rows, larger than 8 -- keep all others, just add a 16 row view")
       8   the whole shelf at a glance. Covers only -- the caption IS the
           cover, and the title/byline rows are taken away rather than shrunk,
           because a two-line title at 150px is a smear
       4   THE APP, and the one the shelf opens on
       2   two books, the covers given the room the caption was using
     1 ¶   ONE BOOK AS ITS OWN PAGE (10 Sep): the cover, and beside it the
           book's text -- title, byline, the facts, the blurb, its languages,
           its voices, its chapters. `1t` in the attribute, the bench's name
       1   one jacket, and the shelf snaps book to book

     One attribute on `#shelf` and every difference between the six is CSS off
     it. Nothing is re-rendered when it changes -- the tiles are already whole
     -- so zooming keeps the book under the cursor rather than jumping to the
     top of the shelf. THERE ARE NO BUTTONS FOR IT (Osca, 10 Sep): the foot
     that carried 16/8/4/2/1 is gone, and the pinch -- cmd/ctrl + wheel, which
     is what a trackpad pinch arrives as -- is the whole of the control.

     1 ¶ LANDED WHEN ITS FACTS BECAME REAL. It stayed off the page while the
     bench could only DRAW its pane (grey bars, labelled outlines) -- a picture
     of a promise. The book record now carries description, publisher, date,
     identifier and subjects (`c0ad5df`, 8 Sep), so the pane is filled from
     `GET /book` and the outlines are gone: a real fact replaces the drawing,
     and a fact the record does not have is not drawn at all. 2 ¶ is still the
     bench's alone.

     The gaps are Osca's own dial, one block, after the rungs: THE SPACING,
     below. ==================================================== */

  /* ---------------- 16 UP: sixteen across, covers only ------------------- */
  /* 75px: 16 x 75 + 15 x 0.90rem (14.4) = 1416, the same width 8's row takes
     (8 x 150 + 7 x 1.90rem = 1413) and 4's (1406) -- so the shelf keeps its
     edge and the covers halve, which is the whole of the step out. The row
     gap keeps 8's own ratio to the column gap (1.70 / 1.90 = .895), so the
     pane's character survives the step. Under 1100px sixteen 75px covers
     would have to shrink past reading, so the row goes to eight -- the same
     width 8 up steps to four at -- and to four under 620. */
  #shelf[data-zoom="16"] .shelfgrid { --cols: 16; --bookw: 75px; }
  #shelf[data-zoom="16"] .railrow.tile { grid-template-rows: auto; row-gap: 0; }
  #shelf[data-zoom="16"] .railrow.tile:hover,
  #shelf[data-zoom="16"] .railrow.tile:focus-visible { transform: translateY(-2px); }
  /* a 75px board with an 11px corner reads as a lozenge, not a book */
  #shelf[data-zoom="16"] .tilecov { border-radius: 3px; }
  #shelf[data-zoom="16"] .railrow.tile .railrow-title,
  #shelf[data-zoom="16"] .railrow.tile .railrow-lang,
  #shelf[data-zoom="16"] .railrow.tile .railrow-verdict,
  #shelf[data-zoom="16"] .railrow.tile .railrow-by { display: none; }
  @container shelf (max-width: 1100px) { #shelf[data-zoom="16"] .shelfgrid { --cols: 8; } }
  @container shelf (max-width: 620px) { #shelf[data-zoom="16"] .shelfgrid { --cols: 4; } }

  /* ---------------- 8 UP: covers only, and they do not step down --------- */
  #shelf[data-zoom="8"] .shelfgrid { --cols: 8; --bookw: 150px; }
  #shelf[data-zoom="8"] .railrow.tile { grid-template-rows: auto; row-gap: 0.45rem; }
  #shelf[data-zoom="8"] .railrow.tile .railrow-title,
  #shelf[data-zoom="8"] .railrow.tile .railrow-lang,
  #shelf[data-zoom="8"] .railrow.tile .railrow-verdict,
  #shelf[data-zoom="8"] .railrow.tile .railrow-by { display: none; }
  @container shelf (max-width: 1100px) { #shelf[data-zoom="8"] .shelfgrid { --cols: 4; } }

  /* ---------------- 2 UP: the covers alone, and they take the caption's room */
  /* 446, not half of 1410: the cover has to land clearly between 4's tile and
     1's jacket, or the step in does not read as one */
  #shelf[data-zoom="2"] .shelfgrid { --cols: 2; --bookw: 446px; }
  #shelf[data-zoom="2"] .railrow.tile { grid-template-rows: auto; row-gap: 0; }
  #shelf[data-zoom="2"] .railrow.tile .railrow-title,
  #shelf[data-zoom="2"] .railrow.tile .railrow-lang,
  #shelf[data-zoom="2"] .railrow.tile .railrow-verdict,
  #shelf[data-zoom="2"] .railrow.tile .railrow-by { display: none; }

  /* ---------------- 1 UP ¶ : one book is the page, its text beside it ---- */
  /* The bench's 1 ¶, value for value: 1180px for the whole tile, the cover in
     42% of it at min(72vh, 700px) and pushed to the gutter, the text in the
     rest, and the sheet snapping book to book like 1. Under 820px of shelf
     the two stack.

     EVERY TILE RULE HERE IS FENCED OFF THE PHONE (`html:not([data-phone])`).
     The zoom is remembered in the library record and rides to the phone, and
     the phone's block (THE PHONE, WHOLE, below) restores the app's own tile
     for whichever step it remembers -- but it names the six things the OTHER
     rungs change, and this rung changes more (columns, placement, the pane).
     Fencing them here is the same guarantee without editing the phone's
     contract: on the phone a remembered 1 ¶ is the phone's one size. */
  #shelf[data-zoom="1t"] { scroll-snap-type: y proximity; }
  #shelf[data-zoom="1t"] .shelfgrid { --cols: 1; --bookw: 1180px; }
  html:not([data-phone]) #shelf[data-zoom="1t"] .railrow.tile {
    scroll-snap-align: center; min-height: calc(100vh - 3.4rem);
    grid-template-columns: minmax(0, 42%) minmax(0, 1fr);
    grid-template-rows: auto auto auto minmax(0, 1fr);
    column-gap: 3.4rem; row-gap: 0.75rem; align-content: center;
    padding: 2.6rem 0; }
  /* nothing lifts when it is the page */
  html:not([data-phone]) #shelf[data-zoom="1t"] .railrow.tile:hover,
  html:not([data-phone]) #shelf[data-zoom="1t"] .railrow.tile:focus-visible { transform: none; }
  html:not([data-phone]) #shelf[data-zoom="1t"] .railrow.tile .tilecov {
    grid-column: 1; grid-row: 1 / -1; align-self: center;
    width: auto; height: min(72vh, 700px); margin-left: auto; }
  html:not([data-phone]) #shelf[data-zoom="1t"] .railrow.tile .railrow-title {
    grid-column: 2; grid-row: 1; align-self: end;
    font-size: 2.5rem; line-height: 1.12; height: auto; -webkit-line-clamp: 4; }
  html:not([data-phone]) #shelf[data-zoom="1t"] .railrow.tile .railrow-by {
    grid-column: 2; grid-row: 2; font-size: 0.78rem; }
  html:not([data-phone]) #shelf[data-zoom="1t"] .railrow.tile .railrow-lang {
    grid-column: 2; grid-row: 3; font-size: 0.78rem; }
  /* not at 1 ¶: the pane below says the word AND the sentence (`.tile-verdictslot`),
     and the same word twice on one screen is furniture. No grid row is taken,
     so the rungs G-SMALL measured are untouched. */
  html:not([data-phone]) #shelf[data-zoom="1t"] .railrow.tile .railrow-verdict,
  html[data-phone] #shelf[data-zoom="1t"] .railrow.tile .railrow-verdict { display: none; }
  html:not([data-phone]) #shelf[data-zoom="1t"] .railrow.tile .tile-pane {
    grid-column: 2; grid-row: 4; display: grid;
    gap: 1.15rem; align-content: start; padding-top: 1.1rem; }
  @container shelf (max-width: 820px) {
    html:not([data-phone]) #shelf[data-zoom="1t"] .railrow.tile {
      grid-template-columns: minmax(0, 1fr); grid-template-rows: auto auto auto auto auto;
      justify-items: start; }
    html:not([data-phone]) #shelf[data-zoom="1t"] .railrow.tile .tilecov {
      grid-column: 1; grid-row: 1; height: 38vh; margin: 0; }
    html:not([data-phone]) #shelf[data-zoom="1t"] .railrow.tile .railrow-title {
      grid-column: 1; grid-row: 2; font-size: 1.8rem; }
    html:not([data-phone]) #shelf[data-zoom="1t"] .railrow.tile .railrow-by { grid-column: 1; grid-row: 3; }
    html:not([data-phone]) #shelf[data-zoom="1t"] .railrow.tile .railrow-lang { grid-column: 1; grid-row: 4; }
    html:not([data-phone]) #shelf[data-zoom="1t"] .railrow.tile .tile-pane { grid-column: 1; grid-row: 5; width: 100%; }
    html:not([data-phone]) #shelf[data-zoom="1t"] .tile-slotrow { grid-template-columns: minmax(0, 1fr); } }

  /* THE PANE'S PARTS. The pane is in every tile and is shown at 1 ¶ alone --
     `display: none` makes no grid row, so every other rung is laid out
     exactly as it was before it existed. Its parts are the bench's: the facts
     line (`.meta` there), and the named regions (`.slot`) -- WITHOUT the
     dashed outline, because the outline was the drawing and what stands here
     now is the thing it was drawing. The name stays, as the region's head. */
  .tile-pane { display: none; }
  .tile-facts { margin: 0; font: 0.71rem/1.4 var(--ui-font); letter-spacing: 0.03em;
    text-transform: uppercase; color: var(--fg-dim);
    display: flex; flex-wrap: wrap; gap: 0.55rem; align-items: center; }
  .tile-facts i { font-style: normal; opacity: 0.4; }
  .tile-details { font: 0.78rem/1.45 var(--ui-font); color: var(--fg-dim);
    display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .tile-details i { font-style: normal; opacity: 0.4; }
  .tile-slot { display: flex; flex-direction: column; gap: 0.5rem; min-width: 0; }
  .tile-slot > b { font: 600 0.6rem/1 var(--ui-font); letter-spacing: 0.13em;
    text-transform: uppercase; color: var(--fg-dim); }
  .tile-slot > span { display: block; font: 0.92rem/1.5 var(--ui-font); color: var(--fg);
    white-space: normal; overflow-wrap: anywhere; }
  .tile-slot.tile-blurb > span { display: -webkit-box; -webkit-box-orient: vertical;
    -webkit-line-clamp: 8; overflow: hidden; }
  .tile-slotrow { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 0.9rem; }
  /* THE VERDICT ON THE BOOK PAGE. A slot like every other one, so it inherits
     the pane's own type and gap and adds no layout of its own; `clean` is the
     head alone (there is no sentence for a clean book -- `facts.verdict_line`
     returns "" for one) and takes the dim ink the other heads take. */
  .tile-verdictslot[data-verdict="damaged"] > b,
  .tile-verdictslot[data-verdict="unusual"] > b,
  .tile-verdictslot[data-verdict="unrecoverable"] > b { color: var(--warn, #b4622a); }
  .tile-chapters > span > span { display: block; white-space: nowrap; overflow: hidden;
    text-overflow: ellipsis; }
  .tile-chapters .tile-more { color: var(--fg-dim); }
  /* the read button, the shape the bench drew for it -- and a real door:
     one press opens the book, which is what a tile's double-click does */
  .tile-read { justify-self: start; align-self: start; cursor: pointer;
    font: 600 0.74rem/1 var(--ui-font); letter-spacing: 0.06em; text-transform: uppercase;
    color: var(--panel-bg, #fff); background: var(--fg); border-radius: var(--r-pill);
    padding: 0.85rem 1.7rem; }

  /* ---------------- 1 UP: the jacket, and nothing else -------------------- */
  /* one column, so the gap is the space between one book and the next as the
     sheet snaps down them, and the ratio is 1 */
  #shelf[data-zoom="1"] { scroll-snap-type: y proximity; }
  #shelf[data-zoom="1"] .shelfgrid { --cols: 1; --bookw: 820px; }
  #shelf[data-zoom="1"] .railrow.tile {
    scroll-snap-align: center; min-height: calc(100vh - 3.4rem);
    grid-template-rows: auto; row-gap: 0;
    justify-items: center; align-content: center; padding: 2rem 0; }
  #shelf[data-zoom="1"] .railrow.tile:hover,
  #shelf[data-zoom="1"] .railrow.tile:focus-visible { transform: none; }
  #shelf[data-zoom="1"] .railrow.tile .tilecov { width: auto; height: min(88vh, 1000px); }
  #shelf[data-zoom="1"] .railrow.tile .railrow-title,
  #shelf[data-zoom="1"] .railrow.tile .railrow-lang,
  #shelf[data-zoom="1"] .railrow.tile .railrow-by { display: none; }

  /* ---- THE SPACING, AS OSCA DIALLED IT (10 Sep, G-LIB2). The spacing log of
     design/reader/bench-library.html, pasted as it prints: every pane in
     ladder order, `gap: <row> <column>`, the bench's `.sheet[data-zoom]`
     written as this page's `#shelf[data-zoom]` (the bench's selector would
     match nothing here). What he moved: 4, from the 5th's 3.2/4.8 to
     4.30/6.45. 16's 0.81 is the log's own rounding of 0.90 x .895, where this
     sheet had written 0.80. 2 ¶ has no rung on this page, so its line matches
     nothing today; it is his block, whole, and it is ready for the day it does.
     The sort bar's two numbers from the same block are on `.sortbar` above:
     `justify-content: flex-end`, `--sortsize: 12px`. */
  #shelf[data-zoom="16"] .shelfgrid { gap: 0.81rem 0.90rem; }
  #shelf[data-zoom="8"]  .shelfgrid { gap: 1.70rem 1.90rem; }
  #shelf[data-zoom="4"]  .shelfgrid { gap: 2px 2px; }   /* PHOTOS rule 1 (30 Sep): was 4.30rem 6.45rem, the card era; the seam */
  #shelf[data-zoom="2t"] .shelfgrid { gap: 3.40rem 4.00rem; }
  #shelf[data-zoom="2"]  .shelfgrid { gap: 2.80rem 3.20rem; }
  #shelf[data-zoom="1t"] .shelfgrid { gap: 0.00rem 0.00rem; }
  #shelf[data-zoom="1"]  .shelfgrid { gap: 0.00rem 0.00rem; }

  /* THE FOOT IS GONE (Osca, 10 Sep: "Remove the 16/8/4/2/1 zoom-ladder
     buttons from the page -- pinch already drives data-zoom and stays"). It
     stood here as `.shelffoot` and `.zoom`, the bench's five buttons under
     the shelf. The bench keeps its own controls; the page has the pinch. */
  #shelf .railrow.tile {
    width: auto; max-width: none; padding: 0;
    display: grid; grid-template-rows: auto 2.5em 1.4em; row-gap: 0.75rem;
    border-left: 0; background: transparent; text-align: left;
    transition: transform 0.15s ease;
  }
  #shelf .railrow.tile:hover { background: transparent; }
  /* the lift is the bench's, and it is the only thing hover moves */
  #shelf .railrow.tile:hover, #shelf .railrow.tile:focus-visible {
    transform: translateY(-3px); }
  /* AN OPEN BOOK'S ✕ (Osca, 10 Sep, PROMPTS/phone-chrome.md: *"In the
     library, each open book carries a small ✕ at its top-right -- press it to
     close."*). library.html draws it only on a book that is open (OPEN BOOKS
     COME FIRST), so it is a mark of that state as much as a control, and on a
     book that is not open there is no node at all. Small to the eye and a
     thumb's worth to the finger: the disc is 22px, the element that takes the
     press is 36px, both in the cover's corner (the tile is `.railrow`,
     `position: relative`). Inverted ink, so it reads on a pale typeset cover
     and on a photograph alike, in both themes. */
  #shelf .railrow.tile .tile-close {
    position: absolute; top: 0; right: 0; z-index: 2;
    width: 36px; height: 36px; display: grid; place-items: center;
    font: 700 11px/1 var(--ui-font); color: var(--bg);
    cursor: pointer; -webkit-tap-highlight-color: transparent; }
  #shelf .railrow.tile .tile-close::before {
    content: ""; position: absolute; z-index: -1; width: 22px; height: 22px;
    border-radius: 50%; background: var(--fg); opacity: 0.78;
    box-shadow: 0 1px 3px rgba(0,0,0,0.25); }
  #shelf .railrow.tile .tile-close:hover::before { opacity: 1; }
  #shelf .railrow.tile .tile-close:focus-visible { outline: none; }
  #shelf .railrow.tile .tile-close:focus-visible::before {
    opacity: 1; outline: 2px solid var(--fg); outline-offset: 2px; }
  /* THE COVER IS NOW FLUID, AND THAT IS THE WHOLE OF WHAT CHANGED IN IT.
     Every number below this was tuned against an 88x120 box; at a quarter of
     1120px the box is ~262px wide, so an 11.5px title would be a caption on a
     poster. So the box is `width:100%` with its ASPECT KEPT AT 88/120 -- not
     the bench's 2/3 -- and every length inside it is now `cqw`, a percentage
     of the cover's own width. Keeping the aspect is the point: a uniform
     scale means all twenty pairings by five layouts arrive at three times the
     size with their proportions untouched, and not one of the hundred had to
     be looked at. Each `cqw` below is its old px x 100/88; nothing was
     redesigned, and if the grid changes width again nothing here needs to
     change at all. */
  .tilecov {
    display: block; position: relative; overflow: hidden;
    width: 100%; height: auto; aspect-ratio: 88 / 120; margin: 0;
    container: cover / inline-size;
    border-radius: 6px; border: 1px solid var(--border);
    box-shadow: 0 1px 2px rgba(0,0,0,0.06);
    background: var(--paper); color: var(--ink);
    font-family: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;   /* ASKS #140: the covers are set in the page's face, Iowan */
    transition: box-shadow 0.15s ease, border-color 0.15s ease;
  }
  /* THE JACKET IS MOUNTED, NOT CROPPED (Osca, 5 Sep: "the covers are fine,
     but they're not centred or framed -- they're not STRAIGHT and don't fit
     the border of the cover, some images will be cropped, so they need a
     small frame to sit straight, in the middle of the book").

     It was `object-fit: cover`, which fills the tile by cutting whatever does
     not fit -- and almost nothing fits, because `parser/cover.py` writes
     whatever shape the source carried and the tile is a fixed 88:120. So the
     Shakespeare portrait lost its head and its feet, the Singapore photograph
     lost its edges, and every one of them sat a little off its own centre.

     `contain` instead: the whole jacket, straight, in the middle, with the
     mount showing around it. The inset is in `cqw` like everything else in
     this box, so the frame is a frame at 88px and still a frame at 294.

     AND THE TYPE UNDERNEATH IS TAKEN AWAY when a jacket is there. Under
     `cover` it could not be seen, so it did not matter; under `contain` it
     would show in the margins, and a book's title set in type peeking out
     from behind its own jacket is the one thing this arrangement must not
     do. The rule that does it is two declarations below. */
  .covimg { position: absolute; inset: 4.545cqw; display: block;
    width: auto; height: auto; max-width: calc(100% - 9.09cqw);
    max-height: calc(100% - 9.09cqw); margin: auto;
    object-fit: contain; }
  /* `:has`, not a class set from the image's `load`. A class needs an event,
     and an image that is already in the cache when the listener attaches
     never fires one -- so the type would show through the frame exactly on
     the second visit and never on the first, which is the worst kind of bug
     to own. `:has(.covimg)` asks the only question that matters, is there a
     picture in this box, and it answers it again the moment the error
     listener REMOVES a picture that would not load. Nothing to keep in sync. */
  .tilecov:has(.covimg) { background: var(--panel-bg, #fff); }
  .tilecov:has(.covimg) .ini, .tilecov:has(.covimg) .t,
  .tilecov:has(.covimg) .a, .tilecov:has(.covimg) .fw { display: none; }

  /* ---- THE COVER IS THE BOOK'S OWN PICTURE WHEN IT HAS ONE, AND TYPESET
     WHEN IT DOES NOT (Osca, 5 Sep: "pictures from the books were used ... Use
     them -- for every book without one, generate one"). This reverses the
     1 Sep rule and restores the contract `parser/cover.py` states in its own
     first paragraph. The typeset box below is drawn for EVERY book and the
     JPEG is laid over it by `.covimg`, so the generated cover is not a
     fallback that has to be selected -- it is simply what is there, finished,
     underneath. Nothing asks whether a book has a cover; the image either
     arrives or removes itself.

     TWO DIALS, HASHED SEPARATELY (Osca, 1 Sep, on the first build of this:
     "mostly beige; use multiple colours; 4-5 different layouts so books look
     different, as they are; and all aligned, the same size").
       * TWENTY PAPER/INK PAIRINGS, `.p1`-`.p20`, pairing = hash(title) mod 20;
       * FIVE LAYOUTS, `.l1`-`.l5`, layout = hash(author + title) mod 5.
     Twenty by five is a hundred covers, and the two hashes are taken over
     different strings so two books that collide on one rarely collide on the
     other. **A tile never takes the pairing of the tile before it on the same
     shelf** -- it steps to the next one -- which is a render-order rule and
     therefore not in `coverPairing()`; see `assignPairings()`.

     The rules below are the mock's, declaration for declaration. `.verse` is
     gone: the layout is the hash's, not the book's form. `form` is still read
     off book.json (it is one key of a fetch that has to happen anyway for
     `first_words`) and is no longer what centres a title. ---- */
  .tilecov .t { position: absolute; left: 10.227cqw; right: 10.227cqw; font-size: 13.068cqw; line-height: 1.2; }
  .tilecov .a { position: absolute; left: 10.227cqw; right: 10.227cqw; font-size: 9.091cqw;
    letter-spacing: 0.06em; text-transform: uppercase; opacity: 0.75;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .tilecov .fw { position: absolute; left: 10.227cqw; right: 10.227cqw; bottom: 9.091cqw; font-size: 6.818cqw;
    opacity: 0.45; font-style: italic; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .tilecov .ini { position: absolute; font-weight: 400; line-height: 1;
    color: var(--ink); opacity: 0.09; letter-spacing: -0.04em; }
  /* L1 -- ranged left, initial bleeding top-right */
  .l1 .t { top: 15.909cqw; } .l1 .a { bottom: 25cqw; }
  .l1 .ini { right: -6.818cqw; top: -15.909cqw; font-size: 104.545cqw; }
  /* L2 -- centred, initial behind the middle */
  .l2 .t, .l2 .a { text-align: center; } .l2 .t { top: 25cqw; } .l2 .a { bottom: 25cqw; }
  .l2 .ini { left: 50%; transform: translateX(-50%); top: 34.091cqw; font-size: 79.545cqw; }
  /* L3 -- a band: the title reversed out across the middle, no initial */
  .l3 .t { left: 0; right: 0; top: 38.636cqw; text-align: center; padding: 9.091cqw 10.227cqw;
    background: var(--ink); color: var(--paper); font-size: 11.932cqw; }
  .l3 .a { bottom: 15.909cqw; text-align: center; }
  .l3 .ini { display: none; }
  /* L4 -- the initial IS the cover: huge, solid, the title small at the foot */
  .l4 .ini { left: 50%; transform: translateX(-50%); top: 6.818cqw; font-size: 134.091cqw; opacity: 0.85; }
  .l4 .t { bottom: 27.273cqw; font-size: 10.795cqw; text-align: center; }
  .l4 .a { bottom: 12.5cqw; text-align: center; font-size: 7.386cqw; }
  .l4 .fw { display: none; }
  /* L5 -- spine: the title runs up the left edge, the author small top-right */
  .l5 .t { left: 13.636cqw; top: auto; bottom: 11.364cqw; width: 113.636cqw; transform: rotate(-90deg);
    transform-origin: left bottom; white-space: nowrap; overflow: hidden;
    text-overflow: ellipsis; font-size: 13.636cqw; }
  .l5 .a { left: auto; right: 10.227cqw; top: 11.364cqw; width: 54.545cqw; text-align: right; font-size: 7.955cqw; }
  .l5 .ini { right: -4.545cqw; bottom: -20.455cqw; font-size: 90.909cqw; }
  .l5 .fw { left: 38.636cqw; }
  /* THE ONE THING THE FIVE LAYOUTS DO NOT SAY, and the plainest answer to it
     (mock/README rule 5). The mock draws each layout with a title that fits;
     none of its rules says what a title does when it does not. Unclamped,
     `The Collected Poems of Wallace Stevens` wraps to three lines inside L4
     and lands across the initial, and `The Singapore Story: Memoirs of Lee
     Kuan Yew, Vol. 1` runs down into the author in L1 -- which is a broken
     tile, not a variant. So each layout's title is clamped to the lines its
     own arrangement leaves room for and ellipsises past that. Nothing else is
     added and no number of the mock's is changed; §6 gives Osca these five
     declarations to paste into the picture if he wants the two to agree
     literally. (L5's title is `nowrap` in the mock already.) */
  .tilecov .t { display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; }
  .l1 .t { -webkit-line-clamp: 4; }
  .l2 .t { -webkit-line-clamp: 4; }
  .l3 .t { -webkit-line-clamp: 3; }
  .l4 .t { -webkit-line-clamp: 2; }
  /* the twenty pairings, value for value from PROMPTS/mock/library.html */
  .p1 { --paper: var(--cov-p1-paper); --ink: var(--cov-p1-ink); }
  .p2 { --paper: var(--cov-p2-paper); --ink: var(--cov-p2-ink); }
  .p3 { --paper: var(--cov-p3-paper); --ink: var(--cov-p3-ink); }
  .p4 { --paper: var(--cov-p4-paper); --ink: var(--cov-p4-ink); }
  .p5 { --paper: var(--cov-p5-paper); --ink: var(--cov-p5-ink); }
  .p6 { --paper: var(--cov-p6-paper); --ink: var(--cov-p6-ink); }
  .p7 { --paper: var(--cov-p7-paper); --ink: var(--cov-p7-ink); }
  .p8 { --paper: var(--cov-p8-paper); --ink: var(--cov-p8-ink); }
  .p9 { --paper: var(--cov-p9-paper); --ink: var(--cov-p9-ink); }
  .p10 { --paper: var(--cov-p10-paper); --ink: var(--cov-p10-ink); }
  .p11 { --paper: var(--cov-p11-paper); --ink: var(--cov-p11-ink); }
  .p12 { --paper: var(--cov-p12-paper); --ink: var(--cov-p12-ink); }
  .p13 { --paper: var(--cov-p13-paper); --ink: var(--cov-p13-ink); }
  .p14 { --paper: var(--cov-p14-paper); --ink: var(--cov-p14-ink); }
  .p15 { --paper: var(--cov-p15-paper); --ink: var(--cov-p15-ink); }
  .p16 { --paper: var(--cov-p16-paper); --ink: var(--cov-p16-ink); }
  .p17 { --paper: var(--cov-p17-paper); --ink: var(--cov-p17-ink); }
  .p18 { --paper: var(--cov-p18-paper); --ink: var(--cov-p18-ink); }
  .p19 { --paper: var(--cov-p19-paper); --ink: var(--cov-p19-ink); }
  .p20 { --paper: var(--cov-p20-paper); --ink: var(--cov-p20-ink); }
  /* the ring is the selection mark on a tile -- `.railrow[aria-selected]`'s
     own background would only tint the caption */
  #shelf .railrow.tile[aria-selected="true"] { background: transparent; }
  #shelf .railrow.tile[aria-selected="true"] .tilecov { box-shadow: 0 0 0 2px var(--fg); }
  #shelf .railrow.tile:hover .tilecov { box-shadow: 0 12px 28px rgba(0,0,0,0.13);
    border-color: var(--fg); }
  #shelf .railrow.tile[aria-selected="true"]:hover .tilecov { box-shadow: 0 0 0 2px var(--fg); }
  /* the caption's type is the mock's, to the tenth of a pixel: `.tile b` is
     12.5px/1.3 and `.tile small` is 11.5px there, and the rem sizes this page
     had (0.8rem = 12.8px, 0.74rem = 11.84px) were a third of a pixel out on
     both. Written in px for the same reason the mock writes px -- a tile is a
     fixed 120px object and its type does not scale with the root. */
  .tile .railrow-title { white-space: normal; margin: 0;
    font-family: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
    font-size: 1rem; font-weight: 600; line-height: 1.25; height: 2.5em;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden; }
  .tile .railrow-by, .tile .railrow-lang { font-family: var(--ui-font); font-size: 0.71rem;
    letter-spacing: 0.03em; text-transform: uppercase; line-height: 1.4; margin: 0;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .tile .railrow-lang { color: var(--fg-dim); }
  /* the order mark rides the cover, not the caption */
  .railrow.tile[data-sel-order]::after { top: 6px; right: 6px; }
  .railrow.tile[data-sel-order="1"]:not(.selFirst)::after { top: 10px; right: 10px; }
  /* a book still being ingested: faded, and its line reads `parsing…` */
  #shelf .railrow.tile.loading { overflow: visible; opacity: 1; }
  #shelf .railrow.tile.loading .tilecov { opacity: 0.35; }
  #shelf .railrow.tile.loading .railrow-title,
  #shelf .railrow.tile.loading .railrow-by { color: var(--fg-dim); opacity: 1; }
  /* the veil (and its progress edge) is the COVER's, so the caption stays
     readable while the bar crosses the picture */
  /* the veil covers the cover, and the cover is no longer 88x120 -- so it is
     given the same box the same way, by the aspect it now has */
  .tile .ingest-veil { inset: auto; position: absolute; top: 0; left: 0;
    width: 100%; aspect-ratio: 88 / 120; height: auto; border-radius: 6px; }
  .tile .ingest-step { display: none; }
  .tile .ingest-veil.failed { position: absolute; top: 0; aspect-ratio: 88 / 120;
    height: auto; padding: 6px; }
  .tile .ingest-fail-title, .tile .ingest-fail-line { font-size: 0.66rem; }
  .tile .ingest-retry { font-size: 0.7rem; min-height: 22px; padding: 1px 8px; }

  /* ---- SOURCES IS THE FOLDER YOU OPENED (PROMPTS/reader-library-mock.md §3).
     The card at the top is the folder itself -- its name, where it is, and a
     ⌄ to open another; under it the files in it, and the only thing a file
     carries besides its name is a dot: grey = already on the shelf, red =
     being parsed, none = not yet. The kind and the megabytes are gone; they
     were two columns of noise beside a list of titles, and the picture draws
     neither. ---- */
  .srcbar { width: 250px; flex: 0 0 250px; border-right: 1px solid var(--border); background: var(--panel-bg);
    overflow: auto; padding: 14px 0; display: flex; flex-direction: column; }
  .srcbar h2 { margin: 0 0 8px; padding: 0 16px; font-size: 0.72rem; font-weight: 600; letter-spacing: 0.09em;
    text-transform: uppercase; color: var(--fg-dim); }
  .srcfolder { margin: 0 12px 12px; padding: 9px 10px; background: var(--bg);
    border: 1px solid var(--border); border-radius: var(--r-1);
    display: flex; align-items: center; gap: 9px; width: calc(100% - 24px);
    font: inherit; color: inherit; text-align: left; cursor: pointer; }
  .srcfolder:hover { background: var(--control-bg); }
  .srcfolder:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
  .srcfolder .ic { flex: 0 0 18px; width: 18px; height: 14px; border: 1.5px solid var(--fg-dim);
    border-radius: 3px; position: relative; }
  .srcfolder .ic::before { content: ""; position: absolute; left: -1.5px; top: -5px; width: 8px; height: 5px;
    border: 1.5px solid var(--fg-dim); border-bottom: 0; border-radius: 2px 2px 0 0; background: var(--bg); }
  .srcfolder .nm { min-width: 0; }
  .srcfolder b { display: block; font-weight: 600; font-size: 0.78rem; }
  .srcfolder small { display: block; color: var(--fg-dim); font-size: 0.7rem;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .srcfolder .ch { margin-left: auto; color: var(--fg-dim); font-size: 0.7rem; }
  .src { display: flex; align-items: center; gap: 8px; padding: 5px 16px; cursor: default; user-select: none;
    color: var(--fg-dim); }
  .src:hover { background: var(--control-bg); }
  .src[aria-selected="true"] { background: var(--control-bg); color: var(--fg); }
  /* the picture's three states, and the dot is the whole of the difference */
  .src[data-state="grey"] { color: var(--fg-dim); opacity: 0.72; }
  .src[data-state="filled"] { color: var(--fg-dim); opacity: 1; }
  .src[data-state="busy"] { color: var(--fg); opacity: 1; }
  .src-dot { flex: none; margin-left: auto; width: 5px; height: 5px; border-radius: 50%; background: transparent; }
  .src[data-state="filled"] .src-dot { background: var(--fg-dim); opacity: 0.7; }
  .src[data-state="busy"] .src-dot { background: var(--accent); }
  .src-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.82rem; }
  .src-empty, .src-note { padding: 8px 16px; font-size: 0.8rem; color: var(--fg-dim); }
  .src-note { padding-top: 14px; }
  #shelf.dropping { outline: 2px dashed var(--accent); outline-offset: -8px; }
  .shelfcount { color: var(--fg-dim); font-weight: 400; }

  /* ---- selection, named shelves, merged entries (PROMPTS/library-shelf.md).
     Every rule here is AFTER the shared .rail block above and adds nothing to
     it: reader/tests/test_library_rail.py holds `.rail` .. `.railrow-status b`
     byte-identical with studio/studio.html, and a selection mark is the
     reader's own idea. `.railrow[aria-selected="true"]` is the shared rule and
     is reused as-is -- what is new is only the ORDER MARK, because two
     identically outlined rows cannot say which of them is first. ---- */
  .railrow { position: relative; }
  .railrow[data-sel-order]::after {
    content: attr(data-sel-order);
    position: absolute; top: 8px; right: 10px;
    min-width: 17px; height: 17px; padding: 0 4px; border-radius: var(--r-pill);
    background: var(--accent); color: var(--panel-bg);
    font-size: 0.66rem; font-weight: 700; line-height: 17px; text-align: center;
  }
  /* one selected row needs no number -- there is no order to tell */
  .railrow[data-sel-order="1"]:not(.selFirst)::after { content: ""; min-width: 8px; padding: 0; height: 8px; top: 12px; }
  .railrow.merged .railrow-title::before {
    content: "pair"; display: inline-block; margin-right: 6px; padding: 0 5px;
    border: 1px solid var(--border); border-radius: var(--r-0);
    font-size: 0.62rem; font-weight: 600; letter-spacing: 0.06em;
    text-transform: uppercase; color: var(--fg-dim); vertical-align: 1px;
  }
  .railrow.merged.stale .railrow-status { color: var(--bad); }
  .shelfgrp { display: block; }
  .shelfhead { display: flex; align-items: center; gap: 2px; padding: 0 8px 0 4px; }
  .shelftoggle {
    flex: 1 1 auto; min-width: 0; display: flex; align-items: baseline; gap: 7px;
    padding: 7px 8px; border: 0; border-radius: var(--r-1); background: transparent;
    font: inherit; font-size: 0.72rem; font-weight: 600; letter-spacing: 0.09em;
    text-transform: uppercase; color: var(--fg-dim); cursor: pointer; text-align: left;
  }
  .shelftoggle:hover { background: var(--control-bg); color: var(--fg); }
  .shelfcaret { font-size: 0.85rem; line-height: 1; letter-spacing: 0;
    opacity: 0; transition: opacity 0.12s; }
  .shelfhead:hover .shelfcaret, .shelftoggle:focus-visible .shelfcaret,
  .shelfgrp[data-collapsed="1"] .shelfcaret { opacity: 1; }
  .shelfname { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .shelfmenu {
    flex: 0 0 auto; min-width: 28px; min-height: 28px; border: 0; border-radius: var(--r-1);
    background: transparent; color: var(--fg-dim); font: inherit; cursor: pointer;
  }
  .shelfmenu { opacity: 0; transition: opacity 0.12s; }
  .shelfhead:hover .shelfmenu, .shelfmenu:focus-visible { opacity: 1; }
  .shelfmenu:hover { background: var(--control-bg); color: var(--fg); }
  .shelfempty { padding: 4px 16px 10px; font-size: 0.78rem; color: var(--fg-dim); }
  .shelfgrp.dropping { background: var(--control-bg); }
  .libline { padding: 6px 16px 10px; font-size: 0.74rem; color: var(--fg-dim); }

  /* ---- right-click menu ---- */
  .menuhead { padding: 6px 14px 4px; font-size: 0.68rem; font-weight: 600;
    letter-spacing: 0.09em; text-transform: uppercase; color: var(--fg-dim); }
  .menu button.off { opacity: 0.55; cursor: default; }
  .menu button .menuwhy { display: block; font-size: 0.72rem; color: var(--fg-dim);
    white-space: normal; margin-top: 1px;
    /* a reason is one line under its item, never a paragraph (EXPORT, 28 Sep: the
       Finder reason ran four lines and pushed the menu to the window's edge);
       the whole sentence is the item's title */
    overflow: hidden; display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; }
  .menu { position: fixed; z-index: 40; min-width: 200px; max-width: 300px; padding: 6px 0; border-radius: var(--r-1);
    background: var(--panel-bg); border: 1px solid var(--border); box-shadow: 0 6px 20px rgba(0,0,0,0.18); }
  .menu[hidden] { display: none; }
  .menu button { display: block; width: 100%; text-align: left; font: inherit; font-size: 0.9rem; color: var(--fg);
    background: transparent; border: none; padding: 7px 14px; cursor: pointer; }
  .menu button:hover, .menu button:focus { background: var(--control-bg); outline: none; }
  .menu button.danger { color: var(--bad); }
  .menu hr { border: none; border-top: 1px solid var(--border); margin: 4px 0; }
  /* paintContext() hides what needs studio by setting `hidden`, and three of
     those elements have a `display` of their own further down (the sources
     sheet is `display: flex` under the phone breakpoint, the toggle
     `inline-flex`, the scrim `block`) -- a rule beats the `hidden`
     attribute's UA style, so it has to be said here, once, for all three. */
  .srcbar[hidden], #sourcesToggle[hidden], #sourcesScrim[hidden] { display: none !important; }
  /* `,` collapses the Sources rail on a desktop. Gated on a width so it can
     never collide with the phone rules below, where the same rail is a sheet
     and `,` opens and closes THAT instead -- one key, the shape of the answer
     decided by the screen rather than by a second binding. */
  @media (min-width: 641px) and (min-height: 501px) {
    .srcbar.srcCollapsed { display: none; }
  }

  /* ---- the phone shelf: the Library's right sidebar (READER_FIRST.md
     "Osca's app decisions -- 30 Aug"). Its own block, after the shared .rail
     one, which it never touches. ---- */
  .phonebar { width: 250px; flex: 0 0 250px; border-left: 1px solid var(--border); background: var(--panel-bg);
    overflow: auto; padding: 14px 0 18px; display: flex; flex-direction: column; }
  .phonebar[hidden] { display: none; }
  .phonebar.dropping { outline: 2px dashed var(--accent); outline-offset: -6px; }
  .phonehead { padding: 0 16px 2px; }
  .phonehead.onphone { margin-top: 22px; }
  .phonehead h2 { margin: 0; font-size: 0.72rem; font-weight: 600; letter-spacing: 0.09em;
    text-transform: uppercase; color: var(--fg-dim); }
  /* Sync is the dark button in the picture, and the line beside it says what
     pressing it will do -- how many books, and where they land. */
  .phonesync { display: flex; align-items: center; gap: 10px; margin: 10px 16px 18px; }
  #syncPhone { font: inherit; font-size: 0.78rem; font-weight: 600; min-height: 30px; padding: 4px 12px;
    border-radius: var(--r-1); border: 0; background: var(--fg); color: var(--bg); cursor: pointer; flex: none; }
  #syncPhone:hover:not([disabled]) { opacity: 0.88; }
  #syncPhone[disabled] { opacity: 0.35; cursor: default; }
  .phonesync small { color: var(--fg-dim); font-size: 0.72rem; min-width: 0;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .phonedrop { margin: 0 12px; padding: 16px 10px; border: 1px dashed var(--border); border-radius: var(--r-1);
    text-align: center; color: var(--fg-dim); font-size: 0.74rem; }
  .phonebar.dropping .phonedrop { border-color: var(--accent); color: var(--fg); }
  .phonerow { position: relative; display: grid; grid-template-columns: 20px 1fr; column-gap: 10px;
    align-items: center; padding: 6px 12px 6px 16px; }
  .phonerow:hover { background: var(--control-bg); }
  .phonerow.busy { opacity: 0.6; }
  /* the row's cover: the same typeset cover as the tile, at a thumbnail's
     size -- one function draws both, so a book is never two different books */
  .phonerow .tilecov { grid-column: 1; grid-row: 1 / span 2; width: 20px; height: 27px;
    margin: 0; border: 0; border-radius: 2px; box-shadow: none; aspect-ratio: auto; }
  /* no frame on a 20px thumbnail -- there is no room for one, and at that
     size a crop reads as a texture rather than as a mistake */
  .phonerow .covimg { inset: 0; max-width: 100%; max-height: 100%;
    width: 100%; height: 100%; object-fit: cover; }
  .phonerow .tilecov .t, .phonerow .tilecov .a, .phonerow .tilecov .fw { display: none; }
  .phonerow .tilecov .ini { position: absolute; inset: 0; right: auto; left: 0; top: 0;
    display: grid; place-items: center; font-size: 11px; opacity: 0.85; transform: none; }
  .phonerow-title { grid-column: 2; font-size: 0.8rem; font-weight: 600;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .phonerow-meta { grid-column: 2; grid-row: 2; font-size: 0.7rem; color: var(--fg-dim); }
  /* Remove, and Reveal, are the picture's silence about hover (mock README
     rule 5): the page at rest is exactly the picture, and the two things a
     person can do to a row are under the pointer when they want them. */
  .phonebtn { position: absolute; top: 50%; transform: translateY(-50%);
    font: inherit; font-size: 0.7rem; min-height: 24px; padding: 1px 8px; border-radius: var(--r-1);
    border: 1px solid var(--border); background: var(--bg); color: var(--fg); cursor: pointer;
    opacity: 0; transition: opacity 0.12s; }
  .phonerow:hover .phonebtn, .phonebtn:focus-visible { opacity: 1; }
  .phonebtn:hover { background: var(--control-bg); }
  .phonebtn.reveal { right: 40px; }
  .phonebtn.x { right: 10px; min-width: 26px; padding: 1px 6px; border-color: transparent; color: var(--fg-dim); }
  .phonebtn.x:hover { border-color: var(--border); color: var(--bad); }
  .phone-empty { padding: 4px 16px 8px; font-size: 0.8rem; color: var(--fg-dim); }
  .phoneline { margin-top: auto; padding: 12px 16px 0; font-size: 0.72rem; color: var(--fg-dim); }

  #note { padding: 6px 16px; font-size: 0.82rem; color: var(--fg-dim); flex: 0 0 auto; }
  #note:empty { display: none; }

  /* ---- the panels (a re-parse, and this book's parses). A sibling of
     `.shell` for `.searchbar`'s reason: `#shelf` is rewritten wholesale on
     every poll, and a report a person is halfway through reading must not be
     blown away by an unrelated repaint. */
  .panel { flex: 0 0 auto; margin: 0 16px 8px; padding: 8px 12px 10px;
    border: 1px solid var(--border); border-radius: var(--r-1); font-size: 0.82rem; }
  .panel[hidden] { display: none; }
  .panel .pnl-head { display: flex; align-items: baseline; gap: 8px; }
  .panel .pnl-head b { font-size: 0.9rem; }
  .panel .pnl-head.warn .pnl-sub { color: var(--warn, #a86b1d); }
  .panel .pnl-head.bad .pnl-sub { color: var(--bad, #b3382c); }
  .panel .pnl-sub { color: var(--fg-dim); }
  .panel .pnl-x { margin-left: auto; font: inherit; font-size: 1rem; line-height: 1;
    padding: 0 4px; border: none; background: none; color: var(--fg-dim); cursor: pointer; }
  .panel .pnl-x:hover { color: var(--fg); }
  /* INDETERMINATE ON PURPOSE: studio counts no steps for a re-parse yet, so
     the bar travels and returns rather than filling to a made-up fraction. */
  .panel .pnl-bar { height: 3px; margin: 8px 0 6px; border-radius: 2px;
    background: var(--border); overflow: hidden; position: relative; }
  .panel .pnl-bar.pulse::after { content: ""; position: absolute; inset: 0 auto 0 0;
    width: 30%; border-radius: 2px; background: var(--accent);
    will-change: transform; animation: pnl-sweep 1.4s ease-in-out infinite; }
  /* slides as a picture, not by left (Osca 14 Oct 17:29, draft D approved): 30% wide from -30% to 100% of the
     track is -100% to +333.333% of its own width -- the same place at every instant */
  @keyframes pnl-sweep { 0% { transform: translateX(-100%); } 100% { transform: translateX(333.333%); } }
  .panel .pnl-why { margin: 4px 0 0; color: var(--fg-dim); font-size: 0.78rem; }
  .panel .pnl-line { margin: 4px 0 0; font-size: 0.75rem; color: var(--fg-dim);
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .panel .pnl-sum { margin: 6px 0 0; padding: 0; list-style: none; }
  .panel .pnl-sum li { padding: 2px 0; border-top: 1px solid var(--border); font-size: 0.8rem; }
  .panel .pnl-sum li:first-child { border-top: none; }
  /* the marks a refused re-parse would have lost, by their own words (Osca 14 Oct #5) */
  .panel .pnl-marks { margin: 6px 0 0; padding: 0; list-style: none; }
  .panel .pnl-marks li { padding: 3px 0; border-top: 1px solid var(--border); font-size: 0.82rem;
    font-style: italic; color: var(--fg); overflow-wrap: anywhere; }
  .panel .pnl-marks li:first-child { border-top: none; }
  /* the parses: two headed sections, never one list -- the live parse of a
     book must never read as one of that book's own kept copies. */
  .panel .prs-head { margin-top: 8px; font-size: 0.72rem; letter-spacing: 0.04em;
    text-transform: uppercase; color: var(--fg-dim); }
  .panel .prs-live li { color: var(--fg); }
  .panel .prs li { display: flex; align-items: baseline; gap: 8px; }
  .panel .prs-facts { color: var(--fg-dim); font-size: 0.78rem; }
  .panel .prs-stamp { margin-left: auto; color: var(--fg-dim); font-size: 0.72rem; }

  /* ---- THE SEARCH BAR IS NOT THIS PAGE'S ANY MORE (Osca, 5 Sep: *"the bar
     IN THE TOP -- SHOULD be the search bar ... inherit that FUNCTION +
     searching library"*). Fifty lines went with it: two boxes, a button, a
     dropdown, its rows, its notes and its file picker. The field is the
     window's (`desktop/src/tabs.html`) and the list is drawn by
     `reader/askbar.js`, which carries its own styles on every page rather
     than each page carrying a copy. What this page kept is every ANSWER --
     the search, the staging, the parse, the shelf -- and none of the
     drawing. */

  /* ---- ingest progress (PROMPTS/reader-ingest-progress.md, READER_FIRST.md
     "Library"): a book on the shelf from the moment its ingest starts. The
     whole entry is greyed over by a veil; --ingest (0..100%) is how far the
     loading bar has come, and the veil is clipped away left -> right by
     exactly that much, so the un-greyed region IS the progress. The step
     label sits under the title. Failed: the veil turns red-ish, shows the
     last log line and a Retry. All of it is appended AFTER the shared base
     row (test_library_rail.py compares only `const base`). ---- */
  #shelf .railrow.loading { position: relative; overflow: hidden; cursor: default; --ingest: 0%; }
  #shelf .railrow.loading:hover { background: transparent; }
  #shelf .railrow.loading .railrow-title, #shelf .railrow.loading .railrow-by { opacity: 0.95; }
  .ingest-step { display: block; margin-top: 3px; font-size: 0.76rem; color: var(--accent); font-weight: 600;
    letter-spacing: 0.02em; }
  .ingest-veil { position: absolute; inset: 0; pointer-events: none;
    background: color-mix(in srgb, var(--fg-dim) 22%, var(--panel-bg)); opacity: 0.82;
    backdrop-filter: grayscale(1); -webkit-backdrop-filter: grayscale(1);
    clip-path: inset(0 0 0 var(--ingest)); transition: clip-path 0.9s linear; }
  /* the bar's leading edge: a 3-px accent line at the boundary */
  .ingest-veil::before { content: ""; position: absolute; top: 0; bottom: 0; left: var(--ingest); width: 3px;
    background: var(--accent); opacity: 0.9; transition: left 0.9s linear; }
  .ingest-veil.pulse::before { animation: ingestpulse 1.6s ease-in-out infinite; }
  @keyframes ingestpulse { 0%, 100% { opacity: 0.9; } 50% { opacity: 0.35; } }
  /* failed: the veil stops moving, tints, and becomes interactive (Retry) */
  #shelf .railrow.loading.failed { --ingest: 0%; }
  .ingest-veil.failed { pointer-events: auto; opacity: 1; clip-path: none; backdrop-filter: none; -webkit-backdrop-filter: none;
    background: color-mix(in srgb, var(--bad) 14%, var(--panel-bg)); display: flex; flex-direction: column;
    justify-content: center; gap: 6px; padding: 8px 12px; transition: none; }
  .ingest-veil.failed::before { display: none; }
  .ingest-fail-title { font-size: 0.8rem; font-weight: 600; color: var(--bad); }
  .ingest-fail-line { font-size: 0.74rem; color: var(--fg-dim); font-family: ui-monospace, Menlo, monospace;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .ingest-retry { align-self: flex-start; font: inherit; font-size: 0.8rem; min-height: 28px; padding: 3px 12px;
    border-radius: var(--r-1); border: 1px solid var(--bad); color: var(--bad); background: var(--panel-bg); cursor: pointer; }
  .ingest-retry:hover { background: var(--control-bg); }
  /* the sidebar while it runs: no spinner. The shelf shows the bar, and the
     file's own dot turns red the moment studio picks it up -- which is what
     the picture draws and all it draws. */

  /* ---- on this device (PROMPTS/phone-book-import.md): the books imported
     from a bundle .zip and held in the Cache API. These ARE shelf rows -- the
     same .railrow, in their own section above the bench's shelf -- so nothing
     in the shared .rail block (byte-identical with studio's,
     test_library_rail.py) is touched; what is added is the import control, a
     per-book remove, and the line that says how much room is left. ---- */
  .devicehead { grid-column: 1 / -1; display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
    padding: 0 16px 8px; }
  .devicehead h2 { margin: 0; padding: 0; flex: 1 1 auto; font-size: 0.72rem; font-weight: 600;
    letter-spacing: 0.09em; text-transform: uppercase; color: var(--fg-dim); }
  #importBtn { font: inherit; font-size: 0.85rem; min-height: 36px; padding: 4px 14px; border-radius: var(--r-1);
    border: 1px solid var(--border); background: var(--panel-bg); color: var(--fg); cursor: pointer; }
  #importBtn:hover { background: var(--control-bg); }
  #importBtn[disabled] { opacity: 0.5; cursor: default; }
  .storageline { grid-column: 1 / -1; padding: 0 16px 10px; font-size: 0.76rem; color: var(--fg-dim); }
  .railrow.device { position: relative; }
  .rmbook { position: absolute; top: 6px; right: 6px; min-width: 30px; min-height: 30px; border-radius: var(--r-1);
    border: 1px solid transparent; background: transparent; color: var(--fg-dim); font: inherit;
    font-size: 0.95rem; line-height: 1; cursor: pointer; }
  .rmbook:hover, .rmbook:focus { border-color: var(--border); background: var(--bg); color: var(--bad); outline: none; }
  .importerr { grid-column: 1 / -1; margin: 0 16px 10px; padding: 8px 12px; border-radius: var(--r-1);
    border: 1px solid var(--bad); color: var(--bad); font-size: 0.8rem; background: var(--panel-bg); }
  .importerr b { display: block; margin-bottom: 3px; }
  .importerr ul { margin: 4px 0 0; padding-left: 18px; }

  /* ---- the phone (PROMPTS/reader-mobile.md step 3): the shelf as a grid of
     covers, the sources sidebar as a sheet, the menu as a bottom sheet from a
     long-press. Same 640 px breakpoint as reader.html; nothing above this
     line changes, and the shared .rail block stays byte-identical with
     studio's (test_library_rail.py) -- these are overrides under the media
     query, not edits to it. ---- */
  :root {
    --safe-top: env(safe-area-inset-top, 0px);
    --safe-bottom: env(safe-area-inset-bottom, 0px);
    --safe-left: env(safe-area-inset-left, 0px);
    --safe-right: env(safe-area-inset-right, 0px);
  }
  body { height: 100dvh; }
  #sourcesToggle { display: none; }
  #sourcesScrim { display: none; }
  @media (max-width: 640px), (max-height: 500px) {
    header {
      padding: calc(8px + var(--safe-top)) calc(10px + var(--safe-right)) 8px calc(10px + var(--safe-left));
      gap: 6px;
    }
    header h1 { font-size: 1rem; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    header button, header .headerLink { min-height: 44px; min-width: 44px; border-radius: var(--r-2); }
    #sourcesToggle { display: inline-flex; }

    /* the sources sidebar as a sheet from the left */
    .srcbar, .srcbar.hidden {
      display: flex; position: fixed; z-index: 50; top: 0; bottom: 0; left: 0;
      width: min(85vw, 320px); flex: none;
      padding-top: calc(14px + var(--safe-top)); padding-bottom: var(--safe-bottom); padding-left: var(--safe-left);
      box-shadow: 0 0 24px rgba(0,0,0,0.25);
      transform: translateX(-105%); transition: transform 0.2s ease-out;
    }
    .srcbar.sheet-open { transform: translateX(0); }
    .src { min-height: 44px; padding: 8px 16px; }
    .src-name { font-size: 1rem; }
    .srcfolder { min-height: 44px; }
    #sourcesScrim {
      display: block; position: fixed; inset: 0; z-index: 49;
      background: rgba(0,0,0,0.35); opacity: 0; pointer-events: none; transition: opacity 0.2s ease-out;
    }
    #sourcesScrim.on { opacity: 1; pointer-events: auto; }

    /* the shelf's padding on the phone. It WAS "the shelf as a grid of
       covers" -- `display: grid; repeat(auto-fill, minmax(150px, 1fr))` on
       #shelf.rail itself -- until 6 Sep, when the grid became .shelfgrid
       INSIDE it and the two fought: one auto-fill cell holding the whole
       shelf, one column of 176px tiles at 393. The PHONE FIT block at the
       foot of this file says `#shelf.rail { display: block }` and owns the
       columns (--ph-cols), the tile (--ph-tile) and the gap (--ph-gap);
       the dead grid lines are gone from here. */
    #shelf.rail {
      align-content: start;
      padding: 12px calc(14px + var(--safe-right)) calc(20px + var(--safe-bottom)) calc(14px + var(--safe-left));
      -webkit-overflow-scrolling: touch;
    }
    #shelf.rail h2, #shelf .empty { padding: 0 2px; }
    #shelf .railrow {
      max-width: none; aspect-ratio: 2 / 3; min-height: 44px;
      display: flex; flex-direction: column; justify-content: flex-end; gap: 2px;
      padding: 12px 12px 12px 14px;
      border: 1px solid var(--border); border-left: 6px solid var(--accent); border-radius: var(--r-2);
      background: var(--panel-bg); box-shadow: 0 2px 8px rgba(0,0,0,0.08);
      -webkit-touch-callout: none; user-select: none; -webkit-user-select: none;
    }
    #shelf .railrow-title { white-space: normal; font-size: 1rem; line-height: 1.25;
      display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; }
    #shelf .railrow-by, #shelf .railrow-lang { white-space: normal; }
    #shelf .railrow-status { margin-top: 6px; }

    /* the long-press menu as a bottom sheet */
    .menu {
      left: calc(12px + var(--safe-left)) !important; right: calc(12px + var(--safe-right)) !important;
      top: auto !important; bottom: calc(12px + var(--safe-bottom)) !important;
      min-width: 0; padding: 8px; border-radius: var(--r-3); box-shadow: 0 10px 30px rgba(0,0,0,0.3);
    }
    .menu button { min-height: 48px; font-size: 1.05rem; text-align: center; border-radius: var(--r-2); }
    .menu hr { margin: 6px 0; }
    #note { padding-left: calc(16px + var(--safe-left)); padding-right: calc(16px + var(--safe-right)); }

    /* Retry is a thumb target */
    .ingest-step { margin-top: 6px; font-size: 0.86rem; }
    .ingest-veil.failed { justify-content: flex-end; padding: 12px; }
    .ingest-fail-line { white-space: normal; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; }
    .ingest-retry { min-height: 44px; min-width: 44px; padding: 8px 18px; font-size: 1rem; border-radius: var(--r-2); }

    /* the phone shelf is the bench's own control -- there is no bench on a
       phone, and paintContext() has already hidden it; this is the belt to
       that braces, for a narrow window on the Mac */
    .phonebar { display: none; }

    /* on this device: thumb-sized import and remove */
    .devicehead, .storageline { padding-left: calc(2px + var(--safe-left)); padding-right: calc(2px + var(--safe-right)); }
    #importBtn { min-height: 44px; font-size: 1rem; padding: 8px 18px; border-radius: var(--r-2); }
    .rmbook { min-width: 44px; min-height: 44px; font-size: 1.1rem; top: 4px; right: 4px;
      border-color: var(--border); background: var(--bg); }
    .importerr { margin-left: 2px; margin-right: 2px; font-size: 0.85rem; }
  }

/* ==================================================================
   PHONE FIT -- from design/phone/phone.html, 6 Sep 2026 (Copy CSS). THE PHONE'S NUMBERS.
   One value per property, one property per rule, no other layout code.
   The values are the same in library.css, settings.css and bar/askbar.css
   (the bar is on the reader too, where neither page sheet is loaded); the
   bench is the source and Copy CSS is how they change. `(max-height: 500px)`
   is in the query because the phone rotated is 852 wide.
   ================================================================== */
@media (max-width: 600px), (max-height: 500px) {
  :root {
    --ph-margin: 14px;
    --ph-gap: 14px;
    --ph-cols: 2;
    --ph-tile: 150px;
    --ph-type: 16px;
    --ph-head: 56px;
    --ph-card: 560px;
    --ph-tap: 44px;
  }
  html { font-size: var(--ph-type); }
  /* the header is FIXED at --ph-head under the safe area (Osca, 6 Sep), and
     its padding is what leaves --ph-tap for the buttons inside it */
  header { height: calc(var(--ph-head) + var(--safe-top, 0px)); }
  header { padding-top: calc(6px + var(--safe-top, 0px)); }
  header { padding-bottom: 6px; }
  header button, header .headerLink { min-width: var(--ph-tap); }
  header button, header .headerLink { min-height: var(--ph-tap); }
  /* the 30 Aug block above made #shelf.rail the grid; since 6 Sep the grid
     is .shelfgrid inside it -- the shelf is the column again */
  #shelf.rail { display: block; }
  #shelf.rail { padding-left: calc(var(--ph-margin) + var(--safe-left, 0px)); }
  #shelf.rail { padding-right: calc(var(--ph-margin) + var(--safe-right, 0px)); }
  /* `[data-zoom]`, not `[data-zoom="4"]` (6 Sep, THE PHONE WHOLE): the phone
     has ONE size, so whichever zoom the shelf remembers, the grid is this */
  #shelf[data-zoom] .shelfgrid { --cols: var(--ph-cols); }
  #shelf[data-zoom] .shelfgrid { --bookw: var(--ph-tile); }
  #shelf[data-zoom] .shelfgrid { gap: var(--ph-gap); }
  #shelf .railrow { min-height: var(--ph-tap); }
  #importBtn, .ingest-retry, .menu button { min-height: var(--ph-tap); }
  /* tap targets under 44 raised (Osca, 6 Sep): the shelf heading's toggle
     (29 tall). The two `.zoom button` lines that stood here went with the bar
     itself (THE PHONE WHOLE, below: "no shelf-zoom bar") */
  .shelftoggle { min-height: var(--ph-tap); }
  /* the first-run gate is settings.js's card, drawn over THIS page; its own
     rules are injected after this sheet, so these weigh one `body` more.
     (`max-width: var(--ph-card)` and the two paddings stood here until 6 Sep:
     the card FILLS the screen now -- THE PHONE WHOLE, below.) */
  body .ttstv-settings .kag-btn { min-height: var(--ph-tap); }
}
@media (max-height: 500px) and (orientation: landscape) {
  /* 3, not 4 (Osca, 6 Sep: "one size, 2 across, 3 in landscape") */
  :root { --ph-cols: 3; }
}

/* ==================================================================
   THE PHONE, WHOLE (Osca, 6 Sep). design/phone/library.html and
   design/phone/firstrun.html are the contract; this block is their text,
   byte for byte (test-phone.mjs checks). It decides what is NOT on the
   phone as much as what is. Every rule keys on `html[data-phone]` -- the
   flag library.html's <head> sets for a coarse pointer with no hover, or
   for ?phone -- INSIDE the media query: the Mac's smallest window matches a
   width test alone, and the Mac is untouched.
   ================================================================== */
@media (max-width: 600px), (max-height: 500px) {
  /* NO SHELF-ZOOM BAR: one size, 2 across, 3 in landscape; zoom by pinch,
     later. The ladder's four steps are the Mac's. */
  html[data-phone] .shelffoot { display: none; }
  /* THE DOOR TO SETTINGS. On the Mac Settings is a window (Cmd-, and the
     app's own menu) and the header has no gear; on the phone there is no
     Cmd-, and no window -- Settings is a page -- so the header carries one
     link to it. A link, so a press is its own proof. */
  html[data-phone] #settingsDoor { display: inline-flex; }
  /* NO "ON THIS MAC" NOTES ANYWHERE: the two lines that name the Mac's
     mirror are bench-only already; on the phone they are gone by rule too */
  html[data-phone] .libline, html[data-phone] .phoneline { display: none; }
  /* THE FIRST-RUN CARD FILLS THE SCREEN: the gate is the screen under the
     safe areas, the sheet is the whole of it, the three choices are rows a
     thumb can find -- the name above, the button the full width below */
  html[data-phone] body .ttstv-settings.fr-gate { padding: var(--safe-top, 0px) 0 var(--safe-bottom, 0px); align-items: stretch; }
  html[data-phone] body .ttstv-settings.fr-gate .fr-sheet { max-width: none; min-height: 100%; display: flex; flex-direction: column; justify-content: center; box-sizing: border-box; }
  html[data-phone] body .ttstv-settings.fr-gate .fr-sheet { padding-left: calc(var(--ph-margin) + var(--safe-left, 0px)); }
  html[data-phone] body .ttstv-settings.fr-gate .fr-sheet { padding-right: calc(var(--ph-margin) + var(--safe-right, 0px)); }
  html[data-phone] body .ttstv-settings.fr-gate .fr-sheet .set-row { flex-direction: column; align-items: stretch; gap: 8px; }
  html[data-phone] body .ttstv-settings.fr-gate .fr-sheet .set-c { width: 100%; justify-content: stretch; }
  html[data-phone] body .ttstv-settings.fr-gate .fr-sheet .kag-btn { width: 100%; min-height: var(--ph-tap); }
  /* ONE SIZE MEANS THE TILE, NOT ONLY THE GRID. PHONE FIT pins the GRID for
     whichever zoom the shelf remembers (`#shelf[data-zoom] .shelfgrid`), but
     the ladder sizes the TILE and the JACKET too and those stayed keyed on the
     step: `[data-zoom="1"]` gives the jacket `height: min(88vh, 1000px)` and
     the tile a 100vh minimum. A shelf that remembered 1 therefore drew a
     546x820 tile inside a 150px column on the iPhone 17 simulator (7 Sep) --
     one book across, clipped both sides, and no zoom bar on the phone to get
     out of it. So the tile is the app's own at every remembered step. */
  html[data-phone] #shelf[data-zoom] { scroll-snap-type: none; }
  html[data-phone] #shelf[data-zoom] .railrow.tile { min-height: 0; padding: 0; justify-items: normal; align-content: normal; }
  html[data-phone] #shelf[data-zoom] .railrow.tile { grid-template-rows: auto 2.5em 1.4em; row-gap: 0.75rem; }
  html[data-phone] #shelf[data-zoom] .railrow.tile .tilecov { width: 100%; height: auto; }
  html[data-phone] #shelf[data-zoom] .railrow.tile .railrow-title { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }   /* a clamp, not a block: a block cut the second line through its middle ("A Minor Collection of W…") */
  html[data-phone] #shelf[data-zoom] .railrow.tile .railrow-by { display: block; }
}

/* ==================================================================
   THE PHONE'S SHELF, ROUND TWO (G-COVERS, 11 Sep 2026). After THE PHONE,
   WHOLE on purpose: that block is design/phone/library.html's contract,
   byte for byte (test-phone.mjs), and it is not this lane's to re-cut --
   so what it said that is no longer true is overruled HERE, by coming
   later at the same weight, and the re-cut is asked of the phone lane
   (library/STATUS.md §6).

   WHAT IT SAID THAT STOPPED BEING TRUE: "one size, 2 across ... zoom by
   pinch, later". Osca, 11 Sep: the phone has the Mac's rungs by pinch
   (library.html, "the zoom on the PHONE"). So the phone gets a ladder of
   its own NUMBERS on the Mac's own STEPS -- the Mac's widths cannot be
   used at 393: under its own container step-downs 8 up comes out SMALLER
   than 16 up there (68 px against 75) and 4 up bigger than 2 up (294
   against 157), because each was tuned for a window and not a hand.
   Measured at 393x852 (365 px of shelf), out to in:

     16   four across, covers only            84 x 114
      8   three across, covers only           112 x 153
      4   THE APP: two across, title + byline  150 x 205 -- PHONE FIT's
          own tile and gap, untouched, and the rung the phone opens on
          (SHELF_ZOOM_DEFAULT; the default is the round's, flagged, Osca's
          to move)
      2   two across, covers only, the caption's room given to the cover
                                              176 x 239
     1 ¶  one book and its words: the cover, the title, the byline, the
          pane -- the Mac's own narrow 1 ¶ (its @container <= 820 stack)
      1   one jacket, the width of the shelf, one screen a book

   Every rule keys on `html[data-phone]` inside the phone's media query,
   the contract's own fence: the Mac is untouched.
   ================================================================== */
@media (max-width: 600px), (max-height: 500px) {
  /* NO MAC CHROME ON THE PHONE (PROMPTS/phone-chrome.md: "Top bar: nothing.
     No top bar at all, ever."). The header's three -- the Sources toggle,
     the settings door, the theme -- go with it; the phone's one bar is at
     the foot and is the reader's (lane 5 mounts it here). Import bundle…
     goes too: the phone's books arrive by Sync. The Sources rail is already
     hidden by context.js (a host with the door is not a bench); this is the
     belt to that brace. */
  html[data-phone] header { display: none; }
  html[data-phone] #importBtn { display: none; }
  html[data-phone] .srcbar, html[data-phone] #sourcesScrim { display: none !important; }
  /* with no header, the shelf clears the status bar itself -- and so does a
     line in `#note` ("Removed …"), which the header used to push down */
  html[data-phone] #shelf.rail { padding-top: calc(12px + var(--safe-top, 0px)); }
  html[data-phone] #note:not(:empty) { padding-top: calc(6px + var(--safe-top, 0px)); }
  /* ...and clears the head's two circles at its right (PHONE-READ: "… is on this iPhone. Its look-ups arrive when it
     syncs from your Mac." ran under the highlighter and the order, 10 Oct) */
  html[data-phone] #note:not(:empty) { padding-right: calc(104px + var(--safe-right, 0px)); }
  /* the line an added book leaves stands over the foot, where the eye is after the shelf scrolls to the book --
     for eight seconds, then it goes (library.html::runParse) */
  html[data-phone] #note.toast:not(:empty) { position: fixed; left: 12px; right: 12px; z-index: 30;
    bottom: calc(76px + var(--safe-bottom, 0px)); padding: 12px 16px; border-radius: 14px;
    background: var(--bg); color: var(--fg); box-shadow: 0 6px 24px rgba(0,0,0,.18); font-size: 0.9rem; }

  /* A TILE IS AS TALL AS WHAT IS IN IT. The 30 Aug phone rule gives every
     `#shelf .railrow` `aspect-ratio: 2/3` -- written for the list rows, before
     there were tiles -- and a tile took it too: at 4 up a 150x225 box, the
     cover SHRUNK to 100x137 to leave the caption room inside it (measured,
     393x852), and at 1 up a 735-tall tile forced 490 wide on a 365 shelf.
     THE PHONE, WHOLE measured the jacket it meant, 149x203 at 88/120; this
     restores it by letting the tile be its cover plus its caption. The same
     rule's `justify-content: flex-end` (a flex row's) also lands on the
     tile's GRID, where it stops the one column stretching: the cover is
     `width: 100%` of a column sized by its caption (114 px at 4 up) or by
     nothing (2 px at 16, 8, 2 and 1 up). One column, the tile's width. */
  html[data-phone] #shelf .railrow.tile { aspect-ratio: auto; justify-content: stretch;
    grid-template-columns: minmax(0, 1fr); }

  /* AN OPEN BOOK'S ✕, ON THE PHONE: a 28 px disc at the cover's top-right,
     round, no box -- the element IS the disc (the Mac's is a 36 px square
     holding a 22 px disc, for a pointer). */
  html[data-phone] #shelf .railrow.tile .tile-close {
    width: 28px; height: 28px; top: 6px; right: 6px; border-radius: 50%;
    background: var(--fg); opacity: 0.82; font-size: 12px;
    box-shadow: 0 1px 3px rgba(0,0,0,0.25); }
  html[data-phone] #shelf .railrow.tile .tile-close::before { display: none; }

  /* THE SHELF'S GESTURES ARE THE SHELF'S (13 Sep). `pan-y` leaves the one
     finger that scrolls the shelf alone and takes the PINCH off the web view,
     which is the half `gesturestart` cannot be trusted with inside a
     WKWebView: without it iOS may claim the two fingers as a page zoom before
     a line of this page's script runs, and `data-zoom` never moves. The
     viewport meta is `initial-scale=1` with no `maximum-scale`, deliberately
     (a reader must stay zoomable); this narrows the refusal to the shelf. */
  html[data-phone] #shelf { touch-action: pan-y; }

  /* ---- 16: SIXTEEN across, covers only (Osca, 14 Sep, chat 108: "I can't
     get up to a 16-column view on the phone"). It said `--cols: 4` here until
     today, and `8` said three -- so the phone's top two rungs were a fourth
     and a third of their own names, the ladder's whole top end was missing,
     and a pinch that reached `16` drew four books. The rung is its name now.

     A FIXED `--bookw` CANNOT DRAW THIS. Sixteen 100px columns want 1600px;
     the shelf is 365 (393 less PHONE FIT's two 14px margins). So these two
     rungs alone take `1fr` and the grid takes the shelf's width -- the
     covers divide what there is, which is what "sixteen across" means on a
     393px screen and is what Photos does with the same gesture. The gaps go
     with them and are dialled where every other phone gap is (THE PHONE'S
     SPACING, at the foot of this block): 2px at 16, 6px at 8.
     `width: 100%` is not decoration -- `.shelfgrid`
     is `width: fit-content`, and `fit-content` collapses `1fr` columns to
     their min-content, which is 0. */
  html[data-phone] #shelf[data-zoom="16"] .shelfgrid { --cols: 16; --bookw: 1fr; }
  html[data-phone] #shelf[data-zoom="16"] .shelfgrid { width: 100%; }
  html[data-phone] #shelf[data-zoom="16"] .railrow.tile { grid-template-rows: auto; row-gap: 0; }
  /* a 21px board with an 11px corner is a dot, not a book (the Mac's 16 makes
     the same cut at 75px) */
  html[data-phone] #shelf[data-zoom="16"] .tilecov { border-radius: 2px; }
  html[data-phone] #shelf[data-zoom="16"] .railrow.tile .railrow-title,
  html[data-phone] #shelf[data-zoom="16"] .railrow.tile .railrow-lang,
  html[data-phone] #shelf[data-zoom="16"] .railrow.tile .railrow-by { display: none; }
  /* ---- 8: EIGHT across, covers only */
  html[data-phone] #shelf[data-zoom="8"] .shelfgrid { --cols: 8; --bookw: 1fr; }
  html[data-phone] #shelf[data-zoom="8"] .shelfgrid { width: 100%; }
  html[data-phone] #shelf[data-zoom="8"] .railrow.tile { grid-template-rows: auto; row-gap: 0; }
  html[data-phone] #shelf[data-zoom="8"] .tilecov { border-radius: 3px; }
  html[data-phone] #shelf[data-zoom="8"] .railrow.tile .railrow-title,
  html[data-phone] #shelf[data-zoom="8"] .railrow.tile .railrow-lang,
  html[data-phone] #shelf[data-zoom="8"] .railrow.tile .railrow-by { display: none; }
  /* ---- 4: PHONE FIT and THE PHONE, WHOLE, as they stand */
  /* ---- 2: two across, covers only, filling the shelf */
  html[data-phone] #shelf[data-zoom="2"] .shelfgrid { --cols: 2; --bookw: 400px; }
  html[data-phone] #shelf[data-zoom="2"] .railrow.tile { grid-template-rows: auto; row-gap: 0; }
  html[data-phone] #shelf[data-zoom="2"] .railrow.tile .railrow-title,
  html[data-phone] #shelf[data-zoom="2"] .railrow.tile .railrow-lang,
  html[data-phone] #shelf[data-zoom="2"] .railrow.tile .railrow-verdict,
  html[data-phone] #shelf[data-zoom="2"] .railrow.tile .railrow-by { display: none; }
  /* ---- 1 ¶ and 1 take the whole shelf: `100cqw` of the `shelf` container,
     a length, where `100%` would resolve against a band that is
     `width: fit-content` and collapse its one book to 44 px (measured) */
  /* ---- 1 ¶: the Mac's narrow stack -- cover, title, byline, the pane */
  html[data-phone] #shelf[data-zoom="1t"] .shelfgrid { --cols: 1; --bookw: 100cqw; }
  html[data-phone] #shelf[data-zoom="1t"] .railrow.tile {
    grid-template-columns: minmax(0, 1fr); grid-template-rows: auto auto auto auto auto;
    justify-items: start; row-gap: 0.75rem; padding: 1.4rem 0 2.2rem; }
  html[data-phone] #shelf[data-zoom="1t"] .railrow.tile .tilecov {
    grid-column: 1; grid-row: 1; width: auto; height: 38vh; margin: 0; }
  html[data-phone] #shelf[data-zoom="1t"] .railrow.tile .railrow-title {
    grid-column: 1; grid-row: 2; font-size: 1.8rem; line-height: 1.12; height: auto; -webkit-line-clamp: 4; }
  html[data-phone] #shelf[data-zoom="1t"] .railrow.tile .railrow-by { grid-column: 1; grid-row: 3; font-size: 0.78rem; }
  html[data-phone] #shelf[data-zoom="1t"] .railrow.tile .railrow-lang { grid-column: 1; grid-row: 4; font-size: 0.78rem; }
  html[data-phone] #shelf[data-zoom="1t"] .railrow.tile .tile-pane {
    grid-column: 1; grid-row: 5; width: 100%; display: grid; gap: 1.15rem; align-content: start; padding-top: 0.6rem; }
  html[data-phone] #shelf[data-zoom="1t"] .tile-slotrow { grid-template-columns: minmax(0, 1fr); }
  /* the ✕ rides the cover's corner, not the tile's, where the cover is narrower */
  html[data-phone] #shelf[data-zoom="1t"] .railrow.tile .tile-close { right: auto; left: calc(38vh * 88 / 120 - 34px); top: calc(1.4rem + 6px); }
  /* ---- 1: one jacket, the shelf's width, one screen a book */
  html[data-phone] #shelf[data-zoom="1"] { scroll-snap-type: y proximity; }
  html[data-phone] #shelf[data-zoom="1"] .shelfgrid { --cols: 1; --bookw: 100cqw; }
  html[data-phone] #shelf[data-zoom="1"] .railrow.tile {
    grid-template-rows: auto; row-gap: 0; scroll-snap-align: center;
    min-height: calc(100dvh - var(--safe-top, 0px) - var(--safe-bottom, 0px) - 24px); align-content: center; }
  html[data-phone] #shelf[data-zoom="1"] .railrow.tile .railrow-title,
  html[data-phone] #shelf[data-zoom="1"] .railrow.tile .railrow-lang,
  html[data-phone] #shelf[data-zoom="1"] .railrow.tile .railrow-by { display: none; }
  /* the jacket is centred in a screen-high tile; the ✕ sits on its corner:
     half the tile, less half the jacket (the shelf's width, 100cqw of the
     `shelf` container, at 120/88), plus the 6 px inset */
  html[data-phone] #shelf[data-zoom="1"] .railrow.tile .tile-close { top: calc(50% - 68.182cqw + 6px); }

  /* THE PHONE'S SPACING, one block in ladder order as the Mac's is (THE
     SPACING, above: `gap: <row> <column>`, and no rung carries a gap of its
     own). 4 is PHONE FIT's --ph-gap and is not restated. Mine, measured at
     393, one place to dial them. 16 and 8 were 10 and 14 until 14 Sep, when
     those rungs became sixteen and eight columns instead of four and three
     (chat 108): 10px between sixteen columns is 150 of the shelf's 365 spent
     on nothing, so 2 and 6 -- the Mac's own ratio (0.90 / 1.90) carried down
     to a phone. */
  html[data-phone] #shelf[data-zoom="16"] .shelfgrid { gap: 2px 2px; }
  html[data-phone] #shelf[data-zoom="8"]  .shelfgrid { gap: 6px 6px; }
  html[data-phone] #shelf[data-zoom="2"]  .shelfgrid { gap: 18px 14px; }
  html[data-phone] #shelf[data-zoom="1t"] .shelfgrid { gap: 0 0; }
  html[data-phone] #shelf[data-zoom="1"]  .shelfgrid { gap: 0 0; }
}

/* ==================================================================
   THE SHELF, DRAWN (LIBRARY-DESIGN, wave 10, 26 Sep 2026). This block is the
   design of the Library on EVERY host -- it takes what wave 9 drew for the
   web alone (the band, the column, one quiet Sort, the tile's rhythm, the
   foot) and makes it the one drawing (POLISH rule 6: the same thing looks
   the same everywhere), with the phone as a state of it (rule 13). It comes
   last so it wins; the app's older numbers above it (the rem gaps, the
   fixed 2.5em title, ORDER as a pill) are what it replaces.

   EVERY NUMBER IS A TOKEN OR A SUM OF TOKENS (rule 2), from
   design/reader/tokens.css: --tap 44 · --gap 8 · --pad 12 · --hair 1 ·
   --gap-hair 2 · --ui-fine 13 · --ui-act 15 · --ui-field 16 · --r-ui 10.
   EVERY COLOUR IS A var(--chrome-*) (rule 12). No px literal below outside
   the seven dials, no #hex anywhere in this block.

   THE DIALS, dialled in design/library/library.html's bench, each one
   property on :root, each rung a token sum (the bench prints the sum):
     --shelf-tile      the book's width      default 36 gap  (288)
     --shelf-gap       between books, both ways  default 4 gap (32)
     --shelf-measure   the page's column     default 4 tile + 3 gap (1248)
     --shelf-title     the title's size      default --ui-act (15)
     html[data-author] the author line: caps (default) or plain
     --shelf-foot-weight  the foot's weight  default 400
     --shelf-foot-ink  the foot's ink        default --chrome-fg-dim
   And the empty sentence is a dial in the page (EMPTY_SENTENCES).
   ================================================================== */
  /* THE PAGE'S REM IS 16 (--ui-field), as it always was: tokens.css sets the
     READING page's fluid rem on <html>, and every rem this sheet spends was
     written against the browser's 16. Pinned here, after the link. */
  html { font-size: var(--ui-field); }
  :root {
    --shelf-tile: calc(36 * var(--gap));
    --shelf-gap: calc(4 * var(--gap));
    /* the column is four books wide by default, so the band's doors stand
       over the first cover's edge and the last cover ends under the theme */
    --shelf-measure: calc(4 * var(--shelf-tile) + 3 * var(--shelf-gap));
    --shelf-title: var(--ui-act);
    --shelf-foot-weight: 400;
    --shelf-foot-ink: var(--chrome-fg-dim);
    /* the band aligns its doors with the shelf's column (band.css) */
    --band-measure: var(--shelf-measure);
  }

  /* THE HEADER IS THE BAND: nothing of the old header's box is left --
     band.css draws the row, its height, its rule. */
  header, html[data-phone] header { padding: 0; border: 0; display: block; height: auto; min-height: 0; gap: 0; }
  header [hidden] { display: none !important; }   /* the two legacy nodes: `header button { display: inline-flex }` would show them */
  header .band { --band-measure: var(--shelf-measure); padding: 0 calc(4 * var(--gap)); }   /* the shelf's own side padding, so the doors and the covers share an edge at every width */
  .webnav, .sortface[hidden] { display: none; }

  /* THE PAGE SITS IN THE WINDOW (rule 7): one measured column, centred,
     equal gutters, a top margin on the scale. The column is every direct
     child of the shelf; the grid keeps its own centring inside it. */
  #shelf.rail { padding: calc(3 * var(--gap)) calc(4 * var(--gap)) calc(6 * var(--gap)); background: var(--bg); }
  #shelf > * { max-width: var(--shelf-measure); margin-left: auto; margin-right: auto; }
  #shelf .shelfband, #shelf .shelfgrid { margin-left: auto; margin-right: auto; }
  #shelf .empty { padding: calc(6 * var(--gap)) 0 calc(2 * var(--gap)); font: 400 var(--ui-act) / 1.5 var(--serif); color: var(--chrome-fg-dim); text-align: center; }
  /* THE EMPTY SHELF, DRAWN (STRANGER-FIX, 28 Sep): a lead, one line in
     Frank's voice, one door (rules 3, 5, 11) -- every size on the scale. */
  #shelf .empty .empty-lead { margin: 0 0 var(--gap); font: 500 var(--ui-field) / 1.4 var(--serif); color: var(--chrome-fg); }
  #shelf .empty .empty-line { margin: 0 auto; max-width: 36em; font: 400 var(--ui-act) / 1.5 var(--serif); color: var(--chrome-fg-dim); }
  #shelf .empty .emptydoor { display: inline-block; margin-top: calc(2 * var(--gap)); padding: var(--gap) calc(2 * var(--gap));
    border: var(--hair) solid var(--chrome-fg-dim); border-radius: var(--r-ui); background: transparent; color: var(--chrome-fg);
    font: 500 var(--ui-fine) / 1 var(--chrome-ui-font); cursor: pointer; text-decoration: none; -webkit-appearance: none; appearance: none; }
  #shelf .empty .emptydoor:hover { border-color: var(--chrome-fg); }
  #shelf .libline { padding: var(--gap) 0 var(--pad); }

  /* THE ROW ABOVE THE GRID EARNS ITS HEIGHT (rule 4): the source line at
     the left, Sort at the right -- a fact and a control, one tap tall. */
  .sortbar { display: flex; align-items: center; justify-content: space-between; gap: var(--gap);
    width: 100%; margin: 0 auto; padding: 0 0 calc(2 * var(--gap)); min-height: var(--tap); }
  .sortbar label { display: none; }
  .sourceline { font: 400 var(--ui-fine) / 1 var(--chrome-ui-font); color: var(--chrome-fg-dim); min-width: 0;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .sourceline .sourcedoor { color: var(--chrome-fg-dim); text-decoration: underline; text-underline-offset: calc(var(--gap-hair) + var(--hair)); text-decoration-thickness: var(--hair); }
  .sourceline .sourcedoor:hover { color: var(--chrome-fg); }
  /* SORT: one quiet word and a chevron; the menu is the page's own (#menu). */
  .sortface { display: inline-flex; align-items: center; gap: calc(var(--gap) - var(--gap-hair)); height: calc(4 * var(--gap)); padding: 0 var(--pad);
    font: 500 var(--ui-fine) / 1 var(--chrome-ui-font); color: var(--chrome-fg-dim);
    background: transparent; border: var(--hair) solid transparent; border-radius: var(--r-ui); cursor: pointer;
    -webkit-appearance: none; appearance: none; margin: 0; }
  .sortface::after { display: none; /* the svg chevron in SORT_FACE is the one caret (two drew "Sort ⌄ ⌄") */ content: ""; width: calc(var(--gap) - var(--gap-hair) - var(--hair)); height: calc(var(--gap) - var(--gap-hair) - var(--hair)); margin-top: calc(-1 * var(--gap-hair) - var(--hair));
    border-right: var(--hair) solid currentColor; border-bottom: var(--hair) solid currentColor; transform: rotate(45deg); }
  .sortface:hover, .sortface[aria-expanded="true"] { background: var(--chrome-control-bg); color: var(--chrome-fg); }
  .sortface:focus-visible { outline: var(--gap-hair) solid var(--chrome-accent); outline-offset: var(--hair); }
  /* the menu's checked row: the current order, marked by a tick, never by a word */
  .menu button[role="menuitemradio"] { padding-left: calc(3 * var(--gap) + var(--pad)); position: relative; }
  .menu button[role="menuitemradio"][aria-checked="true"]::before { content: ""; position: absolute; left: var(--pad); top: 50%;
    width: calc(var(--gap) - var(--gap-hair)); height: calc(var(--pad) - var(--gap-hair)); margin-top: calc(-1 * var(--gap));
    border-right: var(--gap-hair) solid var(--chrome-fg); border-bottom: var(--gap-hair) solid var(--chrome-fg); transform: rotate(45deg); }

  /* THE TILE, AT ONE RHYTHM: cover · pad · title · 2 hair · author. The
     title's box is its text (one line or two); the author line wraps rather
     than truncates where the book is wide enough (rule 8). */
  #shelf .railrow.tile { grid-template-rows: auto auto auto; row-gap: 0; align-content: start; }
  #shelf .railrow.tile .railrow-title { margin-top: var(--pad); height: auto; max-height: calc(2 * 1.3em);
    font: 600 var(--shelf-title) / 1.3 var(--serif); color: var(--chrome-fg); }
  #shelf .railrow.tile .railrow-by { margin-top: calc(2 * var(--gap-hair)); line-height: 1.35; color: var(--chrome-fg-dim);
    font-family: var(--chrome-ui-font); font-size: var(--ui-fine); white-space: normal;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; text-overflow: clip;
    text-transform: uppercase; letter-spacing: 0.04em; }
  /* the author dial's other face: the same line in plain type */
  html[data-author="plain"] #shelf .railrow.tile .railrow-by { text-transform: none; letter-spacing: 0; }
  .tilecov { border-radius: calc(var(--gap) - var(--gap-hair)); border: var(--hair) solid var(--chrome-border); box-shadow: 0 var(--hair) var(--gap-hair) var(--shadow-light); }
  #shelf .railrow.tile:hover .tilecov { box-shadow: 0 var(--pad) calc(3 * var(--gap) + var(--gap-hair) * 2) var(--shadow-md); }
  #shelf .railrow.tile[aria-selected="true"] .tilecov { box-shadow: 0 0 0 var(--gap-hair) var(--chrome-fg); }

  /* THE GRID: books of one width, as many across as the column holds (4 at
     most at the shelf's own rung), row gap = column gap, centred. */
  #shelf.rail[data-zoom] .shelfgrid { --bookw: var(--shelf-tile); gap: var(--shelf-gap); margin-top: 0; padding-bottom: 0;
    grid-template-columns: repeat(auto-fill, var(--shelf-tile)); width: 100%; justify-content: center; }
  #shelf.rail[data-zoom="4"] .shelfgrid { max-width: calc(4 * var(--shelf-tile) + 3 * var(--shelf-gap)); }
  #shelf.rail[data-zoom="16"] .shelfgrid { gap: calc(var(--shelf-gap) / 2); --bookw: calc(var(--shelf-tile) / 4); grid-template-columns: repeat(auto-fill, calc(var(--shelf-tile) / 4)); max-width: none; }
  #shelf.rail[data-zoom="8"]  .shelfgrid { gap: calc(var(--shelf-gap) * 3 / 4); --bookw: calc(var(--shelf-tile) / 2); grid-template-columns: repeat(auto-fill, calc(var(--shelf-tile) / 2)); max-width: none; }
  #shelf.rail[data-zoom="2"]  .shelfgrid, #shelf.rail[data-zoom="2t"] .shelfgrid { gap: var(--shelf-gap); --bookw: calc(var(--shelf-tile) * 3 / 2); grid-template-columns: repeat(auto-fill, calc(var(--shelf-tile) * 3 / 2)); }
  /* the two one-book rungs keep the ladder's own widths (1 ¶ 1180, 1 820 --
     the reader-shaped views, not this drawing's): one column, no gap */
  #shelf.rail[data-zoom="1t"] .shelfgrid { grid-template-columns: minmax(0, 1180px); --bookw: 1180px; max-width: none; gap: 0; }
  #shelf.rail[data-zoom="1"]  .shelfgrid { grid-template-columns: minmax(0, 820px); --bookw: 820px; max-width: none; gap: 0; }
  #shelf .shelfband + .shelfgrid { margin-top: calc(5 * var(--gap)); }
  .bandhead { padding: 0 0 var(--pad); }

  /* THE COVER'S AUTHOR LINE WRAPS (rule 8: "JORGE LUIS B…" on a cover wide
     enough for the name is the fault named): two lines, then a clamp; the
     line-height is the cover's own. */
  .tilecov .a { white-space: normal; text-overflow: clip; line-height: 1.25;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }

  /* THE FOOT: the counts of what is drawn, and Import bundle… as a link --
     the one secondary action, quiet under the last row (rule 9: the covers
     are the loud thing; nothing competes). */
  .webfoot { display: block; padding: calc(3 * var(--gap) + var(--gap-hair) * 2) 0 0; font: var(--shelf-foot-weight) var(--ui-fine) / 1.5 var(--chrome-ui-font); color: var(--shelf-foot-ink); }
  /* `#importBtn` was the raised button (0.85rem, 36 tall, 14 padding); the
     link wears its id for the one delegated click and none of its box */
  #importBtn.importlink { font: inherit; min-height: 0; padding: 0; margin: 0; background: transparent; border: 0; border-radius: 0; display: inline; }
  .importlink { color: var(--shelf-foot-ink); text-decoration: underline; text-underline-offset: calc(var(--gap-hair) + var(--hair)); text-decoration-thickness: var(--hair); }
  .importlink:hover { color: var(--chrome-fg); }
  .importlink[aria-disabled="true"] { opacity: 0.5; pointer-events: none; }
  /* a bundle being read is one loading row above the shelf, the app's own */
  #shelf .railrow.loading[data-importing] { max-width: calc(70 * var(--gap)); margin: 0 auto calc(2 * var(--gap)); }

  /* ==================================================================
     THE PHONE IS A STATE OF THIS PAGE (rule 13). Keyed on html[data-phone]
     ALONE so the bench proves it at any window; on a real phone the PHONE
     FIT block above also fires and these win it, name for name. The band
     is at the foot (band.css), so the shelf ends above it; two books
     across, the gap 2 gap, the margin 2 gap; the tile's rhythm unchanged.
     ================================================================== */
  html[data-phone] #shelf.rail { padding: calc(2 * var(--gap) + var(--safe-top, 0px)) calc(2 * var(--gap) + var(--safe-right, 0px))
    calc(var(--band-h, var(--tap)) + 4 * var(--gap) + var(--safe-bottom, 0px)) calc(2 * var(--gap) + var(--safe-left, 0px)); display: block; }
  /* THE PINCH'S RUNGS ARE BACK (29 Sep, the Photos pass): this said `#shelf[data-zoom]` -- EVERY rung -- and so
     drew two across at 16, 8 and 2 too; Frank's own phone shelf (shell/ at de55dd9) pinches to sixteen across,
     which is Photos' own gesture and what Osca's recordings show. Two across is the default rung's, 4. */
  html[data-phone] #shelf[data-zoom="4"] .shelfgrid { --cols: 2; --bookw: auto; grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: calc(2 * var(--gap)); max-width: none; width: 100%; }
  html[data-phone] .sortbar { padding-bottom: var(--pad); min-height: var(--tap); }
  html[data-phone] #shelf .railrow.tile .railrow-title { font-size: var(--ui-act); }
  html[data-phone] .webfoot { padding-top: calc(3 * var(--gap)); }
  html[data-phone] #shelf .empty { padding-top: calc(8 * var(--gap)); }

/* the Sort face's chevron (28 Sep): a 12 glyph after the word, the word's ink */
.sortface .sortchev{ width: calc(var(--ui-fine) - var(--hair)); height: calc(var(--ui-fine) - var(--hair)); margin-left: calc(var(--gap) / 2); vertical-align: -1px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }


/* ==================================================================
   THE PHONE'S SHELF, AFTER APPLE PHOTOS (Osca, 29 Sep, two screen recordings of Photos' Library on his iPhone:
   "this is pretty much how I want the library in frank to look and interact"). What Photos does that this page
   takes, in order:
   · the word Library, large and bold at the top left, and under it what is on screen now, changing as you
     scroll (library.html's phhead); the order is one glass circle at the right, not a row of its own;
   · the shelf runs UNDER the head, which fades into the page -- no bar, no rule;
   · at the covers-only rungs the grid goes edge to edge with a 2 px seam, like Photos' "All";
   · the bar at the foot floats (band.css, THE PHONE'S BAR FLOATS).
   Still to come, and written down in PROMPTS/lanes/design.md: the section chips at the dense rungs, a cover
   that grows into its book on a tap, and a drag down on a book that shrinks it back into its place.
   ================================================================== */
.phhead { display: none; }
@media (max-width: 600px), (max-height: 500px) {
  html[data-phone] .phhead {
    display: flex; position: fixed; z-index: 40; top: 0; left: 0; right: 0;
    align-items: flex-start; justify-content: space-between; gap: 12px;
    padding: calc(var(--safe-top, 0px) + 10px) 16px 32px;
    pointer-events: none;
    background: linear-gradient(to bottom,
      color-mix(in srgb, var(--bg) 94%, transparent) 0%,
      color-mix(in srgb, var(--bg) 72%, transparent) 58%,
      color-mix(in srgb, var(--bg) 0%, transparent) 100%);
  }
  html[data-phone] .phhead-t { min-width: 0; }
  /* FRANK'S TYPE, NOT APPLE'S (Osca, 29 Sep: "I don't like that font … stick with the frank font stuff … it looks
     like apple BUT not real apple"): the word Library in the book serif the shelf's titles are set in, and the
     line under it in the shelf's own small voice -- the tracked capitals of OPEN and of every byline */
  html[data-phone] .phhead h1 { margin: 0; font: 600 30px / 1.15 var(--serif, "Iowan Old Style", Palatino, Georgia, serif); color: var(--fg); }
  html[data-phone] .phrange { margin: 4px 0 0; min-height: 1.4em; font: 600 11px / 1.4 var(--ui-font); letter-spacing: 0.13em;
    text-transform: uppercase; color: var(--fg); opacity: 0.78; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  html[data-phone] .phsort {
    pointer-events: auto; flex: none; margin: 2px 0 0; padding: 0;
    width: 44px; height: 44px; border-radius: 50%; display: grid; place-items: center;
    color: var(--fg); border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent);
    background: color-mix(in srgb, var(--bg) 58%, transparent);
    -webkit-backdrop-filter: saturate(1.8) blur(18px); backdrop-filter: saturate(1.8) blur(18px);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.10);
    -webkit-appearance: none; appearance: none; -webkit-tap-highlight-color: transparent;
  }
  html[data-phone] .phsort:active { transform: scale(0.94); }
  /* THE RIGHT CLUSTER: ⋯ (only while books are chosen) · Select · the order. One height, 36 -- small, as Osca
     asked -- one glass, one gap; Select is a word in a capsule sized by the word (15 semibold, 14 each side) */
  html[data-phone] .phhead-r { display: flex; align-items: center; gap: 8px; margin-top: 4px; }
  html[data-phone] .phhead-r .phsort { width: 36px; height: 36px; margin: 0; }
  html[data-phone] .phhead-r .phsort svg { width: 18px; height: 18px; }
  html[data-phone] .phsel, html[data-phone] .phact {
    pointer-events: auto; flex: none; margin: 0; height: 36px; border-radius: 18px;
    color: var(--fg); border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent);
    background: color-mix(in srgb, var(--bg) 58%, transparent);
    -webkit-backdrop-filter: saturate(1.8) blur(18px); backdrop-filter: saturate(1.8) blur(18px);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.10);
    -webkit-appearance: none; appearance: none; -webkit-tap-highlight-color: transparent;
  }
  html[data-phone] .phsel { padding: 0 13px; font: 600 11px / 1 var(--ui-font); letter-spacing: 0.13em; text-transform: uppercase; }
  html[data-phone] .phsel[aria-pressed="true"] { background: color-mix(in srgb, var(--fg) 88%, transparent); color: var(--bg); border-color: transparent; }
  html[data-phone] .phact { width: 36px; padding: 0; display: grid; place-items: center; }
  html[data-phone] .phact[hidden] { display: none; }
  html[data-phone] .phact svg { width: 18px; height: 18px; fill: currentColor; }
  html[data-phone] .phsel:active, html[data-phone] .phact:active { transform: scale(0.94); }
  /* a chosen cover, Photos' way: a filled tick at its foot corner and the cover dimmed a little; the rest stay */
  html[data-phone] #shelf[data-picking] .railrow.tile { position: relative; }
  html[data-phone] #shelf[data-picking] .railrow.tile[aria-selected="true"] .tilecov { opacity: 0.72; }
  html[data-phone] #shelf[data-picking] .railrow.tile[aria-selected="true"]::after {
    content: ""; position: absolute; top: auto; left: auto; right: 6px; bottom: 6px; width: 22px; height: 22px; border-radius: 50%; border: 0; margin: 0;
    background: var(--accent, #832f2b) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 22'%3E%3Cpath d='M6.5 11.5l3 3 6-6.5' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 22px no-repeat;
    box-shadow: 0 0 0 1.5px #fff, 0 1px 4px rgba(0, 0, 0, 0.3); }
  html[data-phone] #shelf.rail[data-zoom="16"][data-picking] .railrow.tile[aria-selected="true"]::after { width: 12px; height: 12px; right: 2px; bottom: 2px; background-size: 12px; }
  html[data-phone] .phsort svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; }
  /* the sort row's job is the circle's now */
  html[data-phone] #shelf .sortbar { display: none; }
  /* the first row starts under the head, and scrolls up behind it */
  html[data-phone] #shelf.rail { padding-top: calc(var(--safe-top, 0px) + 96px); min-height: 100vh; box-sizing: border-box; }   /* the page is the shelf's paper to the foot, however few rows (at 16 across three rows left a grey half) */
  /* THE LADDER, PHOTOS' OWN (one place, so no older rung rule decides it): a pinch walks it, out to in --
       16   sixteen across, covers only, a 2 px seam, edge to edge          (Photos' furthest out)
        8   six across, covers only, a 2 px seam, edge to edge
        4   THE ONE THE SHELF OPENS ON: three across, covers only, a 2 px seam, edge to edge -- Photos' "All"
        2   two across with the title and author under each, the page's margin
       1 ¶ / 1  unchanged (one book, and its words / its jacket)
     The covers carry their titles, so the covers-only rungs lose nothing a reader needs; the captions come back
     one rung in. Every older phone rung rule above says a different thing -- this one comes last and is the
     one a Stranger should judge. */
  html[data-phone] #shelf.rail[data-zoom="16"], html[data-phone] #shelf.rail[data-zoom="8"],
  html[data-phone] #shelf.rail[data-zoom="4"] { padding-left: 0; padding-right: 0; }
  html[data-phone] #shelf.rail[data-zoom] .shelfgrid { width: 100%; max-width: none; --bookw: auto; }
  html[data-phone] #shelf.rail[data-zoom="16"] .shelfgrid { --cols: 16; grid-template-columns: repeat(16, minmax(0, 1fr)); gap: 2px; }
  html[data-phone] #shelf.rail[data-zoom="8"] .shelfgrid { --cols: 6; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 2px; }
  html[data-phone] #shelf.rail[data-zoom="4"] .shelfgrid { --cols: 3; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 2px; }
  html[data-phone] #shelf.rail[data-zoom="2"] .shelfgrid { --cols: 2; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px 12px; }
  html[data-phone] #shelf.rail:is([data-zoom="16"], [data-zoom="8"], [data-zoom="4"]) .railrow.tile {
    width: auto; min-width: 0; grid-template-rows: auto; row-gap: 0; padding: 0; }
  html[data-phone] #shelf.rail:is([data-zoom="16"], [data-zoom="8"], [data-zoom="4"]) .railrow.tile :is(.railrow-title, .railrow-by, .railrow-lang, .railrow-verdict) { display: none; }
  html[data-phone] #shelf.rail:is([data-zoom="16"], [data-zoom="8"], [data-zoom="4"]) .tilecov {
    width: 100%; height: auto; border-radius: 0; border: 0; box-shadow: none; }
  /* the books open right now are the first row, on the same grid (it was a 41 px box: fit-content with 1fr
     columns) and its word sits on the page's margin, not on the seam */
  html[data-phone] #shelf.rail .shelfband { width: 100%; max-width: none; margin: 0 0 2px; }
  html[data-phone] #shelf.rail .shelfband .bandhead { padding: 0 16px 8px; margin: 0; }
  /* THE COVERS FILL THEIR CELLS at the covers-only rungs, as Photos' squares do (the Photos Stranger: 26 jackets
     sat `contain`ed inside a 7-11 px mount, so the 2 px seam became ragged frames); one rung in, the mount is back */
  html[data-phone] #shelf.rail:is([data-zoom="16"], [data-zoom="8"], [data-zoom="4"]) .tilecov .covimg {
    inset: 0; width: 100%; height: 100%; max-width: none; max-height: none; object-fit: cover; }
  /* the order menu: the long-press sheet sat under the floating bar -- every menu rides above it now, and the
     order's hangs from its circle at the top right, a glass card, as Photos' filter menu does */
  html[data-phone] .menu { z-index: 70; }
  html[data-phone] .menu.menu-head {
    left: auto !important; right: calc(12px + var(--safe-right, 0px)) !important;
    top: calc(var(--safe-top, 0px) + 60px) !important; bottom: auto !important;
    width: 264px; max-width: calc(100vw - 24px); padding: 6px; border-radius: 16px;
    background: color-mix(in srgb, var(--bg) 96%, var(--fg));   /* near-solid: at 84% the covers read through the words */
    -webkit-backdrop-filter: saturate(1.8) blur(24px); backdrop-filter: saturate(1.8) blur(24px);
    border: 1px solid color-mix(in srgb, var(--fg) 10%, transparent);
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.28);
  }
  html[data-phone] .menu.menu-head button { text-align: left; font-size: 1rem; min-height: 44px; }
  /* the ✕ on an open book is a 28 disc -- bigger than a cover at 16 across (23 px); at the two dense rungs it goes,
     as Photos draws no furniture on a thumbnail that small (closing a book is one rung in) */
  html[data-phone] #shelf.rail:is([data-zoom="16"], [data-zoom="8"]) .railrow.tile .tile-close { display: none; }
  html[data-phone] #shelf.rail[data-zoom="2"] { padding-left: 16px; padding-right: 16px; }
  html[data-phone] #shelf.rail[data-zoom="2"] .shelfband .bandhead { padding-left: 0; }
  html[data-phone] #shelf.rail[data-zoom="2"] .railrow.tile .tilecov { width: 100%; height: auto; }
}


/* ==================================================================
   THE SAME LIBRARY ON THE MAC AND THE WEB (Osca, 29 Sep: "roughly it should be the same for the web and the desktop
   app, all the same idea. It looks nice"). One idea on three hosts: the head (Library in the serif, what is on screen
   in the shelf's small capitals, [⋯] Select [order]) laid over the top of the shelf and faded into it; the covers edge
   to edge on a 2 px seam at the covers-only rungs; the captions one rung in. The phone's block above keeps the
   phone's numbers; this is the window's. The head is `fixed` and library.html's hand lays it exactly over #shelf
   (the shelf is the scroller here, so the covers pass under it). The sort row keeps its source line (the Mac's
   door to Sources) and gives its order to the circle.
   ================================================================== */
html:not([data-phone]) .phhead {
  display: flex; position: fixed; z-index: 40; top: 0; left: 0; width: 100%; box-sizing: border-box;
  align-items: flex-start; justify-content: space-between; gap: 16px;
  padding: 18px 24px 30px; pointer-events: none;
  background: linear-gradient(to bottom,
    color-mix(in srgb, var(--bg) 95%, transparent) 0%,
    color-mix(in srgb, var(--bg) 74%, transparent) 60%,
    color-mix(in srgb, var(--bg) 0%, transparent) 100%);
}
html:not([data-phone]) .phhead-t { min-width: 0; }
html:not([data-phone]) .phhead h1 { margin: 0; font: 600 32px / 1.15 var(--serif, "Iowan Old Style", Palatino, Georgia, serif); color: var(--fg); }
html:not([data-phone]) .phrange { margin: 4px 0 0; min-height: 1.4em; font: 600 11px / 1.4 var(--ui-font); letter-spacing: 0.13em;
  text-transform: uppercase; color: var(--fg); opacity: 0.78; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
html:not([data-phone]) .phhead-r { display: flex; align-items: center; gap: 8px; margin-top: 4px; }
html:not([data-phone]) :is(.phsort, .phsel, .phact) {
  pointer-events: auto; flex: none; margin: 0; height: 32px; padding: 0; cursor: pointer;
  color: var(--fg); border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent);
  background: color-mix(in srgb, var(--bg) 62%, transparent);
  -webkit-backdrop-filter: saturate(1.8) blur(18px); backdrop-filter: saturate(1.8) blur(18px);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
  -webkit-appearance: none; appearance: none; transition: background-color .14s, color .14s;
}
html:not([data-phone]) :is(.phsort, .phact) { width: 32px; border-radius: 50%; display: grid; place-items: center; }
html:not([data-phone]) .phsel { border-radius: 16px; padding: 0 12px; font: 600 11px / 1 var(--ui-font); letter-spacing: 0.13em; text-transform: uppercase; }
html:not([data-phone]) :is(.phsort, .phsel, .phact):hover { background: color-mix(in srgb, var(--fg) 8%, var(--bg)); }
html:not([data-phone]) .phsel[aria-pressed="true"] { background: color-mix(in srgb, var(--fg) 88%, transparent); color: var(--bg); border-color: transparent; }
html:not([data-phone]) .phact[hidden] { display: none; }
html:not([data-phone]) .phsort svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; }
html:not([data-phone]) .phact svg { width: 16px; height: 16px; fill: currentColor; }
/* the order is the circle's; the row keeps where the books come from */
html:not([data-phone]) #shelf #shelforder { display: none; }
html:not([data-phone]) #shelf .sortbar:not(:has(> :not(#shelforder))) { display: none; }   /* nothing left in it (the web): no empty 44 */
html:not([data-phone]) #shelf.rail { padding-top: 88px; }
html:not([data-phone]) #shelf.rail .shelfband .bandhead { padding-top: 0; padding-bottom: 10px; }
/* THE LADDER on a window, the phone's idea at the window's width: 16 · 10 · 6 (the one it opens on) covers only on
   a 2 px seam, edge to edge; 2 is four across with title and author, on the page's margin; 1 ¶ and 1 as they were */
html:not([data-phone]) #shelf.rail:is([data-zoom="16"], [data-zoom="8"], [data-zoom="4"]) { padding-left: 0; padding-right: 0; }
html:not([data-phone]) #shelf.rail:is([data-zoom="16"], [data-zoom="8"], [data-zoom="4"], [data-zoom="2"]) .shelfgrid { width: 100%; max-width: none; --bookw: auto; }
html:not([data-phone]) #shelf.rail[data-zoom="16"] .shelfgrid { --cols: 16; grid-template-columns: repeat(16, minmax(0, 1fr)); gap: 2px; }
html:not([data-phone]) #shelf.rail[data-zoom="8"] .shelfgrid { --cols: 10; grid-template-columns: repeat(10, minmax(0, 1fr)); gap: 2px; }
html:not([data-phone]) #shelf.rail[data-zoom="4"] .shelfgrid { --cols: 6; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 2px; }
html:not([data-phone]) #shelf.rail[data-zoom="2"] .shelfgrid { --cols: 4; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 28px 20px; }
html:not([data-phone]) #shelf.rail:is([data-zoom="16"], [data-zoom="8"], [data-zoom="4"]) .railrow.tile {
  width: auto; min-width: 0; min-height: 0; grid-template-rows: auto; row-gap: 0; padding: 0; }
html:not([data-phone]) #shelf.rail:is([data-zoom="16"], [data-zoom="8"], [data-zoom="4"]) .railrow.tile :is(.railrow-title, .railrow-by, .railrow-lang, .railrow-verdict, .railrow-status) { display: none; }
html:not([data-phone]) #shelf.rail:is([data-zoom="16"], [data-zoom="8"], [data-zoom="4"]) .tilecov { width: 100%; height: auto; border-radius: 0; border: 0; box-shadow: none; }
html:not([data-phone]) #shelf.rail:is([data-zoom="16"], [data-zoom="8"], [data-zoom="4"]) .tilecov .covimg {
  inset: 0; width: 100%; height: 100%; max-width: none; max-height: none; object-fit: cover; }
html:not([data-phone]) #shelf.rail:is([data-zoom="16"], [data-zoom="8"], [data-zoom="4"]) .railrow.tile:hover { transform: none; }
html:not([data-phone]) #shelf.rail:is([data-zoom="16"], [data-zoom="8"], [data-zoom="4"]) .railrow.tile:hover .tilecov { filter: brightness(1.06); }
html:not([data-phone]) #shelf.rail[data-zoom="2"] { padding-left: 24px; padding-right: 24px; }
html:not([data-phone]) #shelf.rail[data-zoom="2"] .railrow.tile .tilecov { width: 100%; height: auto; }
html:not([data-phone]) #shelf.rail .shelfband { width: 100%; max-width: none; margin: 0 0 2px; }
html:not([data-phone]) #shelf.rail:is([data-zoom="16"], [data-zoom="8"], [data-zoom="4"]) :is(.shelfband .bandhead, .sortbar) { padding-left: 24px; padding-right: 24px; }
html:not([data-phone]) #shelf.rail:is([data-zoom="16"], [data-zoom="8"]) .railrow.tile .tile-close { display: none; }
/* picking on a window: the same tick */
html:not([data-phone]) #shelf[data-picking] .railrow.tile { position: relative; }
html:not([data-phone]) #shelf[data-picking] .railrow.tile[aria-selected="true"] .tilecov { opacity: 0.72; }
html:not([data-phone]) #shelf[data-picking] .railrow.tile[aria-selected="true"]::after {
  content: ""; position: absolute; top: auto; left: auto; right: 8px; bottom: 8px; width: 22px; height: 22px; border-radius: 50%; border: 0; margin: 0;
  background: var(--accent, #832f2b) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 22'%3E%3Cpath d='M6.5 11.5l3 3 6-6.5' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 22px no-repeat;
  box-shadow: 0 0 0 1.5px #fff, 0 1px 4px rgba(0, 0, 0, 0.3); }
html:not([data-phone]) .menu.menu-head { z-index: 70; }


/* ==================================================================
   A BOOK, OPENED THE WAY PHOTOS OPENS A PHOTO (library.html's bkview; Osca, 29 Sep, "the scroll up for info, after
   you click on it, we can do that too"). Every host, the same idea. The page goes to the paper; the cover stands in
   the middle at the height of the view less the top and the foot; the capsule and the ⋯ at the top, the strip and
   Read at the foot, all in the head's glass; scroll and the card rises under the cover. Frank's type: the title in
   the serif, the small words in the tracked capitals.
   ================================================================== */
.bkview { position: fixed; inset: 0; z-index: 80; --bkv-fade: 1; }
.bkview[hidden] { display: none; }
.bkview::before { content: ""; position: absolute; inset: 0; background: var(--bg); opacity: var(--bkv-fade); transition: opacity .32s ease; }
.bkv-scroll { position: absolute; inset: 0; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: none; }
.bkv-stage { height: 100%; box-sizing: border-box; padding: 72px 16px 132px; display: grid; place-items: center; }
.bkv-cover { all: unset; cursor: pointer; display: block; aspect-ratio: 88 / 120;
  height: min(100%, calc((100vw - 32px) * 120 / 88)); width: auto; }   /* the tallest that is also no wider than the page */
.bkv-cover .tilecov { width: 100% !important; height: 100% !important; border-radius: 6px; box-shadow: 0 18px 50px rgba(0,0,0,.28); transform-origin: center; }
/* the whole jacket in the view, never cut (the Stranger: "THE" on Monte-Cristo sliced by `cover`) -- the grid fills
   its cells, the view shows the book as it is */
.bkv-cover .tilecov .covimg { inset: 0; width: 100%; height: 100%; max-width: none; max-height: none; object-fit: contain; }
.bkv-info { position: relative; box-sizing: border-box; max-width: 640px; margin: 0 auto; padding: 22px 22px 150px;
  border-radius: 18px 18px 0 0; background: color-mix(in srgb, var(--bg) 96%, var(--fg)); color: var(--fg);
  box-shadow: 0 -10px 30px rgba(0,0,0,.08); opacity: var(--bkv-fade); display: flex; flex-direction: column; gap: 14px; }
.bkv-info .bkv-title { margin: 0; font: 600 24px / 1.2 var(--serif, "Iowan Old Style", Palatino, Georgia, serif); }
.bkv-info .bkv-by { margin: -8px 0 0; font: 600 11px / 1.4 var(--ui-font); letter-spacing: .13em; text-transform: uppercase; color: var(--fg-dim); }
.bkv-info .tile-read { display: none; }
.bkv-info .tile-slotrow { display: flex; gap: 24px; flex-wrap: wrap; }
.bkv-info .tile-chapters > span { display: flex; flex-direction: column; gap: 4px; }
.bkv-info .tile-slot.tile-blurb > span { -webkit-line-clamp: unset; display: block; font: 400 16px / 1.55 var(--serif, Georgia, serif); }
.bkv-top, .bkv-foot { position: absolute; left: 0; right: 0; display: flex; align-items: center; gap: 10px; padding: 0 12px;
  pointer-events: none; opacity: var(--bkv-fade); transition: opacity .32s ease; }
.bkv-top { top: calc(var(--safe-top, 0px) + 14px); justify-content: space-between; }
.bkv-foot { bottom: calc(var(--safe-bottom, 0px) + 14px); }
.bkv-top > *, .bkv-foot > * { pointer-events: auto; }
.bkv-glass, .bkv-cap, .bkv-read, .bkv-strip {
  border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent);
  background: color-mix(in srgb, var(--bg) 64%, transparent);
  -webkit-backdrop-filter: saturate(1.8) blur(18px); backdrop-filter: saturate(1.8) blur(18px);
  box-shadow: 0 4px 18px rgba(0,0,0,.12); color: var(--fg); }
.bkv-glass { -webkit-appearance: none; appearance: none; width: 40px; height: 40px; border-radius: 50%; padding: 0; margin: 0;
  display: grid; place-items: center; cursor: pointer; flex: none; }
.bkv-back svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.bkv-more svg { width: 18px; height: 18px; fill: currentColor; }
.bkv-cap { min-width: 0; max-width: calc(100% - 120px); padding: 6px 16px; border-radius: 20px; text-align: center; display: flex; flex-direction: column; gap: 2px; }
.bkv-cap b { font: 600 14px / 1.2 var(--serif, Georgia, serif); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.bkv-cap i { font: 600 9.5px / 1.3 var(--ui-font); font-style: normal; letter-spacing: .13em; text-transform: uppercase; color: var(--fg-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.bkv-cap i:empty { display: none; }
.bkv-strip { flex: 1 1 auto; min-width: 0; height: 52px; border-radius: 14px; padding: 5px; box-sizing: border-box;
  display: flex; gap: 3px; overflow-x: auto; scrollbar-width: none; }
.bkv-thumb { all: unset; cursor: pointer; flex: none; height: 100%; aspect-ratio: 88 / 120; opacity: .72; }
.bkv-thumb[aria-selected="true"] { opacity: 1; outline: 2px solid var(--fg); outline-offset: 1px; border-radius: 2px; }
.bkv-thumb .tilecov { width: 100% !important; height: 100% !important; border: 0; border-radius: 2px; box-shadow: none; }
.bkv-thumb .tilecov .covimg { inset: 0; width: 100%; height: 100%; max-width: none; max-height: none; object-fit: cover; }
.bkv-read { -webkit-appearance: none; appearance: none; flex: none; height: 52px; padding: 0 22px; border-radius: 26px; cursor: pointer;
  font: 600 12px / 1 var(--ui-font); letter-spacing: .13em; text-transform: uppercase;
  background: color-mix(in srgb, var(--fg) 90%, transparent); color: var(--bg); border-color: transparent; }
body[data-bkview] .phhead { visibility: hidden; }   /* on <body>, not <html>: a flag on <html> woke the theme's own observer and turned a dark Library light (the Mac+web Stranger, 29 Sep) */
body[data-bkview] .band { visibility: hidden; }
/* on a window the view is the width of a reading page, not a phone stretched: the cover a little shorter, the card a
   column, the strip no wider than it needs */
html:not([data-phone]) .bkv-stage { padding: 80px 24px 120px; }
html:not([data-phone]) .bkv-foot { justify-content: center; }
html:not([data-phone]) .bkv-strip { flex: none; width: min(640px, 60vw); }   /* one width, so READ stands in one place whichever book (it rode the strip's end, x 913 or 1061) */
html:not([data-phone]) .bkv-top { max-width: 760px; margin: 0 auto; }   /* ‹ and ⋯ beside the book, not at the window's far edges */
/* no ✕ over a cover in Photos' grid (the open books are the Mac's tabs; closing one is a rung in, where the captions
   are), on every host */
#shelf.rail:is([data-zoom="16"], [data-zoom="8"], [data-zoom="4"]) .railrow.tile .tile-close { display: none; }
/* the Mac's source line sits on the head's own margin, under the word Library */
html:not([data-phone]) #shelf.rail .sortbar { padding: 0 24px 12px; min-height: 0; }
html:not([data-phone]) #shelf.rail .sortbar > * { margin-left: 0; padding-left: 0; }

/* THE FIRST RUN IN THE LIBRARY'S LANGUAGE (DESIGN, 29 Sep). Osca: "make sure they all look like the same app ... ALL
   doors". The gate (settings.js) was the Settings window's grey rows on a blank ground. It keeps its words, its rows
   and its centring (STRANGER-FIX 28 Sep); it gains the Library's shape: FIRST RUN in the shelf's tracked capitals,
   the promise ("Your books, wherever you read them") as the page's name in the book serif at the Library's 30/32,
   one glass card, and the buttons as capsules -- the primary the book view's dark READ capsule, the other glass.
   The shelf behind blurs through the ground, as the Library's head does. */
body .ttstv-settings.fr-gate{
  background: color-mix(in srgb, var(--bg) 80%, transparent);
  -webkit-backdrop-filter: saturate(1.6) blur(28px); backdrop-filter: saturate(1.6) blur(28px);
}
body .ttstv-settings.fr-gate .fr-sheet{ max-width: 460px; }
body .ttstv-settings.fr-gate .set-head{ font: 600 11px / 1.4 var(--ui-font); letter-spacing: .13em; text-transform: uppercase;
  color: var(--fg); opacity: .78; margin: 0 0 8px; padding: 0 2px; }
body .ttstv-settings.fr-gate .set-card{ border-radius: 16px; overflow: hidden;
  border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent);
  background: color-mix(in srgb, var(--bg) 62%, transparent);
  -webkit-backdrop-filter: saturate(1.8) blur(18px); backdrop-filter: saturate(1.8) blur(18px);
  box-shadow: 0 10px 34px rgba(0, 0, 0, 0.10); }
body .ttstv-settings.fr-gate .set-row{ padding: 14px 18px; border-color: color-mix(in srgb, var(--fg) 9%, transparent); }
body .ttstv-settings.fr-gate .tr-lead .set-name{ font: 600 30px / 1.15 var(--serif, "Iowan Old Style", Palatino, Georgia, serif); color: var(--fg); margin-bottom: 6px; }
html:not([data-phone]) body .ttstv-settings.fr-gate .tr-lead .set-name{ font-size: 32px; }
body .ttstv-settings.fr-gate .tr-lead{ padding-top: 22px; padding-bottom: 18px; }
body .ttstv-settings.fr-gate .tr-name, body .ttstv-settings.fr-gate .set-row:not(.tr-lead) .set-name{ font: 600 16px / 1.3 var(--serif, Georgia, serif); }
body .ttstv-settings.fr-gate .kag-btn{ -webkit-appearance: none; appearance: none; height: 36px; padding: 0 16px; border-radius: 18px; cursor: pointer;
  font: 600 11px / 1 var(--ui-font); letter-spacing: .13em; text-transform: uppercase; color: var(--fg);
  border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent);
  background: color-mix(in srgb, var(--bg) 62%, transparent); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08); }
body .ttstv-settings.fr-gate .kag-btn.kag-primary{ background: color-mix(in srgb, var(--fg) 90%, transparent); color: var(--bg); border-color: transparent; }
html[data-phone] body .ttstv-settings.fr-gate .kag-btn{ height: 48px; border-radius: 24px; }

/* FIRST RUN, SAID ONCE (library.html's .fr-look, 29 Sep): the gate's own rows hide; the word Frank in the product
   face, the two ways in under it. The phone uses the whole width (its 20 px margins), the buttons the thumb's 50. */
body .ttstv-settings.fr-gate.fr-drawn .fr-sheet > :not(.fr-look){ display: none; }
body .ttstv-settings.fr-gate.fr-drawn .fr-sheet{ max-width: 360px; }
.fr-look{ display: flex; flex-direction: column; align-items: center; text-align: center; }
.fr-look .fr-name{ margin: 0 0 40px; font: 700 64px / 1 "Bodoni 72", "Bodoni 72 Book", "Bodoni MT", Didot, "Libre Bodoni", var(--serif, serif);
  letter-spacing: -.01em; color: var(--fg); }
/* K · Books made Frank IS the word (ASKS #156): the sentence in the product face, Frank in the accent red (black · red · white) */
.fr-look{ --k-red: #c8102e; }
html[data-theme="dark"] .fr-look{ --k-red: #e5334b; }
@media (prefers-color-scheme: dark){ html:not([data-theme="light"]) .fr-look{ --k-red: #e5334b; } }
.fr-look .fr-name{ font-size: 44px; line-height: 1.05; word-spacing: .12em; }
.fr-look .fr-name em{ font-style: normal; color: var(--k-red); }
.fr-look .fr-acts{ width: 100%; display: flex; flex-direction: column; gap: 10px; }
.fr-look .fr-in{ -webkit-appearance: none; appearance: none; width: 100%; height: 48px; border-radius: 24px; border: 0; cursor: pointer;
  font: 600 12px / 1 var(--ui-font); letter-spacing: .13em; text-transform: uppercase;
  background: color-mix(in srgb, var(--fg) 90%, transparent); color: var(--bg); }
.fr-look .fr-later{ -webkit-appearance: none; appearance: none; width: 100%; height: 44px; border: 0; background: none; cursor: pointer;
  font: 600 11px / 1 var(--ui-font); letter-spacing: .13em; text-transform: uppercase; color: var(--fg); opacity: .62; }
.fr-look .fr-later:hover{ opacity: 1; }
.fr-look .fr-say{ margin: 14px 0 0; font: 400 13px / 1.4 var(--ui-font); color: var(--fg); opacity: .62; max-width: 30em; }
html[data-phone] body .ttstv-settings.fr-gate.fr-drawn .fr-sheet{ max-width: none; padding-left: 20px; padding-right: 20px; }
html[data-phone] .fr-look .fr-name{ font-size: 36px; }
html[data-phone] .fr-look .fr-in{ height: 52px; border-radius: 26px; }
body .ttstv-settings.fr-gate.fr-drawn{ background: var(--bg); -webkit-backdrop-filter: none; backdrop-filter: none; }   /* a welcome on the paper itself: the shelf blurred behind it read as a smudge */

/* THE PHONE ON ITS SIDE (Osca, 29 Sep: "you have to do landscape on the phone, for everything"). Photos on its side
   doubles the columns rather than blowing three covers up to fill 852 px: the ladder is 24 / 12 / 6 / 4 across. The
   head, the shelf and the book view keep clear of the island's side and the other side alike (the safe areas the
   device gives, 59 px each on an iPhone on its side). */
@media (orientation: landscape){
  html[data-phone] #shelf.rail[data-zoom="16"] .shelfgrid { --cols: 24; grid-template-columns: repeat(24, minmax(0, 1fr)); }
  html[data-phone] #shelf.rail[data-zoom="8"] .shelfgrid { --cols: 12; grid-template-columns: repeat(12, minmax(0, 1fr)); }
  html[data-phone] #shelf.rail[data-zoom="4"] .shelfgrid { --cols: 6; grid-template-columns: repeat(6, minmax(0, 1fr)); }
  html[data-phone] #shelf.rail[data-zoom="2"] .shelfgrid { --cols: 4; grid-template-columns: repeat(4, minmax(0, 1fr)); }
  html[data-phone] #shelf.rail[data-zoom] { padding-left: var(--safe-left, env(safe-area-inset-left, 0px)) !important;
    padding-right: var(--safe-right, env(safe-area-inset-right, 0px)) !important; }
  html[data-phone] .phhead { padding-left: calc(var(--safe-left, env(safe-area-inset-left, 0px)) + 16px);
    padding-right: calc(var(--safe-right, env(safe-area-inset-right, 0px)) + 16px); }
}
/* the phone on its side, the landscape Stranger's fix-firsts (29 Sep): the first run's buttons a phone's width, centred;
   the book view's back, more, strip and READ clear of both 59 px strips, and the cover given the height */
@media (orientation: landscape){
  html[data-phone] body .ttstv-settings.fr-gate.fr-drawn .fr-sheet{ max-width: 360px; margin: 0 auto; }
  html[data-phone] .fr-look .fr-name{ font-size: 32px; margin-bottom: 20px; }
  html[data-phone] .bkv-top, html[data-phone] .bkv-foot{ padding-left: calc(var(--safe-left, env(safe-area-inset-left, 0px)) + 8px) !important;
    padding-right: calc(var(--safe-right, env(safe-area-inset-right, 0px)) + 8px) !important; }
  html[data-phone] .bkv-stage{ padding: 68px calc(var(--safe-right, 0px) + 16px) calc(var(--safe-bottom, 0px) + 86px) calc(var(--safe-left, 0px) + 16px);   /* ASKS #111: clear of the title above and the strip below (the cover ran 11 px under the strip) */ }
  html[data-phone] .bkv-cover{ height: 100%; width: auto; }
}

/* THE HEAD IS THE ORDER CIRCLE AND NOTHING ELSE (library.html, "THE LIBRARY SAYS NOTHING ABOUT ITSELF", 29 Sep):
   no word Library, no range line, no Select -- on every host. While choosing, Cancel, the ⋯ and the count come in. */
.phhead .phhead-t h1, .phhead .phhead-t { display: none; }
.phhead:has(.phsel[aria-pressed="true"]) .phhead-t { display: block; }
.phhead:has(.phsel[aria-pressed="true"]) .phhead-t h1 { display: none; }
.phhead .phsel:not([aria-pressed="true"]) { display: none; }
html[data-phone] .phhead, html:not([data-phone]) .phhead { justify-content: flex-end; }
html[data-phone] .phhead { padding-bottom: 12px;
  background: linear-gradient(to bottom, color-mix(in srgb, var(--bg) 88%, transparent) 0%, color-mix(in srgb, var(--bg) 0%, transparent) 100%); }
html:not([data-phone]) .phhead { background: none; padding-bottom: 8px; }
/* and the Mac's "On this Mac · Sources…" line goes too: Sources… is in the order menu */
html:not([data-phone]) .sortbar .shelfsource, html:not([data-phone]) .sortbar { display: none !important; }
/* with the head gone the grid starts under the island (phone) / the window's top (Mac, web): the circle floats over it */
html[data-phone] #shelf.rail { padding-top: calc(var(--safe-top, 0px) + 4px); }
html:not([data-phone]) #shelf.rail { padding-top: 0; }
/* the book view's ⓘ, beside READ */
.bkv-info-btn { flex: none; }
.bkv-info-btn svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; }
.bkv-info-btn[aria-pressed="true"] { background: color-mix(in srgb, var(--fg) 88%, transparent); color: var(--bg); }
/* ONE RECIPE FOR THE SMALL CAPITALS (ASKS #105; surface.css says the three): the book view's slot label ("languages")
   is a LABEL, 11 / 600 / .13; the author under the title in the top capsule is the META line, 400 */
#bkvInfo .tile-slot > b{ font-size:11px; }
.bkv-cap i{ font-weight:400; }
/* ...and the facts line under the byline ("EN · 66 chapters", 11.36 px at .03 em, a fourth recipe) is a LABEL */
#bkvInfo .tile-facts{ font-size:11px; font-weight:600; letter-spacing:.13em; }
/* ONE SIZE OF ROUND BUTTON PER HOST (ASKS #106, the Stranger: "round buttons not one size"; measured in band: Mac 26 /
   32 / 40, phone 36 / 40 / 48). A free-standing round button is 32 on the Mac and the web, 40 on the phone; the
   phone's foot doors (⚙, mic) are the 48 tap; what sits INSIDE a capsule (the player's small keys, the pill's ▶) is
   part of that capsule. */
html:not([data-phone]) .bkv-glass{ width:32px; height:32px; }
html:not([data-phone]) .bkv-glass svg{ width:16px; height:16px; }
html[data-phone] .phhead-r .phsort{ width:40px; height:40px; }
/* THE GRID'S TAGS (ASKS #51; library.html says when they are drawn): the Library's glass in the LABEL recipe (11 / 600 /
   .13), at the cover's top left, never taking a press */
#shelf .railrow.tile{ position: relative; }
#shelf .railrow.tile > .gridtag{ position: absolute; left: 4px; top: 4px; z-index: 2; pointer-events: none;
  padding: 3px 7px; border-radius: 999px; max-width: calc(100% - 8px); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: 600 11px/1.2 var(--ui-font); letter-spacing: .13em; text-transform: uppercase; color: var(--fg);
  background: color-mix(in srgb, var(--bg) 70%, transparent); border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent);
  -webkit-backdrop-filter: saturate(1.8) blur(12px); backdrop-filter: saturate(1.8) blur(12px); }
#shelf[data-zoom="16"] .railrow.tile > .gridtag{ left: 3px; top: 3px; padding: 2px 5px; }

/* ==================================================================
   PHOTOS RULES 1, 4 AND 7, ON EVERY HOST (DESIGN, 30 Sep; PROMPTS/lanes/design.md "PHOTOS, FROM FIRST PRINCIPLES").
   Osca's pictures: the window's four across drew every cover in a card (a 1 px border, a 6 px radius, the paper
   showing round a `contain`ed jacket, 28 / 20 px gaps and a 24 px margin); the phone's tiles still carried the old
   `#shelf .railrow` card (1 px border, the 15 px radius, a shadow) under every rung; the book view stood the jacket
   in a framed, shadowed box. Photos has none of it: the content edge to edge on a hairline seam, the thing itself.
   ================================================================== */
/* no card under any tile on the phone: the old list row's border, radius and shadow outlived the row */
html[data-phone] #shelf .railrow.tile { border: 0; border-radius: 0; box-shadow: none; background: transparent; }
/* the window's four across and the phone's two across are covers-only rungs like their neighbours: edge to edge,
   the 2 px seam, no margin (their captions were already hidden -- the jacket says the title) */
html:not([data-phone]) #shelf.rail[data-zoom="2"],
html[data-phone] #shelf.rail[data-zoom="2"] { padding-left: 0; padding-right: 0; }
html:not([data-phone]) #shelf.rail[data-zoom="2"] .shelfgrid, html[data-phone] #shelf.rail[data-zoom="2"] .shelfgrid { gap: 2px; }
html:not([data-phone]) #shelf.rail[data-zoom="2"] .railrow.tile, html[data-phone] #shelf.rail[data-zoom="2"] .railrow.tile { width: auto; min-width: 0; min-height: 0; grid-template-rows: auto; row-gap: 0; padding: 0; }
html #shelf.rail[data-zoom="2"] .railrow.tile :is(.railrow-title, .railrow-by, .railrow-lang, .railrow-verdict, .railrow-status) { display: none; }
html:not([data-phone]) #shelf.rail[data-zoom="2"] .railrow.tile:hover { transform: none; }
html:not([data-phone]) #shelf.rail[data-zoom="2"] .railrow.tile:hover .tilecov { filter: brightness(1.06); }
html:not([data-phone]) #shelf.rail[data-zoom="2"] :is(.shelfband .bandhead, .sortbar) { padding-left: 24px; padding-right: 24px; }
html[data-phone] #shelf.rail[data-zoom="2"] .shelfband .bandhead { padding-left: 16px; }
/* the cover at every covers-only rung, both hosts: no border, no radius, no shadow, the jacket fills its cell */
html #shelf.rail:is([data-zoom="16"], [data-zoom="8"], [data-zoom="4"], [data-zoom="2"]) .railrow.tile .tilecov {
  width: 100%; height: auto; border: 0; border-radius: 0; box-shadow: none; transition: none; }
html #shelf.rail:is([data-zoom="16"], [data-zoom="8"], [data-zoom="4"], [data-zoom="2"]) .railrow.tile .tilecov .covimg {
  inset: 0; width: 100%; height: 100%; max-width: none; max-height: none; object-fit: cover; }
/* THE BOOK VIEW: the jacket itself, the tallest the view gives it, centred -- no border, no radius, no shadow, and no
   paper round a jacket narrower than the box (only a typeset cover, which IS its paper, keeps it) */
.bkv-cover .tilecov { border: 0; border-radius: 0; box-shadow: none; }
.bkv-cover .tilecov:has(.covimg) { background: transparent; }
/* the long facts line wraps in the view instead of hiding behind a tooltip (rule 7) */
#bkvInfo .tile-details { white-space: normal; overflow: visible; text-overflow: clip; }
/* on the phone the jacket keeps clear of the island (--safe-top) and of the glass row under it (safe + 14 + 40) */
@media (orientation: portrait){
  html[data-phone] .bkv-stage { padding-top: calc(var(--safe-top, 0px) + 64px); }
}

/* ==================================================================
   ALL THE WAY IN (ASKS #165, Osca 30 Sep: "Go all the the way in, the view should be consistent ... Keeping also the
   one book view, which is just slide up and down - without the info"). The ladder is 16 · 8 · 4 · 2 · 1 on every host
   (1 ¶ left it: a book's info lives only in the book view). Rung 1 is Photos pinched all the way in: one jacket a
   screen, the tallest it can be, the book view's own drawing (no border, radius, shadow or card; the whole jacket,
   `contain`, on the paper), the same room above and below as the book view gives it so a press there opens the view
   with the jacket where it already is -- and the shelf snaps one book per screen. No words, no ✕, no pane.
   ================================================================== */
html #shelf.rail[data-zoom="1"] { padding-left: 0; padding-right: 0; scroll-snap-type: y mandatory; scroll-padding: 0; }
html #shelf.rail[data-zoom="1"] .shelfgrid { grid-template-columns: minmax(0, 1fr); --cols: 1; --bookw: auto; width: 100%; max-width: none; gap: 0; }
html #shelf.rail[data-zoom="1"] .railrow.tile {
  width: auto; min-width: 0; height: 100vh; height: 100dvh; min-height: 0; box-sizing: border-box; margin: 0;
  padding: 80px 24px 120px;                                           /* the book view's stage on a window */
  display: grid; grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr); row-gap: 0;
  place-items: center; justify-items: center; align-content: stretch;
  scroll-snap-align: start; scroll-snap-stop: always;
  border: 0; border-radius: 0; box-shadow: none; background: transparent; }
html #shelf.rail[data-zoom="1"] .railrow.tile:is(:hover, :focus-visible) { transform: none; }
html #shelf.rail[data-zoom="1"] .railrow.tile :is(.railrow-title, .railrow-by, .railrow-lang, .railrow-verdict, .railrow-status, .tile-pane, .tile-close, .gridtag) { display: none; }
html #shelf.rail[data-zoom="1"] .railrow.tile .tilecov {
  grid-column: 1; grid-row: 1; margin: 0; aspect-ratio: 88 / 120; width: auto; max-width: 100%;
  height: min(100%, calc((100vw - 32px) * 120 / 88));
  border: 0; border-radius: 0; box-shadow: none; transition: none; filter: none; }
html #shelf.rail[data-zoom="1"] .railrow.tile .tilecov:has(.covimg) { background: transparent; }
html #shelf.rail[data-zoom="1"] .railrow.tile .tilecov .covimg {
  inset: 0; width: 100%; height: 100%; max-width: none; max-height: none; object-fit: contain; }
/* the phone: the document is the scroller, so it is the document that snaps; the room is the phone book view's */
html[data-phone]:has(#shelf.rail[data-zoom="1"]) { scroll-snap-type: y mandatory; }
/* ...and neither body nor #shelf may be a scroll box of its own here (the phone gives both `overflow: auto`; the
   nearest one became the tiles' snap container -- one that never scrolls -- so nothing snapped: tile top -40 after a
   scroll, measured) */
html[data-phone]:has(#shelf.rail[data-zoom="1"]) body,
html[data-phone] #shelf.rail[data-zoom="1"] { overflow: visible; }
html[data-phone] #shelf.rail[data-zoom="1"] .railrow.tile { padding: calc(var(--safe-top, 0px) + 64px) 16px 132px; }   /* the portrait stage's */
@media (orientation: landscape) {
  html[data-phone] #shelf.rail[data-zoom="1"] .railrow.tile {
    padding: 68px calc(var(--safe-right, 0px) + 16px) calc(var(--safe-bottom, 0px) + 86px) calc(var(--safe-left, 0px) + 16px); }
}
/* the book view's walk (library.html, "LEFT AND RIGHT, BOOK TO BOOK"): the neighbour's jacket rides beside the one in
   view, a screen along, drawn exactly as it is; the stage clips them so the page never scrolls sideways */
.bkv-scroll { overflow-x: hidden; }
.bkv-stage { position: relative; overflow: clip; touch-action: pan-y; }
.bkv-cover { will-change: transform; }
.bkv-peek { position: absolute; pointer-events: none; }
.bkv-peek:empty { display: none; }
.bkv-peek .tilecov { width: 100% !important; height: 100% !important; border: 0; border-radius: 0; box-shadow: none; }
.bkv-peek .tilecov:has(.covimg) { background: transparent; }
.bkv-peek .tilecov .covimg { inset: 0; width: 100%; height: 100%; max-width: none; max-height: none; object-fit: contain; }

/* THE COVER FIT (ASKS #169, #173 -- Osca 30 Sep: "we need picturees to fit the books without leaving loads of blank
   space, or it not fitting"). The page's cover-fit script draws every jacket to the slot's own shape (straight, or a
   plain band cut from its middle -- never squeezed) and marks it `data-fit`; that canvas IS the slot's aspect to one
   source pixel, so `fill` here is a uniform scale -- no crop (`cover` cut the title and the author) and no bars
   (`contain` letterboxed rung 1 and the book view). `!important` only on object-fit: the rungs' rules carry #shelf. */
.tilecov .covimg[data-fit] { inset: 0; width: 100%; height: 100%; max-width: none; max-height: none; margin: 0;
  object-fit: fill !important; }
.tilecov:has(.covimg[data-fit]) { background: transparent; }

/* THE TYPESET COVER, SET TO ITS SLOT (ASKS #173: "invluding our own book covers, made in frank"). library.html's type
   fit marks a typeset cover `data-typefit` once it has sized its title, author and initial in cqw so that each lies
   whole inside the cover; the clamps and ellipses that cut a title ("The Bucolics and E") come off here, and the first
   words -- which could only ever be a sentence cut short -- are not drawn. */
.tilecov[data-typefit] .t, .tilecov[data-typefit] .a {
  -webkit-line-clamp: unset; line-clamp: none; overflow: visible; text-overflow: clip; overflow-wrap: normal; }
.tilecov[data-typefit]:not(.l5) .t, .tilecov[data-typefit] .a { display: block; }
.tilecov[data-typefit] .fw { display: none; }

/* THE BOOK YOU COME BACK TO (ASKS #175, the Stranger: "nothing marks the book you came back to"): a ring over the
   jacket that fades on its own -- closing the book view, or a pinch that moved the book out from under the pointer */
#shelf .railrow.tile[data-back] .tilecov::after {
  content: ""; position: absolute; inset: 0; z-index: 3; pointer-events: none;
  box-shadow: inset 0 0 0 3px var(--fg), inset 0 0 0 4px var(--bg);
  animation: tile-back 1.6s ease-out forwards; }
@keyframes tile-back { 0%, 40% { opacity: 1; } 100% { opacity: 0; } }

/* ==================================================================
   SHELVES AS ALBUMS (ASKS #184 #185, Osca 30 Sep; Z7). library.html says the gesture and the page; this is their
   picture. The page reads NEWLY ADDED (a short row) · SHELVES (an album a shelf: the stack of its covers, its name,
   its count) · LIBRARY (every book). The rows are a strip each that runs sideways under the hand, edge to edge like
   the grid, the labels in the LABEL recipe (11 / 600 / .13, the grid tags' and the book view's). A shelf opened is
   its own page: its name (renamed where it stands), its books with what was made from each and the reader's words,
   and the shelf's companion -- the one context layer -- as its own card, at the right on a window, first on a phone.
   ================================================================== */
#shelf { --row-h: 176px; }
html[data-phone] #shelf { --row-h: 148px; }
@media (orientation: landscape) { html[data-phone] #shelf { --row-h: 112px; } }
#shelf .libsec { margin: 0; padding: 0 0 18px; }
#shelf :is(.libsec, .libhead) { width: auto; max-width: none; }   /* edge to edge, as the grid under them is (#shelf's children are capped at the column elsewhere) */
#shelf .libhead { margin: 0; padding: 18px 24px 10px; font: 600 11px / 1.4 var(--ui-font); letter-spacing: .13em;
  text-transform: uppercase; color: var(--fg); opacity: .78; }
#shelf .libhead.lib-all { padding-top: 6px; }
html[data-phone] #shelf .libhead { padding-left: 16px; padding-right: 16px; }   /* #shelf already carries the safe inset on its side */
html[data-phone] #shelf .libsec:first-of-type .libhead { padding-top: 12px; }
#shelf .libstrip { display: flex; gap: 12px; overflow-x: auto; overflow-y: hidden; padding: 0 24px 2px;
  scroll-snap-type: x proximity; scroll-padding: 0 24px; scrollbar-width: none; overscroll-behavior-x: contain; }
#shelf .libstrip::-webkit-scrollbar { display: none; }
html[data-phone] #shelf .libstrip { gap: 8px; padding: 0 16px 2px; scroll-padding: 0 16px; }
/* the one rung that is a book a screen has no rows over it: it is the walk, snapped book to book */
#shelf.rail[data-zoom="1"] :is(.libsec, .libhead.lib-all) { display: none; }

/* a new book: its jacket, the row's height, no frame; a finished render says so in the grid tag's glass */
#shelf .nw-item { flex: none; position: relative; display: block; margin: 0; padding: 0; border: 0; background: none;
  width: calc(var(--row-h) * 88 / 120); height: var(--row-h); cursor: pointer; scroll-snap-align: start;
  -webkit-appearance: none; appearance: none; -webkit-tap-highlight-color: transparent; }
#shelf .nw-item .tilecov, #shelf .al-c .tilecov { width: 100%; height: 100%; border: 0; border-radius: 0; box-shadow: none; }
#shelf .nw-item .tilecov .covimg, #shelf .al-c .tilecov .covimg, .liftghost .tilecov .covimg {
  inset: 0; width: 100%; height: 100%; max-width: none; max-height: none; object-fit: cover; }
#shelf .nw-item:hover .tilecov { filter: brightness(1.06); }
#shelf .nw-tag { position: absolute; left: 4px; bottom: 4px; z-index: 2; pointer-events: none; max-width: calc(100% - 8px);
  padding: 3px 7px; border-radius: 999px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: 600 11px / 1.2 var(--ui-font); letter-spacing: .13em; text-transform: uppercase; color: var(--fg);
  background: color-mix(in srgb, var(--bg) 70%, transparent); border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent);
  -webkit-backdrop-filter: saturate(1.8) blur(12px); backdrop-filter: saturate(1.8) blur(12px); }
#shelf .nw-tag::before { content: "\25B6\FE0E"; margin-right: 5px; font-size: 8px; vertical-align: 1px; }

/* an album: up to three covers stacked, the front one whole, the two behind it rising to its right; a 2 px seam of
   paper between them (rule 1: no card, no frame) */
#shelf .album { flex: none; display: grid; grid-template-rows: auto auto auto; justify-items: start; text-align: left;
  width: calc((var(--row-h) - 16px) * 88 / 120 + 28px); margin: 0; padding: 0; border: 0; background: none; color: var(--fg);
  cursor: pointer; scroll-snap-align: start; -webkit-appearance: none; appearance: none; -webkit-tap-highlight-color: transparent; }
#shelf .al-stack { position: relative; display: block; width: 100%; height: var(--row-h); }
#shelf .al-stack[data-n="0"] { background: color-mix(in srgb, var(--fg) 6%, transparent); }
#shelf .al-c { position: absolute; left: 0; bottom: 0; height: calc(var(--row-h) - 16px); aspect-ratio: 88 / 120;
  outline: 2px solid var(--bg); }
#shelf .al-c[data-i="0"] { z-index: 3; }
#shelf .al-c[data-i="1"] { z-index: 2; left: 14px; bottom: 8px; }
#shelf .al-c[data-i="2"] { z-index: 1; left: 28px; bottom: 16px; }
#shelf .al-name { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; max-width: 100%; margin: 8px 0 0; overflow: hidden; overflow-wrap: anywhere;
  font: 600 calc(14px * var(--read-scale, 1)) / 1.3 var(--read-family, var(--serif, "Iowan Old Style", Palatino, Georgia, serif)); }
#shelf .al-n { display: block; margin: 2px 0 0; font: 600 11px / 1.4 var(--ui-font); letter-spacing: .13em; opacity: .62; }
#shelf .album:hover .al-c[data-i="0"] .tilecov { filter: brightness(1.06); }
#shelf .album:focus-visible, #shelf .nw-item:focus-visible { outline: 2px solid var(--fg); outline-offset: 2px; }
#shelf .album[data-fresh] .al-stack { animation: al-fresh 1.5s ease-out; }
@keyframes al-fresh { 0%, 35% { box-shadow: 0 0 0 3px var(--chrome-accent); } 100% { box-shadow: 0 0 0 3px transparent; } }

/* THE LIFT: the carried cover rides over everything under the hand, the one it came from waits as a ghost of itself,
   and the thing under the hand says what a drop will do -- the page's one loud colour, Frank's red, for the one act */
.liftghost { position: fixed; left: 0; top: 0; z-index: 300; pointer-events: none; will-change: transform;
  box-shadow: 0 18px 40px rgba(0, 0, 0, .28), 0 2px 6px rgba(0, 0, 0, .18); }
.liftghost .tilecov { width: 100%; height: 100%; border: 0; border-radius: 0; box-shadow: none; animation: lift-rise .18s ease-out both; }
@keyframes lift-rise { from { transform: scale(.94); } to { transform: scale(1.04); } }
html[data-lifting], html[data-lifting] * { -webkit-user-select: none; user-select: none; cursor: grabbing !important; }
#shelf [data-lifted] .tilecov { opacity: .3; }
#shelf :is(.railrow.tile, .nw-item) { -webkit-touch-callout: none; }
#shelf :is(.railrow.tile, .nw-item, .album) img { -webkit-user-drag: none; }
#shelf :is(.railrow.tile, .nw-item)[data-drop] .tilecov { outline: 3px solid var(--chrome-accent); outline-offset: -3px; }
#shelf .album[data-drop] .al-stack { outline: 3px solid var(--chrome-accent); outline-offset: 3px; }
#shelf .railrow.tile[data-drop], #shelf .nw-item[data-drop], #shelf .album[data-drop] { position: relative; }
#shelf .railrow.tile[data-drop]::after, #shelf .nw-item[data-drop]::after, #shelf .album[data-drop]::after {
  content: "New shelf"; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: 6; pointer-events: none;
  padding: 5px 10px; border-radius: 999px; white-space: nowrap;
  font: 600 11px / 1.2 var(--ui-font); letter-spacing: .13em; text-transform: uppercase;
  color: #fff; background: var(--chrome-accent); box-shadow: 0 2px 10px rgba(0, 0, 0, .18); }
#shelf .album.album[data-drop]::after { content: "Add"; top: calc(var(--row-h) / 2); }
#shelf .album.album[data-drop="has"]::after { content: "On this shelf"; background: color-mix(in srgb, var(--bg) 80%, transparent); color: var(--fg); }

/* ---- A SHELF, OPENED: its own page (the head's order circle is the library's, and goes) */
body:has(#shelf[data-shelfpage]) .phhead { display: none; }
#shelf.rail[data-shelfpage] { scroll-snap-type: none !important; padding: 0 !important; }
html[data-phone]:has(#shelf[data-shelfpage]) { scroll-snap-type: none; }
#shelf .spage { box-sizing: border-box; max-width: 1180px; margin: 0 auto; padding: 0 24px 64px; }
html[data-phone] #shelf .spage { padding: 0 16px calc(var(--safe-bottom, 0px) + 120px); }   /* #shelf keeps the safe insets on its sides */
@media (min-width: 721px) { html[data-phone] #shelf .sp-body { grid-template-columns: minmax(0, 1fr) minmax(240px, 300px); gap: 16px 24px; } html[data-phone] #shelf .sp-comp { position: static; } }   /* a phone on its side is shorter than the card: it scrolls with the page */
#shelf .sp-head { position: sticky; top: 0; z-index: 5; display: flex; align-items: center; gap: 12px; padding: 18px 0 14px;
  background: linear-gradient(to bottom, var(--bg) 78%, color-mix(in srgb, var(--bg) 0%, transparent)); }
html[data-phone] #shelf .sp-head { padding-top: calc(var(--safe-top, 0px) + 10px); }
#shelf .sp-t { flex: 1; min-width: 0; }
#shelf .sp-glass { flex: none; width: 32px; height: 32px; padding: 0; border-radius: 50%; display: grid; place-items: center; cursor: pointer;
  color: var(--fg); border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent); background: color-mix(in srgb, var(--bg) 62%, transparent);
  -webkit-backdrop-filter: saturate(1.8) blur(18px); backdrop-filter: saturate(1.8) blur(18px); box-shadow: 0 2px 10px rgba(0, 0, 0, .08);
  -webkit-appearance: none; appearance: none; -webkit-tap-highlight-color: transparent; }
html[data-phone] #shelf .sp-glass { width: 40px; height: 40px; }
#shelf .sp-glass:hover { background: color-mix(in srgb, var(--fg) 8%, var(--bg)); }
#shelf .sp-glass svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
#shelf .sp-more svg { fill: currentColor; stroke: none; }
#shelf .sp-name { display: block; width: 100%; box-sizing: border-box; margin: 0; padding: 0 0 2px; border: 0; border-bottom: 1px solid transparent;
  border-radius: 0; background: transparent; color: var(--fg); outline: none; text-overflow: ellipsis;
  font: 600 32px / 1.15 var(--serif, "Iowan Old Style", Palatino, Georgia, serif); -webkit-appearance: none; appearance: none; }
html[data-phone] #shelf .sp-name { font-size: 28px; }
#shelf .sp-name:hover { border-bottom-color: color-mix(in srgb, var(--fg) 18%, transparent); }
#shelf .sp-name:focus { border-bottom-color: color-mix(in srgb, var(--fg) 55%, transparent); }
#shelf .sp-count { margin: 4px 0 0; font: 600 11px / 1.4 var(--ui-font); letter-spacing: .13em; text-transform: uppercase; opacity: .78; }
#shelf .sp-body { display: grid; grid-template-columns: minmax(0, 1fr) minmax(300px, 380px); gap: 20px 48px; align-items: start; padding-top: 12px; }
#shelf .sp-comp { grid-column: 2; grid-row: 1; position: sticky; top: 92px; }
#shelf .sp-books { grid-column: 1; grid-row: 1; }
@media (max-width: 720px) {
  #shelf .sp-body { grid-template-columns: minmax(0, 1fr); }
  #shelf .sp-comp { grid-column: 1; grid-row: 1; position: static; }
  #shelf .sp-books { grid-row: 2; }
}
/* the companion: the one context layer, as the shelf's own card */
#shelf .sp-comp { box-sizing: border-box; padding: 18px 20px 8px; border-radius: var(--r-2, 15px);
  background: color-mix(in srgb, var(--fg) 4.5%, var(--bg)); }
#shelf .sp-ch { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
#shelf .sp-ch b { font: 600 11px / 1.4 var(--ui-font); letter-spacing: .13em; text-transform: uppercase; }
#shelf .sp-ch span { font: 400 11px / 1.4 var(--ui-font); letter-spacing: .06em; opacity: .62; text-align: right; }
#shelf .sp-lead { margin: 10px 0 14px; font: 400 calc(17px * var(--read-scale, 1)) / 1.45 var(--read-family, var(--serif, Georgia, serif)); }
#shelf .sp-cb { padding: 12px 0; border-top: 1px solid color-mix(in srgb, var(--fg) 10%, transparent); }
#shelf .sp-cb h4 { margin: 0; font: 600 calc(15px * var(--read-scale, 1)) / 1.3 var(--read-family, var(--serif, Georgia, serif)); }
#shelf .sp-cf { margin: 4px 0 0; font: 400 12px / 1.5 var(--ui-font); color: var(--fg-dim); }
#shelf .sp-cy { margin: 8px 0 0; font: 400 calc(14px * var(--read-scale, 1)) / 1.5 var(--read-family, var(--serif, Georgia, serif)); }
#shelf .sp-cy span { display: block; margin: 0 0 2px; font: 600 11px / 1.4 var(--ui-font); letter-spacing: .13em; text-transform: uppercase; opacity: .62; }
#shelf .sp-cn { margin: 6px 0 0; font: italic 400 calc(13px * var(--read-scale, 1)) / 1.5 var(--read-family, var(--serif, Georgia, serif)); color: var(--fg-dim); }
/* a book on the shelf: its jacket, and beside it what it is, what was made from it, and the reader's words */
#shelf .sp-books { list-style: none; margin: 0; padding: 0; display: grid; gap: 28px; }
#shelf .sp-book { display: grid; grid-template-columns: 132px minmax(0, 1fr); gap: 20px; align-items: start; }
html[data-phone] #shelf .sp-book { grid-template-columns: 96px minmax(0, 1fr); gap: 14px; }
html #shelf .spage .railrow.tile { width: 100% !important; height: auto !important; min-height: 0 !important; margin: 0 !important; padding: 0 !important;
  display: block !important; border: 0 !important; background: none !important; box-shadow: none !important; scroll-snap-align: none !important; }
html #shelf .spage .railrow.tile .tilecov { width: 100% !important; height: auto !important; aspect-ratio: 88 / 120; border: 0; border-radius: 0; box-shadow: none; }
html #shelf .spage .railrow.tile .tilecov .covimg { inset: 0; width: 100%; height: 100%; max-width: none; max-height: none; object-fit: cover; }
html #shelf .spage .railrow.tile :is(.railrow-title, .railrow-by, .railrow-lang, .railrow-verdict, .railrow-status, .tile-pane, .tile-close, .gridtag) { display: none !important; }
#shelf .sp-bt { margin: 0; font: 600 calc(18px * var(--read-scale, 1)) / 1.25 var(--read-family, var(--serif, Georgia, serif)); }
#shelf .sp-by { margin: 4px 0 0; font: 600 11px / 1.4 var(--ui-font); letter-spacing: .13em; text-transform: uppercase; opacity: .62; }
#shelf .sp-made { list-style: none; margin: 10px 0 0; padding: 0; font: 400 13px / 1.55 var(--ui-font); }
#shelf .sp-made i { display: inline-block; width: 1.5em; font-style: normal; font-size: 10px; opacity: .62; }
#shelf .sp-none, #shelf .sp-empty { margin: 10px 0 0; font: 400 13px / 1.5 var(--ui-font); color: var(--fg-dim); }
#shelf .sp-empty { list-style: none; }
#shelf .sp-about { display: block; margin: 12px 0 0; }
#shelf .sp-about span { display: block; margin: 0 0 4px; font: 600 11px / 1.4 var(--ui-font); letter-spacing: .13em; text-transform: uppercase; opacity: .62; }
#shelf .sp-about textarea { display: block; width: 100%; box-sizing: border-box; min-height: 2.6em; margin: 0; padding: 8px 10px; resize: vertical;
  font: 400 calc(14px * var(--read-scale, 1)) / 1.45 var(--read-family, var(--serif, Georgia, serif)); color: var(--fg);
  background: transparent; border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); border-radius: var(--r-1, 11px); outline: none; }
#shelf .sp-about textarea:focus { border-color: color-mix(in srgb, var(--fg) 45%, transparent); }
#shelf .sp-about textarea::placeholder { color: var(--fg-dim); opacity: 1; }
#shelf .sp-off { margin: 8px 0 0; padding: 0; border: 0; background: none; cursor: pointer; color: var(--fg-dim);
  font: 400 12px / 1.4 var(--ui-font); -webkit-appearance: none; appearance: none; }
#shelf .sp-off:hover { color: var(--fg); text-decoration: underline; text-underline-offset: 3px; }

/* ==================================================================
   DARK COVERS (ASKS #172, Osca 30 Sep: "Dark mode should turn book covers and things black and white, just warmth
   adjusted -- like if you add warmth to black and white you don't get black and white ... if you do want to use
   colours in dark mode, it'd only either be from warmth inverted OR from the frank red"). In dark every cover --
   a jacket, a cover Frank set, the album's stack, the book view, the strip, the carried one -- is drawn in one
   tone: grey (grayscale 1) pushed toward the paper's warmth by sepia in proportion to --warm (0 at Paper, so a
   neutral grey; 18 at Candle), Settings' contrast (--contrast, -2..2, 0 = as drawn) as a contrast step, and a
   little under full brightness so a white jacket is not a lamp in a dark room. Light keeps every colour; contrast
   applies there too. `!important` in dark because the rungs' own `filter` (a hover's brightness, rung 1's none)
   would otherwise put the colour back.
   ================================================================== */
.tilecov { filter: contrast(calc(1 + var(--contrast, 0) * 0.08)); }
html[data-theme="dark"] .tilecov, html[data-dark] .tilecov {
  filter: grayscale(1) sepia(calc(var(--warm, 0) / 18 * 0.62)) contrast(calc(1 + var(--contrast, 0) * 0.08)) brightness(0.9) !important; }
@media (prefers-color-scheme: dark) {
  html:not([data-theme="light"]) .tilecov {
    filter: grayscale(1) sepia(calc(var(--warm, 0) / 18 * 0.62)) contrast(calc(1 + var(--contrast, 0) * 0.08)) brightness(0.9) !important; }
}

/* ============ TYPE TO FIND (ASKS #207, 9 Oct): the shelf's own find, a glass capsule at the top of the window
   while letters are typed -- the letters in the book serif, the count in the LABEL voice, the way out as a hint.
   A tile that does not answer leaves the grid (display none) so the ones that do close up. Mac and web only. */
.typefind{
  position: fixed; left: 50%; top: 14px; transform: translateX(-50%); z-index: 40;
  display: flex; align-items: center; gap: 10px; height: 40px; padding: 0 16px 0 14px; border-radius: 999px;
  color: var(--fg); border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent);
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  -webkit-backdrop-filter: saturate(1.8) blur(18px); backdrop-filter: saturate(1.8) blur(18px);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.14);
  pointer-events: none; white-space: nowrap;
}
.typefind[hidden]{ display: none; }
.typefind .tf-glyph{ width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; opacity: .7; }
.typefind .tf-text{ font: 400 19px/1 var(--chrome-serif, var(--serif, "Iowan Old Style", Palatino, Georgia, serif)); letter-spacing: -.01em; }
.typefind .tf-count{ font: 600 11px/1.2 var(--ui-font); letter-spacing: .13em; text-transform: uppercase; color: var(--fg-dim); }
.typefind .tf-hint{ font: 400 11px/1.2 var(--ui-font); color: var(--fg-dim); margin-left: 4px; }
.typefind.none .tf-count{ color: var(--chrome-accent, #c8102e); }
#shelf .railrow[data-tf-hidden]{ display: none; }
/* while letters are typed the page IS the answer: one grid of the books that answer, no Newly added strip, no
   Shelves albums, no "Library" head above it */
html[data-typefind] #shelf .libsec.lib-new, html[data-typefind] #shelf .libsec.lib-albums,
html[data-typefind] #shelf .libhead.lib-all, html[data-typefind] #shelf .shelfhead{ display: none; }
html[data-phone] .typefind{ display: none; }

/* ==================================================================
   PHONE-LOOK (10 Oct, Osca on his iPhone at 04:08) -- two of his three, and they are the last word in this sheet.
   ================================================================== */
/* NO VIGNETTE (ASKS #239: "this weird vignette kind of thing at the top of the library, after you start scrolling ...
   I HATE THAT. I just want it to look normal"). The head's 128 px fade (paper at 88 % down to nothing) laid a cloud
   over the covers as they scrolled under the island. Now the strip behind the status bar is the plain paper and stops
   at a straight edge, as every iOS list does; the covers pass under it untouched, and the order circle floats over
   them in its own glass. The book page's sticky head on the phone is the same: plain paper, no fade under it. */
html[data-phone] .phhead {
  /* Osca, 10 Oct 23:27: "FRANK should use THE WHoLE screen" -- the plain-paper strip behind the island cut Frank off
     the top of the screen (a white block). No strip and no fade: the covers scroll right up under the island. */
  background: none;
}
html[data-phone] #shelf .sp-head { background: var(--bg); }

/* THE BOOK IN THE MIDDLE (ASKS #241: "every time I click on a book from the library, and view the more info stuff,
   the book is ALWAYS slightly right, like not CENTRED"). The stage is a grid whose one column was `auto`: WebKit (the
   phone's engine) sizes that column from the jacket's height-derived width before the min() clamps it -- 452 px in a
   370 px box -- and puts the 370 px jacket at its start, x 56.9 instead of 16 (41 px right of centre; Chromium sizes
   the column to the box, so no Chromium check ever saw it). One column the box's own width, and the jacket centres
   in it on every engine. (Upright only: on its side the jacket is height-bound and already centred, 353.8 each side.) */
@media (orientation: portrait) {
  .bkv-stage { grid-template-columns: minmax(0, 1fr); }
  .bkv-cover { justify-self: center; max-width: 100%; }
}
/* ...AND THE BOOK VIEW'S ROWS STAND IN THE SAME PLACE (ASKS #240: "the pill exists everywhere"): the strip · ⓘ · READ
   row was 12 px in and ended 48 px off the bottom (826), the shelf's foot now 21 px in and 25 px off (849, band.css
   --foot-side / --band-sink); the ‹ · title · ⋯ row keeps the same side so the two rows line up, as Photos' do. */
@media (orientation: portrait) {
  html[data-phone] .bkv-top, html[data-phone] .bkv-foot { padding-left: var(--foot-side, 21px); padding-right: var(--foot-side, 21px); }
  html[data-phone] .bkv-foot { bottom: var(--band-sink, calc(8px + var(--safe-bottom, 0px) / 2)); }
}

/* THE WAY OUT SAYS WHERE IT GOES (WEIGHT, ASKS #254, Osca 10 Oct: "when opened the info of a book how you get out").
   A bare ‹ in a 32 px disc over the jacket read as decoration; Photos' and Safari's back button is the chevron AND the
   place it returns to. One capsule, the glass the view's other doors wear, the same height as its round neighbours
   (32 Mac/web, 40 phone). The head is three columns so the title capsule stays on the window's centre line however
   wide the way out is. */
.bkv-top { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); }
.bkv-top > .bkv-back { justify-self: start; }
.bkv-top > .bkv-more { justify-self: end; }
.bkv-glass.bkv-back { width: auto; border-radius: 999px; padding: 0 14px 0 9px; display: inline-flex; align-items: center; gap: 3px;
  font: 600 12px / 1 var(--ui-font); letter-spacing: .02em; color: var(--fg); }
html:not([data-phone]) .bkv-glass.bkv-back { width: auto; height: 32px; }
html[data-phone] .bkv-glass.bkv-back { height: 40px; font-size: 14px; padding: 0 16px 0 10px; }
.bkv-back span { white-space: nowrap; }
.bkv-top > .bkv-cap { max-width: min(54vw, 440px); }
html[data-phone] .bkv-top > .bkv-cap { max-width: calc(100vw - 2 * var(--foot-side, 21px) - 2 * 110px); }   /* clear of the way out on a 402 glass */
#shelf .railrow.tile[data-back="land"] .tilecov::after { animation-duration: 2.6s; }   /* WEIGHT: the book you closed, ringed while the shelf arrives */

/* EXPORT -- the book view's "Keep a copy" row (#219): one line that says where the export is, one or two verbs */
.bkv-info .tile-export .tx-row { display: flex; gap: 8px; flex-wrap: wrap; }
.bkv-info .tile-export button, .bkv-info .tile-export .tx-save { font: 500 14px/1 var(--ui-font); padding: 10px 16px; border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--fg) 18%, transparent); background: transparent; color: var(--fg); cursor: pointer; text-decoration: none; }
.bkv-info .tile-export .tx-go { background: var(--fg); color: var(--bg); border-color: transparent; }
.bkv-info .tile-export [hidden] { display: none; }

/* ⓘ: THE COVER SHRINKS TO THE TOP, THE CARD STARTS UNDER IT (ASKS #259). One height for the stage while the card is
   up -- 44 % of the view (the phone; 52 % on the Mac and the web, which have the room) -- and the cover is the
   tallest that fits it, so the jacket is whole and nothing is blank between it and the card. */
.bkview .bkv-stage { transition: height .32s cubic-bezier(.2,.8,.2,1), padding .32s cubic-bezier(.2,.8,.2,1); }
.bkview[data-info] .bkv-stage { height: 52%; padding-bottom: 12px; }
html[data-phone] .bkview[data-info] .bkv-stage { height: 44%; padding-bottom: 10px; }

/* READ BY IS FLUSH WITH THE CARD (ASKS #258, the Stranger twice: its heading and rows 16 px in from every other line of
   the card -- voicepage.css pads its own sections for the Surface's column, where they stand alone) */
.bkv-info .tile-readby .vp-sec { padding-left: 0; padding-right: 0; margin-left: 0; margin-right: 0; font: 600 11px/1 var(--ui-font); color: var(--fg-dim); }
.bkv-info .tile-readby .vp-vrows, .bkv-info .tile-readby .vp-none { margin-left: 0; margin-right: 0; padding-left: 0; padding-right: 0; max-width: none; }
.bkv-info .tile-readby .vp-vrow { padding-left: 0; padding-right: 0; }
html[data-phone] .bkv-glass:focus:not(:focus-visible), html[data-phone] .bkv-glass.bkv-back:focus { outline: none; }   /* WEIGHT: the view focuses ‹ Library for the keyboard; on a phone that drew a thick ring nobody asked for */

/* the Undo in the note after a book comes off a shelf (11 Oct) */
#note .note-undo{ font: inherit; font-weight: 600; color: var(--accent, currentColor); background: none; border: 0; padding: 0 2px; cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }

/* ================================================================ FIX 11 OCT · LANE B (PROMPTS/lanes/fix-11-oct.md §B)
   Mac and phone alike unless the selector says which.
   1. A TWIN'S JACKET SAYS WHICH ONE IT IS -- `.tile-ed`, drawn only on a book whose work another book on the shelf
      shares (editionsOf): the translator, the edition, a pair's two halves, else the language or the length. Never a
      language code on every jacket (Osca, 10 Oct 20:51) -- words, and only where two covers would be the same cover. */
#shelf .railrow.tile > .tile-ed, #shelf .nw-item > .tile-ed, .zs-bc > .tile-ed {
  position: absolute; left: 0; right: 0; bottom: 6px; z-index: 2; pointer-events: none;
  width: fit-content; max-width: calc(100% - 12px); margin: 0 auto; box-sizing: border-box;
  padding: 3px 8px; border-radius: 999px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: 600 10.5px / 1.3 var(--ui-font); letter-spacing: .02em; color: #fff;
  background: rgba(18, 18, 18, .66); -webkit-backdrop-filter: blur(10px) saturate(1.4); backdrop-filter: blur(10px) saturate(1.4); }
#shelf .nw-item > .tile-ed { top: 6px; bottom: auto; }
#shelf[data-zoom="16"] .railrow.tile > .tile-ed { bottom: 3px; max-width: calc(100% - 6px); padding: 2px 4px; font-size: 8px; }
html[data-phone] #shelf[data-zoom="8"] .railrow.tile > .tile-ed { bottom: 3px; max-width: calc(100% - 6px); padding: 2px 4px; font-size: 8px; }
html[data-phone] #shelf[data-zoom="16"] .railrow.tile > .tile-ed,
html #shelf.rail:is([data-zoom="1"], [data-zoom="1t"]) .railrow.tile > .tile-ed,
html #shelf .spage .railrow.tile > .tile-ed { display: none; }
html[data-phone] #shelf .nw-item > .tile-ed, html[data-phone] .zs-bc > .tile-ed { font-size: 9.5px; padding: 2px 6px; }
/* the book you were reading, in the Just opened row: its tag says so, with no ▶ */
#shelf .nw-tag.nw-now::before { content: none; }

/* 2. THE SORT BUTTON SAYS THE ORDER -- one short word and a chevron in the head's capsule (paintShelfOrder) */
.phhead .phhead-r .phsort.phsort-word { width: auto; min-width: 0; padding: 0 10px 0 13px; border-radius: 999px;
  display: inline-flex; align-items: center; gap: 4px; font: 600 12px / 1 var(--ui-font); letter-spacing: .02em; white-space: nowrap; }
.phhead .phsort.phsort-word .phsort-chev { width: 12px; height: 12px; fill: none; stroke: currentColor; stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round; }
html[data-phone] .phhead .phhead-r .phsort.phsort-word { width: auto; height: 36px; font-size: 13px; }
html:not([data-phone]) .phhead .phhead-r .phsort.phsort-word { width: auto; height: 32px; }

/* 3. THE MAC'S GRID KEEPS THE ROWS' EDGE (Mac Explorer #20: the grid began at x=0, Newly added and Shelves at 24) --
      and the right-hand 24 px is where the alphabet stands */
html:not([data-phone]):not([data-web]) #shelf.rail:not([data-zoom="1"]):not([data-zoom="1t"]) > .shelfgrid {
  box-sizing: border-box; padding-left: 24px; padding-right: 24px; }

/* 4. THE ALPHABET INDEX -- #azidx, one column of letters at the right edge (the script above the grid tags) */
.azidx { position: fixed; z-index: 40; right: 3px; top: 50%; transform: translateY(-50%);
  display: flex; flex-direction: column; align-items: center; padding: 6px 1px; border-radius: 12px;
  touch-action: none; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; }
.azidx[hidden] { display: none; }
.azidx button { all: unset; box-sizing: border-box; display: block; width: 18px; height: 15px; border-radius: 4px;
  text-align: center; font: 700 10px / 15px var(--ui-font); color: var(--az-ink, var(--chrome-fg, var(--fg))); cursor: pointer; }
.azidx button:disabled { opacity: .26; cursor: default; }
.azidx button:not(:disabled):hover { background: color-mix(in srgb, var(--fg) 10%, transparent); }
.azidx button.on { background: var(--chrome-accent, #c0392b); color: #fff; }
/* #9 (Osca 14 Oct): the page's ink is read off the column (--az-ink is a letter's own, set by what is behind it); the
   letter of the book at the top of the view is marked (.here) -- the place marker -- and in an order that is not by
   letter a thin thumb runs down the column as the shelf scrolls. */
.azidx { color: var(--chrome-fg, var(--fg)); }
.azidx button { text-shadow: var(--az-halo, none); }
.azidx button.here { background: var(--chrome-accent, #c0392b); color: #fff; }
.azidx button.here, .azidx button.on { text-shadow: none; }
.azidx .azthumb { position: absolute; left: -3px; top: 0; width: 3px; border-radius: 2px; pointer-events: none;
  background: var(--chrome-accent, #c0392b); transition: transform .08s linear; }
.azidx .azthumb[hidden] { display: none; }
.azidx[data-scrub] { background: color-mix(in srgb, var(--bg) 82%, transparent); }
html[data-phone] .azidx { right: calc(var(--safe-right, env(safe-area-inset-right, 0px)) + 1px); padding: 6px 0;
  background: color-mix(in srgb, var(--bg) 60%, transparent); -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); }
html[data-phone] .azidx button { width: 20px; height: 14px; line-height: 14px; font-size: 10px; }
/* THE LETTERS HAVE THEIR OWN LANE ON THE PHONE (Osca, 11 Oct 18:57: "I need this scroll to look better, slightly nicer
   space horizontally"): the strip stood over the right-hand column of covers, a blurred bubble on the art. The grid
   ended 28 px from the right edge for a while. */
/* ...and NO lane (Osca, 11 Oct 23:22: "GET rid of it - not border in the library!"): the covers run to the right
   edge; the letters stand over them, still with no bubble. */
html[data-phone] .azidx { right: calc(var(--safe-right, env(safe-area-inset-right, 0px)) + 4px); padding: 4px 0;
  background: none; -webkit-backdrop-filter: none; backdrop-filter: none; }
html[data-phone] .azidx button { width: 20px; }
@media (max-height: 560px) { .azidx button, html[data-phone] .azidx button { height: 11px; line-height: 11px; font-size: 8.5px; } }
body[data-bkview] .azidx, html[data-typefind] .azidx { display: none !important; }
/* P11 (Osca's iPhone list, 14 Oct 14:21/14:48: "NO other scrollbars, not the IOS native one, NOT this stupid one you
   made inside the app - JUST the alphaet in the library please"). On the phone the PAGE is the scroller (html: overflow
   auto, 4332 of 912 px measured at 420 x 912), so the bar iOS draws is the WKWebView's own -- that one is switched off
   natively (iOS repo, FrankWebView.m frank_webview_no_indicators). Here: no element of the phone Library draws one
   either, as the Mac's already does not (607e7a7). The A-Z is the one scroll bar. */
html[data-phone], html[data-phone] body, html[data-phone] #shelf { scrollbar-width: none; }
html[data-phone]::-webkit-scrollbar,
html[data-phone] body::-webkit-scrollbar,
html[data-phone] #shelf::-webkit-scrollbar { display: none; width: 0; height: 0; }

/* ================================================================ R2 · 11 OCT (PROMPTS/testers/explorer-mac2-11-oct.md)
   9. A TWIN'S LABEL IS READ, NOT SQUINTED AT -- 11 px at every rung that shows one (it was 8 px at 16 across on the Mac
      and at 8 across on the phone), on two lines where a tile is narrow, the whole of it in the tile's title. */
#shelf[data-zoom="16"] .railrow.tile > .tile-ed,
html[data-phone] #shelf[data-zoom="8"] .railrow.tile > .tile-ed {
  bottom: 3px; max-width: calc(100% - 4px); padding: 2px 5px; border-radius: 6px; font-size: 11px; line-height: 1.15;
  white-space: normal; text-align: center; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
html[data-phone] #shelf .nw-item > .tile-ed, html[data-phone] .zs-bc > .tile-ed { font-size: 11px; padding: 2px 6px; }
/* 10. A RESIZED WINDOW KEEPS ITS COVERS' SHAPE -- WebKit kept each grid row at the height it had before the window
   narrowed (a 43 px cover in a 107 px row, until reload; measured in WebKit, Chromium never did it). Rows sized to their
   content are re-asked on every layout. */
#shelf .shelfgrid { grid-auto-rows: min-content; }
/* 7. THE "+" IN THE HEAD -- the sort circle's twin, one glyph, the same size on every host */
.phhead .phhead-r .phadd svg { fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; }
html:not([data-phone]) .phhead .phhead-r .phadd { width: 32px; height: 32px; }

/* COVERS AND NOTHING OVER THEM (Osca, 11 Oct 19:14: "I don't want ANY silly little BUBBLE things OVER the covers ...
   COVERS, and nothing over them, bar the small UI buttons/surface pill we have" -- the Mac too). The group letters and
   months (.gridtag), the edition / language / chapter pills (.tile-ed) and the Reading / voiced tags (.nw-tag) are not
   drawn on any cover; what they said is in the book's info and the A-Z strip. */
#shelf .railrow.tile > .gridtag, #shelf .railrow.tile > .tile-ed, #shelf .nw-item > .tile-ed, .zs-bc > .tile-ed,
#shelf .nw-tag { display: none !important; }
/* THE MAC'S EDGES ARE VERY SMALL AND THE SAME BOTH SIDES (Osca, 11 Oct 23:27: "I actually don't mind a VERY SMALL margin
   on the mac but it HAS to be VERY small, AND it has to be symetrical - not one big one one small one"). The page's
   scrollbar (reader/chrome.css) keeps 11 px at the right whether or not its thumb shows, so the right was 24 + 11 and the
   left 24. Now every row starts 11 px from the left and runs to the scrollbar: 11 px each side. The letters stand over
   the covers at the right, no bubble, as on the phone. */
html:not([data-phone]) { scrollbar-gutter: stable; }
html:not([data-phone]) #shelf .shelfgrid,
html:not([data-phone]) #shelf .libhead,
html:not([data-phone]) #shelf .libstrip,
html:not([data-phone]) #shelf.rail .sortbar,
html:not([data-phone]) #shelf.rail :is(.shelfband .bandhead, .sortbar),
html:not([data-phone]) #shelf.rail[data-zoom="2"],
html:not([data-phone]) #shelf .spage { padding-left: 11px !important; padding-right: 0 !important; }
html:not([data-phone]) #shelf .libstrip { scroll-padding: 0 0 0 11px; }

/* ONE SCROLL BAR, AND IT IS THE ALPHABET; THE SAME EDGE BOTH SIDES (Osca 14 Oct 00:17, on the Mac Library: "you added a
   SECOND scroll bar, no we don't need it, we have the alphabet already, which is our scroll bar -- then, yes, the margin
   is unequal"). The page's own scrollbar (reader/chrome.css's 11 px, kept by `scrollbar-gutter: stable` above) is not
   drawn and keeps no room on the Library, so the rows run 11 px from the left and stop 11 px from the right. The A-Z
   index (#azidx) is how the page is scrolled; the wheel and the trackpad still scroll it. */
html:not([data-phone]), html:not([data-phone]) body, html:not([data-phone]) #shelf {
  scrollbar-gutter: auto !important; scrollbar-width: none; }
html:not([data-phone])::-webkit-scrollbar,
html:not([data-phone]) body::-webkit-scrollbar,
html:not([data-phone]) #shelf::-webkit-scrollbar { display: none; width: 0; height: 0; }
html:not([data-phone]) #shelf .shelfgrid,
html:not([data-phone]) #shelf .libhead,
html:not([data-phone]) #shelf .libstrip,
html:not([data-phone]) #shelf.rail .sortbar,
html:not([data-phone]) #shelf.rail :is(.shelfband .bandhead, .sortbar),
html:not([data-phone]) #shelf.rail[data-zoom="2"],
html:not([data-phone]) #shelf .spage { padding-right: 11px !important; }
html:not([data-phone]) #shelf .libstrip { scroll-padding: 0 11px; }
/* ...and the grid stands in the middle of the row: books of one width left a strip at the right (the tracks are a fixed
   width and the leftover all went to one side). Centred, the leftover is shared, so both edges are the same. (A first
   try let the tracks share the row's width -- minmax(bookw, 1fr) -- and drew one cover across the window: reverted.) */
html:not([data-phone]) #shelf.rail[data-zoom]:not([data-zoom="1"]):not([data-zoom="1t"]) .shelfgrid {
  justify-content: center !important; }

/* ======================================================================================================================
   J2 · THE TOP ROW FROM THE MASTER (PROMPTS/PORT.md J2, design/frank-all-ideas.html #m/ALL/library, 16 Oct).
   Left: home, then the pen and the wand as ONE tinted-glass pair, a little apart from home; a chosen one keeps its
   colour (the pen red, the wand blue). Right: communities · messages · bell (phones) and ···. The controls float on
   their own -- no strip behind them -- and stay on scroll. The + and the sort circle are inside ··· now.
   ==================================================================================================================== */
html .phhead { justify-content: space-between !important; background: none !important; }
html .phhead #phadd, html .phhead #phsort { display: none !important; }
.j2-l { display: flex; align-items: center; gap: 10px; pointer-events: auto; flex: none; }
.j2-pair { display: flex; align-items: center; gap: 4px; padding: 3px; border-radius: 999px;
  border: 1px solid var(--zhair, color-mix(in srgb, var(--fg) 12%, transparent));
  background: linear-gradient(135deg, rgba(200, 16, 46, .12), rgba(43, 63, 140, .12)), var(--zglass, color-mix(in srgb, var(--bg) 62%, transparent));
  -webkit-backdrop-filter: saturate(1.8) blur(18px); backdrop-filter: saturate(1.8) blur(18px); box-shadow: 0 2px 10px rgba(0, 0, 0, .08); }
.j2-pair .phstuff { width: 30px !important; height: 30px !important; border: 0 !important; box-shadow: none !important;
  background: color-mix(in srgb, var(--bg) 70%, transparent) !important; color: #a3273a !important; pointer-events: auto;
  border-radius: 50%; display: grid; place-items: center; padding: 0; cursor: pointer; -webkit-appearance: none; appearance: none; }
.j2-pair .phstuff.phanki { color: #2b3f8c !important; }
.j2-pair .phstuff[aria-pressed="true"] { background: rgba(200, 16, 46, .16) !important; box-shadow: inset 0 0 0 1.5px #a3273a !important; }
.j2-pair .phstuff.phanki[aria-pressed="true"] { background: rgba(43, 63, 140, .16) !important; box-shadow: inset 0 0 0 1.5px #2b3f8c !important; }
html[data-theme="dark"] .j2-pair .phstuff { color: #e0526a !important; }
html[data-theme="dark"] .j2-pair .phstuff.phanki { color: #8fa3e8 !important; }
.j2-pair .phstuff svg { width: 15px; height: 15px; }
.j2-home, .j2-ic { flex: none; width: 36px; height: 36px; padding: 0; border-radius: 50%; display: grid; place-items: center; cursor: pointer;
  color: var(--fg); border: 1px solid var(--zhair, color-mix(in srgb, var(--fg) 12%, transparent));
  background: var(--zglass, color-mix(in srgb, var(--bg) 62%, transparent));
  -webkit-backdrop-filter: saturate(1.8) blur(18px); backdrop-filter: saturate(1.8) blur(18px); box-shadow: 0 2px 10px rgba(0, 0, 0, .08);
  -webkit-appearance: none; appearance: none; -webkit-tap-highlight-color: transparent; pointer-events: auto; }
.j2-home svg, .j2-ic svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.j2-more svg { fill: currentColor; stroke: none; }
.j2-home[aria-pressed="true"] { color: var(--bg); background: var(--fg); border-color: var(--fg); }
.j2-ic[aria-expanded="true"] { color: var(--chrome-accent); border-color: color-mix(in srgb, var(--chrome-accent) 40%, transparent); }
html[data-phone] .j2-home, html[data-phone] .j2-ic { width: 40px; height: 40px; }
html[data-phone] .j2-pair .phstuff { width: 34px !important; height: 34px !important; }
html[data-phone] .phhead-r { gap: 8px; }
/* the people glyphs: on every host here -- the Mac app's Library has no banner of its own over the shelf (the tab strip
   is the window's), so the row carries them; where a banner shows them already (the web) the banner's are hidden */
html:not([data-phone]) .phhead { padding-left: 24px; }
/* the panel a people glyph opens, under its glyph; closed from the same glyph */
.j2pop { position: fixed; z-index: 60; width: min(320px, calc(100vw - 32px)); box-sizing: border-box; padding: 16px 18px;
  border-radius: 16px; border: 1px solid var(--zhair, color-mix(in srgb, var(--fg) 12%, transparent));
  background: color-mix(in srgb, var(--bg) 92%, transparent); -webkit-backdrop-filter: blur(24px); backdrop-filter: blur(24px);
  box-shadow: 0 12px 40px rgba(0, 0, 0, .16); color: var(--fg); font: 400 14px / 1.45 var(--ui-font); }
.j2pop h3 { margin: 0 0 6px; font: 600 15px / 1.3 var(--ui-font); }
.j2pop p { margin: 0; color: var(--fg-dim, color-mix(in srgb, var(--fg) 60%, transparent)); }
/* J2 · Continue: cards in the cover's colour (the pairing's --paper / --ink), the cover small at the left */
.j2-conts { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; }
html[data-phone] .j2-conts { grid-template-columns: 1fr; gap: 10px; }
.j2-cont { all: unset; box-sizing: border-box; cursor: pointer; display: flex; gap: 14px; align-items: center; padding: 12px;
  border-radius: 14px; background: var(--paper, color-mix(in srgb, var(--fg) 6%, var(--bg))); color: var(--ink, var(--fg));
  box-shadow: 0 1px 0 color-mix(in srgb, var(--fg) 8%, transparent); }
.j2-cont:focus-visible { outline: 2px solid var(--chrome-accent); outline-offset: 2px; }
.j2-cc { flex: none; width: 52px; height: 76px; border-radius: 3px; overflow: hidden; position: relative; box-shadow: 0 2px 8px rgba(0,0,0,.18); }
.j2-cc .tilecov { position: absolute; inset: 0; font-size: 5px; }
.j2-ct { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 3px; }
.j2-ct b { font: 500 16px / 1.25 var(--serif, Georgia, serif); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.j2-ct i { font: 400 12px / 1.3 var(--ui-font); font-style: normal; opacity: .75; }
.j2-cw { font: 500 12px / 1.3 var(--ui-font); opacity: .85; margin-top: 4px; }
.j2-cp { display: block; height: 3px; border-radius: 2px; background: color-mix(in srgb, currentColor 20%, transparent); margin-top: 6px; overflow: hidden; }
.j2-cp span { display: block; height: 100%; background: currentColor; border-radius: 2px; }
/* the first heading starts under the floating row */
html[data-phone] #shelf.rail { padding-top: calc(var(--safe-top, 0px) + 62px) !important; }
html:not([data-phone]) #shelf.rail { padding-top: 64px !important; }
/* J2 · the spine view: straight on, book to book, margin to margin. The grids stay, hidden. */
html[data-libview="spines"] #shelf .lib-new .libstrip,
html[data-libview="spines"] #shelf .shelfgrid { display: none !important; }
html[data-libview="spines"] #azidx { display: none !important; }
.j2spines { padding: 6px 0 22px; }
html[data-phone] .j2spines { padding-left: 16px; padding-right: 16px; }
.j2shelf { display: flex; flex-wrap: wrap; align-items: flex-end; row-gap: 26px; column-gap: 2px; position: relative;
  background: linear-gradient(to top, color-mix(in srgb, var(--fg) 14%, transparent) 0 2px, transparent 2px) bottom / 100% calc(var(--row, 0px) + 0px) repeat-y; }
.j2sp { all: unset; box-sizing: border-box; position: relative; flex: none; width: var(--w); height: var(--h); cursor: pointer;
  transition: transform .22s cubic-bezier(.2,.7,.2,1); border-bottom: 2px solid color-mix(in srgb, var(--fg) 18%, transparent); }
.j2sp-face { position: absolute; inset: 0; overflow: hidden; border-radius: 2px 2px 1px 1px; background: var(--paper, #ddd); color: var(--ink, #222);
  box-shadow: inset -2px 0 3px rgba(0,0,0,.14), inset 2px 0 2px rgba(255,255,255,.18); }
.j2sp-tint { display: none; }
.j2sp-face.j2-onink { color: #fff; }
.j2sp-t { position: absolute; left: 50%; top: 8%; height: 76%; transform: translateX(-50%); writing-mode: vertical-rl; display: flex; gap: .8em;
  align-items: center; justify-content: flex-start; overflow: hidden; white-space: nowrap; font: 500 clamp(9px, calc(var(--w) * .38), 15px) / 1 var(--serif, Georgia, serif); }
.j2sp-t b { font-weight: 500; overflow: hidden; text-overflow: ellipsis; }
.j2sp-t i { font-style: normal; font-size: .78em; letter-spacing: .08em; text-transform: uppercase; opacity: .8; }
.j2sp-m { position: absolute; left: 0; right: 0; bottom: 5%; text-align: center; font: 700 italic 10px/1 var(--serif, Georgia, serif); opacity: .7; }
.j2sp-cover { position: absolute; bottom: 0; left: 50%; width: calc(var(--h) * .66); height: var(--h); z-index: 3; pointer-events: none;
  transform: translateX(calc(-50% + var(--dx, 0px))) scale(.6); opacity: 0; transition: opacity .2s, transform .24s cubic-bezier(.2,.7,.2,1);
  box-shadow: 0 14px 30px rgba(0,0,0,.28); border-radius: 2px; overflow: hidden; }
.j2sp-cover .tilecov { position: absolute; inset: 0; }
.j2sp.j2-up { transform: translateY(-10px); z-index: 4; }
.j2sp.j2-up .j2sp-cover { opacity: 1; transform: translateX(calc(-50% + var(--dx, 0px))) translateY(-6px) scale(1); }
.j2sp.j2-nl { transform: translateX(-6px); }
.j2sp.j2-nr { transform: translateX(6px); }
@media (hover: none) { .j2sp.j2-up .j2sp-cover { opacity: 0; } }

/* ======================================================================================================================
   J5 · THE BOOK CARD (the master's .fm-bookcard): a veil and a card. Phone: a sheet edge to edge, from near the top to
   the bottom, two panes side by side that swipe. Mac and web: a card in the middle; the pane glyph opens a second column.
   ==================================================================================================================== */
#j5 { position: fixed; inset: 0; z-index: 80; }
#j5[hidden] { display: none; }
.j5-veil { position: absolute; inset: 0; background: rgba(0, 0, 0, .32); opacity: 0; transition: opacity .24s; }
#j5.j5-in .j5-veil { opacity: 1; }
.j5-card { position: absolute; left: 50%; top: 5vh; bottom: 5vh; width: min(720px, calc(100vw - 48px)); transform: translate(-50%, 24px); opacity: 0;
  transition: transform .26s cubic-bezier(.2,.8,.2,1), opacity .2s, width .26s cubic-bezier(.2,.8,.2,1);
  background: var(--bg); color: var(--fg); border-radius: 18px; overflow: hidden; box-shadow: 0 24px 80px rgba(0, 0, 0, .3); display: flex; flex-direction: column; }
#j5.j5-in .j5-card { transform: translate(-50%, 0); opacity: 1; }
.j5-card.j5-two { width: min(1180px, calc(100vw - 48px)); }
.j5-pull { position: absolute; top: 0; left: 0; right: 0; height: 22px; z-index: 3; display: grid; place-items: center; cursor: pointer; }
.j5-pull i { width: 40px; height: 5px; border-radius: 3px; background: color-mix(in srgb, var(--fg) 28%, transparent); }
.j5-paneg { position: absolute; top: 14px; right: 14px; z-index: 4; width: 38px; height: 38px; border-radius: 50%; display: grid; place-items: center; padding: 0; cursor: pointer;
  color: var(--fg); border: 1px solid var(--zhair, color-mix(in srgb, var(--fg) 12%, transparent)); background: var(--zglass, color-mix(in srgb, var(--bg) 62%, transparent));
  -webkit-backdrop-filter: blur(18px); backdrop-filter: blur(18px); -webkit-appearance: none; appearance: none; }
.j5-paneg svg { width: 19px; height: 19px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.j5-paneg[aria-pressed="true"] { background: var(--fg); color: var(--bg); border-color: var(--fg); }
.j5-panes { flex: 1; min-height: 0; display: flex; }
.j5-scroll { flex: 1 0 100%; min-width: 0; overflow-y: auto; overscroll-behavior: contain; }
.j5-right { display: none; border-left: 1px solid color-mix(in srgb, var(--fg) 10%, transparent); }
.j5-card.j5-two .j5-scroll { flex: 1 1 50%; }
.j5-card.j5-two .j5-right { display: block; }
.j5-head { padding: 40px 28px 26px; text-align: center; background: var(--paper, color-mix(in srgb, var(--fg) 6%, var(--bg))); color: var(--ink, var(--fg)); }
.j5-cov { width: 150px; height: 220px; margin: 0 auto 18px; position: relative; border-radius: 3px; overflow: hidden; box-shadow: 0 12px 34px rgba(0,0,0,.3); }
.j5-cov .tilecov { position: absolute; inset: 0; font-size: 13px; }
.j5-head h2 { margin: 0; font: 500 26px / 1.2 var(--serif, Georgia, serif); }
.j5-au { margin-top: 6px; font: 400 15px / 1.3 var(--ui-font); opacity: .8; }
.j5-meta { margin-top: 8px; font: 600 11px / 1.3 var(--ui-font); letter-spacing: .12em; text-transform: uppercase; opacity: .7; }
.j5-rest { padding: 18px 24px 40px; }
.j5-panel { border-radius: 14px; padding: 14px 16px; margin-bottom: 12px; background: color-mix(in srgb, var(--fg) 5%, var(--bg)); }
.j5-k { font: 600 11px / 1.3 var(--ui-font); letter-spacing: .12em; text-transform: uppercase; opacity: .6; margin-bottom: 6px; }
.j5-m { margin: 6px 0 0; font: 400 13px / 1.45 var(--ui-font); color: var(--fg-dim, color-mix(in srgb, var(--fg) 65%, transparent)); }
.j5-btns { display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap; }
.j5-b { all: unset; cursor: pointer; padding: 9px 16px; border-radius: 999px; font: 600 14px / 1 var(--ui-font); border: 1px solid color-mix(in srgb, var(--fg) 18%, transparent); }
.j5-b.j5-solid { background: var(--fg); color: var(--bg); border-color: var(--fg); }
.j5-play { all: unset; cursor: pointer; display: flex; align-items: center; gap: 12px; }
.j5-play b { display: block; font: 600 16px / 1.25 var(--ui-font); }
.j5-play i { display: block; font: 400 13px / 1.3 var(--ui-font); font-style: normal; opacity: .7; }
.j5-pc { flex: none; width: 40px; height: 40px; border-radius: 50%; display: grid; place-items: center; background: var(--fg); color: var(--bg); }
.j5-pc svg { width: 15px; height: 15px; margin-left: 2px; }
.j5-dots { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 12px; }
.j5-dots i { width: 9px; height: 9px; border-radius: 50%; background: color-mix(in srgb, var(--fg) 18%, transparent); }
.j5-dots i.r { background: color-mix(in srgb, var(--fg) 55%, transparent); }
.j5-dots i.here { background: var(--chrome-accent, #c02c46); }
.j5-sh { display: flex; justify-content: space-between; align-items: baseline; margin: 22px 0 10px; font: 600 11px / 1.3 var(--ui-font); letter-spacing: .12em; text-transform: uppercase; opacity: .7; }
.j5-sh i { font-style: normal; letter-spacing: 0; text-transform: none; font-weight: 400; }
.j5-about > div { display: flex; justify-content: space-between; gap: 16px; padding: 8px 0; border-bottom: 1px solid color-mix(in srgb, var(--fg) 8%, transparent); font: 400 14px / 1.4 var(--ui-font); }
.j5-about span { opacity: .6; }
.j5-about b { font-weight: 500; text-align: right; }
.j5-blurb { font: 400 16px / 1.55 var(--serif, Georgia, serif); margin: 16px 0 0; }
.j5-ch { all: unset; box-sizing: border-box; cursor: pointer; display: flex; align-items: center; gap: 12px; width: 100%; padding: 8px 0; border-bottom: 1px solid color-mix(in srgb, var(--fg) 7%, transparent); }
.j5-ch .j5-pc { width: 28px; height: 28px; background: color-mix(in srgb, var(--fg) 8%, var(--bg)); color: var(--fg); }
.j5-ch .j5-pc svg { width: 11px; height: 11px; }
.j5-ch.here .j5-cn { color: var(--chrome-accent, #c02c46); }
.j5-cn { font: 400 15px / 1.35 var(--serif, Georgia, serif); }
.j5-surf { padding: 44px 24px 40px; }
.j5-surf h2 { margin: 0 0 12px; font: 500 22px / 1.25 var(--serif, Georgia, serif); }
.j5-note { padding: 10px 0; border-bottom: 1px solid color-mix(in srgb, var(--fg) 8%, transparent); }
.j5-note b { display: block; font: 600 11px / 1.3 var(--ui-font); letter-spacing: .08em; text-transform: uppercase; opacity: .6; }
.j5-note q { display: block; margin-top: 4px; font: 400 15px / 1.45 var(--serif, Georgia, serif); }
.j5-note p { margin: 4px 0 0; font: 400 14px / 1.45 var(--ui-font); }
/* Mac and web, wide: the card's head beside its rest */
@media (min-width: 900px) {
  html:not([data-phone]) .j5-card:not(.j5-two) .j5-cols { display: grid; grid-template-columns: 280px 1fr; min-height: 100%; }
  html:not([data-phone]) .j5-card:not(.j5-two) .j5-head { padding-top: 56px; }
}
/* the phone: a sheet from near the top to the bottom, edge to edge; the panes swipe */
html[data-phone] .j5-card { left: 0; right: 0; width: auto; top: calc(var(--safe-top, 0px) + 12px); bottom: 0; border-radius: 16px 16px 0 0; transform: translateY(100%); opacity: 1; }
html[data-phone] #j5.j5-in .j5-card { transform: translateY(0); }
html[data-phone] .j5-panes { overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; }
html[data-phone] .j5-panes::-webkit-scrollbar { display: none; }
html[data-phone] .j5-scroll { scroll-snap-align: start; scroll-snap-stop: always; }
html[data-phone] .j5-right { display: block; border-left: 0; }
html[data-phone] .j5-rest { padding: 16px 16px calc(var(--safe-bottom, 0px) + 40px); }
html[data-phone] .j5-head { padding-top: 34px; }
html[data-phone] .j5-cov { width: 140px; height: 205px; }
html[data-phone] .lib-cont .j2-conts { padding: 0 16px; }
html[data-web]:not([data-phone]) .j2-people { display: none; }

/* ======================================================================================================================
   J2 v2 · THE LIBRARY AS THE MASTER DRAWS IT (PROMPTS/PORT-GAPS.md L1-L10). Supersedes the first pass's continue cards
   and section heads. One margin for every section (24 phone, 60 Mac); heads in sentence case; Continue as deep cards in
   a sideways row; Today's goal; Recent and All books as book objects on one baseline with their titles; Your voices.
   ==================================================================================================================== */
:root { --j2-gx: 60px; --j2-ink2: color-mix(in srgb, var(--fg) 58%, var(--bg)); --j2-bg2: color-mix(in srgb, var(--fg) 5%, var(--bg)); --j2-line: color-mix(in srgb, var(--fg) 12%, var(--bg)); }
html[data-phone] { --j2-gx: 24px; }
/* heads */
#shelf#shelf .libhead { display: flex; justify-content: space-between; align-items: baseline; padding: 22px var(--j2-gx) 10px !important;
  font: 600 19px / 1.2 var(--ui-font) !important; letter-spacing: -.01em !important; text-transform: none !important; color: var(--fg) !important; opacity: 1 !important; }
#shelf#shelf .libhead .j2-aside { font: 400 13px / 1.2 var(--ui-font); color: var(--j2-ink2); letter-spacing: 0; }
html[data-phone] #shelf#shelf .libsec:first-of-type .libhead { padding-top: 6px !important; }
/* Continue */
#shelf#shelf .lib-cont .j2-conts { display: flex !important; gap: 12px; overflow-x: auto; scrollbar-width: none; padding: 0 var(--j2-gx) 2px !important; grid-template-columns: none; }
#shelf#shelf .lib-cont .j2-conts::-webkit-scrollbar { display: none; }
#shelf#shelf .j2-cont { flex: none; min-width: 250px; max-width: 320px; padding: 10px 16px 10px 10px; border-radius: 16px; gap: 12px; color: #fff; background: #3a3a3a; box-shadow: none; }
#shelf#shelf .j2-cont.j2-paper { background: var(--paper); color: var(--ink); }
#shelf#shelf .j2-cc { width: 40px; height: 56px; border-radius: 2px; box-shadow: 2px 2px 0 #f6f2e8, 3px 3px 0 rgba(0,0,0,.2), 4px 6px 10px rgba(0,0,0,.25); }
#shelf#shelf .j2-ct { gap: 2px; }
#shelf#shelf .j2-ct b { font: 600 15px / 1.25 var(--ui-font); }
#shelf#shelf .j2-cw { font: 400 12px / 1.3 var(--ui-font); opacity: .85; margin: 0; }
/* Today's goal */
#shelf#shelf .lib-goal { padding: 12px var(--j2-gx) 0; }
#shelf#shelf .j2-goal { display: flex; align-items: center; gap: 14px; background: var(--j2-bg2); border-radius: 16px; padding: 12px 16px; color: var(--fg); }
#shelf#shelf .j2-gt { font: 400 24px / 1 var(--serif, Georgia, serif); margin-bottom: 3px; }
#shelf#shelf .j2-ca { font: 400 12px / 1.35 var(--ui-font); color: var(--j2-ink2); }
/* Recent: book objects in a row, titles under */
#shelf#shelf .lib-new .libstrip { display: flex !important; gap: 22px; align-items: flex-start; overflow-x: auto; scrollbar-width: none; padding: 14px var(--j2-gx) 8px !important; }
#shelf#shelf .lib-new .nw-item { flex: none; width: 132px; display: flex; flex-direction: column; align-items: center; background: none !important; border: 0; padding: 0; cursor: pointer; }
html[data-phone] #shelf#shelf .lib-new .nw-item { width: 108px; }
#shelf#shelf .lib-new .nw-item[data-obj] .tilecov { position: relative; display: block; width: calc(150px * var(--ar, .66) * var(--k, 1)); height: calc(150px * var(--k, 1)); margin-top: calc(150px * (1 - var(--k, 1)));
  border-radius: 1px 3px 3px 1px; overflow: hidden; font-size: 7px; }
html[data-phone] #shelf#shelf .lib-new .nw-item[data-obj] .tilecov { width: calc(118px * var(--ar, .66) * var(--k, 1)); height: calc(118px * var(--k, 1)); margin-top: calc(118px * (1 - var(--k, 1))); font-size: 6px; }
#shelf#shelf .j2-t { display: block; margin-top: 10px; font: 400 14px / 1.2 var(--serif, Georgia, serif); color: var(--fg); text-align: center; max-width: 100%; }
#shelf#shelf .j2-a { display: block; margin-top: 3px; font: 400 11.5px / 1.2 var(--ui-font); color: var(--j2-ink2); text-align: center; }
#shelf#shelf .lib-new .nw-tag { display: none; }
/* the hinge crease and a soft sheen on every object's face, as book-engine.css draws it */
#shelf#shelf [data-obj] .tilecov::after { content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 3;
  background: linear-gradient(90deg, rgba(0,0,0,.16) 0, rgba(255,255,255,.14) 2.5%, rgba(0,0,0,.06) 4%, transparent 8%, transparent 90%, rgba(0,0,0,.05)); }
/* All books at the Library's own zoom: objects on one baseline, titles under; 3 across on the phone, 6 on the Mac */
#shelf#shelf[data-zoom="4"] .shelfgrid { display: grid !important; grid-template-columns: repeat(6, minmax(0, 1fr)) !important; gap: 26px 18px !important;
  padding: 10px var(--j2-gx) 20px !important; width: auto !important; max-width: none !important; margin: 0 !important; align-items: start !important; }
html[data-phone] #shelf#shelf[data-zoom="4"] .shelfgrid { grid-template-columns: repeat(3, minmax(0, 1fr)) !important; gap: 22px 16px !important; }
@media (max-width: 1100px) { html:not([data-phone]) #shelf#shelf[data-zoom="4"] .shelfgrid { grid-template-columns: repeat(4, minmax(0, 1fr)) !important; } }
#shelf#shelf[data-zoom="4"] .railrow.tile[data-obj] { display: flex !important; flex-direction: column; align-items: center; background: none !important; border: 0 !important;
  padding: 0 !important; aspect-ratio: auto !important; height: auto !important; width: auto !important; box-shadow: none !important; overflow: visible !important; }
#shelf#shelf[data-zoom="4"] .railrow.tile[data-obj] .tilecov { position: relative !important; inset: auto !important; display: block; flex: none;
  width: calc(140px * var(--ar, .66) * var(--k, 1)) !important; height: calc(140px * var(--k, 1)) !important; margin-top: calc(140px * (1 - var(--k, 1))) !important;
  border-radius: 1px 3px 3px 1px !important; overflow: hidden; font-size: 7px; transform: none !important; }
html[data-phone] #shelf#shelf[data-zoom="4"] .railrow.tile[data-obj] .tilecov { width: calc(104px * var(--ar, .66) * var(--k, 1)) !important; height: calc(104px * var(--k, 1)) !important; margin-top: calc(104px * (1 - var(--k, 1))) !important; font-size: 5.5px; }
#shelf#shelf[data-zoom="4"] .railrow.tile[data-obj] .railrow-title { display: block !important; position: static !important; margin-top: 10px; font: 400 14px / 1.2 var(--serif, Georgia, serif) !important;
  color: var(--fg) !important; text-align: center; white-space: normal !important; opacity: 1 !important; max-width: 100%; }
#shelf#shelf[data-zoom="4"] .railrow.tile[data-obj] .railrow-by { display: block !important; position: static !important; margin-top: 3px; font: 400 11.5px / 1.2 var(--ui-font) !important;
  color: var(--j2-ink2) !important; text-align: center; opacity: 1 !important; text-transform: none !important; letter-spacing: 0 !important; }
html[data-phone] #shelf#shelf[data-zoom="4"] .railrow.tile[data-obj] .railrow-title { font-size: 13.5px !important; }
#shelf#shelf [data-obj] .tilecov .covimg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
#shelf#shelf [data-obj]:hover .tilecov { transform: translateY(-3px) !important; transition: transform .25s; }
/* Your voices: dark cards, play, name, waveform moving */
#shelf#shelf .lib-voices .j2-vrow { display: flex; gap: 10px; overflow-x: auto; scrollbar-width: none; padding: 0 var(--j2-gx) 4px; }
#shelf#shelf .j2-wave { all: unset; box-sizing: border-box; cursor: pointer; flex: none; display: flex; align-items: center; gap: 10px; border-radius: 16px; padding: 10px 14px 10px 10px;
  background: #1d1a15; color: #f3efe2; min-width: 240px; }
html:not([data-theme="dark"]) #shelf#shelf .j2-wave { background: #fff; color: #17150f; box-shadow: 0 0 0 1px var(--j2-line); }
#shelf#shelf .j2-pb { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: #2b3f8c; color: #fff; flex: none; }
#shelf#shelf .j2-pb svg { width: 13px; height: 13px; fill: currentColor; margin-left: 2px; }
#shelf#shelf .j2-wt b { display: block; font: 600 14px / 1.2 var(--ui-font); }
#shelf#shelf .j2-wt i { display: block; font: 400 11.5px / 1.2 var(--ui-font); font-style: normal; opacity: .65; }
#shelf#shelf .j2-wv { display: flex; align-items: center; gap: 2px; height: 30px; margin-left: 8px; }
#shelf#shelf .j2-wv i { width: 2.5px; border-radius: 2px; background: currentColor; opacity: .5; animation: j2wv 1.3s ease-in-out infinite alternate; transform-origin: center; }
#shelf#shelf .j2-wave.on .j2-wv i { opacity: .9; animation-duration: .5s; }
@keyframes j2wv { 0% { transform: scaleY(.45); } 50% { transform: scaleY(1); } 100% { transform: scaleY(.65); } }
@media (prefers-reduced-motion: reduce) { #shelf#shelf .j2-wv i { animation: none; } }
/* the top row: the master's solid glyphs and fills */
.j2-pair .phstuff svg, .j2-pair .phanki svg { width: 17px !important; height: 17px !important; }
.j2-pair .phstuff svg path { fill: currentColor; fill-opacity: .9; }
.j2-ic { background: var(--j2-bg2) !important; border: 0 !important; box-shadow: none !important; width: 34px !important; height: 34px !important; }
html[data-phone] .j2-ic { width: 36px !important; height: 36px !important; }
.j2-home { background: var(--fg) !important; color: var(--bg) !important; border-color: var(--fg) !important; }
.j2-home[aria-pressed="false"] { background: var(--zglass, var(--bg)) !important; color: var(--fg) !important; }
/* the A-Z: small, light, evenly spaced */
#azidx { font: 600 9px / 1 var(--ui-font) !important; color: var(--j2-ink2) !important; }
#azidx * { font-weight: 600 !important; color: inherit; }

/* the spines as the master: straight on, book to book, a page edge at the top, the cover's colour */
.j2sp-face { box-shadow: inset -3px 0 4px rgba(0,0,0,.18), inset 3px 0 3px rgba(255,255,255,.14), inset 0 3px 0 rgba(255,255,255,.22); }
.j2sp { border-bottom: 0; }
.j2shelf { border-bottom: 2px solid color-mix(in srgb, var(--fg) 14%, transparent); padding-bottom: 0; }
html[data-libview="spines"] #shelf#shelf .lib-new .libstrip, html[data-libview="spines"] #shelf#shelf .shelfgrid { display: none !important; }
#shelf#shelf .j2spines { padding: 14px var(--j2-gx) 22px; }
#shelf#shelf .j2spines { max-width: none !important; width: auto !important; margin: 0 !important; box-sizing: border-box; } #shelf#shelf .j2shelf { justify-content: flex-start !important; margin: 0 !important; }

/* =====================================================================================================================
   J5 v2 · THE BOOK CARD AS THE MASTER DRAWS IT (PORT-GAPS B1-B5): the cover's deep colour over the top and the panels;
   "Book" and "Listen" in the serif on glass; the pills; the dashes; the right pane's head, chips and cards.
   ==================================================================================================================== */
.j5-tint { background: #5a4a3a; color: #fff; }
.j5-tint .j5-head { background: transparent !important; color: #fff !important; padding-bottom: 18px; }
.j5-tint .j5-au, .j5-tint .j5-meta { opacity: .85; }
.j5-tint .j5-meta { text-transform: none; letter-spacing: 0; font: 400 14px / 1.3 var(--ui-font); }
.j5-panels { padding: 0 20px 22px; }
.j5-tint .j5-panel { background: rgba(255, 255, 255, .13); color: #fff; border-radius: 18px; padding: 14px 14px 16px; }
.j5-pk { font: 400 21px / 1.2 var(--serif, Georgia, serif); }
.j5-tint .j5-m { color: rgba(255, 255, 255, .78); margin-top: 4px; }
.j5-tint .j5-btns { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.j5-tint .j5-b { text-align: center; padding: 12px 10px; border: 0; font: 600 15px / 1 var(--ui-font); border-radius: 999px; }
.j5-tint .j5-glass { background: rgba(255, 255, 255, .2); color: #fff; }
.j5-tint .j5-solid { background: #fff; color: #17150f; }
.j5-tint .j5-play { display: flex; width: 100%; box-sizing: border-box; margin-top: 10px; padding: 12px 14px; border-radius: 16px; background: #fff; color: #17150f; }
.j5-tint .j5-play .j5-pc { background: transparent; color: #17150f; box-shadow: inset 0 0 0 2px #17150f; width: 38px; height: 38px; }
.j5-tint .j5-play i { opacity: .65; }
.j5-dash { display: flex; gap: 4px; margin-top: 14px; }
.j5-dash i { flex: 1; height: 5px; border-radius: 3px; background: rgba(255, 255, 255, .35); }
.j5-dash i.r { background: rgba(255, 255, 255, .95); }
.j5-dash i.here { background: transparent; box-shadow: 0 0 0 2px #fff; height: 9px; margin-top: -2px; flex: 1.6; border-radius: 5px; }
.j5-pull { background: var(--j5c, transparent); }
.j5-pull i { background: rgba(255, 255, 255, .6); }
.j5-card .j5-paneg { background: rgba(255, 255, 255, .22); color: #fff; border-color: transparent; }
.j5-card .j5-paneg[aria-pressed="true"] { background: #fff; color: #17150f; }
.j5-rest .j5-sh { font: 600 18px / 1.2 var(--ui-font); letter-spacing: -.01em; text-transform: none; opacity: 1; }
/* Mac: the tinted column is the whole left; the panels under the head */
@media (min-width: 900px) {
  html:not([data-phone]) .j5-card:not(.j5-two) .j5-cols { display: block; }
  html:not([data-phone]) .j5-card .j5-head { padding-top: 46px; }
  html:not([data-phone]) .j5-card .j5-cov { width: 160px; height: 236px; }
  html:not([data-phone]) .j5-panels { padding: 0 32px 28px; }
}
/* the right pane */
.j5-sh0 { display: flex; gap: 14px; align-items: flex-start; margin-bottom: 14px; }
.j5-sc { flex: none; width: 34px; height: 50px; position: relative; overflow: hidden; border-radius: 2px; box-shadow: 2px 2px 0 #f2ede2, 3px 4px 8px rgba(0,0,0,.2); }
.j5-sc .tilecov { position: absolute; inset: 0; font-size: 4px; }
.j5-surf h2 { margin: 2px 0 2px; font-size: 28px; }
.j5-chips { display: flex; gap: 8px; flex-wrap: wrap; margin: 4px 0 14px; }
.j5-chips span { padding: 7px 13px; border-radius: 999px; background: color-mix(in srgb, var(--fg) 7%, var(--bg)); font: 500 13.5px / 1 var(--ui-font); }
.j5-chips span.on { background: var(--fg); color: var(--bg); }
.j5-comp { margin-top: 16px; padding: 14px 16px; border-radius: 16px; background: color-mix(in srgb, var(--fg) 5%, var(--bg)); }
.j5-comp p { margin: 6px 0 0; font: 400 15px / 1.45 var(--ui-font); color: color-mix(in srgb, var(--fg) 70%, var(--bg)); }
.j5-notes { display: flex; gap: 10px; overflow-x: auto; scrollbar-width: none; }
.j5-notes .j5-note { flex: none; width: 250px; border: 0; border-radius: 16px; padding: 14px 16px; background: color-mix(in srgb, var(--fg) 5%, var(--bg)); }
.j5-notes .j5-note q { font-style: italic; }
.j5-surf .j5-sh { font: 600 18px / 1.2 var(--ui-font); letter-spacing: -.01em; text-transform: none; opacity: 1; margin-top: 24px; } .j5-surf .j5-sh i { font-size: 13px; opacity: .6; }
/* L7 · Shelves as the master: tinted cards, the covers fanned on a baseline, the name, the count */
#shelf#shelf .lib-albums .libstrip { display: grid !important; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; padding: 0 var(--j2-gx) !important; overflow: visible; }
html[data-phone] #shelf#shelf .lib-albums .libstrip { grid-template-columns: repeat(3, minmax(0, 1fr)); }
#shelf#shelf .album { all: unset; box-sizing: border-box; cursor: pointer; display: flex; flex-direction: column; border-radius: 16px; padding: 12px; min-width: 0;
  background: color-mix(in srgb, var(--fg) 6%, var(--bg)); }
#shelf#shelf .album:nth-child(4n+1) { background: #f3e2d3; } #shelf#shelf .album:nth-child(4n+2) { background: #dfe3f3; }
#shelf#shelf .album:nth-child(4n+3) { background: #d9eee8; } #shelf#shelf .album:nth-child(4n+4) { background: #efe6c9; }
html[data-theme="dark"] #shelf#shelf .album { background: color-mix(in srgb, var(--fg) 9%, var(--bg)) !important; }
#shelf#shelf .album .al-stack { position: relative; display: flex; align-items: flex-end; height: 82px; width: auto; }
#shelf#shelf .album .al-c { position: relative !important; inset: auto !important; transform: none !important; width: 52px; height: 74px; margin-right: -22px; flex: none;
  box-shadow: 2px 1px 0 #f6f2e8, 3px 2px 0 #e6dfcd, 4px 2px 0 rgba(0,0,0,.18), 6px 8px 12px rgba(0,0,0,.16); border-radius: 1px 3px 3px 1px; overflow: hidden; }
#shelf#shelf .album .al-c .tilecov { font-size: 4px; }
#shelf#shelf .album .al-name { display: block; margin-top: 10px; font: 600 13px / 1.25 var(--ui-font); color: #17150f; }
#shelf#shelf .album .al-n { display: block; font: 400 11px / 1.3 var(--ui-font); color: #6f6c64; }
#shelf#shelf .album .al-n::after { content: " books"; }
html[data-theme="dark"] #shelf#shelf .album .al-name { color: var(--fg); }
#shelf#shelf .album .al-n { letter-spacing: 0 !important; text-transform: none !important; } #shelf#shelf .album .al-stack { flex-direction: row-reverse; justify-content: flex-end; } #shelf#shelf .album .al-c { width: 50px !important; height: 72px !important; }
/* J8 · First run as the master (PORT-GAPS F1): the mark, the sentence in the book serif, three books as objects, the
   sign-in buttons, the line about free and paid */
.fr-look .fr-mark{ width: 92px; height: 92px; border-radius: 22px; box-shadow: 0 10px 30px rgba(0,0,0,.14); margin-bottom: 28px; }
html:not([data-phone]) .fr-look .fr-mark{ width: 110px; height: 110px; border-radius: 26px; }
.fr-look .fr-name{ font: 400 36px / 1.1 var(--serif, "EB Garamond", Georgia, serif) !important; letter-spacing: 0 !important; word-spacing: 0 !important; margin: 0 0 30px !important; text-transform: none !important; }
.fr-look .fr-name em{ font-weight: 600; }
.fr-books{ display: flex; align-items: flex-end; justify-content: center; gap: 16px; height: 160px; margin-bottom: 34px; }
.fr-bk{ display: block; width: calc(110px * .7 * var(--k)); height: calc(150px * var(--k)); perspective: 600px; }
.fr-face{ display: block; width: 100%; height: 100%; position: relative; overflow: hidden; border-radius: 1px 3px 3px 1px; background: #ece6d6; transform-origin: 0 50%;
  box-shadow: 1px 1px 0 #f6f2e8, 2px 1px 0 #e6dfcd, 3px 2px 0 #f6f2e8, 4px 2px 0 #e6dfcd, 5px 3px 0 rgba(0,0,0,.18), 8px 10px 16px rgba(0,0,0,.18);
  transition: transform .7s cubic-bezier(.2,.9,.2,1); }
.fr-face img{ position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.fr-bk:hover .fr-face{ transform: rotateY(-28deg); }
.fr-look .fr-acts{ max-width: 400px; margin: 0 auto; gap: 10px !important; }
.fr-look .fr-acts button{ -webkit-appearance: none; appearance: none; width: 100%; height: 36px; border-radius: 999px; font: 500 14px / 1 var(--ui-font) !important;
  text-transform: none !important; letter-spacing: 0 !important; cursor: pointer; }
.fr-look .fr-apple{ background: #17150f; color: #fff; border: 0; }
.fr-look .fr-email{ background: transparent; color: var(--fg); border: 1px solid color-mix(in srgb, var(--fg) 16%, transparent); }
.fr-look .fr-later{ background: none !important; border: 0 !important; height: auto !important; color: var(--fg) !important; opacity: .7; text-decoration: underline; font-size: 13px !important; }
.fr-look .fr-in{ background: none !important; border: 0 !important; height: auto !important; color: var(--fg) !important; opacity: .5; font-size: 12px !important; }
.fr-look .fr-free{ margin: 22px auto 0; max-width: 360px; font: 400 13px / 1.5 var(--ui-font); color: color-mix(in srgb, var(--fg) 62%, var(--bg)); }
.fr-face.fr-typo{ display: flex; flex-direction: column; justify-content: space-between; padding: 12% 10%; box-sizing: border-box; background: #c75b84; color: #fff; text-align: center; } .fr-face.fr-typo b{ font: 400 13px/1.15 var(--serif, Georgia, serif); } .fr-face.fr-typo i{ font: 400 8px/1.2 var(--ui-font); font-style: normal; letter-spacing: .1em; text-transform: uppercase; opacity: .85; }
/* J6 · the voice card (the master): the coloured strip, the serif name, the pills, the sample, what it has read */
.j5-card.j5-other .j5-paneg, .j5-card.j5-other .j5-right { display: none !important; }
.j5-card.j5-other .j5-pull { background: var(--j5c); height: 22px; }
.j6v { padding: 40px 24px 40px; max-width: 720px; }
html[data-phone] .j6v { padding: 34px 18px 40px; }
.j6-name { margin: 4px 0 6px; font: 400 36px / 1.1 var(--serif, Georgia, serif); }
.j6-meta { font: 400 14px / 1.4 var(--ui-font); color: color-mix(in srgb, var(--fg) 62%, var(--bg)); }
.j6-btns { display: flex; gap: 10px; flex-wrap: wrap; margin: 16px 0 18px; }
.j6-b { -webkit-appearance: none; appearance: none; cursor: pointer; padding: 10px 16px; border-radius: 999px; font: 500 14px / 1 var(--ui-font); background: transparent; color: var(--fg); border: 1px solid color-mix(in srgb, var(--fg) 16%, transparent); }
.j6-b.j6-solid { background: var(--fg); color: var(--bg); border-color: var(--fg); }
.j6-seg { display: inline-flex; gap: 2px; padding: 3px; border-radius: 999px; background: color-mix(in srgb, var(--fg) 6%, var(--bg)); }
.j6-seg span { padding: 7px 14px; border-radius: 999px; font: 500 13px / 1 var(--ui-font); } .j6-seg span.on { background: var(--fg); color: var(--bg); }
.j6-wait { font: 400 13.5px / 1.5 var(--ui-font); color: color-mix(in srgb, var(--fg) 58%, var(--bg)); margin: 10px 0; }
.j6-sh { display: flex; justify-content: space-between; align-items: baseline; margin: 24px 0 10px; font: 600 18px / 1.2 var(--ui-font); letter-spacing: -.01em; }
.j6-sh i { font: 400 13px / 1 var(--ui-font); font-style: normal; color: color-mix(in srgb, var(--fg) 58%, var(--bg)); }
.j6-sample { all: unset; box-sizing: border-box; cursor: pointer; display: flex; align-items: center; gap: 12px; width: 100%; padding: 12px 14px; border-radius: 16px; background: var(--bg);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--fg) 10%, transparent); }
.j6-pb, .j6-pp { flex: none; width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: #b5513f; color: #fff; }
.j6-pp { width: 26px; height: 26px; background: color-mix(in srgb, var(--fg) 8%, var(--bg)); color: var(--fg); }
.j6-pb svg, .j6-pp svg { width: 12px; height: 12px; fill: currentColor; margin-left: 2px; }
.j6-st b { display: block; font: 500 14px / 1.2 var(--ui-font); } .j6-st i { display: block; font: 400 12px / 1.2 var(--ui-font); font-style: normal; opacity: .6; }
.j6-wv { display: flex; align-items: center; gap: 2px; height: 26px; margin-left: auto; overflow: hidden; }
.j6-wv i { width: 2px; border-radius: 1px; background: currentColor; opacity: .45; flex: none; }
.j6-sample.on .j6-wv i { opacity: .9; animation: j2wv .5s ease-in-out infinite alternate; }
.j6-row { all: unset; box-sizing: border-box; cursor: pointer; display: flex; align-items: center; gap: 12px; width: 100%; padding: 10px 0; border-bottom: 1px solid color-mix(in srgb, var(--fg) 8%, transparent); }
.j6-row b { font: 600 14.5px / 1.3 var(--ui-font); flex: 1; } .j6-row i { font: 400 12.5px / 1 var(--ui-font); font-style: normal; opacity: .6; }
.j6-built { padding: 14px 16px; border-radius: 16px; border-left: 3px solid #4f8a5b; background: color-mix(in srgb, var(--fg) 4%, var(--bg)); }
.j6-built i { display: block; margin-top: 8px; font: 400 13px / 1.3 var(--ui-font); font-style: normal; opacity: .6; }
.j6-built .j6-wv { margin: 0; }
#shelf#shelf [data-obj] .tilecov { aspect-ratio: auto !important; max-width: none !important; max-height: none !important; min-width: 0 !important; flex: none !important; }
#shelf#shelf .lib-new .nw-item[data-obj] { width: auto !important; min-width: 96px; max-width: 150px; }
#shelf#shelf .lib-new .libstrip { height: auto !important; max-height: none !important; overflow-y: visible !important; }
#shelf#shelf .lib-new .nw-item { height: auto !important; max-height: none !important; overflow: visible !important; }
#shelf#shelf .j2-pb { background: var(--vc, #2b3f8c); }

/* J2 · THE PEOPLE PANELS (the master's people/*: fm-side, fm-nrow, fm-conv, fm-cmc). On the phone a page over the shelf
   under the band, sliding up; on the Mac and the web a 370px column at the right, sliding in, the shelf still showing. */
.j2pp { position: fixed; z-index: 55; top: 0; bottom: 0; right: 0; width: 370px; box-sizing: border-box; overflow-y: auto; scrollbar-width: none;
  background: var(--bg); color: var(--fg); border-left: .5px solid var(--j2-line); padding: 18px 20px 120px; font: 13.5px / 1.4 var(--ui-font);
  transform: translateX(24px); opacity: 0; transition: transform .26s cubic-bezier(.2,.8,.2,1), opacity .2s; box-shadow: -18px 0 40px -28px rgba(0,0,0,.35); }
.j2pp::-webkit-scrollbar { display: none; }
.j2pp.on { transform: none; opacity: 1; }
.j2pp[hidden] { display: none; }
html[data-phone] .j2pp { left: 0; width: auto; border-left: 0; box-shadow: none; padding: calc(env(safe-area-inset-top, 0px) + 10px) 18px 140px; transform: translateY(18px); }
html[data-phone] .j2pp.on { transform: none; }
.j2pp-top { display: flex; align-items: center; gap: 4px; min-height: 40px; margin-bottom: 6px; }
.j2pp-top .sp { flex: 1; }
.j2pp-back, .j2pp-back2 { border: 0; background: none; color: var(--j2-ink2); font: 300 28px / 1 var(--ui-font); padding: 2px 8px 4px 0; cursor: pointer; }
html:not([data-phone]) .j2pp-back { position: absolute; top: 14px; right: 14px; width: 32px; height: 32px; padding: 0; border-radius: 50%; font-size: 20px; background: var(--j2-bg2); }
html:not([data-phone]) .j2pp-top { min-height: 0; margin: 0; }
.j2ppg { border: 0; background: none; width: 40px; height: 40px; border-radius: 50%; display: grid; place-items: center; cursor: pointer; color: var(--fg); padding: 0; }
.j2ppg svg { width: 21px; height: 21px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.j2ppg.on { background: var(--fg); color: var(--bg); }
.j2pp-h { font: 600 18px / 1.2 var(--ui-font); letter-spacing: -.01em; margin: 4px 0 12px; }
html:not([data-phone]) .j2pp-h { margin-top: 2px; padding-right: 40px; }
.j2pp-chips { display: flex; gap: 7px; margin: 0 0 8px; overflow-x: auto; scrollbar-width: none; }
.j2pp-chips span { padding: 6px 13px; border-radius: 18px; background: var(--j2-bg2); font-size: 13px; white-space: nowrap; cursor: pointer; }
.j2pp-chips span.on { background: var(--fg); color: var(--bg); }
.j2av { border-radius: 50%; display: inline-grid; place-items: center; color: #fff; font: 600 12px var(--ui-font); flex: none; letter-spacing: .02em; }
.j2pp-row { display: flex; gap: 10px; align-items: flex-start; width: 100%; text-align: left; padding: 11px 0; border: 0; border-bottom: .5px solid var(--j2-line);
  background: none; color: inherit; font: inherit; cursor: pointer; }
div.j2pp-row { cursor: default; padding: 8px 0; align-items: center; }
.j2pp-row .tx, .j2pp-req .tx, .j2pp-cm .tx, .j2pp-vt .tx { flex: 1; min-width: 0; line-height: 1.4; }
.j2pp-row .tx i, .j2pp-req .tx i, .j2pp-cm .tx i, .j2pp-vt .tx i { display: block; font-style: normal; font-size: 11.5px; color: var(--j2-ink2); margin-top: 2px; }
.j2pp-row:hover { background: linear-gradient(90deg, transparent, var(--j2-bg2) 12%, var(--j2-bg2) 88%, transparent); }
.j2pp-req { display: flex; gap: 10px; align-items: center; background: var(--j2-bg2); border-radius: 12px; padding: 10px; margin-bottom: 4px; }
.j2pp-empty { color: var(--j2-ink2); font-size: 14px; line-height: 1.5; margin: 18px 0; max-width: 32ch; }
.j2pp-k { font-size: 11.5px; color: var(--j2-ink2); margin: 16px 0 2px; letter-spacing: .02em; }
.j2pp-cal svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; display: block; }
.j2pp-cm { display: flex; gap: 12px; align-items: center; background: var(--j2-bg2); border-radius: 14px; padding: 8px; margin-bottom: 8px; cursor: pointer; }
.j2pp-cm .cb { width: 58px; height: 66px; border-radius: 10px; flex: none; overflow: hidden; display: grid; place-items: center;
  box-shadow: inset 0 0 0 .5px rgba(0,0,0,.15); }
.j2pp-cm .cb img { width: 40px; height: auto; box-shadow: 1px 2px 6px rgba(0,0,0,.35); border-radius: 1px 3px 3px 1px; }
.j2pp-cm b { font-size: 14px; display: block; }
.j2pp-cm em { display: block; font-style: normal; font-size: 11.5px; margin-top: 3px; color: #c4501d; font-weight: 500; }
.j2pp-more { width: 100%; margin-top: 14px; padding: 11px 14px; border-radius: 22px; border: .5px solid var(--j2-line); background: var(--j2-bg2);
  color: var(--fg); font: 500 13.5px var(--ui-font); cursor: pointer; }
.j2pp-who { display: flex; align-items: center; gap: 8px; margin: 0 0 10px; }
.j2pp-who b { font-size: 15px; }
.j2pp-conv .m { max-width: 78%; width: fit-content; padding: 9px 13px; border-radius: 18px; font-size: 14px; line-height: 1.4; margin: 6px 0; background: var(--j2-bg2);
  animation: j2ppin .3s both; }
.j2pp-conv .m.me { background: var(--fg); color: var(--bg); margin-left: auto; }
.j2pp-conv > :nth-child(2) { animation-delay: .06s; } .j2pp-conv > :nth-child(3) { animation-delay: .12s; } .j2pp-conv > :nth-child(4) { animation-delay: .18s; }
@keyframes j2ppin { from { opacity: 0; transform: translateY(6px); } }
.j2pp-vt { --vc: #b5513f; display: flex; align-items: center; gap: 10px; max-width: 86%; padding: 8px 12px 8px 8px; border-radius: 18px; margin: 6px 0;
  background: color-mix(in srgb, var(--vc) 14%, var(--bg)); animation: j2ppin .3s .24s both; }
.j2pp-vp { width: 32px; height: 32px; border-radius: 50%; background: var(--vc); display: grid; place-items: center; flex: none; cursor: pointer; }
.j2pp-vp svg { width: 13px; height: 13px; fill: #fff; margin-left: 2px; }
.j2pp-vt .tx { flex: 0 0 auto; }
.j2pp-wv { display: flex; align-items: center; gap: 2px; height: 26px; flex: 1; min-width: 60px; }
.j2pp-wv i { flex: 1; max-width: 3px; border-radius: 2px; background: var(--vc); transform-origin: center; animation: j2wv 1.3s ease-in-out infinite alternate; }
.j2pp-send { display: flex; gap: 8px; margin-top: 16px; position: sticky; bottom: 0; padding: 8px 0; background: var(--bg); }
html[data-phone] .j2pp-send { bottom: 96px; }
.j2pp-send input { flex: 1; min-width: 0; border: .5px solid var(--j2-line); background: var(--j2-bg2); color: var(--fg); border-radius: 20px; padding: 10px 14px; font: 14px var(--ui-font); }
.j2pp-send button { border: 0; border-radius: 20px; padding: 0 16px; background: var(--fg); color: var(--bg); font: 500 13.5px var(--ui-font); cursor: pointer; }
.j2-ic[aria-expanded="true"] { background: var(--fg); color: var(--bg); border-radius: 50%; }
html[data-phone] .j2pp-back2 { display: none; }
.j2pp-vt .j2pp-wv { min-width: 110px; }

/* J2 · A COMMUNITY'S CARD (the master's commCard: fm-cmhero, fm-cmaim, fm-live, fm-post, fm-ev) */
.j2cm { margin: -22px -0px 0; }
.j2cm-hero { background: linear-gradient(160deg, var(--bc), color-mix(in srgb, var(--bc) 55%, #1d1a15)); color: #fff; display: flex; gap: 18px; align-items: flex-end; padding: 34px 22px 22px; }
html:not([data-phone]) .j2cm-hero { padding: 40px 48px 32px; gap: 26px; }
.j2cm-bk { flex: none; width: 86px; }
html:not([data-phone]) .j2cm-bk { width: 116px; }
.j2cm-bk img { width: 100%; display: block; border-radius: 2px 4px 4px 2px; box-shadow: 2px 4px 14px rgba(0,0,0,.45), inset 3px 0 4px rgba(0,0,0,.2); }
.j2cm-bk0 { aspect-ratio: 2 / 3; background: rgba(255,255,255,.08); border-radius: 2px 4px 4px 2px; display: grid; place-items: center; font: 600 15px Georgia, serif; box-shadow: 2px 4px 14px rgba(0,0,0,.45); }
.j2cm-hx { min-width: 0; }
.j2cm-k { font-size: 11.5px; letter-spacing: .04em; color: rgba(255,255,255,.75); }
.j2cm-t { font: 400 34px / 1.05 Georgia, "Iowan Old Style", serif; margin-top: 4px; }
html:not([data-phone]) .j2cm-t { font-size: 52px; }
.j2cm-s { font-size: 13px; opacity: .85; margin-top: 6px; }
.j2cm-j { display: flex; gap: 12px; align-items: center; margin-top: 14px; }
.j2cm-in { background: #fff; color: #111; border-radius: 18px; padding: 7px 16px; font: 500 13px var(--ui-font); }
.j2cm-fs { display: flex; } .j2cm-f { margin-right: -8px; } .j2cm-f .j2av { box-shadow: 0 0 0 2px color-mix(in srgb, var(--bc) 70%, #1d1a15); }
.j2cm-body { padding: 4px 20px 60px; }
html:not([data-phone]) .j2cm-body { padding: 4px 48px 80px; }
.j2cm-tabs { margin: 16px 0 14px; }
.j2cm-cols { display: grid; gap: 22px; }
html:not([data-phone]) .j2cm-cols { grid-template-columns: 1fr 340px; gap: 40px; }
.j2cm-aim { background: var(--j2-bg2); border-radius: 14px; padding: 14px; }
.j2cm-k2 { font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--j2-ink2); }
.j2cm-aimt { font: 19px / 1.35 Georgia, serif; margin-top: 4px; }
.j2cm-prog { height: 5px; border-radius: 3px; background: var(--j2-line); margin: 10px 0 5px; overflow: hidden; }
.j2cm-prog i { display: block; height: 100%; background: #c4501d; transform-origin: left; animation: j2cmgrow 1s .2s cubic-bezier(.2,.8,.2,1) both; }
@keyframes j2cmgrow { from { transform: scaleX(0); } }
.j2cm-ca { font-size: 12px; color: var(--j2-ink2); }
.j2cm-h { font: 600 17px var(--ui-font); margin: 22px 0 8px; }
aside .j2cm-h { margin-top: 18px; }
.j2cm-post { border-bottom: .5px solid var(--j2-line); padding: 11px 0; }
.j2cm-by { display: flex; align-items: center; gap: 7px; font-size: 12.5px; } .j2cm-by i { font-style: normal; color: var(--j2-ink2); font-size: 11.5px; }
.j2cm-bd { font-size: 14px; line-height: 1.5; margin: 6px 0; }
.j2cm-act { display: flex; gap: 14px; font-size: 11.5px; color: var(--j2-ink2); }
.j2cm-poll { display: grid; gap: 5px; margin: 8px 0; }
.j2cm-poll div { position: relative; background: var(--j2-bg2); border-radius: 8px; overflow: hidden; height: 30px; }
.j2cm-poll i { position: absolute; inset: 0 auto 0 0; background: color-mix(in srgb, #c4501d 22%, transparent); animation: j2cmgrow .9s .3s cubic-bezier(.2,.8,.2,1) both; transform-origin: left; }
.j2cm-poll span { position: relative; line-height: 30px; padding: 0 10px; font-size: 12.5px; }
.j2cm-live { border-radius: 16px; padding: 14px; background: color-mix(in srgb, #c4501d 9%, var(--bg)); box-shadow: inset 0 0 0 .5px color-mix(in srgb, #c4501d 30%, transparent); }
.j2cm-lh { display: flex; justify-content: space-between; }
.j2cm-dot { color: #c4501d; font-size: 11.5px; letter-spacing: .04em; text-transform: uppercase; animation: j2cmpulse 1.6s ease-in-out infinite; }
@keyframes j2cmpulse { 50% { opacity: .45; } }
.j2cm-lt { font: 22px / 1.2 Georgia, serif; margin: 6px 0 2px; }
.j2cm-lf { display: flex; gap: 6px; margin: 10px 0; }
.j2cm-btn { display: inline-block; background: var(--fg); color: var(--bg); border-radius: 18px; padding: 8px 16px; font: 500 13px var(--ui-font); cursor: pointer; }
.j2cm-ev { display: flex; gap: 12px; align-items: center; padding: 10px 0; border-bottom: .5px solid var(--j2-line); }
.j2cm-ev .d { width: 46px; flex: none; text-align: center; font: 600 11px / 1.3 var(--ui-font); color: #c4501d; text-transform: uppercase; background: var(--j2-bg2); border-radius: 10px; padding: 6px 0; }
.j2cm-ev .tx { flex: 1; min-width: 0; } .j2cm-ev .tx i { display: block; font-style: normal; font-size: 12px; color: var(--j2-ink2); }
.j5-card.j5-other #j5left, .j5-card.j5-other .j5-left { padding-top: 0; }
.j2pp-cm .cb .cbt { color: rgba(255,255,255,.88); font: 600 13px / 1 Georgia, serif; margin: 0; letter-spacing: .02em; }
html[data-phone] .j2cm-hero { padding-top: 56px; }
.j2cm-f { margin-right: -6px; }

/* J6 v2 · the voice card and the builder (the master's fm-vcard, fm-vcols, fm-vtog, fm-v3, fm-zm, fm-vwin, the builder) */
.j6-cols { display: grid; grid-template-columns: 1.2fr 1fr; gap: 40px; }
.j6-seg { display: flex; width: max-content; margin: 4px auto 8px; }
.j6-seg button { border: 0; background: none; color: inherit; cursor: pointer; padding: 7px 14px; border-radius: 999px; font: 500 12.5px / 1 var(--ui-font); }
.j6-seg button.on { background: var(--fg); color: var(--bg); }
.j6-v3 { position: relative; display: flex; justify-content: center; min-height: 300px; border-radius: 18px; overflow: hidden;
  background: radial-gradient(ellipse at 50% 45%, color-mix(in srgb, var(--j5c, #888) 8%, var(--bg)), var(--bg) 70%); }
html:not([data-phone]) .j6-v3 { min-height: 360px; }
.j6-v3 canvas { display: block; margin: 0 auto; cursor: grab; touch-action: none; }
.j6-v3 { flex-direction: column; align-items: center; }
.j6-v3 .dirbar { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; justify-content: center; padding: 2px 12px 46px; font: 12px var(--ui-font); }
.j6-v3 .dirbar .dl { color: var(--j2-ink2); font-size: 11.5px; margin-right: 2px; }
.j6-v3 .dirbar .chip { cursor: pointer; padding: 5px 11px; border-radius: 16px; background: var(--j2-bg2); font-size: 12px; }
.j6-v3 .dirbar .chip.on { background: #2b3f8c; color: #fff; }
.j6-zm { position: absolute; right: 8px; bottom: 8px; display: flex; gap: 4px; z-index: 5; }
.j6-zm button { min-width: 30px; height: 30px; padding: 0 7px; border-radius: 15px; border: 0; cursor: pointer; color: var(--fg);
  background: color-mix(in srgb, var(--bg) 70%, transparent); -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  box-shadow: 0 0 0 .5px var(--j2-line); font: 600 14px var(--ui-font); }
.j6-hint { text-align: center; font: 400 12px / 1.4 var(--ui-font); color: var(--j2-ink2); margin: 6px 0 4px; }
.j6-hint2 { opacity: .75; margin-bottom: 10px; }
.j6-sample { background: color-mix(in srgb, var(--vc, #b5513f) 10%, var(--bg)); box-shadow: inset 0 0 0 .5px color-mix(in srgb, var(--vc, #b5513f) 30%, transparent); }
.j6-sample .j6-pb { background: var(--vc, #b5513f); }
.j6-sample .j6-wv { color: var(--vc, #b5513f); flex: 1; justify-content: flex-end; }
.j6-wv i { opacity: .75; transform-origin: center; animation: j2wv 1.3s ease-in-out infinite alternate; }
.j6-sample.on .j6-wv i { opacity: 1; animation-duration: .45s; }
.j6-built { --wc: #4f8a5b; border: 1px solid var(--j2-line); border-left: 3px solid var(--wc); background: none; margin-bottom: 8px; padding: 10px 12px; border-radius: 12px; }
.j6-built .j6-wv { color: var(--wc); height: 30px; justify-content: flex-start; }
.j6-built .j6-wv i { width: 2.5px; }
.j6-bh { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; }
.j6-bh .j6-b { margin-top: 18px; }
.j6-tabs { display: flex; gap: 7px; overflow-x: auto; scrollbar-width: none; margin: 16px 0 10px; }
.j6-tabs button { border: 0; cursor: pointer; padding: 6px 13px; border-radius: 18px; background: var(--j2-bg2); color: var(--fg); font: 13px var(--ui-font); white-space: nowrap; }
.j6-tabs button.on { background: var(--fg); color: var(--bg); }
.j6-bvb { animation: j2ppin .25s both; }
.j6-sl { margin: 6px 0 12px; cursor: pointer; touch-action: none; }
.j6-sll { display: flex; justify-content: space-between; font: 500 13px var(--ui-font); }
.j6-sll em { font-style: normal; color: var(--j2-ink2); font-variant-numeric: tabular-nums; }
.j6-tr { position: relative; height: 4px; border-radius: 4px; background: var(--j2-line); margin: 9px 0 5px; }
.j6-tr i { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 4px; background: #2b3f8c; }
.j6-tr b { position: absolute; top: 50%; width: 18px; height: 18px; margin: -9px 0 0 -9px; border-radius: 50%; background: #fff; box-shadow: 0 0 0 .5px rgba(0,0,0,.18), 0 1px 4px rgba(0,0,0,.28); }
.j6-tr em { position: absolute; top: -5px; width: 2px; height: 14px; margin-left: -1px; background: #7a2f4d; border-radius: 1px; }
.j6-slr { display: flex; justify-content: space-between; font: 10.5px var(--ui-font); color: var(--j2-ink2); }
.j6-pad { position: relative; height: 210px; border-radius: 14px; overflow: hidden; margin: 4px 0 14px; box-shadow: inset 0 0 0 .5px var(--j2-line);
  background: radial-gradient(circle at 80% 20%, #ffe6a8, transparent 55%), radial-gradient(circle at 20% 80%, #b9c4dc, transparent 55%), radial-gradient(circle at 80% 80%, #f2c9cf, transparent 50%), var(--j2-bg2); }
.j6-pad .grid { position: absolute; inset: 0; opacity: .7; background: linear-gradient(var(--j2-line) 1px, transparent 1px) 0 50% / 100% 50%, linear-gradient(90deg, var(--j2-line) 1px, transparent 1px) 50% 0 / 50% 100%; }
.j6-pad .pl { position: absolute; font: 10.5px var(--ui-font); color: var(--j2-ink2); }
.j6-pad .t { top: 5px; left: 50%; transform: translateX(-50%); } .j6-pad .b { bottom: 5px; left: 50%; transform: translateX(-50%); } .j6-pad .l { left: 7px; top: 50%; } .j6-pad .r { right: 7px; top: 50%; }
.j6-pad .mp { position: absolute; font: 600 11px var(--ui-font); transform: translate(-50%, -50%); opacity: .55; color: #222; }
.j6-pad .pt, .j6-pad .pt0 { position: absolute; width: 18px; height: 18px; border-radius: 50%; transform: translate(-50%, -50%); }
.j6-pad .pt { background: #2b3f8c; border: 3px solid #fff; box-shadow: 0 2px 6px rgba(0,0,0,.3); animation: j6breathe 2.6s ease-in-out infinite; }
.j6-pad .pt0 { border: 2px dashed #7a2f4d; }
@keyframes j6breathe { 50% { box-shadow: 0 2px 6px rgba(0,0,0,.3), 0 0 0 8px rgba(43,63,140,.12); } }
.j6-inp { border: 1px solid var(--j2-line); border-radius: 10px; padding: 10px 12px; font: italic 16px / 1.4 Georgia, serif; color: var(--fg); margin: 6px 0 8px; outline: none; }
.j6-inp:focus { border-color: #2b3f8c; }
@media (prefers-reduced-motion: reduce) { .j6-wv i, .j2pp-wv i, .j6-pad .pt { animation: none; } }
.j5-card.j5-wide { width: min(1100px, calc(100vw - 48px)); }
.j5-card.j5-other .j6v { padding: 34px 44px 48px; max-width: none; }
html[data-phone] .j5-card.j5-other .j6v { padding: 26px 18px 40px; }
.j6-cols > div { min-width: 0; }
.j6bld .j6-cols { grid-template-columns: 1fr 1fr; }
.j6bld .j6-st { min-width: 0; flex: 0 1 46%; }
.j6bld .j6-st i { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
html[data-j2sc] .phhead { background: linear-gradient(to bottom, var(--bg) 0%, var(--bg) 62%, color-mix(in srgb, var(--bg) 0%, transparent) 100%) !important; padding-bottom: 22px !important; }

/* J2 v3 · EVERY RUNG IS BOOKS (Osca 16 Oct 23:49: "the covers, really bad, in All books ... improve how they look").
   At every zoom of All books a tile is a book standing on a shelf: the tile's cover box is only the SLOT (3:4, no
   colour); the picture inside stands on the slot's floor at its own shape (--ar) and size (--k), with its page block
   (--dp) to the right and a soft shadow; every row is a shelf -- a floor line and its shade run under the books, gap to
   gap. A book with no picture keeps its drawn face, at the same size and on the same floor. Titles and authors under
   the books at 4, 2 and 1; at 8 and 16 the books alone. The sizes are CSS, so the pinch still reflows live. */
#shelf#shelf .shelfgrid { --pu: 1px; --gx: 18px; }
#shelf#shelf[data-zoom="16"] .shelfgrid { --pu: .45px; --gx: 14px; gap: 22px 14px !important; padding: 12px var(--j2-gx) 24px !important; width: auto !important; max-width: none !important; margin: 0 !important; }
#shelf#shelf[data-zoom="8"] .shelfgrid { --pu: .7px; --gx: 20px; gap: 30px 20px !important; padding: 12px var(--j2-gx) 24px !important; width: auto !important; max-width: none !important; margin: 0 !important; }
#shelf#shelf[data-zoom="4"] .shelfgrid { --pu: 1px; --gx: 18px; }
#shelf#shelf[data-zoom="2"] .shelfgrid, #shelf#shelf[data-zoom="2t"] .shelfgrid { --pu: 1.5px; --gx: 40px; gap: 44px 40px !important; padding: 16px var(--j2-gx) 28px !important; width: auto !important; max-width: none !important; margin: 0 !important; }
html[data-phone] #shelf#shelf[data-zoom="16"] .shelfgrid { --gx: 6px; gap: 14px 6px !important; grid-template-columns: repeat(8, minmax(0, 1fr)) !important; }
html[data-phone] #shelf#shelf[data-zoom="8"] .shelfgrid { --gx: 10px; gap: 20px 10px !important; grid-template-columns: repeat(5, minmax(0, 1fr)) !important; }
html[data-phone] #shelf#shelf[data-zoom="2"] .shelfgrid { --gx: 18px; gap: 30px 18px !important; grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
#shelf#shelf .shelfgrid .railrow.tile[data-obj] { display: flex !important; flex-direction: column; align-items: stretch; background: none !important; border: 0 !important; padding: 0 !important;
  aspect-ratio: auto !important; height: auto !important; width: auto !important; box-shadow: none !important; overflow: visible !important; border-radius: 0 !important; }
/* the slot */
#shelf#shelf .shelfgrid .railrow.tile[data-obj] .tilecov { position: relative !important; inset: auto !important; display: block !important; flex: none; margin: 0 !important;
  width: 100% !important; height: auto !important; aspect-ratio: 3 / 4 !important; max-width: none !important; max-height: none !important;
  border-radius: 0 !important; overflow: visible !important; transform: none !important; }
#shelf#shelf .shelfgrid .railrow.tile[data-obj][data-img] .tilecov { background: none !important; box-shadow: none !important; }
#shelf#shelf[data-zoom="1"] .shelfgrid .railrow.tile[data-obj] .tilecov, #shelf#shelf[data-zoom="1t"] .shelfgrid .railrow.tile[data-obj] .tilecov { width: min(100%, 360px) !important; margin: 0 auto !important; }
/* the floor: a shelf line under the books, running on through the gap to the next book, and its shade */
#shelf#shelf .shelfgrid .railrow.tile[data-obj] .tilecov::before { content: ""; position: absolute; left: calc(var(--gx) / -2 - 1px); right: calc(var(--gx) / -2 - 1px); bottom: -1px; height: 7px; z-index: 0;
  border-top: 1px solid color-mix(in srgb, var(--fg) 13%, transparent); background: linear-gradient(color-mix(in srgb, var(--fg) 7%, transparent), transparent); pointer-events: none; }
/* the book: the picture at its own shape, on the floor, its page block and shadow */
#shelf#shelf .shelfgrid .railrow.tile[data-obj][data-img] .covimg { position: absolute !important; inset: auto 0 0 0 !important; margin: 0 auto !important; z-index: 1;
  height: calc(100% * var(--k, 1)) !important; width: auto !important; max-width: calc(100% - var(--dp, 1) * var(--pu) * 6) !important; aspect-ratio: var(--ar, .66); object-fit: cover !important;
  border-radius: 1px 3px 3px 1px; transform: translateX(calc(var(--dp, 1) * var(--pu) * -2));
  box-shadow: calc(var(--dp, 1) * var(--pu) * 1) calc(var(--dp, 1) * var(--pu) * -.5) 0 #f6f2e8, calc(var(--dp, 1) * var(--pu) * 2) calc(var(--dp, 1) * var(--pu) * -1) 0 #e6dfcd,
    calc(var(--dp, 1) * var(--pu) * 3) calc(var(--dp, 1) * var(--pu) * -1.5) 0 #f6f2e8, calc(var(--dp, 1) * var(--pu) * 4) calc(var(--dp, 1) * var(--pu) * -2) 0 #e2dac6,
    calc(var(--dp, 1) * var(--pu) * 5) calc(var(--dp, 1) * var(--pu) * -2.5) 0 rgba(0,0,0,.16),
    calc(var(--pu) * 3) calc(var(--pu) * -1) calc(var(--pu) * 10) rgba(0,0,0,.20), 0 0 0 .5px rgba(0,0,0,.08) !important; }
/* the hinge and sheen on the picture's face */
#shelf#shelf .shelfgrid .railrow.tile[data-obj][data-img] .tilecov::after { content: ""; position: absolute; z-index: 2; pointer-events: none; bottom: 0; left: 50%; inset: auto auto 0 50%;
  height: calc(100% * var(--k, 1)); aspect-ratio: var(--ar, .66); max-width: 100%; transform: translateX(calc(-50% - var(--dp, 1) * var(--pu) * 2)); border-radius: 1px 3px 3px 1px;
  background: linear-gradient(90deg, rgba(0,0,0,.18) 0, rgba(255,255,255,.16) 2.5%, rgba(0,0,0,.05) 4.5%, transparent 9%, transparent 88%, rgba(0,0,0,.06)); }
#shelf#shelf .shelfgrid .railrow.tile[data-obj][data-img] .tilecov > :not(.covimg) { display: none !important; }
/* no picture: the drawn face (the typeset cover, sized in its own cqw) as a book of the same size on the same floor --
   the face is the box itself, its width a share of the tile, pushed down so its foot is the slot's floor (a % margin
   is a share of the tile's width, so 133.33 % is the slot's height) */
#shelf#shelf .shelfgrid .railrow.tile[data-obj]:not([data-img]) { --f: calc(.8 * var(--k, 1)); }
#shelf#shelf .shelfgrid .railrow.tile[data-obj]:not([data-img]) .tilecov { width: calc(100% * var(--f)) !important; aspect-ratio: 88 / 120 !important;
  margin: calc(133.333% - 100% * var(--f) * 1.3636) auto 0 !important; overflow: hidden !important; border-radius: 1px 3px 3px 1px !important;
  translate: calc(var(--dp, 1) * var(--pu) * -2) 0;
  box-shadow: calc(var(--dp, 1) * var(--pu) * 1) calc(var(--dp, 1) * var(--pu) * -.5) 0 #f6f2e8, calc(var(--dp, 1) * var(--pu) * 2) calc(var(--dp, 1) * var(--pu) * -1) 0 #e6dfcd,
    calc(var(--dp, 1) * var(--pu) * 3) calc(var(--dp, 1) * var(--pu) * -1.5) 0 #f6f2e8, calc(var(--dp, 1) * var(--pu) * 4) calc(var(--dp, 1) * var(--pu) * -2) 0 #e2dac6,
    calc(var(--dp, 1) * var(--pu) * 5) calc(var(--dp, 1) * var(--pu) * -2.5) 0 rgba(0,0,0,.16), calc(var(--pu) * 3) calc(var(--pu) * -1) calc(var(--pu) * 10) rgba(0,0,0,.20) !important; }
#shelf#shelf .shelfgrid .railrow.tile[data-obj]:not([data-img]) .tilecov::before { display: none; }
#shelf#shelf .shelfgrid .railrow.tile[data-obj]:not([data-img])::before { content: ""; position: absolute; left: calc(var(--gx) / -2 - 1px); right: calc(var(--gx) / -2 - 1px); top: 0; aspect-ratio: auto;
  height: 0; padding-top: calc(133.333% - 1px); pointer-events: none; z-index: 0;
  border-bottom: 1px solid color-mix(in srgb, var(--fg) 13%, transparent); }
#shelf#shelf .shelfgrid .railrow.tile[data-obj]:not([data-img]) .tilecov::after { content: ""; position: absolute; inset: 0; z-index: 3; pointer-events: none;
  background: linear-gradient(90deg, rgba(0,0,0,.18) 0, rgba(255,255,255,.16) 2.5%, rgba(0,0,0,.05) 4.5%, transparent 9%, transparent 88%, rgba(0,0,0,.06)); }
/* not display:none -- a lazy picture that is not displayed never loads; it waits, unseen, in the face's own box */
#shelf#shelf .shelfgrid .railrow.tile[data-obj]:not([data-img]) .covimg { display: block !important; visibility: hidden !important; position: absolute !important; inset: 0 !important; width: 100% !important; height: 100% !important; }
#shelf#shelf .shelfgrid .railrow.tile[data-obj] { position: relative !important; }
/* the book lifts a little under the pointer, nothing else moves */
#shelf#shelf .shelfgrid .railrow.tile[data-obj][data-img]:hover .covimg { translate: 0 -4px; transition: translate .25s cubic-bezier(.2,.8,.2,1); }
#shelf#shelf .shelfgrid .railrow.tile[data-obj][data-img]:hover .tilecov::after { translate: 0 -4px; transition: translate .25s cubic-bezier(.2,.8,.2,1); }
#shelf#shelf .shelfgrid .railrow.tile[data-obj]:hover .tilecov { transform: none !important; }
/* titles: at 4, 2 and 1 under the book; at 8 and 16 none */
#shelf#shelf[data-zoom="2"] .railrow.tile[data-obj] .railrow-title, #shelf#shelf[data-zoom="1"] .railrow.tile[data-obj] .railrow-title { display: block !important; position: static !important; margin-top: 12px;
  font: 400 15px / 1.25 var(--serif, Georgia, serif) !important; color: var(--fg) !important; text-align: center; white-space: normal !important; opacity: 1 !important; }
#shelf#shelf[data-zoom="2"] .railrow.tile[data-obj] .railrow-by, #shelf#shelf[data-zoom="1"] .railrow.tile[data-obj] .railrow-by { display: block !important; position: static !important; margin-top: 3px;
  font: 400 12px / 1.2 var(--ui-font) !important; color: var(--j2-ink2) !important; text-align: center; opacity: 1 !important; text-transform: none !important; letter-spacing: 0 !important; }
#shelf#shelf[data-zoom="16"] .railrow.tile[data-obj] .railrow-title, #shelf#shelf[data-zoom="16"] .railrow.tile[data-obj] .railrow-by,
#shelf#shelf[data-zoom="8"] .railrow.tile[data-obj] .railrow-title, #shelf#shelf[data-zoom="8"] .railrow.tile[data-obj] .railrow-by { display: none !important; }
#shelf#shelf[data-zoom="4"] .railrow.tile[data-obj] .railrow-title { margin-top: 12px; }

/* J2 v3 · THE SPINES, ON SHELVES (Osca 16 Oct 23:49: "same with spines ... improve how they look"). Every row is a
   shelf: a board runs margin to margin under each row of books (a repeating ground on the row box), the books stand on
   it at their own heights (--k) and thicknesses, cloth with a rounded back (light on the left, shade on the right),
   head and tail bands or a title panel by binding, the title set to fit its spine -- never cut short. */
#shelf#shelf .j2spines { --sh: var(--h, 230px); }
#shelf#shelf .j2shelf { --row: calc(var(--shh) + 54px); row-gap: 54px !important; column-gap: 3px !important; align-items: flex-end !important; border-bottom: 0 !important; padding: 0 !important;
  background: repeating-linear-gradient(to bottom, transparent 0, transparent var(--shh),
    color-mix(in srgb, var(--fg) 16%, var(--bg)) var(--shh), color-mix(in srgb, var(--fg) 10%, var(--bg)) calc(var(--shh) + 3px),
    color-mix(in srgb, var(--fg) 6%, var(--bg)) calc(var(--shh) + 12px), color-mix(in srgb, var(--fg) 13%, var(--bg)) calc(var(--shh) + 13px),
    color-mix(in srgb, var(--fg) 4%, transparent) calc(var(--shh) + 14px), transparent calc(var(--shh) + 30px), transparent var(--row)) !important; }
#shelf#shelf .j2sp-all .j2shelf { --shh: 230px; } #shelf#shelf .j2sp-recent .j2shelf { --shh: 190px; }
html[data-phone] #shelf#shelf .j2sp-all .j2shelf { --shh: 170px; } html[data-phone] #shelf#shelf .j2sp-recent .j2shelf { --shh: 150px; }
#shelf#shelf .j2sp { height: var(--h) !important; background: none !important; }
#shelf#shelf .j2sp .j2sp-face { inset: auto 0 0 0 !important; height: calc(var(--h) * var(--k, 1)) !important; border-radius: 3px 3px 1px 1px !important;
  background-image: linear-gradient(90deg, rgba(0,0,0,.26) 0, rgba(255,255,255,.10) 12%, rgba(255,255,255,.18) 26%, rgba(255,255,255,0) 46%, rgba(0,0,0,.06) 70%, rgba(0,0,0,.28) 100%),
    repeating-linear-gradient(0deg, rgba(255,255,255,.025) 0 1px, rgba(0,0,0,.025) 1px 2px) !important;
  box-shadow: 1px 0 0 rgba(0,0,0,.18), 2px 1px 6px rgba(0,0,0,.18) !important; }
/* bindings: 2 -- a head and a tail band in the ink's colour; 3 -- a title panel */
#shelf#shelf .j2sp.j2b2 .j2sp-face::before, #shelf#shelf .j2sp.j2b2 .j2sp-face::after { content: ""; position: absolute; left: 0; right: 0; height: 2px; background: currentColor; opacity: .55; }
#shelf#shelf .j2sp.j2b2 .j2sp-face::before { top: 6%; box-shadow: 0 4px 0 -1px currentColor; } #shelf#shelf .j2sp.j2b2 .j2sp-face::after { bottom: 13%; box-shadow: 0 -4px 0 -1px currentColor; }
#shelf#shelf .j2sp.j2b3 .j2sp-t b { padding: .55em .25em; background: color-mix(in srgb, currentColor 14%, transparent); box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 40%, transparent); border-radius: 1px; }
/* the title, set to fit */
#shelf#shelf .j2sp .j2sp-t { top: 9% !important; height: 70% !important; font-size: var(--fs) !important; line-height: 1.12 !important; gap: .9em !important; align-items: center; justify-content: flex-start; }
#shelf#shelf .j2sp .j2sp-t b { overflow: visible !important; text-overflow: clip !important; white-space: nowrap; font-family: var(--serif, Georgia, serif); letter-spacing: .01em; }
#shelf#shelf .j2sp .j2sp-t.two b { white-space: normal; max-height: 100%; }
#shelf#shelf .j2sp .j2sp-t i { font-size: max(6.5px, .62em) !important; white-space: nowrap; }
#shelf#shelf .j2sp .j2sp-m { font-size: max(8px, calc(var(--w) * .26)) !important; bottom: 4.5% !important; }
/* drawn out: the book rises off the board and its cover shows over its neighbours */
#shelf#shelf .j2sp.j2-up { transform: translateY(-14px); transition: transform .28s cubic-bezier(.2,.8,.2,1); }
#shelf#shelf .j2sp.j2-up .j2sp-face { box-shadow: 1px 0 0 rgba(0,0,0,.18), 4px 14px 18px rgba(0,0,0,.22) !important; }
#shelf#shelf .j2sp.j2-nl, #shelf#shelf .j2sp.j2-nr { transition: transform .28s cubic-bezier(.2,.8,.2,1); }
#shelf#shelf .j2sp .j2sp-t { top: 7% !important; height: 58% !important; }
#shelf#shelf .j2sp .j2sp-au { position: absolute; left: 50%; top: 68%; height: 18%; transform: translateX(-50%); writing-mode: vertical-rl; display: flex; align-items: center; justify-content: flex-start;
  font: 500 var(--fa) / 1 var(--ui-font); letter-spacing: .12em; text-transform: uppercase; white-space: nowrap; opacity: .82; }
#shelf#shelf .j2sp.j2b2 .j2sp-face::after { bottom: 12%; }
#shelf#shelf .j2shelf { padding-bottom: 34px !important; }
#shelf#shelf .j2sp.j2b2 .j2sp-face::after { bottom: 11%; } #shelf#shelf .j2sp.j2b2 .j2sp-t { top: 11% !important; height: 54% !important; }
#shelf#shelf .j2sp .j2sp-au { top: 66%; height: 19%; } #shelf#shelf .j2sp .j2sp-m { bottom: 2.5% !important; }
/* the 3D voice, lifted out of its card: it fills the window over everything, then settles back */
#j6stage { position: fixed; inset: 0; z-index: 140; pointer-events: none; }
#j6stage .liftov { position: absolute; pointer-events: auto; overflow: hidden; border-radius: 22px; background: color-mix(in srgb, var(--bg) 96%, transparent);
  -webkit-backdrop-filter: blur(24px); backdrop-filter: blur(24px); box-shadow: 0 30px 80px rgba(0,0,0,.30), 0 0 0 .5px var(--j2-line);
  transition: left .38s cubic-bezier(.2,.9,.2,1), top .38s cubic-bezier(.2,.9,.2,1), width .38s cubic-bezier(.2,.9,.2,1), height .38s cubic-bezier(.2,.9,.2,1); }
#j6stage .liftov canvas { display: block; cursor: grab; touch-action: none; }
.j6-zmup { right: 14px; bottom: 14px; }
.j6-zmup button { min-width: 36px; height: 36px; border-radius: 18px; font-size: 16px; }
.frank-say { position: fixed; left: 50%; bottom: 96px; z-index: 300; max-width: min(520px, calc(100vw - 32px)); padding: 12px 18px; border-radius: 14px; box-sizing: border-box;
  background: color-mix(in srgb, var(--fg) 88%, transparent); color: var(--bg); font: 500 13.5px / 1.4 var(--ui-font); text-align: center;
  -webkit-backdrop-filter: blur(18px); backdrop-filter: blur(18px); box-shadow: 0 12px 34px rgba(0,0,0,.25);
  transform: translate(-50%, 10px); opacity: 0; transition: opacity .25s, transform .25s; pointer-events: none; }
.frank-say.on { transform: translate(-50%, 0); opacity: 1; }
#shelf#shelf .j2-wave .j2-wv i { background: var(--vc, currentColor) !important; opacity: .8; }
/* titles and authors under every book at every rung (Osca 17 Oct 00:16: "where are the titles and authors?") -- the
   title two lines at most, the author one, both sized to the rung */
#shelf#shelf .shelfgrid .railrow.tile[data-obj] .railrow-title { display: -webkit-box !important; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden !important;
  position: static !important; margin-top: 10px; font: 400 14px / 1.22 var(--serif, Georgia, serif) !important; color: var(--fg) !important; text-align: center;
  white-space: normal !important; opacity: 1 !important; max-width: 100%; }
#shelf#shelf .shelfgrid .railrow.tile[data-obj] .railrow-by { display: block !important; position: static !important; margin-top: 3px; font: 400 11.5px / 1.25 var(--ui-font) !important;
  color: var(--j2-ink2) !important; text-align: center; opacity: 1 !important; text-transform: none !important; letter-spacing: 0 !important;
  white-space: nowrap; overflow: hidden !important; text-overflow: ellipsis; max-width: 100%; }
#shelf#shelf[data-zoom="8"] .shelfgrid .railrow.tile[data-obj] .railrow-title { font-size: 12.5px !important; margin-top: 8px; }
#shelf#shelf[data-zoom="8"] .shelfgrid .railrow.tile[data-obj] .railrow-by { font-size: 10.5px !important; }
#shelf#shelf[data-zoom="16"] .shelfgrid .railrow.tile[data-obj] .railrow-title { font-size: 11px !important; line-height: 1.2 !important; margin-top: 7px; }
#shelf#shelf[data-zoom="16"] .shelfgrid .railrow.tile[data-obj] .railrow-by { font-size: 9.5px !important; margin-top: 2px; }
#shelf#shelf[data-zoom="2"] .shelfgrid .railrow.tile[data-obj] .railrow-title, #shelf#shelf[data-zoom="1"] .shelfgrid .railrow.tile[data-obj] .railrow-title { font-size: 15.5px !important; margin-top: 12px; }
#shelf#shelf[data-zoom="2"] .shelfgrid .railrow.tile[data-obj] .railrow-by, #shelf#shelf[data-zoom="1"] .shelfgrid .railrow.tile[data-obj] .railrow-by { font-size: 12px !important; }
html[data-phone] #shelf#shelf[data-zoom="16"] .shelfgrid .railrow.tile[data-obj] .railrow-title { font-size: 9px !important; }
html[data-phone] #shelf#shelf[data-zoom="16"] .shelfgrid .railrow.tile[data-obj] .railrow-by { font-size: 8px !important; }
#shelf#shelf[data-zoom="16"] .shelfgrid { row-gap: 26px !important; } #shelf#shelf[data-zoom="8"] .shelfgrid { row-gap: 32px !important; }

/* J2 v4 · NO SHELF, NO ROW LINE (Osca 17 Oct 00:33: "I DON'T WANT THEM ON A SHELF ... NO ROW LINE"); the books stand on
   one baseline by themselves, each with its own soft shadow. A cover OPENS a little as you pass, as in the master (its
   .bkp:hover: the board swings on its hinge, -34deg, over the page block) -- not a lift. */
#shelf#shelf .shelfgrid .railrow.tile[data-obj] .tilecov::before, #shelf#shelf .shelfgrid .railrow.tile[data-obj]:not([data-img])::before { content: none !important; display: none !important; }
/* the page block behind the board: what shows when it opens */
#shelf#shelf .shelfgrid .railrow.tile[data-obj][data-img] .tilecov { perspective: 900px; }
#shelf#shelf .shelfgrid .railrow.tile[data-obj][data-img] .tilecov::after { z-index: 0 !important; background: repeating-linear-gradient(#fbf8f0 0 calc(var(--pu) * 3), #ebe5d6 calc(var(--pu) * 3) calc(var(--pu) * 3.6)) !important;
  box-shadow: inset 6px 0 8px rgba(0,0,0,.10); }
#shelf#shelf .shelfgrid .railrow.tile[data-obj][data-img] .covimg { transform-origin: 0 50%; transition: transform .7s cubic-bezier(.2,.9,.2,1), box-shadow .7s, translate .25s !important; }
#shelf#shelf .shelfgrid .railrow.tile[data-obj][data-img]:hover .covimg { translate: 0 0 !important;
  transform: translateX(calc(var(--dp, 1) * var(--pu) * -2)) rotateY(-34deg) !important; }
#shelf#shelf .shelfgrid .railrow.tile[data-obj][data-img]:hover .tilecov::after { translate: 0 0 !important; }
/* a face with no picture of its own opens the same way, the pages behind it drawn on the tile */
#shelf#shelf .shelfgrid .railrow.tile[data-obj]:not([data-img]) { perspective: 900px; }
#shelf#shelf .shelfgrid .railrow.tile[data-obj]:not([data-img])::after { content: ""; position: absolute; top: 0; left: calc(50% - 50% * var(--f) - var(--dp, 1) * var(--pu) * 2); width: calc(100% * var(--f));
  aspect-ratio: 88 / 120; margin-top: calc(133.333% - 100% * var(--f) * 1.3636); z-index: 0; pointer-events: none; border-radius: 1px 3px 3px 1px;
  background: repeating-linear-gradient(#fbf8f0 0 calc(var(--pu) * 3), #ebe5d6 calc(var(--pu) * 3) calc(var(--pu) * 3.6)); box-shadow: inset 6px 0 8px rgba(0,0,0,.10); }
#shelf#shelf .shelfgrid .railrow.tile[data-obj]:not([data-img]) .tilecov { position: relative !important; z-index: 1; transform-origin: 0 50%; transition: transform .7s cubic-bezier(.2,.9,.2,1) !important; }
#shelf#shelf .shelfgrid .railrow.tile[data-obj]:not([data-img]):hover .tilecov { transform: rotateY(-34deg) !important; }
#shelf#shelf .shelfgrid .railrow.tile[data-obj] .railrow-title, #shelf#shelf .shelfgrid .railrow.tile[data-obj] .railrow-by { position: relative !important; z-index: 2; }

/* J2 v4 · THE SPINES AS 3D BOOKS (the master's .fm-sp3): no shelf, no line; books stand cover to cover, rows fill from
   the left margin to margin; the one you pass turns on the spot to face you, on the same bottom line. --z is the zoom. */
#shelf#shelf .j3s { padding: 14px var(--j2-gx) 30px !important; max-width: none !important; width: auto !important; margin: 0 !important; box-sizing: border-box; }
#shelf#shelf .j3s .j3row { zoom: var(--z, 1); }
html[data-phone] #shelf#shelf .j3s { padding-left: 16px !important; padding-right: 16px !important; }
.j3row { display: flex; flex-wrap: wrap; justify-content: flex-start; align-items: flex-end; gap: 34px 0; }
.j3 { all: unset; box-sizing: border-box; position: relative; flex: none; height: var(--h); width: var(--sw); cursor: pointer; perspective: 2400px; perspective-origin: 50% 50%;
  transition: transform .35s cubic-bezier(.22,.8,.24,1); -webkit-tap-highlight-color: transparent; }
.j3.j3-face { z-index: 10; } .j3.j3-nl { transform: translateX(-5px); } .j3.j3-nr { transform: translateX(5px); }
.j3-b { position: absolute; bottom: 0; left: calc(var(--sw) / 2 - var(--cw) / 2); width: var(--cw); height: var(--h); transform-style: preserve-3d; transform: rotateY(90deg);
  transition: transform .45s cubic-bezier(.22,.8,.24,1); }
.j3.j3-face .j3-b { transform: translateX(var(--shift, 0px)) translateZ(46px) rotateY(0deg); transition: transform .6s cubic-bezier(.22,.8,.24,1) .06s; }
.j3-b > span { position: absolute; display: block; backface-visibility: hidden; -webkit-backface-visibility: hidden; }
.j3-front { inset: 0; transform: translateZ(calc(var(--sw) / 2)); overflow: hidden; border-radius: 1px 3px 3px 1px; background: #ddd; }
.j3.j3-face .j3-front { box-shadow: 0 22px 40px -12px rgba(0,0,0,.5); }
.j3-front .j3-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.j3-front > .cv.fcov { position: absolute; inset: 0; }
.j3-hinge { position: absolute; inset: 0; z-index: 3; pointer-events: none; background: linear-gradient(90deg, rgba(0,0,0,.22), rgba(255,255,255,.18) 2.5%, rgba(0,0,0,.06) 4.2%, transparent 7%), linear-gradient(115deg, rgba(255,255,255,.10), transparent 40%, rgba(0,0,0,.06)); }
.j3-slab { left: 0; top: 0; width: calc(var(--cw) + var(--dx)); height: calc(var(--h) + var(--dy)); transform: translateZ(calc(var(--sw) / 2 - .5px)); opacity: 0; transition: opacity .2s; }
.j3.j3-face .j3-slab { opacity: 1; transition: opacity .25s .35s; }
.j3-side { position: absolute; inset: 0; clip-path: polygon(var(--cw) 0, calc(var(--cw) + var(--dx)) var(--dy), calc(var(--cw) + var(--dx)) calc(var(--h) + var(--dy)), var(--cw) var(--h)); background: repeating-linear-gradient(90deg, #f1efea 0 .5px, #e2dfd8 .5px 1px); }
.j3-bot { position: absolute; inset: 0; clip-path: polygon(0 var(--h), var(--cw) var(--h), calc(var(--cw) + var(--dx)) calc(var(--h) + var(--dy)), var(--dx) calc(var(--h) + var(--dy))); background: repeating-linear-gradient(0deg, #dedbd4 0 .5px, #cfcbc3 .5px 1px); }
.j3-back { inset: 0; transform: rotateY(180deg) translateZ(calc(var(--sw) / 2)); filter: brightness(.8); background: #555; }
.j3-spine { top: 0; bottom: 0; width: var(--sw); left: calc(50% - var(--sw) / 2); transform: rotateY(-90deg) translateZ(calc(var(--cw) / 2)); overflow: hidden; border-radius: 2px 2px 1px 1px; background: #555; }
.j3-spine::after { content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 3;
  background: linear-gradient(90deg, rgba(0,0,0,.32) 0, rgba(0,0,0,.08) 7%, rgba(255,255,255,.16) 24%, rgba(255,255,255,.04) 48%, rgba(0,0,0,.06) 76%, rgba(0,0,0,.30) 100%), linear-gradient(0deg, rgba(0,0,0,.12), transparent 6%, transparent 96%, rgba(255,255,255,.12)); }
.j3-edge { top: 1px; bottom: 1px; width: var(--sw); left: calc(50% - var(--sw) / 2); transform: rotateY(90deg) translateZ(calc(var(--cw) / 2 - 1px)); background: repeating-linear-gradient(90deg, #f4efe3 0 1px, #e2dccd 1px 2px); }
.j3::after { content: ""; position: absolute; left: 0; right: 0; bottom: -4px; height: 6px; background: radial-gradient(ellipse at 50% 0, rgba(0,0,0,.18), transparent 70%); pointer-events: none; }
@media (hover: none) { .j3.j3-face .j3-b { transform: rotateY(90deg); } }
@media (prefers-reduced-motion: reduce) { .j3, .j3-b { transition: none !important; } }
.j3-flat { position: absolute; inset: auto 0 0 0; height: var(--h); overflow: hidden; border-radius: 2px 2px 1px 1px; z-index: 1; transition: opacity .12s; }
.j3-flat::after { content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 3;
  background: linear-gradient(90deg, rgba(0,0,0,.32) 0, rgba(0,0,0,.08) 7%, rgba(255,255,255,.16) 24%, rgba(255,255,255,.04) 48%, rgba(0,0,0,.06) 76%, rgba(0,0,0,.30) 100%), linear-gradient(0deg, rgba(0,0,0,.12), transparent 6%, transparent 96%, rgba(255,255,255,.12)); }
.j3 .j3-b { opacity: 0; }
.j3.j3-face .j3-b { opacity: 1; }
.j3.j3-face .j3-flat { opacity: 0; }

/* A PERSON'S CARD (the master's profile) */
.j2pc { padding: 34px 44px 48px; }
html[data-phone] .j2pc { padding: 26px 18px 40px; }
.j2pc-h { display: flex; gap: 18px; align-items: center; }
.j2pc-h .j2av { font-size: 26px; }
.j2pc-n h2 { margin: 0; font: 400 38px / 1.05 Georgia, "Iowan Old Style", serif; }
html[data-phone] .j2pc-n h2 { font-size: 28px; }
.j2pc-ca { font: 13px / 1.4 var(--ui-font); color: var(--j2-ink2); margin-top: 4px; }
.j2pc-bio { font: 17px / 1.45 Georgia, serif; margin: 14px 0 12px; max-width: 46ch; }
.j2pc-btns { display: flex; gap: 10px; margin: 6px 0 14px; }
.j2pc-stats { display: flex; gap: 18px; flex-wrap: wrap; font: 13px var(--ui-font); color: var(--j2-ink2); padding: 10px 0; border-top: .5px solid var(--j2-line); border-bottom: .5px solid var(--j2-line); }
.j2pc-stats b { color: var(--fg); font-weight: 600; }
.j2pc-pins { display: grid; gap: 12px; }
html:not([data-phone]) .j2pc-pins { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.j2pc-pin { display: grid; grid-template-columns: auto 1fr; gap: 12px; align-items: end; background: var(--j2-bg2); border-radius: 16px; padding: 12px; }
.j2pc-bk { display: block; aspect-ratio: 2 / 3; position: relative; border-radius: 1px 3px 3px 1px; overflow: hidden; box-shadow: 1px 0 0 #f6f2e8, 2px 1px 0 #e6dfcd, 3px 2px 0 rgba(0,0,0,.14), 4px 8px 14px rgba(0,0,0,.18); }
.j2pc-bk img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.j2pc-pt { font: 16px / 1.25 Georgia, serif; }
.j2pc-pq { font: italic 14px / 1.4 Georgia, serif; margin-top: 6px; color: var(--j2-ink2); }
.j2pc-shelves { display: grid; gap: 10px; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); }
.j2pc-shelf { border-radius: 16px; padding: 14px; min-height: 64px; display: flex; flex-direction: column; justify-content: flex-end; color: #1d1a15; }
.j2pc-shelf b { font: 600 13px var(--ui-font); } .j2pc-shelf span { font: 11px var(--ui-font); opacity: .6; }
.j2pc-note { border-left: 3px solid #c4501d; padding: 4px 0 4px 12px; }
.j2pc-nw { font: 12px var(--ui-font); color: var(--j2-ink2); } .j2pc-nt { font: 15px / 1.45 Georgia, serif; margin: 4px 0; } .j2pc-nr { font: 13px var(--ui-font); color: var(--j2-ink2); padding-left: 12px; border-left: 2px solid var(--j2-line); }
.j2pc-voices { display: grid; gap: 8px; }
.j2av[data-ppwho] { cursor: pointer; }
/* the Library's communities, under the shelves */
#shelf#shelf .lib-comms .j2lc { display: grid; gap: 8px; padding: 0 var(--j2-gx); }
html:not([data-phone]) #shelf#shelf .lib-comms .j2lc { grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); }
#shelf#shelf .lib-comms [data-ppopen] { cursor: pointer; }
/* the top row's ground, deeper: the shelf never shows under the buttons */
html[data-j2sc] .phhead { background: linear-gradient(to bottom, var(--bg) 0%, var(--bg) 78%, color-mix(in srgb, var(--bg) 0%, transparent) 100%) !important; padding-bottom: 30px !important; }
/* L12: in the Mac app the people doors live in the title bar */
html[data-tauri]:not([data-phone]) .j2-ic.j2-people { display: none !important; }
/* Recent: the cover opens on its hinge as you pass, as in All books and the master (Osca 17 Oct 10:23) */
#shelf#shelf .lib-new .nw-item[data-obj] .tilecov { perspective: 900px; overflow: visible !important;
  background: repeating-linear-gradient(#fbf8f0 0 3px, #ebe5d6 3px 3.6px) !important; }
#shelf#shelf .lib-new .nw-item[data-obj] .tilecov .covimg, #shelf#shelf .lib-new .nw-item[data-obj] .tilecov > .cv.fcov { transform-origin: 0 50%; transition: transform .7s cubic-bezier(.2,.9,.2,1), box-shadow .7s; border-radius: 1px 3px 3px 1px; backface-visibility: hidden; }
#shelf#shelf .lib-new .nw-item[data-obj]:hover .tilecov .covimg, #shelf#shelf .lib-new .nw-item[data-obj]:hover .tilecov > .cv.fcov { transform: rotateY(-34deg); box-shadow: 8px 4px 14px rgba(0,0,0,.25); }
#shelf#shelf .lib-new .nw-item[data-obj]:hover .tilecov { transform: none !important; }
#shelf#shelf .lib-new .nw-item[data-obj] .tilecov::after { z-index: 3; pointer-events: none; }
#shelf#shelf .lib-new .nw-item[data-obj]:hover .tilecov::after { opacity: 0; transition: opacity .3s; }
/* Recent starts at the margin: the strip snaps, and a snap with no padding of its own had pulled the first book to the
   window's edge (Osca's shot, 17 Oct) */
#shelf#shelf .lib-new .libstrip { scroll-padding-left: var(--j2-gx); scroll-padding-right: var(--j2-gx); }

/* D110: Accept / Decline on a message request */
.j2pp-reqb { display: flex; gap: 6px; flex: none; }
.j2pp-reqb .j6-b { padding: 6px 12px; font-size: 13px; }
