/* ================================================================
   /books/ inside the main site (website/). The shelf and its entries
   are the same source as building a website/_src/books.css; this block
   adapts them to the site's sticky header and its resets. Written by
   tools/build_site.py from tools/site_books.py — edit there.
   ================================================================ */
.vh{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}
/* the site gives every paragraph a bottom margin and colours every link on hover; the
   shelf sets its own spacing and colours by class, so these are undone at a specificity
   below any class rule (main p, not body.press p) */
main p{margin:0}
main a:hover{color:inherit}
/* the site paints every heading Ink Black; here they take the page's ink */
body.press h1,body.press h2,body.press h3{color:inherit}
/* the frosted header over a dark page: nearly opaque, so its blue nav text keeps 4.5:1 */
body.press .site-header{background:rgba(255,255,255,.94)}
/* the sticky header owns the top of the screen: the close button sits under it, the
   shelf centres in what is left, and on a wide screen the pinned book stage starts
   below it */
body.press .press-x{top:calc(var(--header-h) + var(--frame) + 12px)}
body.press .shelf-view{min-height:calc(100vh - var(--header-h))}
@media (min-width:901px){
  body.press .detail-stage{top:var(--header-h);height:calc(100vh - var(--header-h))}
}

/* ============================================================
   AXELERATE BOOKS — /books/

   One document, two states.

     SHELF    the books seen edge-on, stacked like a shelf viewed
              from slightly above. Each spine carries author, title
              and the mark, with the cover receding away above it.
     DETAIL   click a spine and the whole page takes that book's
              colour: the solid swings in on the left, the entry
              runs down the right.

   State lives in one attribute on .press-app (data-open) plus the
   URL hash, so a detail view is linkable and the back button works.
   Without JavaScript the shelf and every entry render as a plain
   stacked document — nothing is hidden behind a script.

   The books themselves live in _src/books_data.py; the markup is
   generated from it by tools/site_books.py on every build.

   Type: serif for the books, Josefin Sans for labels and chrome.
   No webfont beyond the site's own — the serif is a system stack.
   ============================================================ */

/* ---------- the page ---------- */
body.press{
  --press-bg:#001233;
  --press-ink:#FFFFFF;
  --press-accent:#FFE600;
  --serif:Georgia,"Iowan Old Style","Palatino Linotype","Book Antiqua","Times New Roman",serif;

  --color-link:var(--press-ink);
  --color-primary:var(--press-ink);
  --dim:color-mix(in srgb,var(--press-ink) 58%,transparent);
  --hair:color-mix(in srgb,var(--press-ink) 22%,transparent);

  background:var(--press-bg);
  color:var(--press-ink);
  transition:background-color 620ms cubic-bezier(.4,0,.2,1),color 620ms cubic-bezier(.4,0,.2,1);
}
body.press .skip{background:var(--press-ink);color:var(--press-bg)}
/* Arriving and leaving are soft: the page fades in on load, and books.js fades it out
   before following a link to another page (html.leaving). The fill is `backwards` so
   the fade-in holds nothing once it is done and the fade-out can take over. */
@keyframes pageIn{from{opacity:0}}
html.js body.press{animation:pageIn 420ms ease backwards}
html.leaving body.press{opacity:0;transition:opacity 260ms ease}
body.press :focus-visible{outline:3px solid var(--press-accent);outline-offset:3px}

/* --frame is the margin between the viewport edge and the fixed chrome — masthead, rail,
   close, help. (It once also framed a glass card behind the shelf; that backdrop was
   retired on 2026-09-10 and its recipe kept in _src/_removed/.) */
/* --flight is the length of the book's flight (FLIGHT in books.js sets it): the spines'
   return is timed from it. The ground and the sea are not on a timer at all — books.js
   sets them every frame from the flight's own progress, so they cannot run ahead of
   the book or behind it (2026-09-11) */
.press-app{position:relative;min-height:100vh;--frame:14px;--flight:2200ms}
.cv-defs{position:absolute;width:0;height:0;overflow:hidden}

/* ---------- masthead, top left ---------- */
.press-mast{position:fixed;top:calc(var(--frame) + 8px);left:calc(var(--frame) + 10px);z-index:30;
  display:flex;align-items:flex-start;gap:11px;
  color:var(--press-ink)}
.press-mast img{width:26px;height:19px;object-fit:contain;flex:none;margin-top:2px}
.press-mast .mark-dark{display:none}
.press-app[data-tone="light"] .mark-light{display:none}
.press-app[data-tone="light"] .mark-dark{display:block}
.mast-text{display:flex;flex-direction:column;line-height:1.32}
.mast-text b{font-family:var(--serif);font-weight:700;font-size:14px;letter-spacing:.01em}
.mast-text i{font-family:var(--serif);font-style:italic;font-size:14px;color:var(--dim)}
.press-mast:hover .mast-text i{color:var(--press-ink)}

/* ---------- left rail: back arrow + one tick per book ---------- */
.press-rail{position:fixed;left:calc(var(--frame) + 10px);top:50%;transform:translateY(-50%);z-index:30;
  display:flex;flex-direction:column;align-items:flex-start;gap:18px}
.rail-back{width:22px;height:22px;display:none;align-items:center;justify-content:center;
  color:var(--press-ink);opacity:.75;transition:opacity 140ms,transform 220ms}
.rail-back svg{width:20px;height:12px}
.rail-back:hover{opacity:1;transform:translateX(-3px)}
.press-app[data-open] .rail-back{display:inline-flex}

.rail-ticks{list-style:none;display:flex;flex-direction:column;gap:5px;margin:0;padding:0}
.rail-ticks button{display:block;padding:3px 0;cursor:pointer}
.rail-ticks i{display:block;width:15px;height:1px;background:var(--press-ink);opacity:.45;
  transition:width 240ms cubic-bezier(.4,0,.2,1),opacity 240ms}
.rail-ticks button:hover i{width:22px;opacity:.8}
.rail-ticks button[aria-current="true"] i{width:26px;opacity:1;height:2px}

/* ---------- close ---------- */
/* Not in the page at all while the shelf shows (display:none rather than opacity:0 —
   the main site's audit treats anything invisible inside <main> as a reveal that never
   ran), and it arrives with a short pop when an entry opens. */
.press-x{position:fixed;top:calc(var(--frame) + 12px);left:50%;transform:translateX(-50%);z-index:30;
  width:42px;height:42px;border-radius:50%;display:none;align-items:center;justify-content:center;
  background:color-mix(in srgb,var(--press-ink) 12%,transparent);color:var(--press-ink);
  transition:background-color 160ms}
.press-x svg{width:20px;height:20px}
.press-x:hover{background:color-mix(in srgb,var(--press-ink) 22%,transparent)}
.press-app[data-open] .press-x{display:flex;animation:xIn 300ms cubic-bezier(.2,.9,.3,1.12) both}
@keyframes xIn{from{opacity:0;transform:translateX(-50%) scale(.8)}to{opacity:1;transform:translateX(-50%) scale(1)}}

/* ---------- help ---------- */
.press-help{position:fixed;left:calc(var(--frame) + 12px);bottom:calc(var(--frame) + 8px);z-index:30;
  width:26px;height:26px;
  font-family:var(--serif);font-size:17px;line-height:1;color:var(--dim);transition:color 140ms}
.press-help:hover,.press-help[aria-expanded="true"]{color:var(--press-ink)}
.press-hint{position:fixed;left:calc(var(--frame) + 12px);bottom:calc(var(--frame) + 44px);z-index:30;max-width:300px;
  background:color-mix(in srgb,var(--press-ink) 10%,var(--press-bg));
  border:1px solid var(--hair);border-radius:10px;padding:15px 17px}
.press-hint p{font-family:var(--serif);font-size:14.5px;line-height:1.55;color:var(--press-ink)}

/* ============================================================
   SHELF
   ============================================================ */
.shelf-view{
  /* the layout arrives inline from the generator (PILES in books_data.py): how many
     piles side by side on a wide screen and on a tablet, and the rows each needs */
  --cols:var(--cols-wide,1);
  --rows:var(--rows-wide,4);
  --gap:clamp(24px,3vw,44px);     /* between the piles */
  /* spine width: 338px, or whatever lets --cols piles fit the viewport (the 68px covers
     the page padding and a scrollbar) */
  --sw:min(338px,calc((100vw - 68px - (var(--cols) - 1) * var(--gap)) / var(--cols)));
  --sf:clamp(40px,3.6vw,50px);    /* spine face height */
  --st:clamp(17px,1.7vw,24px);    /* receding cover strip */
  --pull:clamp(48px,6vw,84px);    /* how far a book comes off the shelf, toward you */
  --pull-depth:900px;             /* the eye's distance from the shelf: less = more drama */
  min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;
  padding:120px 24px 90px;
}
/* One grid filled column-first: with PILES at 1 it is a single stack, top to bottom;
   at 2 or 3 a pile is simply the next run of --rows books. Nothing here knows how many
   books there are. */
.spines{list-style:none;margin:0;padding:0;display:grid;grid-auto-flow:column;
  grid-template-columns:repeat(var(--cols),var(--sw));grid-template-rows:repeat(var(--rows),auto);
  column-gap:var(--gap);row-gap:clamp(18px,2.4vw,34px);justify-content:center}

/* The pull. You are standing in front of the shelf; the book you want comes straight
   off it toward you. --lift is 0 on the shelf and 1 in the hand, and every part of the
   effect derives from it, so hover, keyboard focus and a finger press all produce the
   same motion: the book advances along your line of sight (translateZ, so it grows as
   it nears rather than sliding sideways), the top face opens further as it clears the
   book above, the marker appears at the fore-edge, and the shadow it leaves on the
   shelf spreads and softens with the distance. Each spine carries its own vanishing
   point — the perspective() inside its transform — which is what makes all three move
   alike; under one perspective shared by the shelf the top book would also drift up
   and the bottom one down. The ease overshoots by a hair and settles: the weight of a
   real book. */
.spine{--lift:0;display:block;width:var(--sw);cursor:pointer;position:relative;z-index:1;
  transform-style:preserve-3d;
  transform:perspective(var(--pull-depth)) translateZ(calc(var(--pull) * var(--lift)));
  transition:transform 480ms cubic-bezier(.2,.9,.3,1.12),z-index 0s linear 480ms}
@media (hover:hover){
  .spine:hover{--lift:1;z-index:2;transition:transform 480ms cubic-bezier(.2,.9,.3,1.12),z-index 0s}
}
.spine:focus-visible{--lift:1;z-index:2;transition:transform 480ms cubic-bezier(.2,.9,.3,1.12),z-index 0s}
/* the press: on a touch screen it is the whole pull, on a desktop it is the book
   giving a little under the click before the entry opens */
.spine:active{--lift:.7;z-index:2;transition:transform 140ms ease-out,z-index 0s}

/* The top of the book, receding away from you: a page block, with the cover board's
   edge as a thin lip at the front. Squashing the cover art into this strip was tried
   first and read as a stray graphic — these covers carry a corner swoosh and big type,
   neither of which survives being compressed to 24px. */
.spine-top{position:relative;display:block;height:var(--st);overflow:hidden;
  transform-origin:bottom center;
  /* 56° at rest; 44° in the hand, as the book clears the one above and you see more of
     the page block */
  transform:perspective(300px) rotateX(calc(56deg - 12deg * var(--lift)));
  border-radius:3px 3px 0 0;
  background:
    linear-gradient(to bottom,rgba(0,0,0,.34),rgba(0,0,0,.02) 62%),
    repeating-linear-gradient(to right,rgba(90,80,66,.16) 0 1px,transparent 1px 4px),
    linear-gradient(to bottom,#CFC8BB,#F3EFE6 58%,#E6E1D6);
  transition:transform 480ms cubic-bezier(.2,.9,.3,1.12)}
/* the cover board, wrapping over the front edge — its top edge catches the light */
.spine-top::after{content:"";position:absolute;left:0;right:0;bottom:0;height:20%;
  background:linear-gradient(to bottom,rgba(255,255,255,.28),rgba(255,255,255,0) 65%),var(--spine);
  opacity:.95}

/* the spine face itself */
.spine-face{position:relative;display:grid;grid-template-columns:1fr auto 1fr;align-items:center;
  gap:14px;height:var(--sf);padding:0 clamp(10px,1.2vw,16px);
  /* The colour is not flat. A spine is slightly rounded, lit from above: a highlight
     along its upper third falling to shadow at the bottom edge, and the two joints at
     its ends sitting a little deeper. Laid over the book's colour, so it works for
     every scheme. */
  background:
    linear-gradient(to bottom,rgba(255,255,255,.17) 0%,rgba(255,255,255,.06) 26%,rgba(255,255,255,0) 50%,rgba(0,0,0,.22) 100%),
    linear-gradient(to right,rgba(0,0,0,.26),rgba(0,0,0,0) 5%,rgba(0,0,0,0) 95%,rgba(0,0,0,.26)),
    var(--spine);
  color:var(--spine-ink);border-radius:0 0 2px 2px;
  /* the inset ring is what keeps the Ink Black book visible against the Ink Black
     shelf — a real board edge catches light, and without it book one disappears.
     The drop shadow is the one the book leaves on the shelf: it falls straight down
     and spreads as the book comes toward you. */
  box-shadow:0 calc(10px + 16px * var(--lift)) calc(26px + 16px * var(--lift)) calc(-14px + 2px * var(--lift)) rgba(0,0,0,.85),
             inset 0 0 0 1px color-mix(in srgb,var(--spine-ink) 20%,transparent);
  /* nearer the light, a touch brighter */
  filter:brightness(calc(1 + .06 * var(--lift)));
  transition:box-shadow 480ms ease,filter 480ms ease}
/* a hairline of light along the top edge, where the cover folds over */
.spine-face::before{content:"";position:absolute;top:0;left:0;right:0;height:1px;
  background:color-mix(in srgb,var(--spine-ink) 30%,transparent)}
/* accent only, and only on the book in hand: a yellow marker at the fore-edge */
.spine-face::after{content:"";position:absolute;left:0;top:0;bottom:0;width:calc(5px * var(--lift));
  background:#FFE600;transition:width 300ms cubic-bezier(.22,.61,.36,1)}
.sp-author{justify-self:start;font-family:var(--serif);font-style:italic;
  font-size:clamp(10px,1vw,13px);line-height:1.2;opacity:.88;text-align:left}
.sp-title{justify-self:center;font-family:var(--serif);font-size:clamp(11px,1.15vw,15px);
  line-height:1.2;text-align:center;white-space:nowrap}
.sp-mark{justify-self:end;display:flex;align-items:center}
.sp-mark img{width:clamp(14px,1.4vw,19px);height:auto;object-fit:contain;opacity:.9}

.shelf-note{margin-top:clamp(38px,5vw,64px);font-family:var(--serif);font-style:italic;
  font-size:clamp(14px,1.3vw,17px);line-height:1.5;color:var(--dim);text-align:center;max-width:44ch}

/* ============================================================
   DETAIL
   ============================================================ */
.detail{
  --bw:clamp(150px,17vw,250px);
  --bh:calc(var(--bw) * 1.46);
  --bd:calc(var(--bw) * 0.135);
  display:grid;grid-template-columns:1fr 1fr;gap:clamp(24px,4vw,70px);
  padding:0 clamp(24px,4vw,64px);
  /* the open cover can reach past the stage on a narrow screen: clip, never scroll
     (clip rather than hidden, so the pinned stage still sticks) */
  overflow-x:clip;
}

/* left: the solid, pinned while the entry scrolls past it */
.detail-stage{position:sticky;top:0;align-self:start;height:100vh;
  display:flex;align-items:center;justify-content:center;perspective:calc(var(--bw) * 5)}

/* The book, in three layers:
     .book3d     floats — an even rise and fall for as long as the entry is open, with
                 a whisper of tilt at the top so it reads as floating rather than
                 sliding on a rail. One keyframe pair and a sine-shaped ease, so it is
                 never seen to stop mid-travel; the amplitude is a share of the book's
                 width, the period 2.8s a cycle (6.8s and 4.2s both read as sluggish
                 to the client).
     .book-body  holds the pose and turns between the two — lying flat, as it lay on
                 the shelf (seen from above and a little in front, turned on the table
                 so the spine runs up to the left: the client's reference), and
                 standing up to face you, turned a shade so the spine shows. See
                 "opening and closing" below.
     .f-*        the seven faces.
   Motion is switched off only by the visitor's own reduced-motion setting (see the
   end of this file). */
@keyframes bookFloat{
  0%,100%{transform:translate3d(0,0,0) rotateX(0deg) rotateY(0deg)}
  50%    {transform:translate3d(0,calc(var(--bw) * -.07),0) rotateX(-1.6deg) rotateY(1.4deg)}
}
.book3d{position:relative;width:var(--bw);height:var(--bh);transform-style:preserve-3d;
  animation:bookFloat 2.8s cubic-bezier(.45,0,.55,1) infinite}
.book-body{position:absolute;inset:0;transform-style:preserve-3d;
  transform:rotateX(0deg) rotateY(-18deg) rotateZ(0deg)}   /* standing, once risen */
.face{position:absolute;top:50%;left:50%;transform-origin:center center;backface-visibility:hidden;overflow:hidden}
.f-cover,.f-page,.f-back{width:var(--bw);height:var(--bh)}
/* The front cover is a hinge. It turns about the spine edge (transform-origin at its
   left), carrying its outside — the art — and its inside — the endpaper — as two
   faces. Closed at rest; it swings open when the entry arrives and shut before the
   shelf returns (see "opening" below). */
.f-cover{transform-origin:0 50%;transform-style:preserve-3d;overflow:visible;backface-visibility:visible;
  transform:translate(-50%,-50%) translateZ(calc(var(--bd) / 2)) rotateY(0deg)}
.cv-out,.cv-in{position:absolute;inset:0;backface-visibility:hidden;overflow:hidden}
.cv-out{border-radius:1px 4px 4px 1px}
.cv-in{transform:rotateY(180deg);border-radius:4px 1px 1px 4px;
  background:linear-gradient(to left,rgba(0,0,0,.14),rgba(0,0,0,0) 12%),
             repeating-linear-gradient(-30deg,rgba(0,0,0,.03) 0 2px,transparent 2px 9px),#EFE9DA}
/* the first page, under the cover: a title page in the book's serif */
.f-page{transform:translate(-50%,-50%) translateZ(calc(var(--bd) / 2 - 1px));border-radius:1px 4px 4px 1px;
  background:linear-gradient(to right,rgba(0,0,0,.16),rgba(0,0,0,0) 10%),#F7F4EC;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:7%;
  padding:14% 12%;text-align:center;color:#1A1814}
.pg-title{font-family:var(--serif);font-weight:700;font-size:calc(var(--bw) * .085);line-height:1.15}
.pg-line{font-family:var(--font-1);font-weight:500;font-size:calc(var(--bw) * .04);letter-spacing:.14em;
  text-transform:uppercase;opacity:.7}
.pg-rule{width:18%;height:1px;background:#003DFF}
.f-back{transform:translate(-50%,-50%) translateZ(calc(var(--bd) / -2)) rotateY(180deg);
  border-radius:4px 1px 1px 4px;background:var(--cv-deep,#0A0A0A)}
.f-spine,.f-edge{width:var(--bd);height:var(--bh)}
.f-spine{transform:translate(-50%,-50%) translateX(calc(var(--bw) / -2)) rotateY(-90deg);
  background:linear-gradient(to right,rgba(255,255,255,.16),rgba(0,0,0,.28)),var(--cv-deep,#0A0A0A);
  display:flex;align-items:center;justify-content:center}
.f-edge{transform:translate(-50%,-50%) translateX(calc(var(--bw) / 2)) rotateY(90deg);
  background:repeating-linear-gradient(to right,rgba(0,0,0,.16) 0 1px,transparent 1px 3px),
             linear-gradient(to right,#D9D4CC,#F4F1EA 40%,#E7E2D9)}
.f-top,.f-bottom{width:var(--bw);height:var(--bd)}
.f-top{transform:translate(-50%,-50%) translateY(calc(var(--bh) / -2)) rotateX(90deg);
  background:repeating-linear-gradient(to right,rgba(0,0,0,.10) 0 1px,transparent 1px 4px),
             linear-gradient(to bottom,#F4F1EA,#DAD5CC)}
.f-bottom{transform:translate(-50%,-50%) translateY(calc(var(--bh) / 2)) rotateX(-90deg);
  background:repeating-linear-gradient(to right,rgba(0,0,0,.10) 0 1px,transparent 1px 4px),
             linear-gradient(to bottom,#DAD5CC,#F4F1EA)}
.cv-out svg{width:100%;height:100%;display:block}
.cv-out::after,.f-back::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(100deg,rgba(255,255,255,.20) 0%,rgba(255,255,255,0) 26%,rgba(0,0,0,.16) 78%,rgba(0,0,0,.34) 100%)}
.cv-out::before{content:"";position:absolute;left:0;top:0;bottom:0;width:9%;z-index:2;pointer-events:none;
  background:linear-gradient(to right,rgba(0,0,0,.42),rgba(0,0,0,.10) 55%,rgba(255,255,255,.07))}

/* --- opening and closing ---------------------------------------------------------
   The movement between the shelf and the standing book is the flight in books.js: a
   ghost fixed over the clicked spine — the spine drawn exactly as it stands, with the
   solid behind it in its shelf pose — whose cover tips up first, while it still lies
   along the shelf, and which then rises in the plane of the screen to stand upright
   where the entry's book stands, the real book then shown in its place. Closing is
   the same flight backwards, ending exactly on the spine. This file supplies the
   states around it:

     [data-flying]     the shelf stays in view, out of the flow, its other books
                       dropping away; the entry's own book, shadow and text are
                       hidden until the ghost lands
     .is-landed        the ghost has landed: the cover swings open on its hinge
     [data-closing]    the cover swings shut
     [data-returning]  the entry is an overlay fading out while the shelf takes the
                       page back and the ghost flies home; the sea fades back in.
                       (Starting this at the click, under the closing cover, was
                       tried on 2026-09-11 and read as worse: it follows the cover.)

   The client's brief (2026-09-10): one continuous, fluid movement from the exact
   shelf position to upright, the cover opening after; closing the same movement
   backwards to the exact shelf position, orientation and scale. */
@keyframes coverOpen{
  from{transform:translate(-50%,-50%) translateZ(calc(var(--bd) / 2)) rotateY(0deg)}
  to  {transform:translate(-50%,-50%) translateZ(calc(var(--bd) / 2)) rotateY(-152deg)}
}
@keyframes coverClose{
  from{transform:translate(-50%,-50%) translateZ(calc(var(--bd) / 2)) rotateY(-152deg)}
  to  {transform:translate(-50%,-50%) translateZ(calc(var(--bd) / 2)) rotateY(0deg)}
}
@keyframes textOut{to{opacity:0;transform:translateY(20px)}}

/* Timings (2026-09-11, "far too fast", then "still needs to be slower"): the flight
   is 2200ms each way (FLIGHT in books.js); the cover takes 1300ms to open once landed
   and 800ms to close (the close delay in books.js matches it). (2600 / 1500 / 900 read
   as a little too slow.) */
.js .detail.is-landed .f-cover{animation:coverOpen 1300ms cubic-bezier(.6,.05,.25,1) 420ms both}   /* after the ghost's 220ms blend */
.js .press-app[data-closing] .detail.is-open .f-cover{animation:coverClose 800ms cubic-bezier(.6,.05,.3,1) both}

/* while the book is in the air the entry's own copy waits, unseen, where it will land */
.detail.is-flying .book3d,.detail.is-flying .book-shadow{visibility:hidden}
/* the shelf, as the book leaves it: still there, pinned out of the flow so the entry
   can take the page underneath. Pinned at the top only, keeping its own height: with
   inset:0 a shelf taller than the viewport (a short screen) would be squeezed to the
   viewport and re-centre — every other book jumping at the click. */
.js .press-app[data-open][data-flying] .shelf-view{display:flex;position:fixed;top:0;left:0;right:0;z-index:1;
  pointer-events:none}
/* The other books drop away as the chosen one leaves — quickly, top to bottom — and
   settle back, bottom to top, in the last moments of the flight home, so the shelf is
   never seen half-there under the entry. --i is each spine's row, set by books.js;
   the note under the shelf is the last row. The spine in flight (data-in-flight)
   takes no part: it is hidden for the flight and shown again at rest, and the
   ghost is built from its box and look — which the settle-back's delay would
   otherwise hold 26px low. */
@keyframes spineAway{to{opacity:0;transform:translateY(26px)}}
@keyframes spineBack{from{opacity:0;transform:translateY(26px)}}
.js .press-app[data-flying] .shelf-view .spine:not([data-in-flight]),.js .press-app[data-flying] .shelf-note{
  animation:spineAway 300ms ease-in both;animation-delay:calc(var(--i,0) * 50ms)}
.js .press-app[data-returning] .shelf-view .spine:not([data-in-flight]),.js .press-app[data-returning] .shelf-note{
  animation:spineBack 300ms ease-out both;animation-delay:calc(var(--flight) - 300ms - var(--i,0) * 50ms)}
/* the shelf, as the book comes home: back in the flow under an entry that has become
   an overlay (its text already gone with the cover) */
.js .press-app[data-open][data-returning] .shelf-view{display:flex}
.js .press-app[data-open][data-returning] .detail.is-open{position:fixed;inset:0;overflow:hidden;z-index:2;
  pointer-events:none}

/* the ghost: a fixed box the size of the spine, carrying the spine drawing and, behind
   it, the solid; both are animated from books.js. It has the same perspective as the
   stage, so on landing it is pixel for pixel the book it hands over to. */
.fly{position:fixed;z-index:25;pointer-events:none;perspective:calc(var(--bw) * 5);transform-style:preserve-3d}
/* the frame inside the wrapper carries the fraction of a pixel the wrapper leaves out
   (books.js, makeGhost); it passes the wrapper's 3D space straight through */
.fly .fly-frame{position:absolute;width:100%;height:100%;transform-style:preserve-3d}
/* the drawn spine in a flat holder: the ghost's perspective reaches the solid (a child)
   but not the spine (a grandchild in a flat context), so the spine renders exactly as it
   does on the shelf. In the ghost's 3D space the holder is a plane at the shelf, and the
   solid's front rises through it as it fades in — the holder is what fades out. */
/* the drawn spine's own flat wrapper, UNDER the solid: as the cover lifts it covers the
   drawing, which fades away unseen beneath it — on top, the drawing lingered over the
   rising book and read as the shelf's copy staying put (and, on the way home, as the
   shelf's copy appearing before the book had landed) */
.fly.fly-2d{perspective:none;transform-style:flat;z-index:24}
.fly .fly-flat{position:absolute;width:100%;height:100%}
.fly .fly-spine{position:absolute;inset:0;width:100%;height:100%;margin:0}
.fly .book3d{position:absolute;left:50%;top:50%;margin:calc(var(--bh) / -2) 0 0 calc(var(--bw) / -2);
  animation:none;z-index:1}
.fly .book-body{animation:none;transform:none}
.fly .f-cover{animation:none}
.fly .book-shadow{display:none}
.spine-text{font-family:var(--serif);font-size:calc(var(--bd) * .40);line-height:1;
  color:var(--cv-text,rgba(255,255,255,.92));writing-mode:vertical-rl;white-space:nowrap;
  max-height:88%;overflow:hidden}
/* The shadow answers the bob in the same period and the same ease: it shrinks and
   fades as the book lifts away from the ground and returns as it settles, which is
   what keeps the book attached to the surface it floats above. */
@keyframes shadowFloat{
  0%,100%{transform:translateX(-50%) scale(1);opacity:.55}
  50%    {transform:translateX(-50%) scale(.84);opacity:.30}
}
.book-shadow{position:absolute;left:50%;top:calc(50% + var(--bh) / 2 + 26px);
  width:calc(var(--bw) * 1.05);height:calc(var(--bw) * .22);transform:translateX(-50%);
  background:radial-gradient(50% 50% at 50% 50%,rgba(0,0,0,.42),rgba(0,0,0,0) 70%);
  filter:blur(16px);pointer-events:none;
  animation:shadowFloat 4.2s cubic-bezier(.45,0,.55,1) infinite}
/* the shadow gathers under the book once it has landed rather than appearing in one
   frame (its opacity belongs to the float, so the fade rides on the filter) */
@keyframes shadowIn{from{filter:blur(16px) opacity(0)}to{filter:blur(16px) opacity(1)}}
.js .detail.is-landed .book-shadow{animation:shadowFloat 4.2s cubic-bezier(.45,0,.55,1) infinite,shadowIn 900ms ease both}

/* right: the entry */
.detail-text{padding:clamp(96px,13vh,150px) 0 clamp(70px,10vh,120px);max-width:63ch}
.detail-text h2{font-family:var(--serif);font-weight:700;font-size:clamp(23px,2.1vw,31px);
  line-height:1.18;letter-spacing:-.008em}
.d-author{font-family:var(--serif);font-style:italic;font-size:clamp(15px,1.3vw,18px);
  line-height:1.45;margin-top:9px;color:var(--dim)}
/* the buy button: the entry's accent (brand blue, or brand yellow on the slate ground)
   with its own type colour, both set inline on the article from the book's scheme. It
   links to the book's checkout when there is one and orders by email until then. */
.d-buy{display:flex;flex-wrap:wrap;align-items:center;gap:12px 18px;margin-top:20px}
.d-buybtn{display:inline-flex;align-items:center;gap:14px;height:48px;padding:0 20px 0 22px;
  border-radius:4px;background:var(--buy,var(--press-accent));color:var(--buy-ink,#FFFFFF);
  font-family:var(--serif);font-size:clamp(15px,1.2vw,17px);line-height:1;white-space:nowrap;
  box-shadow:0 12px 26px -12px rgba(0,0,0,.6),inset 0 1px 0 rgba(255,255,255,.18);
  transition:transform 180ms cubic-bezier(.2,.9,.3,1.12),box-shadow 180ms ease,filter 180ms ease}
.d-buybtn:hover{transform:translateY(-1px);box-shadow:0 16px 30px -12px rgba(0,0,0,.65),inset 0 1px 0 rgba(255,255,255,.18);
  filter:brightness(1.05)}
.d-buybtn:active{transform:translateY(0);transition-duration:80ms}
.d-price{font-family:var(--font-1);font-weight:600;font-size:.94em;letter-spacing:.01em;
  padding-left:14px;border-left:1px solid color-mix(in srgb,var(--buy-ink,#FFFFFF) 40%,transparent)}
.d-buynote{font-family:var(--serif);font-style:italic;font-size:clamp(13px,1.05vw,15px);color:var(--dim)}

/* a stand-in entry says so, the same dashed chip the main site uses for its placeholders */
.d-ph{margin-top:12px}
.d-ph span{display:inline-block;padding:5px 9px 4px;border:1px dashed var(--hair);border-radius:3px;
  font-family:var(--font-1);font-size:10.5px;font-weight:500;letter-spacing:.1em;text-transform:uppercase;
  line-height:1;color:var(--dim)}
.d-rule{width:46px;height:1px;background:var(--hair);margin:22px 0 24px}
.detail-text > p{font-family:var(--serif);font-size:clamp(15px,1.15vw,17px);line-height:1.62}
.detail-text > p + p{margin-top:15px}

.d-label{font-family:var(--serif);font-style:italic;font-size:clamp(15px,1.3vw,18px);
  margin-top:38px;margin-bottom:13px}

.d-rows{list-style:none;margin:0;padding:0;border:1px solid var(--hair);border-radius:3px}
.d-row{border-top:1px solid var(--hair)}
.d-row:first-child{border-top:0}
.d-row > a,.d-row.is-static{display:flex;align-items:center;justify-content:space-between;gap:14px;
  padding:11px 14px;font-family:var(--serif);font-size:clamp(14px,1.1vw,16px);line-height:1.35}
.d-row > a{color:var(--press-ink);transition:background-color 140ms}
.d-row > a:hover{background:color-mix(in srgb,var(--press-ink) 10%,transparent)}
.d-row > a svg{width:12px;height:12px;flex:none;opacity:.75}
.d-row.is-static{color:var(--dim)}
.d-row .d-val{text-align:right}

.d-toc{list-style:none;margin:0;padding:0}
.d-toc li{display:flex;gap:14px;padding:9px 0;border-bottom:1px solid var(--hair);
  font-family:var(--serif);font-size:clamp(14px,1.1vw,16px);line-height:1.45}
.d-toc li:last-child{border-bottom:0}
.d-toc .n{flex:none;width:20px;font-family:var(--font-1);font-size:11px;font-weight:500;
  color:var(--dim);padding-top:.32em;line-height:1}

.d-from{font-family:var(--serif);font-size:clamp(14px,1.1vw,16px);line-height:1.6;color:var(--dim)}
.d-links{display:flex;flex-wrap:wrap;gap:9px;margin-top:16px}
.d-links a{color:var(--press-ink);display:inline-flex;align-items:center;height:36px;padding:0 15px;
  border:1px solid var(--hair);border-radius:3px;font-family:var(--serif);
  font-size:clamp(13px,1vw,15px);line-height:1;transition:background-color 140ms,border-color 140ms}
.d-links a:hover{background:color-mix(in srgb,var(--press-ink) 12%,transparent);border-color:var(--press-ink)}

.d-next{margin-top:46px;padding-top:24px;border-top:1px solid var(--hair)}
.d-nextbtn{font-family:var(--serif);font-style:italic;font-size:clamp(15px,1.3vw,18px);
  line-height:1.4;color:var(--dim);cursor:pointer;text-align:left;transition:color 140ms}
.d-nextbtn:hover{color:var(--press-ink)}

/* ============================================================
   STATE — only once JS is confirmed present
   ============================================================ */
.js .press-app .detail{display:none}
.js .press-app .detail.is-open{display:grid}
.js .press-app[data-open] .shelf-view{display:none}

/* The entries sit on mid-tone grounds (the data-bg values in books-body.html: a slate
   blue, an ochre, a blue-grey — chosen 2026-09-10 over the saturated brand colours the
   client found noisy). A mid-tone leaves less room between ink and ground than the
   shelf has, so the secondary text — author line, availability rows, the "where it
   comes from" note — is dimmed less here, and the hairlines are a shade firmer. */
.press-app[data-open]{
  --dim:color-mix(in srgb,var(--press-ink) 86%,transparent);
  --hair:color-mix(in srgb,var(--press-ink) 28%,transparent)}

/* entrance — brisk when the entry is reached by a tick, an arrow or a link. When the
   book flies in from the shelf the ground changes with the flight (books.js paces
   it from the flight's own progress) and the text waits, rising in the beat between
   the landing and the cover opening; on a close it drops out as the cover shuts, so
   the book flies home over a clean ground */
.js .detail.is-open .detail-stage{animation:stageFade 460ms ease both}
.js .detail.is-open .detail-text{animation:textIn 420ms cubic-bezier(.22,.61,.36,1) 80ms both}
.js .detail.is-flying .detail-stage{animation:none}
.js .detail.is-flying .detail-text{animation:none;opacity:0}
.js .detail.is-landed .detail-text{animation:textIn 400ms cubic-bezier(.22,.61,.36,1) both}
.js .press-app[data-closing] .detail.is-open .detail-text{animation:textOut 400ms ease-in both}
@keyframes stageFade{from{opacity:0}to{opacity:1}}
@keyframes textIn{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:none}}

/* without JS the entries are just sections, so give them separators */
.detail{border-top:1px solid var(--hair)}
.js .detail{border-top:0}

/* ---------- responsive ---------- */
@media (max-width:900px){
  .detail{grid-template-columns:1fr;gap:0;--bw:clamp(130px,34vw,220px)}
  /* stacked: the book sits above the entry. The stage is no longer a full-height pin,
     so the shadow is hung from the stage's bottom edge instead of its middle — the
     bottom padding is the room it needs to stay clear of the text. */
  /* The stage centres the closed book; the open cover swings out to the LEFT of it,
     nearly a book's width, so on a screen this narrow the open book sat left-heavy
     with the cover off the edge (his phone, 2026-09-11). The left padding shifts the
     centred book right by half of itself — .46 of the book's width, the open cover's
     share — so the OPEN book is what sits centred. The flight lands wherever the book
     is, so the motion is unchanged. */
  .detail-stage{position:relative;height:auto;padding:clamp(86px,16vh,130px) 0 72px calc(var(--bw) * .92)}
  .detail-text{padding-top:26px;max-width:none}
  /* hung from the stage's bottom edge here, where the stage is no longer a full-height pin */
  .book-shadow{top:auto;bottom:10px;left:calc(50% + var(--bw) * .46)}
  .press-rail{display:none}
  /* the entry's text runs the full width here, so the fixed "?" would sit on top of it
     as it scrolls; the hint is about the shelf anyway, so it goes away with the shelf */
  .press-app[data-open] .press-help,.press-app[data-open] .press-hint{display:none}
}
/* the piles: at most two on a tablet, one column on a phone */
@media (max-width:1100px){
  .shelf-view{--cols:var(--cols-mid,1);--rows:var(--rows-mid,4)}
}
@media (max-width:640px){
  .shelf-view{--cols:1;--sw:min(88vw,420px)}
  .spines{grid-auto-flow:row;grid-template-rows:none}
  /* the spines run under the fixed "?" at this width, so it becomes a small disc rather
     than a bare glyph lying on a book */
  .press-help{width:34px;height:34px;border-radius:50%;background:var(--press-bg);border:1px solid var(--hair)}
}
@media (max-width:560px){
  /* centred, the close button lands on top of the masthead at this width */
  .press-x{left:auto;right:calc(var(--frame) + 4px);transform:none;animation-name:xInR}
  @keyframes xInR{from{opacity:0;transform:scale(.85)}to{opacity:1;transform:scale(1)}}
  .press-mast{top:calc(var(--frame) + 6px);left:calc(var(--frame) + 6px)}
  .mast-text b,.mast-text i{font-size:13px}
  .sp-author{display:none}
  .spine-face{grid-template-columns:1fr auto}
  .sp-title{justify-self:start;white-space:normal}
  .press-help,.press-hint{left:calc(var(--frame) + 6px)}
}


/* ============================================================
   BACKDROP — the sea. Sunlit at the surface and darker with
   every metre down, to near-black at the bottom; the sun itself
   blooms just over the top edge, and its rays fan down through
   the water and dissolve with the depth. That gradient of light
   is what gives the page its depth (the client's brief, 2026-09-10:
   "like the sea — the surface is light, the deeper you go the
   darker it gets"). Fixed under the page; it fades out when an
   entry opens, and the entries keep their own flat grounds.
   (A blurred-room backdrop with a picker was tried and retired
   the same day; its recipe is in _src/_removed/.)
   ============================================================ */
.backdrop{position:fixed;inset:0;z-index:0;overflow:hidden;pointer-events:none;
  /* the water column, top to bottom */
  background:linear-gradient(to bottom,
    #0D419E 0%,#0A3688 12%,#072B70 26%,#041F52 44%,#02143A 62%,#010B24 82%,#00040E 100%);
  transition:opacity 620ms cubic-bezier(.4,0,.2,1)}
.press-app[data-open]:not([data-returning]) .backdrop{opacity:0}
.shelf-view,.detail{position:relative;z-index:1}

/* the sun: a bloom just over the top edge, breathing slowly */
.bd-glow{position:absolute;inset:0;
  background:
    radial-gradient(58% 54% at 50% -12%,
      #FFFFFF 0%, rgba(214,226,255,.88) 8%, rgba(120,160,255,.62) 20%,
      rgba(0,61,255,.46) 34%, rgba(0,54,188,.20) 50%, rgba(0,18,51,0) 68%),
    radial-gradient(95% 48% at 50% 0%, rgba(0,61,255,.16), transparent 72%);
  transform-origin:50% 0;
  animation:bdBreathe 11s ease-in-out infinite}
@keyframes bdBreathe{0%,100%{transform:scale(1)}50%{transform:scale(1.05)}}

/* the rays: soft bands fanning down from the sun, masked so they dissolve with the
   depth, swaying a degree or two either way. Soft gradient stops rather than a blur,
   so the layer costs nothing per frame; oversized so the sway never shows an edge. */
.backdrop::before{content:"";position:absolute;inset:-12%;
  background:conic-gradient(from 151deg at 50% 0%,
    transparent 0deg, rgba(255,255,255,.05) 3deg, transparent 7deg,
    transparent 9deg, rgba(255,255,255,.035) 12deg, transparent 15deg,
    transparent 19deg, rgba(255,255,255,.065) 23deg, transparent 27deg,
    transparent 30deg, rgba(255,255,255,.04) 33deg, transparent 37deg,
    transparent 41deg, rgba(255,255,255,.055) 44deg, transparent 49deg,
    transparent 52deg, rgba(255,255,255,.03) 55deg, transparent 58deg,
    transparent 360deg);
  -webkit-mask-image:linear-gradient(to bottom,rgba(0,0,0,.95) 0%,rgba(0,0,0,.5) 40%,rgba(0,0,0,0) 78%);
  mask-image:linear-gradient(to bottom,rgba(0,0,0,.95) 0%,rgba(0,0,0,.5) 40%,rgba(0,0,0,0) 78%);
  transform-origin:50% 0;
  animation:raysSway 17s ease-in-out infinite alternate}
@keyframes raysSway{from{transform:rotate(-1.6deg)}to{transform:rotate(1.6deg)}}
@media (max-width:760px){.press-app{--frame:8px}}

/* ---------- reduced motion ---------- */
@media (prefers-reduced-motion:reduce){
  body.press{transition:none}
  .book3d,.book-shadow,.bd-glow,.backdrop::before{animation:none}
  .js .detail.is-open .f-cover{animation:none;transform:translate(-50%,-50%) translateZ(calc(var(--bd) / 2)) rotateY(-152deg)}
  html.js body.press{animation:none}
  html.leaving body.press{transition:none}
  .spine,.spine-top,.spine-face,.spine-face::after{transition:none}
  .spine:hover,.spine:focus-visible,.spine:active{--lift:0}
  .spine:hover .spine-face::after,.spine:focus-visible .spine-face::after{width:5px}
  .js .detail.is-open .detail-stage,.js .detail.is-open .detail-text{animation:none}
}
