/* ============================================================================
   THE PROTOTYPE'S STYLESHEET, TAKEN VERBATIM.
   Copied out of barlins-books-prototype.html, not re-implemented. Pixel-perfect is not reachable by
   rewriting 480 selectors by hand — I tried, and produced a site that looked like a different design
   with the same colours.

   DO NOT hand-edit this file. It is generated by scripts/sync-prototype-css.mjs. Change the
   prototype, re-run the sync, and the change arrives here — so the prototype stays the single
   source of truth for how the site looks, which is what it was built to be.

   Two things the prototype has that the real site does not need, left in place because removing
   them risks changing something else: the dev bar (.devbar), and the boot-failure banner used
   because the whole prototype is one script block.
   ============================================================================ */


:root{
  --navy:#112D4A; --navy-d:#0B1F35; --gold:#917237; --gold-t:#7E6330; --sand:#CDC29F; --char:#2B2B2B;
  --paper:#FFFFFF; --paper-2:#F7F4EE; --line:#E4DDCE; --mute:#7A736A; --maxw:1320px;
  --sans:'Inter',system-ui,sans-serif; --display:'Bricolage Grotesque',system-ui,sans-serif;
  --disp2:'Instrument Serif',Georgia,serif; --serif:'Source Serif 4',Georgia,serif;
}
*{box-sizing:border-box;margin:0;padding:0}
body{background:var(--paper);color:var(--char);font-family:var(--sans);-webkit-font-smoothing:antialiased;overflow-x:hidden}
body:before{content:"";position:fixed;inset:0;pointer-events:none;z-index:1;opacity:.05;
  background-image:radial-gradient(circle at 1px 1px,rgba(90,70,40,.5) 1px,transparent 0);background-size:4px 4px}
a{color:inherit;text-decoration:none}
button{font-family:inherit}
.wrap{max-width:var(--maxw);margin:0 auto;padding:0 28px;position:relative;z-index:2}

/* ---------- prototype chrome ---------- */
.protobar{background:var(--navy-d);color:#8399B0;font:500 11px/1 var(--sans);letter-spacing:.1em;
  text-transform:uppercase;padding:9px 20px;display:flex;align-items:center;gap:14px;flex-wrap:wrap;position:relative;z-index:70}
.protobar b{color:#fff;font-weight:700}
.protobar .jump{display:flex;gap:5px;flex-wrap:wrap;margin-left:auto}
.protobar .jump a{color:#8399B0;border:1px solid #24466B;border-radius:999px;padding:4px 10px;letter-spacing:.06em}
.protobar .jump a:hover{color:#fff;border-color:#3C6693}
.protobar .jump a.on{background:var(--sand);color:var(--navy-d);border-color:var(--sand);font-weight:700}
.protobar .scrub{border:1px solid #24466B;background:transparent;color:#8399B0;border-radius:999px;
  padding:4px 10px;font:600 10px var(--sans);letter-spacing:.1em;text-transform:uppercase;cursor:pointer}
.protobar .scrub.on{background:#B23A2E;border-color:#B23A2E;color:#fff}
body.scrub a[href^="#/"],body.scrub .clk{outline:1.5px dashed rgba(178,58,46,.55);outline-offset:2px;border-radius:2px}

/* freshness stamp riding along in a section subtitle */
.sh .sub .fresh{margin-left:10px;padding-left:11px;border-left:1px solid var(--line)}

/* ---------- bottom-of-home search: full-width field under a standard section head ---------- */
.findbar{padding:2px 0 4px}
.fb-in{display:flex;align-items:center;gap:12px;border:2px solid var(--navy);border-radius:8px;
  padding:15px 18px;cursor:text;color:var(--navy);background:#fff;transition:box-shadow .15s}
.fb-in:hover{box-shadow:0 3px 12px rgba(17,45,74,.1)}
.fb-in:focus-within{box-shadow:0 0 0 3px rgba(17,45,74,.12)}
.fb-in input{flex:1;border:0;outline:0;background:transparent;font:400 17px var(--sans);color:var(--char);min-width:0}
.fb-in input::placeholder{color:#A79E90}
.fb-chips{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px}
.fb-chips a{border:1px solid var(--line);border-radius:999px;padding:6px 13px;font:500 12.5px var(--sans);color:#5A554E}
.fb-chips a:hover{border-color:var(--navy);color:var(--navy)}
/* results sit under the field rather than in an overlay: the visitor who scrolled the whole
   page is one keystroke from an answer, with the page still around them for context. */
.homeres{margin-top:10px}
.homeres:empty{display:none}
.homeres .pgh{font:700 9.5px var(--sans);letter-spacing:.14em;text-transform:uppercase;color:var(--mute);
  padding:12px 2px 6px;border-top:1px solid var(--line);margin-top:8px}
.homeres .pgh:first-child{border-top:0;margin-top:0}
.homeres a{display:grid;grid-template-columns:38px 1fr auto;gap:13px;align-items:center;
  padding:8px 10px;border-radius:5px;color:inherit}
.homeres a:hover{background:var(--paper-2)}
.homeres a img{width:38px;aspect-ratio:2/3;object-fit:cover;border-radius:2px;display:block}
.homeres .ent{width:38px;height:38px;border-radius:5px;background:var(--paper-2);color:var(--navy);
  display:flex;align-items:center;justify-content:center;font:700 12px var(--sans)}
.homeres .t{font-family:var(--display);font-size:14.5px;font-weight:700;color:var(--navy);letter-spacing:-.02em}
.homeres .a{font-size:11.5px;color:var(--mute);margin-top:1px}
.homeres mark{background:rgba(145,114,55,.22);color:inherit;border-radius:2px}
.homeres .more{display:block;text-align:center;font:700 11px var(--sans);letter-spacing:.12em;
  text-transform:uppercase;color:var(--gold-t);padding:12px 0 2px}
.protonote{font:500 11.5px var(--sans);color:#8A6D2F;background:#FDF6E7;border:1px dashed var(--sand);
  border-radius:5px;padding:8px 12px;margin-top:14px;display:inline-block}

/* ---------- rank mark ---------- */
.mk{display:inline-flex;align-items:baseline;gap:.02em;font-family:var(--disp2);line-height:1;color:var(--navy);letter-spacing:-.02em}
.mk .h{font-size:.56em;opacity:.4}
.rb{display:inline-flex;align-items:center;justify-content:center;flex:none;box-sizing:border-box;border-radius:50%;
  background:#fff;border:2px solid var(--navy);box-shadow:0 3px 5px rgba(11,31,53,.2)}

/* ---------- header ---------- */
header{position:sticky;top:0;z-index:40;background:var(--navy);color:#fff}
/* The share row's confirmation, written by share.js. */
.sharemsg{font:500 12.5px var(--sans);color:var(--navy)}
.hrow{display:flex;align-items:center;gap:20px;height:56px}
/* two-line lockup — the tagline fits inside the existing 56px bar, so the sticky
   header costs no extra height on any page */
/* The wordmark. Was Bricolage 800 with a sand full stop — "BARLIN'S.BOOKS" — which read as a domain
   suffix rather than punctuation, exactly the wrong association for a site whose whole promise is that
   its URLs do not change. Now the display serif, light and widely tracked, which is closer to the
   lettering on the old Wix masthead. No .mark span rule any more: there is nothing to colour.

   SAND, not the brand gold. #917237 is mixed for gold on paper — the stars, the section labels — and
   on navy it measures 3.1:1, under the 4.5:1 floor for type this size. Sand is the palette's gold for
   dark grounds, which is why the search pill and the nav underline already wear it; at 7.9:1 the
   wordmark stays the most legible thing in the bar.

   The tagline takes the white the wordmark gave up, so the two lines still separate.

   HOVER goes to white, not to the nav's sand: sand is where the wordmark starts, so hovering to sand
   would do nothing at all. White is what every sand element in this bar does on hover — the search
   pill, and the tagline before this change — so the gesture is the bar's, even though the value
   differs from nav's. */
.mark{font-family:var(--disp2);font-weight:400;font-size:23px;letter-spacing:.13em;text-transform:uppercase;
  white-space:nowrap;line-height:1;display:block;color:var(--sand);transition:color .15s}
.mark em{display:block;font-family:var(--sans);font-style:normal;font-weight:500;font-size:11px;
  letter-spacing:.02em;text-transform:none;color:#fff;margin-top:4px}
.mark:hover{color:#fff}
/* The tagline used to be hidden below 720px. It is the one line on screen that says what this site is,
   and a phone is where that matters most, so it stays at every width now. */
/* The phone's menu button, hidden here. It sits in the markup on every page so that one header serves
   both widths, and the rule that reveals it lives in the phone block. */
.navtog{display:none}
nav{display:flex;gap:18px;font-size:13px;color:#B3C2D3}
nav a:hover{color:var(--sand)}
nav a.here{color:#fff;font-weight:600;border-bottom:2px solid var(--sand);padding-bottom:2px}
/* outlined in the same sand as the wordmark, and going white on hover the same way */
.s-trigger{margin-left:auto;display:flex;align-items:center;gap:9px;background:transparent;
  border:1px solid var(--sand);border-radius:999px;padding:7px 13px;color:var(--sand);
  font:500 12.5px var(--sans);cursor:pointer;white-space:nowrap;transition:.15s}
.s-trigger:hover{background:rgba(205,194,159,.16);border-color:#E4DCC5;color:#fff}

/* ---------- palette ---------- */
.scrim{position:fixed;inset:0;z-index:100;background:rgba(11,31,53,.55);backdrop-filter:blur(3px);display:none}
.scrim.open{display:block}
.palette{position:fixed;z-index:101;top:11vh;left:50%;transform:translateX(-50%);width:min(680px,92vw);
  background:#fff;border-radius:12px;box-shadow:0 40px 90px -20px rgba(11,31,53,.6);overflow:hidden;display:none}
.palette.open{display:block}
.pbar{display:flex;align-items:center;gap:13px;padding:18px 22px;border-bottom:1px solid var(--line)}
.pbar input{flex:1;border:0;outline:0;font:400 19px var(--sans);color:var(--char)}
.pbar .esc{font:600 10.5px var(--sans);color:var(--mute);border:1px solid var(--line);border-radius:4px;padding:3px 7px}
.presults{max-height:52vh;overflow-y:auto}
.pgh{font:700 9.5px var(--sans);letter-spacing:.15em;text-transform:uppercase;color:var(--mute);padding:14px 22px 8px}
.pitem{display:flex;align-items:center;gap:13px;padding:9px 22px;cursor:pointer}
.pitem.sel{background:var(--paper-2)}
.pitem img{width:32px;height:49px;object-fit:cover;border-radius:2px;flex:none;box-shadow:0 2px 6px rgba(17,45,74,.2)}
.pitem .ent{width:32px;height:32px;border-radius:5px;background:var(--paper-2);border:1px solid var(--line);flex:none;
  display:flex;align-items:center;justify-content:center;font:700 12px var(--sans);color:var(--navy)}
.pitem .t{font-size:14.5px;font-weight:600;color:var(--navy)}
.pitem .t mark{background:#FBF0CE;border-radius:2px}
.pitem .a{font-size:12px;color:var(--mute);margin-top:1px}
.pitem .s{margin-left:auto;font-size:12px;letter-spacing:1.5px;color:var(--gold);white-space:nowrap}
.pitem .s i,.s i{color:#DCD4C4;font-style:normal}
.pfoot{display:flex;gap:16px;border-top:1px solid var(--line);padding:11px 22px;font-size:11.5px;color:var(--mute);background:var(--paper-2)}
.pfoot kbd{font:600 10px var(--sans);background:#fff;border:1px solid var(--line);border-radius:3px;padding:2px 5px;margin-right:4px}
.pempty{padding:34px 22px;text-align:center}
.pempty b{font-family:var(--display);font-size:16px;color:var(--navy);display:block}
.pempty p{font-size:13px;color:var(--mute);margin-top:6px}

/* ---------- shared page furniture ---------- */
.phead{padding:22px 0 16px;border-bottom:1px solid var(--line)}
.crumbs{font-size:12px;color:var(--mute);margin-bottom:12px}
.crumbs a{color:var(--gold-t);font-weight:500}
.headrow{display:flex;align-items:flex-end;justify-content:space-between;gap:40px;flex-wrap:wrap}
h1{font-family:var(--display);font-size:clamp(32px,3.9vw,50px);font-weight:800;letter-spacing:-.04em;line-height:.96;color:var(--navy)}
h1 em{font-style:normal;color:var(--gold)}
.statline{display:flex;align-items:flex-end;padding-bottom:3px}
.statline div{padding:0 18px;border-left:1px solid var(--line)}
.statline div:first-child{border-left:0;padding-left:0}
.statline b{display:block;font-family:var(--display);font-size:21px;font-weight:800;color:var(--navy);letter-spacing:-.035em;line-height:1}
.statline span{font-size:10.5px;color:var(--mute);letter-spacing:.08em;text-transform:uppercase;margin-top:4px;display:block}
.intro{font-family:var(--serif);font-size:15.5px;line-height:1.55;color:#4A453E;margin-top:12px;max-width:88ch}
/* The post's own introduction, which is several paragraphs rather than one line. */
.listintro p+p{margin-top:11px}
/* A list whose entries carry a paragraph of commentary makes a much taller row, and --covw is
   derived from a two-line title plus a short verdict — so the cover ends up floating in white space.
   Same rule, bigger text block. Inside a min-width query so the phone's fixed 56px cover still wins. */
/* Both selectors, every time: the rowhead is a SIBLING of #rows, so a variable set on the rows alone
   leaves the header's columns at the default width and its labels drift off the columns they name. */
@media(min-width:761px){#rows.prose,.rowhead.prose{--covw:calc(var(--txth)/1.05)}}
/* An author ranking — Emily Henry — is the extreme case: several paragraphs per entry and no year
   column, so the jacket goes bigger again and the write-up runs the full width of its column instead
   of stopping at 74ch. AFTER .prose, because both are (1,1,0) and order is what decides.
   The rowhead carries the same class or its labels stop lining up with the columns beneath them. */
@media(min-width:761px){
  #rows.roomy,.rowhead.roomy{--covw:calc(var(--txth)/0.75)}
  #rows.roomy .lrow .v{max-width:none}
  /* The introduction widens with the rows beneath it. 104ch rather than none: past roughly 90
     characters a reader starts losing the line they are on when their eye returns to the left margin,
     which is why .intro caps at 88ch in the first place. This is a deliberate step past that, not the
     removal of the idea — the rows can run full width because each line is short; a paragraph cannot. */
  .listintro.roomy{max-width:104ch}
}
@media(min-width:1041px){
  #rows.roomy .lrow,.rowhead.roomy{grid-template-columns:62px var(--covw) 1fr 92px 104px}
}
.intro a{color:var(--gold-t);font-weight:600;font-family:var(--sans);font-size:13px}
/* On its own line. It is a control, not part of the sentence, and it was wrapping into the prose so
   that "How" sat at the end of a line about closing out the year. Scoped to this one link rather than
   applied to .intro a, because the authored preambles on the list pages carry links INSIDE their
   prose and those must stay inline. */
.sh{display:flex;align-items:baseline;gap:16px;padding:28px 0 14px}
.sh h2{font-family:var(--display);font-size:22px;font-weight:800;letter-spacing:-.035em;color:var(--navy);white-space:nowrap}
.sh .sub{font-size:13px;color:var(--mute)}
.sh .ln{flex:1;height:1px;background:var(--line)}
.sh .go{font-size:12px;letter-spacing:.13em;text-transform:uppercase;font-weight:700;color:var(--gold-t);white-space:nowrap}
.sh.sm h2{font-size:19px}
.years{display:flex;gap:6px;flex-wrap:wrap;padding:14px 0 0;align-items:center}
.years .lbl{font:700 9.5px var(--sans);letter-spacing:.16em;text-transform:uppercase;color:var(--mute);margin-right:6px}
.years a{border:1px solid var(--line);border-radius:999px;padding:7px 14px;font:600 13px var(--sans);color:#5A554E}
.years a:hover{border-color:var(--sand)}
.years a.on{background:var(--navy);border-color:var(--navy);color:#fff}
.years a.themed{border-style:dashed;color:var(--gold-t)}
/* list-years read as a distinct set so the two destinations don't look identical */
.years a.lst{background:var(--paper-2);color:#6E6659}
.years a.lst:hover{border-color:var(--gold);color:var(--gold-t)}
.years a.lst.on{background:var(--gold);border-color:var(--gold);color:#fff}
.years .ydiv{width:1px;height:20px;background:var(--line);margin:0 8px}
.years .lbl+a{margin-left:0}
.nc{width:100%;aspect-ratio:2/3;border-radius:2px;background:linear-gradient(160deg,#1B3E63,#0B1F35);
  display:flex;align-items:center;justify-content:center}
.nc span{font-family:var(--disp2);font-size:17px;color:rgba(205,194,159,.55)}
.stars{font-size:14.5px;letter-spacing:2px;color:var(--gold)}
/* The unearned stars are an <i> inside the run. This rule was scoped to ".s i", so any star
   run using .stars alone — the rating group headers — rendered all five in gold. */
.stars i{color:#DCD4C4;font-style:normal}
.bdg{display:inline-flex;font:700 9px var(--sans);letter-spacing:.1em;text-transform:uppercase;padding:3px 6px;border-radius:3px}
.b-arc{background:var(--gold);color:#fff}
.b-new{background:var(--paper-2);color:var(--navy);border:1px solid var(--sand)}

/* filters */
.tools{position:sticky;top:56px;z-index:20;background:rgba(255,255,255,.96);backdrop-filter:blur(10px);
  border-bottom:1px solid var(--line);padding:12px 0;margin-top:18px}
.toolrow{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.pill{border:1px solid var(--line);background:transparent;border-radius:999px;padding:7px 13px;font:500 12.5px var(--sans);color:#5A554E;cursor:pointer}
.pill:hover{border-color:var(--sand)}
.pill.on{background:var(--gold);border-color:var(--gold);color:#fff}
.spacer{flex:1}
.count{font-size:12.5px;color:var(--mute)}
.fresh{display:inline-flex;align-items:center;gap:7px;font-size:12px;color:var(--gold-t);font-weight:600;white-space:nowrap}
.fdot{width:7px;height:7px;border-radius:50%;background:var(--gold);animation:p 2.4s infinite;flex:none}
@keyframes p{0%,100%{opacity:1}50%{opacity:.3}}
.sel{position:relative;display:inline-flex;align-items:center}
.sel select{appearance:none;border:1px solid var(--line);border-radius:999px;background:#fff;padding:7px 30px 7px 13px;
  font:500 12.5px var(--sans);color:#5A554E;cursor:pointer}
.sel.active select{background:var(--navy);border-color:var(--navy);color:#fff}
.sel:after{content:"▾";position:absolute;right:12px;font-size:11px;color:var(--mute);pointer-events:none}
.sel.active:after{color:#fff}
.resorted{display:flex;align-items:center;gap:10px;background:var(--paper-2);border:1px solid var(--sand);
  border-radius:5px;padding:9px 14px;margin:12px 0 0;font-size:12.5px;color:#5A554E}
.resorted b{color:var(--navy)}
.resorted button{margin-left:auto;border:1px solid var(--navy);background:var(--navy);color:#fff;border-radius:999px;
  padding:5px 12px;font:600 11.5px var(--sans);cursor:pointer}
body.resort .lrow .mk{opacity:.45}

/* the shared list row */
/* Cover height is derived from the text block, not guessed. --txth is the worst-case
   text height (2-line title + author + 2-line verdict), so it's a constant — which is
   what lets every cover share one width while still ending exactly at the verdict's
   last line. Deriving it from the type scale means changing a font size below moves
   the covers with it. No JS, and nothing to re-measure on resize. */
:root{
  --t-fs:18px;   --t-lh:1.12;
  --a-fs:16px;   --a-lh:1.2;  --a-mt:3px;
  --v-fs:14px;   --v-lh:1.5;  --v-mt:8px;
  --txth:calc(2*var(--t-fs)*var(--t-lh) + var(--a-mt) + var(--a-fs)*var(--a-lh)
              + var(--v-mt) + 2*var(--v-fs)*var(--v-lh));
  --covw:calc(var(--txth)/1.5);   /* 2:3 covers */
}
.lrow{display:grid;grid-template-columns:62px var(--covw) 1fr 104px 124px;gap:18px;align-items:start;
  padding:15px 10px;border-bottom:1px solid #EDE7DB;border-radius:3px}
.lrow:hover{background:var(--paper-2)}
/* No rank to show means no column reserved for one, rather than an empty 62px gutter. */
.lrow.norank,.rowhead.norank{grid-template-columns:var(--covw) 1fr 104px 124px}
.lrow .mk{font-size:27px;padding-top:2px}
.lrow .cv{position:relative;container-type:inline-size}
/* A single review covering two books (Greg titles them "A + B" and ranks them as one entry).
   Both covers share the one cover column, overlapped, so the row keeps its rhythm and the
   rank stays unambiguous — one rank, one row, two books. */
.lrow .cv.duo{display:flex}
.lrow .cv.duo img{width:76%}
.lrow .cv.duo img:last-child{margin-left:-52%;box-shadow:-3px 2px 10px rgba(17,45,74,.28)}
.lrow img{display:block;width:100%;aspect-ratio:2/3;object-fit:cover;border-radius:2px;box-shadow:0 2px 8px rgba(17,45,74,.18)}
/* min-height, never height: --txth is a FLOOR, so two-line rows land exactly on the
   cover's bottom edge, and anything longer simply grows the row. Nothing is ever
   truncated — which matters most on tablet/mobile, where a narrow text column pushes
   most verdicts to three or four lines. */
.lrow .txt{min-height:var(--txth);display:flex;flex-direction:column}
.lrow .t{font-family:var(--display);font-size:var(--t-fs);font-weight:700;color:var(--navy);
  letter-spacing:-.028em;line-height:var(--t-lh)}
.lrow .t .bdg{display:inline-block;margin-left:8px;vertical-align:2px}
.lrow:hover .t{color:var(--gold-t)}
.lrow .a{font-family:var(--disp2);font-size:var(--a-fs);line-height:var(--a-lh);color:var(--mute);margin-top:var(--a-mt)}
/* margin-top:auto bottom-aligns the verdict while there's slack, and quietly collapses
   to zero once the text outgrows the floor — so the alignment is a bonus, not a cage */
.lrow .v{font-family:var(--serif);font-size:var(--v-fs);color:#4A453E;line-height:var(--v-lh);
  margin-top:auto;padding-top:var(--v-mt);max-width:74ch}
.lrow .s{padding-top:3px}
/* The phone's stars-and-pills line. Present in every row's markup and displayed only there — see
   listRow for why the pills are emitted twice and why exactly one copy is ever displayed. */
.mrow{display:none}
.tag+.tag{margin-top:3px}
.tag{display:inline-block;font-size:11px;color:#5A554E;background:var(--paper-2);border:1px solid var(--line);border-radius:3px;padding:3px 8px}
.rowhead{display:grid;grid-template-columns:62px var(--covw) 1fr 104px 124px;gap:18px;padding:14px 10px 10px;
  font:700 10.5px var(--sans);letter-spacing:.12em;text-transform:uppercase;color:var(--mute);border-bottom:2px solid var(--navy)}

/* covers + wall */
.bleed{--bkw:174px;width:100vw;margin-left:calc(50% - 50vw);position:relative}
/* Two things fixed here. (1) padding-top was 18px, but overflow-x:auto makes overflow-y auto
   too, so anything above the padding box is CLIPPED — the mark now hangs 21px up and rises
   another 9px on hover, so it needs 30px of headroom. (2) margin-top was -18px, pulling the wall
   up under the section head; with the mark occupying that space it collided with the heading and
   its rule. Removing the negative margin lets the padding do its job: the assembly's highest
   point, on hover, still clears the section head's box. */
.wall{display:flex;gap:18px;overflow-x:auto;scroll-behavior:smooth;padding:34px max(28px,calc((100vw - var(--maxw))/2 + 28px)) 14px;scrollbar-width:none}
.wall::-webkit-scrollbar{display:none}
.bk{flex:0 0 var(--bkw);display:block;position:relative}
.bk .ph{position:relative;transition:transform .2s cubic-bezier(.2,.7,.3,1);container-type:inline-size}
.bk img{width:100%;aspect-ratio:2/3;object-fit:cover;border-radius:2px;box-shadow:0 1px 2px rgba(17,45,74,.2),0 13px 22px -11px rgba(17,45,74,.5)}
.bk:hover .ph{transform:translateY(-9px)}
/* A tile for a book with no review of its own — 2019's eleven, whose write-ups live in the list post.
   It keeps the row's rhythm and loses only the affordances that would lie: no lift, no pointer. */
.bk.noreview{cursor:default}
.bk.noreview:hover .ph{transform:none}
/* top comes from badge()'s inline style now, because the two .bk contexts need different lifts
   (their marks are 42px and 38px). left stays here — it is the same in both. */
.bk .rb{position:absolute;top:-12px;left:-10px;z-index:3}
.bk h4{font-family:var(--display);font-size:14.5px;font-weight:700;line-height:1.18;margin-top:12px;color:var(--navy);letter-spacing:-.02em}
.bk .a{font-size:11.5px;color:var(--mute);margin-top:3px}
/* Best of <year> — a single-line tab, flush left, near-flush top, notched right edge.
   TWO stacked clipped layers, because the notch is a clip-path and a clip-path cuts a border off
   along with everything else — so the white outline has to be its own layer underneath.
   white-space:nowrap and cqw padding are both load-bearing: with % padding the text wrapped and
   the tab doubled in height. The outline width has a px floor because .7cqw is half a pixel on a
   46px palette cover. Sits UNDER the rank mark's z-index so the mark keeps its corner. */
.bestof{position:absolute;left:0;top:2%;z-index:2;
  --bw:max(1px,.7cqw); --notch:3.8cqw; --inset:4cqw;
  background:#fff;
  padding:var(--bw) var(--bw) var(--bw) 0;
  clip-path:polygon(0 0,100% 0,calc(100% - var(--notch)) 50%,100% 100%,0 100%)}
/* --inset pushes the WORDS clear of the rank mark while the ribbon itself still starts at the
   cover edge — so the mark overlaps the ribbon's blunt left end and it reads as a ribbon coming
   out from behind the mark rather than a separate tab sitting next to it.
   The inset is in PIXELS and comes from the call site, because the mark is px-sized (38 in the
   ranking strip, 42 on the home wall, 30 in a collapsed carousel item) while the cover is not.
   A single cqw value cannot clear all of them without leaving a hole on the widest cover. */
/* Centred on the mark rather than offset from the cover top: top puts the ribbon's box at the
   mark's centre line and translateY(-50%) pulls it back by half its OWN height, so the two share
   a centre line no matter what the ribbon's height resolves to. */
.bestof.onmark{top:var(--centre);transform:translateY(-50%)}
.bestof i{display:block;font-style:normal;background:var(--navy);color:#fff;white-space:nowrap;
  padding:1.7cqw 6.5cqw 1.7cqw var(--inset);
  font:800 5.9cqw/1 var(--sans);letter-spacing:.055em;
  clip-path:polygon(0 0,100% 0,calc(100% - var(--notch)) 50%,100% 100%,0 100%)}
.bestof b{color:var(--sand)}
/* GOLD, matching the Advance copy banner at the top of the review — the two are saying the same
   thing about the same book, and in navy the strip read as chrome rather than as part of that
   statement. Flat gold rather than the arcbar's gradient: on a 56px-wide list cover a gradient is
   invisible, and one component should not need two treatments to look the same at both sizes. */
.covb{position:absolute;left:0;right:0;bottom:0;background:var(--gold);color:#fff;font-family:var(--sans);
  font-weight:800;font-size:7cqw;letter-spacing:.04em;text-align:center;padding:2.6% 4%;line-height:1.15}
/* Pinned to the edges of the scroll viewport, not to the content edge. They used to sit
   centered on the content edge — where the first and last covers begin — so covers slid
   directly underneath them as you scrolled. Now they hold the gutter and the covers pass
   behind a fade instead. Vertical center is derived from the cover height (--bkw x 1.5)
   rather than a magic 104px, so it stays centered if the cover size changes. */
/* Rails are now a component rather than one hand-wired wall. Any .rail gets arrows, edge
   fades and end-detection from the same code; the only thing that varies is the height of the
   thing being scrolled, so the arrow's vertical centre is a variable the rail sets. */
.rail{--arrtop:calc(34px + (var(--bkw) * 1.5 / 2) - 19px)}   /* tracks .wall's padding-top */
/* NO scroll-snap. It was here as a nicety and it broke the rail on load: the first card had
   scroll-snap-align:start, and the scroller's big left padding (which is what lines the first
   card up with the page's text column) is not part of the snap area — so the browser scrolled
   the rail by that padding on first layout to satisfy the snap. Two visible symptoms, one
   cause: the first card sat clipped at the left edge, and scrollLeft was no longer 0, so the
   back arrow and its fade correctly showed themselves for a scroll position nobody asked for.
   The cover wall has never had snapping and has never had either problem. */
.crail{display:flex;gap:18px;overflow-x:auto;scroll-behavior:smooth;
  padding:6px max(28px,calc((100vw - var(--maxw))/2 + 28px)) 12px;scrollbar-width:none}
.crail::-webkit-scrollbar{display:none}
.crail>.lc{flex:0 0 var(--lcw,262px)}
/* A one-card shelf is legitimate — a category with a single list still gets its own row. The
   arrows and fades hide themselves when there's nothing to scroll, so no special case. */
.rail:has(.crail){--arrtop:150px}
.arrow{position:absolute;top:var(--arrtop);
  width:38px;height:38px;border-radius:50%;background:#fff;border:1px solid var(--line);
  box-shadow:0 4px 16px rgba(17,45,74,.2);cursor:pointer;z-index:7;display:flex;align-items:center;justify-content:center;
  font:400 21px var(--disp2);color:var(--navy);padding-bottom:3px;transition:opacity .18s}
.arrow.prev{left:14px}
.arrow.next{right:14px}
.arrow[hidden]{display:none}
.arrow.off{opacity:0;pointer-events:none}
/* the covers run off both edges behind a soft fade, which is what makes an edge-pinned
   arrow read as an overlay rather than as something dropped on top of a cover */
.fade{position:absolute;top:0;bottom:0;width:76px;pointer-events:none;z-index:6;transition:opacity .18s}
.fade.l{left:0;background:linear-gradient(90deg,#fff 22%,rgba(255,255,255,0))}
.fade.r{right:0;background:linear-gradient(270deg,#fff 22%,rgba(255,255,255,0))}
.fade.off{opacity:0}
.hint{font-size:11.5px;color:var(--mute)}
/* A cover that will not load names itself. Sized to fill a cover slot so layout does not jump. */
.imgdead{display:flex;align-items:center;justify-content:center;text-align:center;width:100%;
  aspect-ratio:2/3;background:#FDF6F6;border:1px dashed #C98B86;border-radius:2px;padding:6px;
  font:600 8px/1.25 ui-monospace,SFMono-Regular,Menlo,monospace;color:#8C1D18;word-break:break-all;
  overflow:hidden}
.bootfail{margin:40px 0;border:1px solid #E4B9B9;border-left:3px solid #B3261E;border-radius:7px;
  background:#FDF6F6;padding:22px 24px}
.bootfail b{display:block;font-family:var(--display);font-size:19px;font-weight:800;color:#8C1D18}
.bootfail code{display:block;font:600 13px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;
  background:#fff;border:1px solid #E4B9B9;border-radius:5px;padding:10px 12px;margin:12px 0;color:#8C1D18}
.bootfail span{font-family:var(--serif);font-size:14.5px;color:#5A4A48}

/* ---------- entry-list month timeline ---------- */
.st{display:inline-flex;align-items:center;font:700 9.5px var(--sans);letter-spacing:.1em;
  text-transform:uppercase;padding:4px 8px;border-radius:3px;white-space:nowrap}
.st-read{background:var(--gold);color:#fff}
.st-arc{background:var(--navy);color:#fff}
.st-soon{background:var(--paper-2);color:#6E6659;border:1px solid var(--line)}
.mo{display:grid;grid-template-columns:132px 1fr;gap:28px;padding:26px 0;border-top:1px solid var(--line)}
/* Was .mo:first-of-type, which depends on SIBLING ORDER and so did different things on
   different pages: on the Most Anticipated timeline the first block really is the first div of
   its type, so it got the intended 2px navy rule; on the genre page a .sh subhead now precedes
   it, so it silently fell back to the faint 1px .mo border. Two appearances from one rule and
   nobody asked for either. Explicit classes now — .lead opens a set, .first opens one that
   already has a heading above it and needs no rule at all. */
.mo.lead{border-top:2px solid var(--navy)}
.mo.first{border-top:0}
.mo .mh{position:sticky;top:70px;align-self:start}
.mo .mh b{display:block;font-family:var(--display);font-size:23px;font-weight:800;color:var(--navy);letter-spacing:-.035em;line-height:1}
.mo .mh span{display:block;font-size:11px;color:var(--mute);margin-top:5px}
/* Same rule as the ranking rows: the cover's height IS the row's floor, so it fills the row
   vertically instead of floating in it. 110px wide x 1.5 = 165px; the longest real blurb here
   runs about four lines (153px), so every row currently lands exactly on the cover height and
   anything longer simply grows past it. */
.erow{--acovw:110px;display:grid;grid-template-columns:var(--acovw) 1fr 104px;gap:22px;padding:0 0 26px;
  min-height:calc(var(--acovw) * 1.5)}
.erow:last-child{padding-bottom:0}
.erow img{width:100%;aspect-ratio:2/3;object-fit:cover;border-radius:2px;display:block;box-shadow:0 2px 10px rgba(17,45,74,.22)}
/* Author under the title and the rating in its own right-hand column, matching .lrow — the whole
   site reads title-then-author, and this was the one list where they sat side by side. The 104px
   stars column is the same width as the ranked lists so the two line up when a reader moves
   between them. */
.erow .hd{display:block}
.erow h4{font-family:var(--display);font-size:19px;font-weight:700;color:var(--navy);letter-spacing:-.028em;line-height:1.1}
.erow .by{display:block;font-family:var(--disp2);font-size:16px;color:var(--mute);margin-top:3px}
.erow .pills{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:10px}
.erow .s{padding-top:3px;text-align:right}
.erow p{font-family:var(--serif);font-size:15px;line-height:1.6;color:#4A453E;margin-top:9px;max-width:80ch}
.erow .go2{display:inline-block}
.mo.rated .mh b.stars{display:block;font-size:19px;letter-spacing:2.5px;line-height:1}
.mo.rated .mh span{display:block;font-family:var(--display);font-size:15px;font-weight:800;
  color:var(--navy);letter-spacing:-.025em;margin-top:6px}
.mo.rated .mh span.ct{font-family:var(--sans);font-size:11px;font-weight:500;color:var(--mute);
  letter-spacing:0;margin-top:3px}
.mo.rated .rowhead{padding-top:0}
.mo.rated .lrow:first-of-type{border-top:0}
@media(max-width:900px){.mo{grid-template-columns:1fr;gap:10px}.mo .mh{position:static}
  /* Stars drop under the text rather than out of sight: on a release calendar the rating is
     half the reason to read a row, so it moves instead of being hidden like it is on .lrow. */
  .erow{--acovw:84px;gap:16px;grid-template-columns:var(--acovw) 1fr}
  .erow .s{grid-column:2;padding-top:9px;text-align:left}}

/* ---------- year navigation, directly under the wall ---------- */
.yearnav{display:flex;align-items:center;gap:9px;flex-wrap:wrap;padding:20px 0 4px;
  margin-top:14px;border-top:1px solid var(--line)}
.yearnav .lbl{font:700 9.5px var(--sans);letter-spacing:.16em;text-transform:uppercase;color:var(--mute);margin-right:4px}
.yearnav a{display:flex;align-items:baseline;gap:8px;border:1px solid var(--line);border-radius:999px;
  padding:8px 16px;font:600 14px var(--sans);color:var(--navy);transition:.15s}
.yearnav a i{font-style:normal;font-size:11.5px;font-weight:500;color:var(--mute)}
.yearnav a:hover{border-color:var(--navy);background:var(--paper-2)}
.yearnav a.themed{border-style:dashed;color:var(--gold-t);font-size:12.5px;font-weight:700;
  letter-spacing:.06em;margin-left:auto}
.yearnav a.themed:hover{border-color:var(--gold);background:transparent}

/* the thin years, demoted below the featured list cards */
.earlier{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:16px 0 0;margin-top:16px;
  border-top:1px solid var(--line)}
.earlier .lbl{font:700 9.5px var(--sans);letter-spacing:.16em;text-transform:uppercase;color:var(--mute);margin-right:4px}
.earlier a{display:flex;align-items:baseline;gap:7px;background:var(--paper-2);border:1px solid var(--line);
  border-radius:999px;padding:6px 13px;font:600 13px var(--sans);color:#5A554E}
.earlier a i{font-style:normal;font-size:11px;font-weight:500;color:var(--mute)}
.earlier a:hover{border-color:var(--gold);color:var(--gold-t)}
.earlier .note{flex:1;min-width:260px;font-family:var(--serif);font-size:13px;color:var(--mute);line-height:1.45}
@media(max-width:760px){.yearnav a.themed{margin-left:0}.earlier .note{min-width:0}}

/* subscribe */
.subctx{display:flex;align-items:center;gap:36px;background:var(--navy);color:#fff;border-radius:8px;padding:28px 32px;margin-top:24px}
.subctx b{font-family:var(--display);font-size:23px;font-weight:800;letter-spacing:-.03em;display:block;line-height:1.12}
.subctx p{font-size:13px;color:#9DB0C6;margin-top:8px;line-height:1.5;max-width:54ch}
.subctx form{display:flex;gap:8px;margin-left:auto;flex:none}
.subctx input{border:0;border-radius:4px;padding:12px 14px;font:400 13.5px var(--sans);width:240px}
.subctx button{background:var(--gold);color:#fff;border:0;border-radius:4px;padding:12px 20px;font:600 13px var(--sans);cursor:pointer}
footer{padding:34px 0 70px;font-size:12px;color:var(--mute);display:flex;justify-content:space-between;
  border-top:1px solid var(--line);margin-top:34px}
/* The social row is links now rather than three words that did nothing. */
.fsoc a{color:var(--mute)}
.fsoc a:hover{color:var(--gold-t)}

/* lists */
.lists{display:grid;grid-template-columns:repeat(4,1fr);gap:18px}
.lc{display:block;border:1px solid var(--line);border-radius:7px;overflow:hidden;background:#fff;transition:.18s}
.lc:hover{transform:translateY(-3px);box-shadow:0 16px 32px -14px rgba(17,45,74,.42);border-color:var(--sand)}
/* The composite shows only the covers that exist. It used to pad to eight with navy
   placeholder blocks, so a three-cover list rendered five empty slabs. --cols is set per card
   from the real count: up to four covers make one full-height row, more than four make two
   rows of ceil(n/2). Nothing is ever a stand-in for a book that isn't there. */
.mos{display:grid;grid-template-columns:repeat(var(--cols,4),1fr);gap:3px;background:var(--navy);padding:3px}
.mos img{width:100%;aspect-ratio:2/3;object-fit:cover;display:block}
/* a list with no covers yet: quiet paper, not a navy void */
.mos.needs{background:#FDF6E7;border-bottom:1px dashed var(--sand);aspect-ratio:3/2;padding:0 18px;
  display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:6px}
.mos.needs b{font-family:var(--display);font-size:17px;font-weight:800;color:#8A6D2F}
.mos.needs span{font:500 11.5px/1.45 var(--sans);color:#9A7F3F;max-width:24ch}
.lc.short{border-color:var(--sand)}
.lc.nopage{cursor:default;border-style:dashed}
.lc.nopage:hover{transform:none;box-shadow:none}
.lc.nopage .go2{color:#9A7F3F}
.lc .txt{padding:14px 16px 16px}
.lc h3{font-family:var(--display);font-size:16px;font-weight:800;letter-spacing:-.03em;line-height:1.15;color:var(--navy)}
.lc .m{font-size:11.5px;color:var(--mute);margin-top:5px}
.handoff.feat{display:flex;align-items:center;justify-content:space-between;gap:24px;margin-top:16px;
  background:var(--paper-2);border:1px solid var(--line);border-left:3px solid var(--gold);
  border-radius:7px;padding:18px 22px;transition:.15s}
.handoff.feat:hover{background:#fff;border-color:var(--sand);border-left-color:var(--gold)}
.handoff.feat b{display:block;font-family:var(--display);font-size:17px;font-weight:800;color:var(--navy);letter-spacing:-.03em}
.handoff.feat span.go2{margin-top:0;white-space:nowrap}
.handoff.feat>div span{display:block;font-family:var(--serif);font-size:14.5px;color:#4A453E;margin-top:5px;max-width:82ch}
.go2{font-size:11px;letter-spacing:.13em;text-transform:uppercase;color:var(--gold-t);font-weight:700;margin-top:10px}
.pin{display:inline-flex;font:700 9px var(--sans);letter-spacing:.1em;text-transform:uppercase;background:var(--gold);
  color:#fff;padding:3px 6px;border-radius:3px;margin-bottom:8px}
/* Filtered views are not lists — nobody wrote them and nobody ranked them. Giving them the
   same cover-mosaic card as an authored list would claim an equivalence that isn't there,
   so they get a flat text tile: same grid, visibly lighter weight. */
.fviews{display:grid;grid-template-columns:repeat(3,1fr);gap:18px}
.fv{display:block;background:var(--paper-2);border:1px solid var(--line);border-left:3px solid var(--sand);
  border-radius:7px;padding:16px 18px;transition:.15s}
.fv:hover{background:#fff;border-color:var(--sand);border-left-color:var(--gold)}
.fv h3{font-family:var(--display);font-size:15px;font-weight:800;letter-spacing:-.03em;color:var(--navy)}
.fv .m{font-family:var(--serif);font-size:13.5px;color:#4A453E;margin-top:4px}
/* The editorial queue is Greg's, not the reader's. It reads as a working document on purpose —
   flat, dense, no cover art, visibly not part of the published page. */
.queue{border:1px dashed var(--navy);border-radius:8px;background:#F7F9FC;padding:20px 22px;margin-bottom:30px}
.qhead b{display:block;font-family:var(--display);font-size:16px;font-weight:800;color:var(--navy);letter-spacing:-.03em}
.qhead span{display:block;font-family:var(--serif);font-size:13.5px;color:#4A453E;margin-top:4px;max-width:80ch}
.qhead code{font:600 12px ui-monospace,monospace;background:#fff;border:1px solid var(--line);border-radius:3px;padding:1px 4px}
.qb{margin-top:18px;padding-top:16px;border-top:1px solid #DFE6EF}
.qb h3{font-family:var(--display);font-size:14px;font-weight:800;color:var(--navy)}
.qgate{font-family:var(--serif);font-size:13px;color:#5A6472;margin:3px 0 10px}
.qgrp{display:flex;align-items:baseline;gap:12px;margin-bottom:7px}
.ql{flex:0 0 190px;font:700 10px var(--sans);letter-spacing:.11em;text-transform:uppercase;padding-top:4px}
.ql.ready{color:#1F7A4D}.ql.close{color:#8A6D2F}.ql.done{color:#7A8390}
.qchips{display:flex;flex-wrap:wrap;gap:6px}
.qc{display:inline-flex;align-items:center;gap:6px;font:600 12px var(--sans);border-radius:4px;padding:3px 8px;border:1px solid}
.qc i{font-style:normal;font-weight:800;font-size:10.5px;opacity:.7}
.qc.ready{background:#E8F5EE;border-color:#B8DCC8;color:#155F3B}
.qc.close{background:#FDF6E7;border-color:var(--sand);color:#7A5F28}
.qc.done{background:#F1F3F6;border-color:#DFE6EF;color:#5A6472}
.qnone{font-family:var(--serif);font-size:13px;color:#8A9099;padding-top:3px}
.qfoot{font-family:var(--serif);font-size:13.5px;color:#4A453E;margin-top:18px;padding-top:14px;border-top:1px solid #DFE6EF;max-width:88ch}
@media(max-width:760px){.qgrp{flex-direction:column;gap:5px}.ql{flex:none}}

/* coming soon */
.arcbox{border:1px solid var(--line);border-radius:7px;overflow:hidden}
.arcbox .ah{display:flex;align-items:center;justify-content:space-between;background:var(--paper-2);
  border-bottom:1px solid var(--line);padding:12px 18px;font-size:12.5px;color:var(--mute)}
.arcbox .ah a{color:var(--gold-t);font-weight:700;letter-spacing:.1em;text-transform:uppercase;font-size:11.5px}
.arow{display:grid;grid-template-columns:38px 1fr 150px 108px;gap:16px;align-items:center;padding:11px 18px;border-bottom:1px solid #F2EDE4}
.arow:last-child{border-bottom:0}
.arow:hover{background:var(--paper-2)}
.arow img{width:38px;height:58px;object-fit:cover;border-radius:2px;box-shadow:0 2px 8px rgba(17,45,74,.2)}
.arow .t{font-size:14.5px;font-weight:600;color:var(--navy)}
.arow .a{font-size:12px;color:var(--mute);margin-top:2px}
.arow .d{text-align:right;font-size:12px;font-weight:700;color:var(--gold-t)}
.arow .d span{display:block;font-weight:400;color:var(--mute);font-size:11px;margin-top:2px}
.handoff{display:flex;align-items:center;gap:16px;background:var(--paper-2);border:1px solid var(--line);border-radius:6px;padding:18px 22px;margin-top:20px}
.handoff b{font-family:var(--display);font-size:16px;font-weight:800;color:var(--navy);letter-spacing:-.025em}
.handoff span{font-size:12.5px;color:var(--mute);display:block;margin-top:3px}
.handoff a{margin-left:auto;background:var(--navy);color:#fff;border-radius:4px;padding:11px 18px;font:600 13px var(--sans);white-space:nowrap}

/* review page */
.artgrid{display:grid;grid-template-columns:1fr 348px;gap:56px;padding:20px 0 0;align-items:start}
.arcbar{background:linear-gradient(90deg,var(--gold),#A6853F);color:#fff;padding:11px 0;position:relative;z-index:4}
.arcbar .in{display:flex;align-items:center;gap:14px;font-size:13.5px;font-weight:500}
.arcbar b{font-weight:700;letter-spacing:.06em;text-transform:uppercase;font-size:11px;background:rgba(255,255,255,.22);padding:4px 9px;border-radius:3px}
.byauthor{font-family:var(--disp2);font-size:27px;color:var(--mute);margin-top:11px}
.byauthor a{color:var(--navy);border-bottom:1px solid var(--sand)}
.meta{display:flex;align-items:center;gap:11px;margin-top:16px;flex-wrap:wrap}
.spill{display:inline-flex;align-items:baseline;background:transparent;color:#5A554E;
  border:1px solid var(--line);border-radius:999px;padding:2px 9px;font:500 10.5px var(--sans);
  margin-left:7px;white-space:nowrap;vertical-align:1px}
.spill b{color:var(--navy);font-weight:700;font-size:10.5px}
.lrow:hover .spill{border-color:var(--sand)}
.ypill{display:inline-flex;align-items:baseline;background:var(--navy);color:#fff;border-radius:999px;
  padding:3px 9px;font:600 10.5px var(--sans);margin-left:9px;white-space:nowrap;vertical-align:1px}
.ypill b{color:var(--sand);margin-right:.28em;font-weight:700}
/* Search results are a dense stack — seven or eight rows of one author is the common case, which
   is exactly when the rank is the thing you want. A full-size navy pill repeated down the list
   fights the covers for attention, so results get a smaller, outlined variant: same information,
   same wording, quieter. */
.ypill.sm{background:transparent;color:#5A554E;border:1px solid var(--line);padding:1px 7px;
  font-size:9.5px;margin-left:7px}
.ypill.sm b{color:var(--navy);font-size:9.5px}
/* The award pill. Gold, because the ribbon on the cover is gold-on-navy and this is the same
   claim at row scale — the 60px cover is too small to carry the ribbon legibly, so in lists the
   distinction moves into the text where it can be read. Solid gold rather than outlined: unlike
   the rank pill it appears on a minority of rows, so it can afford to be loud. */
.bopill{display:inline-flex;align-items:baseline;background:var(--gold);color:#fff;border-radius:999px;
  padding:3px 9px 3px 8px;font:700 10.5px var(--sans);letter-spacing:.02em;margin-left:9px;
  white-space:nowrap;vertical-align:1px}
.bopill b{font-weight:800;margin-left:.3em}
.bopill.sm{padding:1px 7px 1px 6px;font-size:9.5px;margin-left:7px}
/* Audiobook is a fact about how HE consumed it, not a property of the book, so it stays quiet:
   outlined, grey, no fill. It sits beside the award pill without competing with it. */
/* align-items:BASELINE, like .bopill and .ypill beside it, and this is the reason: an inline-flex box
   takes its baseline from its first flex item, and with align-items:center that item was the SVG —
   which has no baseline, so the box fell back to reporting its bottom edge. Against a gold pill whose
   baseline comes from its text, that read as the audiobook pill sitting a couple of pixels high.
   The icon keeps its own centring with align-self, so only the box's baseline changes. */
.aupill{display:inline-flex;align-items:baseline;gap:4px;border:1px solid var(--line);border-radius:999px;
  padding:2px 8px;font:600 10px var(--sans);color:#5A554E;margin-left:7px;white-space:nowrap;vertical-align:1px}
.aupill svg{width:10px;height:10px;display:block;align-self:center}
.lrow:hover .aupill{border-color:var(--sand)}
.aupill.sm{font-size:9px;padding:1px 7px}
.chip.ghost.audio{display:inline-flex;align-items:center;gap:5px}
.chip.ghost.audio svg{width:12px;height:12px}
/* The diagonal corner ribbon was tried here and removed: on a 54-78px cover the band is about
   nine pixels deep and the date inside it is unreadable, so it registered as a smudge. The
   information moves to pills, which is also where it can carry two separate facts. */
.tbrcard{position:relative;container-type:inline-size}
/* Publication date: navy, white text, shown only for a book not yet out — "OUT NOV 17" is
   information, "OUT NOW" on a shelf of published books is noise. */
/* Page count: back to the original quiet treatment — sand border, near-white fill, navy text.
   Solid gold read as a second emphasis competing with the navy date pill; separating the two
   facts was the point, not making them equally loud. The date is the one you act on. */
.b-pages{background:var(--paper-2);color:var(--navy);border:1px solid var(--sand)}
/* No cover art on a book he hasn't read: a typographic card instead of a broken image or a
   placeholder repeated a dozen times. */
.nocov{aspect-ratio:2/3;border-radius:2px;background:var(--navy);color:#fff;display:flex;
  flex-direction:column;justify-content:flex-end;padding:9% 9% 11%;overflow:hidden}
.nocov b{font-family:var(--display);font-weight:800;font-size:13cqw;line-height:1.06;
  letter-spacing:-.03em;display:block}
.nocov span{font-family:var(--disp2);font-size:10cqw;color:var(--sand);margin-top:.5em;display:block}
.tbrempty{border:1px dashed var(--sand);border-radius:7px;background:var(--paper-2);
  padding:22px 24px;font-family:var(--serif);font-size:14.5px;color:#7A6A45;line-height:1.55}
.tbrempty b{display:block;font-family:var(--sans);font-weight:700;font-size:12.5px;color:var(--navy);
  letter-spacing:.02em;margin-bottom:5px}
/* Greg reads one and listens to another at the same time, so Reading Now is normally a pair.
   One book keeps the full-width card; two share the row. */
/* Greg's descriptions on the most-anticipated post run 200-600 characters — a real paragraph,
   sometimes two. So the cards are sized for prose, and the grid narrows as commitment falls:
   two-up for what he is reading and what he has committed to, three-up for the maybes, whose
   note is clamped. The full paragraph always lives on the book's own page. */
.tbrq{display:grid;grid-template-columns:1fr 1fr;gap:18px;align-items:start}
.tbrq.three{grid-template-columns:repeat(3,1fr)}
.tbrq .tcard{padding:17px 19px}
.tbrq .tcard .cw{width:64px}
.tbrq.three .tcard .cw{width:54px}
.tbrq .tcard h4{font-size:16px}
.tbrq.three .tcard h4{font-size:14.5px}
.tbrq .tcard .n{font-family:var(--serif);font-size:13.5px;line-height:1.5;color:#4A453E;margin-top:7px}
.tbrq.three .tcard .n{font-size:12.5px;
  display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;overflow:hidden}
@media(max-width:1000px){.tbrq,.tbrq.three{grid-template-columns:1fr 1fr}}
@media(max-width:700px){.tbrq,.tbrq.three{grid-template-columns:1fr}}
.nowgrid{display:grid;grid-template-columns:1fr 1fr;gap:18px;align-items:stretch}
.nowgrid .nowcard{align-items:flex-start}
.nowgrid .nowcard h3{font-size:21px}
.nowgrid .nowcard .cw{width:78px}
.nowgrid .nowcard .n{font-size:14px;max-width:none}
@media(max-width:900px){.nowgrid{grid-template-columns:1fr}}
/* Unmissable while sample rows are standing in. The page it replaced looked finished, which is
   why nobody noticed it was fiction. */
.samplebar{display:flex;align-items:center;gap:12px;background:#FDF3DE;border:1px solid var(--gold);
  border-left:4px solid var(--gold);border-radius:6px;padding:13px 18px;margin:18px 0 4px;
  font:500 13px/1.5 var(--sans);color:#6B5320}
.samplebar b{font-weight:800;letter-spacing:.1em;text-transform:uppercase;font-size:10px;
  background:var(--gold);color:#fff;border-radius:3px;padding:4px 8px;white-space:nowrap}
.pitem.sel .ypill.sm,.homeres a:hover .ypill.sm{border-color:var(--sand)}
.lrow:hover .ypill{background:var(--navy-d)}
.chip{display:inline-flex;background:var(--navy);color:#fff;border-radius:999px;padding:6px 13px;font-size:12px;font-weight:600}
/* the bold rank sat flush against the following word */
.chip b{margin-right:.28em;color:var(--sand)}
.chip.ghost{background:transparent;color:var(--navy);border:1px solid var(--line)}
a.chip:hover{background:var(--navy-d)}
a.chip.ghost:hover{border-color:var(--navy);background:var(--paper-2)}
.body .pending{background:var(--paper-2);border:1px dashed var(--sand);border-radius:6px;
  padding:15px 18px;margin:18px 0;font:500 13px/1.6 var(--sans);color:#7A6A45;max-width:74ch}
.byline{margin-top:14px;font-size:12.5px;color:var(--mute);border-bottom:1px solid var(--line);padding-bottom:18px}
.body{font-family:var(--serif);font-size:18.5px;line-height:1.68;color:#33302B;max-width:68ch;padding:24px 0 0}
.body p{margin-bottom:19px}
.body p:first-child:first-letter{font-family:var(--disp2);font-size:58px;float:left;line-height:.82;padding:4px 10px 0 0;color:var(--navy)}
.share{display:flex;align-items:center;gap:8px;margin-top:14px;flex-wrap:wrap;border-top:1px solid var(--line);padding-top:20px}
.share span{font:700 9.5px var(--sans);letter-spacing:.16em;text-transform:uppercase;color:var(--mute)}
.share .b{border:1px solid var(--line);background:#fff;border-radius:999px;padding:7px 13px;font:600 12px var(--sans);color:var(--navy);cursor:pointer}
.rail{display:flex;flex-direction:column;gap:18px}
/* Clear of the cover's shadow. .covertilt casts drop-shadow(0 28px 48px …), which reaches far below
   the jacket — further than the rail's 18px gap — so the first card under it sat inside the shadow
   and looked smudged rather than separated. The extra space is on the card after the cover only;
   every other gap in the rail stays 18px. */
.coverstage + .card{margin-top:42px}
.coverstage{position:relative;padding:4px 6px 0}
.covertilt{position:relative;container-type:inline-size;transform:rotate(-2deg);
  filter:drop-shadow(0 3px 5px rgba(17,45,74,.18)) drop-shadow(0 28px 48px rgba(17,45,74,.32))}
.covertilt img{width:100%;aspect-ratio:2/3;object-fit:cover;border-radius:3px;display:block}
.covertilt .sheen{position:absolute;inset:0;border-radius:3px;background:linear-gradient(102deg,rgba(255,255,255,.3),rgba(255,255,255,0) 36%)}
.covertilt .covb{border-radius:0 0 3px 3px}
.coverstage .rb{position:absolute;top:-12px;left:-12px;z-index:3}
.card{border:1px solid var(--line);border-radius:6px;overflow:hidden}
.card .ch{font:700 9.5px var(--sans);letter-spacing:.16em;text-transform:uppercase;color:var(--navy);
  background:var(--paper-2);border-bottom:1px solid var(--line);padding:11px 16px}
.card .cb{padding:16px}
.vcard{border-left:4px solid var(--gold)}
.vcard h2{font-family:var(--display);font-size:14.5px;font-weight:700;color:var(--navy);margin-bottom:8px;line-height:1.25}
.vcard p{font-family:var(--serif);font-size:16.5px;line-height:1.5}
.ifs>div{padding:14px 16px}
.ifs>div+div{border-top:1px solid var(--line)}
.ifs h3{font:700 10px var(--sans);letter-spacing:.14em;text-transform:uppercase;margin-bottom:7px}
.ifs .y h3{color:#2E6B4F}.ifs .n h3{color:#8E4034}
.ifs p{font-size:13.5px;line-height:1.55;color:#4A453E}
.cmp .r{display:flex;gap:11px;align-items:center;padding:10px 16px;border-bottom:1px solid #F1ECE2}
.cmp .r:last-child{border-bottom:0}
.cmp a.r:hover{background:var(--paper-2)}
.cmp img{width:34px;height:52px;object-fit:cover;border-radius:2px;flex:none;box-shadow:0 2px 7px rgba(17,45,74,.2)}
.cmp .ph2{width:34px;height:52px;background:var(--paper-2);border-radius:2px;flex:none}
.cmp .t{font-size:13.5px;font-weight:600;color:var(--navy);line-height:1.2}
.cmp .a{font-size:11.5px;color:var(--mute);margin-top:2px}
.cmp .un{font-size:10px;color:var(--mute);font-style:italic;margin-top:3px}
.qf table{width:100%;border-collapse:collapse;font-size:13px}
.qf td{padding:8px 16px;border-bottom:1px solid #F1ECE2}
.qf tr:last-child td{border-bottom:0}
.qf td:first-child{color:var(--mute);width:104px;font:700 11px var(--sans);letter-spacing:.04em;text-transform:uppercase}
.qf td b{color:var(--navy)}
h2.sec{font-family:var(--display);font-size:13px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:var(--navy);
  padding:40px 0 4px;border-bottom:2px solid var(--navy);margin-bottom:18px;display:flex;justify-content:space-between;align-items:baseline}
h2.sec span{font:400 12px var(--sans);letter-spacing:0;text-transform:none;color:var(--mute)}
.faq details{border-bottom:1px solid var(--line)}
.faq summary{cursor:pointer;padding:13px 0;font-size:15px;font-weight:600;color:var(--navy);list-style:none;display:flex;align-items:center;gap:16px}
.faq summary::-webkit-details-marker{display:none}
.faq summary>span{flex:1}
.faq summary:after{content:"+";font-family:var(--disp2);font-size:21px;color:var(--gold);flex:none}
.faq details[open] summary:after{content:"–"}
.faq p{font-family:var(--serif);font-size:16px;line-height:1.6;color:#4A453E;padding:0 0 15px;max-width:72ch}
/* Comment and sign-in styles (.cmt, .gate) deleted 28 July — comments deferred and no
   accounts anywhere in v1. Still present in review-page-v3.html if a later phase wants them. */
.hook{display:flex;align-items:center;gap:20px;background:var(--navy);color:#fff;border-radius:7px;padding:20px 26px;margin-top:24px}
.hook b{font-family:var(--display);font-size:18px;font-weight:800;letter-spacing:-.028em;display:block}
.hook p{font-size:13px;color:#9DB0C6;margin-top:6px;line-height:1.5;max-width:62ch}
.hook a{margin-left:auto;background:var(--gold);color:#fff;border-radius:5px;padding:12px 20px;font:600 13px var(--sans);white-space:nowrap}
/* ---------- "Keep going" carousel ---------- */
.carowrap{padding:4px 0 0}
/* Every cover sits in a fixed-height band, bottom-aligned inside it. Three things fall out of
   that at once: the unselected covers line up with each other, every title starts on the same
   baseline regardless of whether it wraps to two lines, and the selected cover — being taller
   — protrudes upward out of the row. A negative margin would have got the same offset but
   clipped against the container's padding. --caroh is the selected cover's height (168 x 1.5). */
.caro{--caroh:252px;
  display:flex;align-items:flex-start;gap:20px;overflow-x:auto;scroll-behavior:smooth;
  padding:26px max(28px,calc((100vw - var(--maxw))/2 + 28px)) 18px;scrollbar-width:none}
.caro::-webkit-scrollbar{display:none}
.caroi{flex:0 0 104px;position:relative;transition:flex-basis .22s cubic-bezier(.2,.7,.3,1),opacity .2s}
.caroi .ph{height:var(--caroh);display:flex;align-items:flex-end}
.caroi .cimg{position:relative;width:100%;container-type:inline-size}
.caroi img{width:100%;aspect-ratio:2/3;object-fit:cover;border-radius:2px;display:block;
  box-shadow:0 1px 2px rgba(17,45,74,.18),0 8px 16px -8px rgba(17,45,74,.4)}
/* The lift moves the WHOLE lockup, so it is on .cimg — which holds the cover, the rank mark and the
   Best of ribbon — and not on the img. On the img alone the cover rose and the mark and ribbon
   stayed behind, pulling the three apart on every hover. .bk:hover .ph does the same thing on the
   home wall; this was the odd one out. */
.caroi .cimg{transition:transform .2s cubic-bezier(.2,.7,.3,1)}
/* top is set inline from the mark's own size, so the mark's CENTRE lands on the cover's top edge —
   the same relationship the ranking strip uses (MARK_STRIP 38 lifted by LIFT_STRIP 19). The -10px
   here was a fixed lift that left a 42px mark sitting 11px low, and the ribbon rides the mark, so
   the ribbon sat 11px low with it. Kept as the fallback for a mark rendered without a lift. */
.caroi .cimg .rb{position:absolute;top:-10px;left:-8px;z-index:3}
/* identical type to the homepage wall (.bk h4 / .bk .a) — the cover size and opacity carry
   the emphasis, so the titles don't need to differ as well */
.caroi h5{font-family:var(--display);font-size:14.5px;font-weight:700;line-height:1.18;
  color:var(--navy);margin-top:12px;letter-spacing:-.02em;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.caroi .a{font-size:11.5px;color:var(--mute);margin-top:3px}
.caroi:not(.on){opacity:.62}
.caroi:not(.on):hover{opacity:1}
.caroi:not(.on):hover .cimg{transform:translateY(-5px)}
/* the book you're reading: larger cover, full strength */
.caroi.on{flex-basis:168px}
.caroi.on img{box-shadow:0 2px 4px rgba(17,45,74,.22),0 18px 30px -12px rgba(17,45,74,.55)}
.carofoot{display:flex;justify-content:center;padding-top:2px}
.carofoot a{font:700 11.5px var(--sans);letter-spacing:.12em;text-transform:uppercase;color:var(--gold-t)}
@media(max-width:760px){.caro{--caroh:198px}.caroi{flex:0 0 82px}.caroi.on{flex-basis:132px}}

.authbox{border:1px solid var(--line);border-radius:6px;overflow:hidden}
.authhd{background:var(--navy);color:#fff;padding:18px 22px;display:flex;justify-content:space-between;align-items:center}
.authhd h3{font-family:var(--display);font-size:19px;font-weight:800;letter-spacing:-.025em}
.authhd p{font-size:12.5px;color:#9DB0C6;margin-top:3px}
.authhd a{font-size:12.5px;color:var(--sand);font-weight:600}
.arank{display:flex;align-items:center;gap:15px;padding:13px 22px;border-bottom:1px solid #F1ECE2}
.arank:last-child{border-bottom:0}
.arank:hover{background:var(--paper-2)}
.an{width:46px;flex:none;display:flex;justify-content:center}
.arank img{width:38px;height:58px;object-fit:cover;border-radius:2px;flex:none;box-shadow:0 2px 8px rgba(17,45,74,.16)}
.arank .t{font-size:14.5px;font-weight:600;color:var(--navy)}
.arank .y{font-size:12px;color:var(--mute);margin-top:2px}
.arank .s{margin-left:auto;font-size:12.5px;white-space:nowrap}
.thisone{background:var(--paper-2)}
.pn{display:grid;grid-template-columns:1fr 1fr;gap:18px}
.pnc{border:1px solid var(--line);border-radius:6px;padding:18px 20px}
.pnc:hover{border-color:var(--navy);background:var(--paper-2)}
.pnc .lb{font:700 9.5px var(--sans);letter-spacing:.16em;text-transform:uppercase;color:var(--mute);margin-bottom:9px}
.pnc .r{display:flex;align-items:center;gap:13px}
.pnc .t{font-family:var(--display);font-size:16.5px;font-weight:700;color:var(--navy);letter-spacing:-.025em;line-height:1.15}
.pnc .a{font-size:12px;color:var(--mute);margin-top:3px}
.pnc.next{text-align:right}.pnc.next .r{flex-direction:row-reverse}

/* author page */
.take{font-family:var(--serif);font-size:16px;line-height:1.6;color:#463F36;margin-top:14px;max-width:82ch}
.take b{font-family:var(--sans);font-size:11px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;color:var(--gold-t);display:block;margin-bottom:6px}
.poles{display:grid;grid-template-columns:1fr 1fr;gap:18px;padding:22px 0 4px}
.pole{display:flex;gap:16px;border:1px solid var(--line);border-radius:7px;padding:16px 18px;align-items:center}
.pole.top{border-left:3px solid var(--gold)}.pole.bot{border-left:3px solid #C4B9A4}
.pole .lb{font:700 9.5px var(--sans);letter-spacing:.16em;text-transform:uppercase;color:var(--mute);margin-bottom:6px}
.pole .cw{width:52px;flex:none}
.pole h4{font-family:var(--display);font-size:17px;font-weight:800;color:var(--navy);letter-spacing:-.028em;line-height:1.12}
.pole .m{font-size:12px;color:var(--mute);margin-top:4px}
.chips{display:flex;flex-wrap:wrap;gap:9px}
.chips a{border:1px solid var(--line);border-radius:999px;padding:8px 15px;font-size:13px;color:#5A554E}
.chips a:hover{border-color:var(--navy);color:var(--navy)}
.chips a i{font-style:normal;font-size:11px;color:var(--mute);margin-left:6px}
.chips a.on{background:var(--navy);border-color:var(--navy);color:#fff}
.chips a.on i{color:var(--sand)}
/* .chips a.on was declared twice, identically, with the i rule sandwiched between. Harmless but
   it is the kind of duplicate that later gets edited in one place only. */
/* Every book leads the row as the broadest option and reads as the way out of the shelves. It is
   deliberately NOT the landing state: one real shelf shows what the page is for better than 311
   rows of everything. */
.chips a.every{font-weight:700;border-color:var(--sand)}
.chips a.every.on{border-color:var(--navy)}
.authgrid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px}
.ac{border:1px solid var(--line);border-radius:7px;padding:16px 18px}
.ac:hover{border-color:var(--sand);box-shadow:0 10px 24px -12px rgba(17,45,74,.34)}
.ac h4{font-family:var(--display);font-size:16px;font-weight:800;color:var(--navy);letter-spacing:-.028em}
.ac .m{font-size:12px;color:var(--mute);margin-top:5px}

/* TBR */
.nowcard{display:flex;gap:26px;border:1px solid var(--line);border-left:4px solid var(--gold);border-radius:7px;padding:22px 26px;align-items:center}
.nowcard .cw{width:100px;flex:none}
.nowcard img{width:100%;aspect-ratio:2/3;object-fit:cover;border-radius:3px;box-shadow:0 3px 6px rgba(17,45,74,.16),0 20px 34px -14px rgba(17,45,74,.4)}
.nowcard h3{font-family:var(--display);font-size:25px;font-weight:800;color:var(--navy);letter-spacing:-.032em;line-height:1.08}
.nowcard .by{font-family:var(--disp2);font-size:18px;color:var(--mute);margin-top:5px}
.nowcard .n{font-family:var(--serif);font-size:15px;line-height:1.55;color:#4A453E;margin-top:11px;max-width:62ch}
.nowcard .meta2{display:flex;gap:9px;margin-top:12px;flex-wrap:wrap}
.b-pick{background:var(--paper-2);color:var(--navy);border:1px solid var(--sand)}
.b-date{background:var(--navy);color:#fff}
.cards{display:grid;grid-template-columns:repeat(4,1fr);gap:18px}
.tcard{display:flex;gap:14px;border:1px solid var(--line);border-radius:7px;padding:15px;background:#fff}
.tcard:hover{border-color:var(--sand);box-shadow:0 12px 26px -14px rgba(17,45,74,.36)}
.tcard .cw{width:54px;flex:none}
.tcard img{width:100%;aspect-ratio:2/3;object-fit:cover;border-radius:2px;box-shadow:0 2px 8px rgba(17,45,74,.2)}
.tcard h4{font-family:var(--display);font-size:14.5px;font-weight:800;color:var(--navy);letter-spacing:-.026em;line-height:1.14}
/* The display serif, like every other author line on the site — the review header, the nowcard, the
   ranking rows. This was the one place an author was set in the sans body face at 12px, which is why
   the "Up next" cards read as a different component from the "Reading now" ones beside them. */
.tcard .by{font-family:var(--disp2);font-size:15px;color:var(--mute);margin-top:2px;line-height:1.2}
.tcard .n{font-size:12px;color:#5A554E;line-height:1.45;margin-top:6px}
.tcard .meta2{display:flex;gap:6px;margin-top:8px;flex-wrap:wrap}
.shelf .tcard{padding:12px}.shelf .tcard .cw{width:42px}.shelf .tcard .n{display:none}
.quiet{display:flex;align-items:center;gap:26px;background:var(--paper-2);border:1px solid var(--line);border-radius:7px;padding:22px 26px;margin-top:30px}
.quiet .qm{font-family:var(--disp2);font-size:74px;line-height:.72;color:var(--gold);opacity:.5;flex:none;align-self:flex-start;margin-top:4px}
.quiet blockquote{font-family:var(--disp2);font-size:23px;line-height:1.28;color:var(--navy);letter-spacing:-.012em;max-width:66ch}
.quiet cite{display:block;font-style:normal;font:700 10px var(--sans);letter-spacing:.16em;text-transform:uppercase;color:var(--gold-t);margin-top:10px}
.quiet .ask{font-size:13px;color:#5A554E;line-height:1.5;margin-top:10px}
.quiet .go3{margin-left:auto;background:var(--navy);color:#fff;border-radius:5px;padding:12px 20px;font:600 13px var(--sans);white-space:nowrap;flex:none;cursor:pointer}
.moved{display:grid;grid-template-columns:repeat(4,1fr);gap:16px}
.mv{border:1px solid var(--line);border-radius:7px;padding:15px 17px;background:var(--paper-2)}
.mv .lb{font:700 9px var(--sans);letter-spacing:.14em;text-transform:uppercase;color:#2E6B4F;margin-bottom:8px}
.mv h4{font-family:var(--display);font-size:15px;font-weight:800;color:var(--navy);letter-spacing:-.026em;line-height:1.14}
.mv .by{font-size:12px;color:var(--mute);margin-top:3px}

/* search page */

.hit.norank{grid-template-columns:44px 1fr 104px}
.hit{display:grid;grid-template-columns:56px 44px 1fr 104px;gap:16px;align-items:center;padding:12px 10px;border-bottom:1px solid #F1ECE2}
.hit:hover{background:var(--paper-2)}
.hit img{width:100%;aspect-ratio:2/3;object-fit:cover;border-radius:2px;box-shadow:0 2px 7px rgba(17,45,74,.18)}
.hit .t{font-size:15.5px;font-weight:600;color:var(--navy)}
.hit .a{font-size:12.5px;color:var(--mute);margin-top:2px}
.entity{display:flex;gap:12px;flex-wrap:wrap;padding:10px 0}
.entity a{border:1px solid var(--line);border-radius:7px;padding:12px 16px;min-width:190px}
.entity a:hover{border-color:var(--sand)}
.entity b{font-family:var(--display);font-size:15.5px;font-weight:800;color:var(--navy);letter-spacing:-.026em;display:block}
.entity span{font-size:12px;color:var(--mute);display:block;margin-top:4px}

@media(max-width:1100px){.lists,.cards,.moved,.authgrid,.fviews{grid-template-columns:1fr 1fr}.artgrid{grid-template-columns:1fr;gap:28px}
  .rail{order:-1}.coverstage{max-width:210px}.poles{grid-template-columns:1fr}}
/* Tablet: drop the genre column to buy the text back some width, and ease the type
   scale a notch. --covw is derived, so the covers shrink to match on their own. */
@media(max-width:1040px){
  :root{--t-fs:17px;--v-fs:13.5px}
  .lrow,.rowhead{grid-template-columns:56px var(--covw) 1fr 96px;gap:15px}
  .lrow>div:last-child,.rowhead>div:last-child{display:none}
}
@media(max-width:760px){
  /* nav is NOT hidden here any more — see the header block below. The rule that used to sit on this
     line is gone rather than overridden, so nobody has to read two contradictory declarations to
     find out whether a phone has navigation. */
  .s-trigger{margin-left:auto}
  /* At phone width the verdict runs four-plus lines no matter what, so bottom-alignment
     isn't reachable — pin the cover to a fixed size instead of deriving it. */
  :root{--covw:56px}
  .lrow{grid-template-columns:44px var(--covw) 1fr;gap:13px}
  /* ---- PHONE: the reading order of a row ----
     Title, author, pills, stars, teaser, genre — top to bottom, which is the order a reader needs them
     in. It cannot be done by ordering .txt, because the stars are a SIBLING of .txt and the pills are
     inside it: two containers again. display:contents dissolves .txt so its three parts and the stars
     become items of the row's own grid and can be placed individually.
     The genre cell comes back at the end. It was display:none here, and the two things a reader wants
     from a row they cannot see the cover of are the rating and the shelf. */
  /* ROW-GAP ZERO. The row's `gap:13px` is a grid shorthand, so it applied between every stacked item
     as well as between the columns — 13px between title and author, and again before the stars, the
     teaser and the genre. That is where the airy spacing came from. The column gap stays; each item
     brings its own top padding, so the amounts are visible in one place. */
  .lrow{row-gap:0}
  .lrow .txt{display:contents}
  .lrow .cv{grid-row:1 / span 5}
  .lrow .t{grid-column:3;grid-row:1}
  /* Author sits straight under the title: its own 3px and nothing else. */
  .lrow .a{grid-column:3;grid-row:2;margin-top:2px}
  /* Stars and pills share one wrapping line, stars first. The desktop copy of the pills — the one
     inline in .a — goes, and so does the standalone stars cell. */
  .lrow .a .pil{display:none}
  .lrow .s.stars{display:none}
  .lrow .mrow{display:flex;flex-wrap:wrap;align-items:center;gap:8px;grid-column:3;grid-row:3;padding-top:9px}
  .lrow .mrow .pil{display:flex;flex-wrap:wrap;gap:6px}
  .lrow .mrow .pil>*{margin-left:0}
  .lrow .mrow .stars{font-size:15px;letter-spacing:2px}
  /* "not yet reviewed" on Amazon's lists takes the same slot as the stars it stands in for. */
  .lrow .s.nrn{grid-column:3;grid-row:3;display:block;padding-top:9px}
  .lrow .v{grid-column:3;grid-row:4;margin-top:0;padding-top:11px}
  /* The genre cell: shown, last, and its tags side by side rather than stacked. */
  .lrow>div:last-child{display:flex;flex-wrap:wrap;gap:6px;grid-column:3;grid-row:5;padding-top:11px}
  .lrow>div:last-child .tag{margin-top:0}
  .lrow.norank .t,.lrow.norank .a,.lrow.norank .v,.lrow.norank .mrow,
  .lrow.norank .s.nrn,.lrow.norank>div:last-child{grid-column:2}

  /* All that survives from the first pass at this: the rank mark stays on row 1. The rest of what it
     did — moving .s under the text, sizing it down, hiding the genre cell — is handled by the
     reading-order block above, and its rules are DELETED rather than left to be overridden. The star
     sizing went with them: it styled .s.stars, which is display:none here now, so it was a rule
     dressing an invisible element. */
  .lrow .mk{grid-row:1}

  /* ---- PHONE: the header ----
     Three rows rather than one squeezed line: the wordmark with its tagline, then search at full width,
     with the four destinations behind a menu button top-right. The previous version put the nav on a
     visible second line — which worked, but spent a whole row on links most readers are not looking
     for, and left search as a small pill in the corner when it is the control they are. */
  /* 10px read as flush against the browser chrome above and the page below it. Three stacked rows need
     more air than one line did, and the navy band is the only place on a phone with room to give it. */
  /* header .hrow, not .hrow: this element is class="wrap hrow", and .wrap{padding:0 18px} sits later in
     this same block at equal specificity — so its SHORTHAND was resetting the vertical padding to
     zero, which is why the space under the search box never appeared. One extra element in the
     selector settles it; the horizontal padding still comes from .wrap. */
  header .hrow{flex-wrap:wrap;height:auto;padding-top:18px;padding-bottom:17px;row-gap:11px;align-items:center}
  .mark{flex:1 1 auto}
  .navtog{display:inline-flex;align-items:center;justify-content:center;order:2;flex:none;
    width:42px;height:42px;padding:0;background:transparent;color:var(--sand);
    border:1px solid var(--sand);border-radius:9px;cursor:pointer}
  header.open .navtog{background:rgba(205,194,159,.16);color:#fff;border-color:#E4DCC5}
  /* Search gets a full-width row: a 42px target across the screen is a different proposition from a
     pill in a corner. */
  .s-trigger{order:3;width:100%;margin-left:0;justify-content:flex-start;padding:11px 14px;font-size:13.5px}
  /* The menu. Closed by default, so with JavaScript off the header is a wordmark and a search box
     rather than a broken menu — see nav.js for why that trade was made deliberately. */
  nav{order:4;width:100%;display:none;flex-direction:column;gap:0;font-size:15.5px;
    border-top:1px solid rgba(205,194,159,.26);margin-top:2px}
  header.open nav{display:flex}
  /* 44px targets, and the desktop underline dropped: in a stacked list the current page reads as the
     white one, and an underline the width of the row looks like a divider. */
  nav a{padding:12px 2px;color:#C9D5E3}
  nav a.here{color:#fff;font-weight:600;border-bottom:0;padding-bottom:12px}

  /* ---- PHONE: stop iOS zooming on focus ----
     Safari zooms the viewport whenever a focused field is under 16px and does not zoom back out, so
     tapping the subscribe box threw the page sideways. 16px everywhere a finger can land. */
  .subctx input,.share .submini input,.sform input,.sform textarea,.sel select,.fb-in input,.pbar input
    {font-size:16px}

  /* ---- PHONE: the section header, which is what was pushing the page sideways ----
     .sh is one flex row — heading, subtitle, a flex:1 rule, then a link — and the heading is
     white-space:nowrap. "The Best Books of 2026" alone is about 250px at 22px; add the gaps and a
     nowrap "SEE ALL 59 →" and the row cannot go below roughly 400px against 355px of usable width.
     Every band on the home page and every section of a review has one, so the whole document ended up
     wider than the screen and the page scrolled sideways — which is what made the header look cut off
     and the review text run past the right edge. The row wraps now, the heading is allowed to break,
     and the divider rule is dropped because a 1px line adds nothing on a narrow screen. */
  .sh{flex-wrap:wrap;gap:5px 12px;padding:22px 0 12px}
  .sh h2{white-space:normal;font-size:19px;flex:1 1 100%}
  .sh .ln{display:none}
  .sh .sub{flex:1 1 auto}
  .sh .go{flex:0 0 auto}
  .sh .sub .fresh{display:block;margin:4px 0 0;padding-left:0;border-left:0}

  /* ---- PHONE: give the words some room ----
     28px of padding each side of a 411px screen leaves 355px, and 18.5px serif in 355px is about 35
     characters a line — a paragraph becomes a column of fragments. Narrower gutters and one step down
     the type scale, which together buy back roughly ten characters a line. */
  .wrap{padding:0 18px}
  :root{--t-fs:16.5px;--a-fs:14.5px;--v-fs:13px}
  .body{font-size:17px;line-height:1.62}
  .byauthor{font-size:23px}
  .intro{font-size:15px}

  /* ---- PHONE: three layouts that had no rule here at all ----
     All three are flex rows sized for a desktop: the year heading beside its stats, the stats in four
     columns, and the footer's copyright opposite its links. */
  .headrow{flex-direction:column;align-items:flex-start;gap:12px}
  .statline{flex-wrap:wrap;gap:12px 0;padding-bottom:0}
  .statline div:first-child{padding-left:0;border-left:0}
  footer{flex-direction:column;gap:12px;align-items:flex-start;padding-bottom:52px}

  .rowhead{display:none}
  .lists,.cards,.moved,.authgrid,.pn,.fviews{grid-template-columns:1fr}
  .arrow{display:none}.bleed{--bkw:140px}.crail>.lc{flex-basis:var(--lcw,218px)}
  .subctx,.handoff,.quiet,.hook{flex-direction:column;align-items:flex-start;gap:16px}
  .subctx form,.handoff a,.quiet .go3,.hook a{margin-left:0}.subctx input{width:100%}
  .arow{grid-template-columns:38px 1fr}.arow .s,.arow .d{display:none}
  
.hit.norank{grid-template-columns:44px 1fr 104px}
/* Search results keep their RATING on a phone; only the rank mark goes. .hit.norank above already
     reserves a third column for the stars, and it was sitting empty because they were hidden. */
  .hit{grid-template-columns:44px 1fr 92px}.hit .mk{display:none}
  .hit .s.stars{font-size:13.5px;letter-spacing:1.5px}
  /* ---- PHONE: the ⌘K palette ----
     Opening search scrolled the page sideways. The panel fits — 92vw — but .pfoot is a flex row of four
     hints, and "↑↓ navigate  ↵ open  esc close  ·  312 reviews · no server round-trip" is comfortably
     450px inside a 334px panel. A FIXED element that overflows still adds to the scrollable area on
     mobile, so the document shifted the moment the panel opened.
     The keyboard hints are dropped rather than wrapped: they describe keys a phone does not have. */
  /* left+right+width:auto sizes it to the gap between them. max-width is the belt: the base rule sets
     width:min(680px,92vw) and a transform, and if either survives — a specificity accident, a stale
     stylesheet — the panel must still not be able to exceed the screen. */
  .palette{left:12px;right:12px;width:auto;max-width:calc(100vw - 24px);transform:none;top:7vh}
  .pbar{padding:14px 16px;gap:10px}
  .presults{max-height:62vh}
  .pitem{padding:10px 16px}
  .pfoot{flex-wrap:wrap;gap:6px 12px;padding:10px 16px}
  .pfoot span:not(#pcount){display:none}
  #pcount{margin-left:0}
  /* ---- PHONE: the header scrolls away ----
     Three rows come to about 137px, which is a sixth of the screen held permanently by position:sticky.
     The tagline and the search box are things a reader wants on arrival, not pinned to their eyeline
     for the length of a review — so the header scrolls off and a flick to the top brings it back. */
  header{position:static}

  /* ---- PHONE: the review page reads in the order a reader needs ----
     .artgrid is main + aside, and collapsing it put the WHOLE aside first: cover, verdict, similar
     titles and Quick Facts all landed above the title of the book. display:contents dissolves both
     wrappers so their children become items of one column and can be ordered individually. It is the
     only way to interleave two containers' children without rebuilding the markup — and rebuilding it
     would put the desktop layout at risk to fix the phone.
     Note: the wrappers keep their landmark roles in current browsers; this was a real bug in 2020 and
     is worth re-testing if a screen reader ever reports the main region missing. */
  /* align-items:STRETCH, and this one declaration is why Quick Facts was sized to its content.
     The base .artgrid rule carries align-items:start — right for a two-column grid, where it stops the
     rail stretching to the height of a 2,000-word review. Switching the container to flex kept that
     value, and in a COLUMN flex container align-items governs the horizontal axis: every card became
     shrink-to-fit and left-aligned.
     It is also the real reason the cover rendered 0×0. I blamed the auto margins alone; they were
     enough on their own, but stretch was already off for every item in the column. The cover keeps its
     explicit width because auto margins still opt it out. */
  .artgrid{display:flex;flex-direction:column;gap:0;align-items:stretch}
  .artgrid>main,.artgrid>.rail{display:contents}
  /* WIDTH, not max-width, and this is the whole reason the cover vanished.
     A flex item stretches to fill the cross axis — which is how max-width:216px worked while the cover
     sat in the rail. Give it `margin:0 auto` to centre it and stretch is switched off, per spec: an
     item with auto cross-axis margins is sized to its CONTENT. Its content is an absolutely positioned
     rank mark, which contributes nothing, and an <img width:100%> — a percentage against a
     shrink-to-fit parent, which resolves to zero. The image loaded fine and rendered 0×0.
     An explicit width gives the percentage something to resolve against, and the auto margins still
     centre it. */
  .coverstage{order:1;width:216px;max-width:100%;margin:0 auto 22px}
  .artgrid main>h1{order:2}
  .artgrid .byauthor{order:3}
  .artgrid .meta{order:4}
  .artgrid .byline{order:5}
  /* The verdict sits with the title block, before the review. On a phone the review is a long scroll,
     and "is it worth reading" is the question that decides whether the reader starts it. */
  .artgrid .vcard{order:6;margin-top:18px}
  .artgrid .body{order:7}
  .artgrid .share{order:8}
  .artgrid .qf{order:9}
  .artgrid .cmp{order:10}
  /* The rail's flex gap disappeared with display:contents, so the cards carry their own spacing. */
  .artgrid .card{margin-top:20px}

  /* ---- PHONE: the share row ----
     Bigger targets, and the label above them rather than inline — "SHARE" in a 9.5px caps face beside
     four pills reads as a fifth pill. */
  .share{gap:9px;padding-top:16px}
  .share>span{flex:1 1 100%;margin-bottom:2px}
  .share .b{padding:10px 15px;font-size:12.5px}
  /* The label, then the field and the button under it. As one row it needed about 180px for the
     label, 90px for the button and whatever was left for the input — which is how Subscribe ended up
     hanging off the right edge of the screen. */
  .share .submini{flex-wrap:wrap;gap:8px}
  .share .submini .lbl{flex:1 1 100%}
  .share .submini input{flex:1 1 auto;min-width:0}
  .share .submini button{flex:0 0 auto}
  .sharemsg{flex:1 1 100%}

  /* ---- PHONE: the grouped pages (genres, Most Anticipated) ----
     .mo is a 132px heading column beside the rows, and nothing collapsed it on a phone: 132px plus a
     28px gap took 160px of a 375px screen, leaving about 215px for every row in the group. That is why
     a genre row wraps its verdict every three words while the same row on The Rankings reads fine —
     the row was never the problem, the column it sat in was.
     The heading goes above the rows and becomes a proper sticky header: one line, opaque, so it reads
     as "you are still in the five-star group" rather than as two lines of overlay with rows showing
     through. This is the iOS table-view pattern, and it is what makes a 107-book group navigable. */
  .mo{grid-template-columns:1fr;gap:12px;padding:18px 0}
  .mo .mh{position:sticky;top:0;z-index:15;display:flex;align-items:baseline;gap:9px;flex-wrap:wrap;
    background:rgba(255,255,255,.97);backdrop-filter:blur(10px);
    padding:9px 0 8px;border-bottom:1px solid var(--line)}
  .mo .mh b,.mo .mh span,.mo.rated .mh b.stars,.mo.rated .mh span,.mo.rated .mh span.ct{
    display:inline-block;margin-top:0}

  /* ---- PHONE: a row with no rank ----
     Genre pages and the rating groups render .lrow.norank, whose template is still the desktop's four
     columns — cover, text, a 104px rating column and a 124px genre column. The last two are hidden here
     but their TRACKS remain, so 228px of a 375px screen was reserved for nothing and the text took what
     was left. Two columns, and the stars drop under the text exactly as they do on a ranked row. */
  .lrow.norank{grid-template-columns:var(--covw) 1fr}
  .lrow.norank .cv{grid-column:1;grid-row:1 / span 5}

  /* ---- PHONE: what the sticky things stick to ----
     .tools sits at top:56px and .mo .mh at top:70px — both offsets for the sticky header's height. The
     header is static on a phone now, so those became gaps: the filter bar pinned a finger's width down
     the screen with rows sliding behind it above, which reads as a rendering fault rather than a
     control. Flush to the top, which is where every list app on a phone puts them. */
  .tools{top:0}
  .mo .mh{top:0}

  /* ---- PHONE: the ranking toolbar ----
     Three controls of three different sizes, because each was sized by its own content: a select is as
     wide as its longest option, a pill as wide as its label. Stacked full-width they read as one set of
     controls, and each becomes a 44px target instead of a 31px one. The count and the freshness stamp
     are labels rather than controls, so they share the last line.
     16px on the select is not a style choice — see the iOS zoom note above. */
  .toolrow{align-items:stretch}
  .toolrow .sel,.toolrow .pill{flex:1 1 100%}
  .toolrow .sel select,.toolrow .pill{width:100%;height:44px;font-size:16px;padding-left:15px;text-align:left}
  .toolrow .sel select{padding-right:34px}
  .toolrow .sel:after{right:16px}
  /* The flexible gap exists to push the sort control to the right of a wide row. There is no right on
     a phone, and left as flex:1 it inserts an empty line of its own. */
  .toolrow .spacer{display:none}
  .toolrow .count,.toolrow .fresh{flex:0 0 auto;align-self:center}

  /* ---- PHONE: the year strip ----
     "ALSO RANKED" sat inline with the first pill, so 2025 was pushed onto the label's line and the rest
     wrapped underneath it in a ragged block. The label takes its own line and the pills form a clean
     grid below. Mobile only: on a desktop the whole strip fits one row, which reads better. */
  .yearnav .lbl,.years .lbl{flex:1 1 100%;margin-bottom:2px}
  .protobar .jump{width:100%;margin-left:0}
}
