/* ============================================================================
   JONAH BECKETT — a novelist's author site. Deep teal-black ink on warm paper,
   a teal accent, and three saturated bands (harbour, plum, rose) behind the
   pull quotes. Cormorant Garamond small-caps display over Montserrat prose.
   Everything is scoped under `.jb-site` so it can never leak into the Puck
   editor chrome. Ported from the static Jonah Beckett site (Bootstrap 5.3 plus
   a 22KB assets/css/site.css of overrides).

   THIS IS NOT A PORT OF THAT STYLESHEET. The source's layout is Bootstrap's
   grid, spelled out in utility classes on the markup — `col-lg-4`,
   `d-flex flex-column`, `py-7`, `mb-sm-0`. None of that ships here, so every
   grid, gap, breakpoint and stacking rule below is written from what the
   source RENDERS rather than copied from what it declares. What did carry over
   verbatim is `site.css`'s own token block: the `--jb-*` families are the
   source's, at the source's values, and the three that moved are named below.

   THREE COLOUR VALUES ARE DELIBERATELY DARKER THAN THE SOURCE:

     meta / eyebrow / genre    --jb-slate-500 #7b8a8c  3.41:1 → #607072 4.92:1
     "in progress" status      --jb-brass-400 #cc5a61  3.84:1 → #b6363f 5.56:1
     badge fill (white on it)  --jb-brass-400 #cc5a61  3.84:1 → #b6363f 5.85:1

   Each was measured against the ground it actually lands on, and the middle
   pair matter twice over: the source paints its "5% complete" badge and its
   "In progress—details may change." line in `text-warning`, which is the same
   #cc5a61 it uses to FILL the progress bar. A fill has a 3:1 bar and clears it;
   the same value as lettering does not. `--rose` is the text tone and
   `--rose-lit` stays on the bar, which is the one place the source's lighter
   value was always right.

   Every tone below is checked on BOTH light grounds — the paper #fbf9f4 and
   the linen #f4f0e7 — because a band can carry either, and the linen is always
   the binding surface (roughly 0.4 of a point tighter).

   THE FOUR BAND GROUNDS CARRY WHITE LETTERING, and the source lays a dark
   gradient mask over each at 40% before typesetting on it. That mask is kept:
   it is what takes the harbour blue from 4.86:1 against white — passing, but
   with nothing left over for a merchant who re-tints — to 8.43:1, and it is
   why the on-band greys can be white alphas rather than pinned values. An
   alpha composited over a re-tinted band lands where the design intended; a
   pinned grey strands itself.

   `--dark` and the band grounds are deliberately PINNED rather than derived
   from the ink (lib/brandOverrides.ts). Deriving them turns every dark band
   mid-grey under a light `text` override while the on-band inks stay white —
   the finding sunwire and marlowe both recorded, and the same answer.
   ========================================================================== */

/* <ThemeImg> emits each asset's intrinsic width/height so the browser reserves
   the box before the photo loads. Those attributes are author-origin
   presentational hints, one per axis: a rule that sets only a width overrides
   that hint and leaves the height hint standing, both axes go definite, and the
   rule's own aspect-ratio is then ignored — the photo renders at its full pixel
   height. Handing the height back to the ratio is what every sizing rule below
   already assumes. */
img {
  height: auto;
}

/* Cart drawer + product-detail + search tokens. All three render at the
   document root, OUTSIDE `.jb-site`, so they cannot read the wrapper's
   palette. The theme ships cart-off — an author sells through booksellers —
   but a merchant can turn it on per site to sell signed editions, and these
   keep the drawer, /shop and the search overlay in the site's colours. */
:root {
  --cart-bg: #fbf9f4;
  --cart-fg: #0b1d20;
  --cart-muted: #5a6a6c;
  --cart-border: #e8e2d5;
  --cart-accent: #236f75;
  --cart-accent-fg: #fbf9f4;
  --cart-sale: #b6363f;
  --cart-radius: 8px;
  --cart-font-heading: "Cormorant Garamond", Georgia, "Times New Roman", serif;
  --cart-font-body: "Montserrat", system-ui, -apple-system, "Segoe UI", sans-serif;
  --pd-card-bg: #ffffff;
  --pd-card-radius: 12px;
}

.jb-site {
  /* ---------------------------------------------------------------- palette */
  /* The source's own `--jb-*` block, renamed to the short slots the brand
     override maps onto (lib/brandOverrides.ts). */
  --ink: #0b1d20;
  --paper: #fbf9f4;
  --linen: #f4f0e7;
  /* The one hue that is a brand colour rather than a decoration: links, the
     enquiry form's accent, the active nav mark, the cart. */
  --accent: #236f75;
  /* The lettering that lands on an --accent fill. Fixed paper at the shipped
     palette; lib/brandOverrides.ts re-chooses it by luminance for whatever
     accent a merchant picks. */
  --on-accent: #fbf9f4;

  /* Measured text tones — see the header note. */
  --ink-soft: #3d4b4d;
  --ink-mute: #5a6a6c;
  --ink-faint: #607072;

  /* The source's "warning" red, split in two: the darker value carries
     lettering, the lighter one fills the progress bar and nothing else. */
  --rose: #b6363f;
  --rose-lit: #cc5a61;

  /* The lettering on the blank cover mock-up. Pinned like the band grounds and
     for the same reason: it lands on a fixed photographic asset, not on a
     surface a merchant can re-tint, and a light `text` override would strand
     it on kraft paper. A merchant who uploads real cover art replaces the
     whole plate, type included. */
  --plate-ink: #22201c;

  /* The four band grounds. Pinned — see the header note. */
  --dark: #0b1d20;
  --harbour: #43788f;
  --plum: #753f5d;
  --band-mask: rgba(20, 23, 39, 0.4);

  /* Type ON a band or a photograph. White alphas, so they composite correctly
     over whichever ground they land on. The floor is the harbour blue: at 0.72
     white measures 5.28:1 on it, which is the tightest pair on the site. */
  --on-band: #ffffff;
  --on-band-soft: rgba(255, 255, 255, 0.86);
  --on-band-mute: rgba(255, 255, 255, 0.72);
  --on-band-line: rgba(255, 255, 255, 0.28);

  /* Hairlines. Decoration, so alpha is fine — and mixing from the ink is what
     keeps them following a brand override rather than staying teal-black on a
     re-tinted page. */
  --line: color-mix(in srgb, var(--ink) 12%, transparent);
  --line-strong: color-mix(in srgb, var(--ink) 22%, transparent);

  /* THE CARD SURFACE, and it rides the PAGE GROUND rather than being pinned
     white. Every card on this site is white on the cream paper, delineated by
     its shadow. Left as a literal, a dark-page override leaves the cards white
     while the ink goes light and every line inside one measures 1.1–2.4:1 —
     which is exactly what the inverted sweep found. It is listed in the
     `background` slot (lib/brandOverrides.ts) so it re-tints with the page, and
     the hairline below is what keeps a card readable as a card once its fill no
     longer differs from the ground. */
  --card: #ffffff;

  /* The scrim under the hero and every page banner, and the ink that lands on
     the white pill. Both are PINNED, and for the reason marlowe and sunwire
     both recorded: derived from `--ink`, a light `text` override turns the
     scrim into a pale wash under white display type (1.3:1) and flips the
     pill's lettering to paper-on-white (1.14:1). Both surfaces they land on —
     a photograph and the fixed band grounds — cannot be re-tinted either, so
     deriving them buys nothing and costs the two pairs above. */
  --scrim: color-mix(in srgb, var(--dark) 82%, #000);
  --on-light: #0b1d20;

  /* ------------------------------------------------------------------ shape */
  --serif: "Cormorant Garamond", Georgia, "Times New Roman", serif;
  --sans: "Montserrat", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  --maxw: 1180px;
  --edge: clamp(20px, 4.4vw, 48px);
  --r-sm: 5px;
  --r-md: 8px;
  --r-lg: 12px;
  --r-xl: 16px;
  --r-pill: 999px;
  --track: 0.14em;
  --bar-h: 76px;
  --shadow-md: 0 2px 4px rgba(11, 29, 32, 0.06), 0 8px 20px rgba(11, 29, 32, 0.07);
  --shadow-lg: 0 4px 8px rgba(11, 29, 32, 0.07), 0 18px 40px rgba(11, 29, 32, 0.1);
  /* The same two shadows again as filters. The cover artwork is a transparent
     3-D render on a 456x700 canvas with ~20px of clear margin around it, so a
     `box-shadow` on the plate draws a rectangle floating a few pixels off the
     jacket. These follow the book's own silhouette instead. Same offsets, same
     ink — swapping one for the other changes nothing but the shape. */
  --drop-md: drop-shadow(0 2px 4px rgba(11, 29, 32, 0.06))
    drop-shadow(0 8px 20px rgba(11, 29, 32, 0.07));
  --drop-lg: drop-shadow(0 4px 8px rgba(11, 29, 32, 0.07))
    drop-shadow(0 18px 40px rgba(11, 29, 32, 0.1));

  /* Shared nav dropdown / mega-menu tokens
     (components/storefront/nav-menu.css). The bar is the dark capsule, but a
     dropdown PANEL hangs below it onto the page, so it takes the page's own
     light surface rather than the bar's. */
  --menu-bg: var(--card);
  --menu-fg: var(--ink);
  --menu-muted: var(--ink-mute);
  --menu-accent: var(--accent);
  --menu-border: var(--line);
  --menu-hover-bg: var(--linen);
  --menu-radius: 12px;
  --menu-heading-font: var(--serif);
  --menu-ph-bg: var(--linen);

  /* NOTE: the --loc-* family is NOT declared here. `.loc-section` and
     `.loc-detail` re-declare those tokens on themselves (locations.css), so an
     inherited value loses and the block falls back to the app-wide brown in
     app/globals.css. They are set on those two selectors further down. */

  background: var(--paper);
  color: var(--ink-soft);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  /* `clip`, never `hidden`: a page renders THREE sibling wrappers (header zone,
     body, footer zone), and `overflow-x: hidden` computes overflow-y to `auto`
     per spec — turning the short header wrapper into a scroll container that
     clips the mobile panel hanging below it (docs/THEMES-GUIDE.md §6). `clip`
     leaves the y axis `visible`. */
  overflow-x: clip;
}

/* The anchor offset is a LITERAL: `--bar-h` is declared on the site wrapper,
   and a var() referenced from `html` has nothing to resolve against — it would
   compute to `auto` and land every anchor under the bar. */
html:has(.jb-site) {
  scroll-behavior: smooth;
  scroll-padding-top: 108px;
}

/* -------------------------------------------------------------------- reset */
.jb-site *,
.jb-site *::before,
.jb-site *::after {
  box-sizing: border-box;
}
.jb-site img {
  display: block;
  max-width: 100%;
}
/* `:where()` for the same reason as the type scale above: `.jb-site a` is
   (0,1,1) and `.jb-btn` is (0,1,0), so a plain element rule OUTRANKS every
   button on the site — the dark pills rendered accent-teal lettering on the
   ink at 2.97:1. Zero-specificity here makes this a tie with any single class,
   decided by source order, and every button rule comes later. */
.jb-site :where(a) {
  color: var(--accent);
  text-decoration: none;
  transition: color 0.16s ease;
}
.jb-site :where(a:hover) {
  color: var(--ink);
}
.jb-site ::selection {
  background: var(--accent);
  color: var(--on-accent);
}
/* The source zeroes these and spaces everything with Bootstrap's margin
   utilities. Restated inside the scope so it can never reach the editor chrome
   — and note the legal pages restore their own margins further down, because
   richpage.css loses to this reset on source order. */
.jb-site :where(h1, h2, h3, h4, p, figure, blockquote, ul) {
  margin: 0;
}
.jb-site :where(ul) {
  padding: 0;
  list-style: none;
}
.jb-site :focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/* --------------------------------------------------------------- type scale */
/* The source runs Cormorant Garamond at 300–400 for every display line and
   sets its h1/h2 in small caps. Both carry over; the weights stay light
   because the face is a Garamond and a 600 in it reads as a different font.

   THE ELEMENT SELECTORS ARE WRAPPED IN `:where()`, AND THAT IS LOAD-BEARING.
   `.jb-site h1` is (0,1,1) and a component class like `.jb-hero-name` is
   (0,1,0), so a plain element base OUTRANKS every block that means to restyle
   its own heading — the hero name rendered at the body h1 size in `--ink` on
   the dark hero and was very nearly invisible. `:where()` contributes zero, so
   this block is (0,1,0): a tie with any single class, decided by source order,
   and every component rule below comes later. The same reason the class
   spellings are their own rules rather than riding these — `.jb-h3` on an
   `<h2>` has to beat the h2 size, and grouped with it, it could not. */
.jb-site :where(h1, h2, h3, h4) {
  font-family: var(--serif);
  font-weight: 400;
  line-height: 1.12;
  letter-spacing: 0.01em;
  color: var(--ink);
}
.jb-site :where(h1) {
  font-size: clamp(2.3rem, 4.4vw, 3.5rem);
}
.jb-site :where(h2) {
  font-size: clamp(1.9rem, 3.2vw, 2.75rem);
}
.jb-site :where(h3) {
  font-size: clamp(1.5rem, 2.3vw, 2rem);
}
.jb-h1 {
  font-family: var(--serif);
  font-size: clamp(2.3rem, 4.4vw, 3.5rem);
}
.jb-h2 {
  font-family: var(--serif);
  font-size: clamp(1.9rem, 3.2vw, 2.75rem);
}
.jb-h3 {
  font-family: var(--serif);
  font-size: clamp(1.5rem, 2.3vw, 2rem);
}
.jb-body {
  margin-bottom: 1.15em;
  color: var(--ink-soft);
}
.jb-body:last-child {
  margin-bottom: 0;
}
.jb-lead {
  margin-top: 12px;
  max-width: 62ch;
  color: var(--ink-mute);
}

/* The source has no eyebrow at all — its sections open on a bare h2. This is
   the platform's, in the same tracked caps as the nav links so it reads as
   part of the same system. */
.jb-eyebrow {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.jb-eyebrow.is-light {
  color: var(--on-band-soft);
}

.jb-container {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--edge);
}
.jb-section {
  padding-block: clamp(56px, 7vw, 104px);
}
.jb-section-head {
  margin-bottom: clamp(28px, 3.4vw, 44px);
}
.jb-section-head .jb-eyebrow {
  margin-bottom: 10px;
}
.jb-head-row {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
  margin-bottom: clamp(28px, 3.4vw, 44px);
}
.jb-head-row .jb-section-head {
  margin-bottom: 0;
}

/* ------------------------------------------------------------- swatch grounds */
/* `bgColorField` stores a token; the block renders it as `bg-<token>`. Two
   lists share these: the section grounds (paper / linen / ink) and the band
   grounds (harbour / plum / rose / ink). */
.jb-site .bg-paper {
  background: var(--paper);
}
.jb-site .bg-linen {
  background: var(--linen);
}
.jb-site .bg-ink {
  background: var(--dark);
}
.jb-site .bg-harbour {
  background: var(--harbour);
}
.jb-site .bg-plum {
  background: var(--plum);
}
.jb-site .bg-rose {
  background: var(--rose);
}
/* A section on the ink ground flips every tone it owns. The band grounds do
   their own flipping inside the quote/newsletter rules, because those two also
   paint the mask. */
.jb-site .jb-section.bg-ink {
  color: var(--on-band-soft);
}
.jb-site .jb-section.bg-ink :is(h1, h2, h3, h4, .jb-h1, .jb-h2, .jb-h3) {
  color: var(--on-band);
}
.jb-site .jb-section.bg-ink .jb-body,
.jb-site .jb-section.bg-ink .jb-lead {
  color: var(--on-band-soft);
}
.jb-site .jb-section.bg-ink .jb-eyebrow {
  color: var(--on-band-mute);
}

/* ------------------------------------------------------------------ buttons */
/* The source has three: a dark pill (`btn bg-gradient-dark btn-round`), an
   outlined dark pill and a white pill on a photograph. */
.jb-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 13px 30px;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  background: var(--ink);
  color: var(--paper);
  font-family: var(--sans);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: var(--track);
  text-transform: uppercase;
  /* A wider substitute face in the body slot wraps a two-word label onto a
     second line and grows every card foot by 20px. */
  white-space: nowrap;
  cursor: pointer;
  transition:
    background 0.18s ease,
    color 0.18s ease,
    border-color 0.18s ease;
}
.jb-btn:hover {
  background: var(--accent);
  color: var(--on-accent);
}
.jb-btn-ghost {
  background: transparent;
  border-color: var(--line-strong);
  color: var(--ink);
}
.jb-btn-ghost:hover {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}
/* The white pill only ever lands on a photograph or a pinned band, so both of
   its colours are pinned too — `var(--ink)` here is paper-on-white the moment a
   merchant inverts the palette. */
.jb-btn-light {
  background: #ffffff;
  color: var(--on-light);
}
.jb-btn-light:hover {
  background: #ece7dc;
  color: var(--on-light);
}
.jb-btn-sm {
  padding: 10px 22px;
  font-size: 11px;
}
.jb-btn-lg {
  padding: 16px 38px;
  font-size: 12.5px;
}
.jb-btn-block {
  display: flex;
  width: 100%;
}
.jb-btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 28px;
}
.jb-link {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.04em;
  border-bottom: 1px solid var(--line-strong);
  padding-bottom: 2px;
}

/* The source's `badge bg-warning`: white on the red. */
.jb-badge {
  display: inline-block;
  margin-bottom: 16px;
  padding: 6px 14px;
  border-radius: var(--r-pill);
  background: var(--rose);
  color: #ffffff;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

/* Empty-state card the live blocks show inside the editor. */
.jb-empty-card {
  padding: 28px;
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-lg);
  background: var(--linen);
  color: var(--ink-mute);
  font-size: 14px;
}
/* Placeholder box wherever an image slot is empty. */
.jb-ph {
  display: block;
  aspect-ratio: 2 / 3;
  border-radius: var(--r-lg);
  background: var(--linen);
}
.jb-ph-wide {
  aspect-ratio: 3 / 2;
}

/* -------------------------------------------------------------------- grids */
.jb-grid3 {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(24px, 3vw, 40px);
}
.jb-grid2 {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(28px, 3.4vw, 48px) clamp(24px, 3vw, 40px);
}

/* ================================================================== HEADER */
/* The source's bar is a rounded dark capsule floating over the top of the
   page: `position-absolute … mx-4 bg-gradient-dark blur border-radius-3xl`.
   Kept, as a fixed capsule so it survives the scroll the source's absolute one
   does not.

   THE FROSTED BACKDROP SITS ON A CHILDLESS ::before. `backdrop-filter` on the
   bar itself clips every descendant to its own box, which swallows the mobile
   panel hanging below it at `top: 100%` (docs/THEMES-GUIDE.md §6). */
.jb-header {
  position: fixed;
  inset: 16px 0 auto;
  z-index: 60;
  pointer-events: none;
}
/* Inside the editor the bar is laid out in flow: a fixed element inside the
   Puck canvas iframe pins to the iframe, not the page, and the drag layer
   cannot measure it. */
.jb-header.is-static {
  position: relative;
  inset: auto;
}
.jb-bar {
  position: relative;
  width: min(calc(100% - 2 * var(--edge)), var(--maxw));
  margin-inline: auto;
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-lg);
  pointer-events: auto;
}
.jb-bar::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: color-mix(in srgb, var(--dark) 88%, transparent);
  -webkit-backdrop-filter: saturate(1.4) blur(14px);
  backdrop-filter: saturate(1.4) blur(14px);
}
.jb-bar-inner {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 18px;
  min-height: var(--bar-h);
  padding: 10px clamp(16px, 2.2vw, 28px);
}
.jb-brand {
  display: flex;
  align-items: center;
  margin-right: auto;
  color: var(--on-band);
}
.jb-brand:hover {
  color: var(--on-band);
}
.jb-wordmark {
  font-family: var(--serif);
  font-size: clamp(1.35rem, 2vw, 1.7rem);
  font-weight: 400;
  font-variant-caps: small-caps;
  letter-spacing: 0.04em;
  line-height: 1;
}
.jb-logo-img {
  height: var(--jb-logo-h, 24px);
  width: auto;
}

.jb-nav-links {
  display: flex;
  align-items: center;
  gap: clamp(14px, 1.8vw, 28px);
}
/* NavMenu renders its trigger as an <a> (or a <button> for a dropdown); both
   take the same type here. */
.jb-nav-links :is(a, button) {
  background: none;
  border: 0;
  padding: 0;
  font-family: var(--sans);
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: 0.06em;
  color: var(--on-band-soft);
  cursor: pointer;
  transition: color 0.16s ease;
}
.jb-nav-links :is(a, button):hover {
  color: var(--on-band);
}
.jb-nav-links a.is-active,
.jb-nav-links button.is-active {
  color: var(--on-band);
  font-weight: 600;
}
.jb-nav-cta {
  padding: 9px 20px !important;
  border: 1px solid var(--on-band-line) !important;
  border-radius: var(--r-pill);
  text-transform: uppercase;
  font-size: 11px !important;
  letter-spacing: 0.16em !important;
  white-space: nowrap;
  transition:
    background 0.16s ease,
    color 0.16s ease;
}
.jb-nav-cta:hover {
  background: var(--on-band);
  color: var(--ink) !important;
}

.jb-bar-actions {
  display: flex;
  align-items: center;
  gap: 10px;
}
/* SiteSearch and CartTrigger both render their own button; recolour rather
   than re-layout. */
.jb-site .jb-search button,
.jb-site .jb-cart {
  color: var(--on-band-soft);
}
.jb-site .jb-search button:hover,
.jb-site .jb-cart:hover {
  color: var(--on-band);
}

/* ---------------------------------------------------------- mobile nav */
.jb-nav-toggle {
  display: none;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  color: var(--on-band);
}
.jb-burger {
  display: grid;
  gap: 5px;
  justify-items: center;
}
.jb-burger span {
  display: block;
  width: 22px;
  height: 1.5px;
  background: currentColor;
  transition:
    transform 0.22s ease,
    opacity 0.18s ease;
}
.jb-nav-toggle.is-open .jb-burger span:nth-child(1) {
  transform: translateY(6.5px) rotate(45deg);
}
.jb-nav-toggle.is-open .jb-burger span:nth-child(2) {
  opacity: 0;
}
.jb-nav-toggle.is-open .jb-burger span:nth-child(3) {
  transform: translateY(-6.5px) rotate(-45deg);
}

/* IN DOWN, OUT UP. The closed state holds `translateY(-10px)`, so opening
   settles it to 0 (down) and closing returns it upward. `visibility` is
   delayed by the fade duration on close so the panel is not yanked out of the
   hit-test mid-transition. */
.jb-mobile-menu {
  position: absolute;
  top: calc(100% + 10px);
  left: 0;
  right: 0;
  z-index: 5;
  display: grid;
  gap: 6px;
  padding: 18px;
  border-radius: var(--r-xl);
  background: var(--paper);
  box-shadow: var(--shadow-lg);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-10px);
  transition:
    opacity 0.24s ease,
    transform 0.24s ease,
    visibility 0s linear 0.24s;
}
.jb-mobile-menu.is-open {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  transition:
    opacity 0.24s ease,
    transform 0.24s ease,
    visibility 0s;
}
.jb-mobile-links {
  display: grid;
}
.jb-mobile-links a {
  padding: 12px 4px;
  border-bottom: 1px solid var(--line);
  color: var(--ink);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.04em;
}
.jb-mobile-links a.is-active {
  color: var(--accent);
  font-weight: 600;
}
.jb-mobile-cta {
  margin-top: 12px;
  padding: 13px 24px;
  border-radius: var(--r-pill);
  background: var(--ink);
  color: var(--paper);
  text-align: center;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: var(--track);
  text-transform: uppercase;
}
.jb-mobile-cta:hover {
  background: var(--accent);
  color: var(--on-accent);
}
.jb-site .jb-search-mobile {
  margin-bottom: 6px;
}
.jb-site .jb-search-mobile button {
  color: var(--ink);
}

/* ==================================================================== HERO */
/* The source's three-layer hero. Each layer is positioned as a PERCENTAGE OF
   THE STAGE — a 110rem-max, centred box — rather than of the viewport, which
   is what keeps the cut-out, the book and the copy in the same relationship
   from 390px to 2560px. Percentages of the viewport would slide the copy off
   the book's pages on a wide monitor and behind the cut-out on a narrow one.

   Below 900px the layered composition is abandoned rather than shrunk: three
   overlapping images inside 390px is a smear, so the book and the cut-out drop
   out and the copy takes the whole frame over the photograph. */
.jb-hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  height: clamp(30rem, 40vw, 42rem);
  background: var(--dark);
}
.jb-hero-backdrop {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Darkens the left half so the cut-out reads, and the middle so the white
   display type stays legible over the book's pages. */
.jb-hero-scrim {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(
      90deg,
      color-mix(in srgb, var(--scrim) 78%, transparent) 0%,
      color-mix(in srgb, var(--scrim) 46%, transparent) 40%,
      color-mix(in srgb, var(--scrim) 12%, transparent) 72%,
      transparent 100%
    ),
    linear-gradient(180deg, color-mix(in srgb, var(--scrim) 58%, transparent) 0%, transparent 42%);
}
.jb-hero-stage {
  position: relative;
  height: 100%;
  width: 100%;
  max-width: 110rem;
  margin-inline: auto;
}
.jb-hero-book {
  position: absolute;
  left: 35%;
  top: 8%;
  width: 37%;
  opacity: 0.62;
  transform: rotate(-1.5deg);
  filter: grayscale(1) brightness(0.78) contrast(1.02)
    drop-shadow(0 1rem 3rem rgba(0, 0, 0, 0.55));
}
/* Slightly cooled and darkened so the cut-out sits in the scene instead of
   reading as a sticker on top of it. */
.jb-hero-figure {
  position: absolute;
  left: 11%;
  bottom: 0;
  height: 76%;
  width: auto;
  filter: brightness(0.88) contrast(1.03) saturate(0.92)
    drop-shadow(0 1.5rem 3rem rgba(0, 0, 0, 0.6));
}
.jb-hero-copy {
  position: absolute;
  left: 38%;
  top: 63%;
  width: 35%;
  z-index: 1;
  transform: translateY(-50%);
}
/* Soft pool of shadow so the type stays readable over the book's pages. */
.jb-hero-copy::before {
  content: "";
  position: absolute;
  inset: -20% -14% -24% -10%;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(
    ellipse at center,
    color-mix(in srgb, var(--scrim) 66%, transparent) 0%,
    color-mix(in srgb, var(--scrim) 36%, transparent) 46%,
    transparent 78%
  );
}
.jb-hero-name {
  font-family: var(--serif);
  font-weight: 400;
  font-variant-caps: small-caps;
  font-feature-settings: "smcp" 1;
  font-size: clamp(2.5rem, 5.5vw, 6.25rem);
  line-height: 0.95;
  letter-spacing: 0.02em;
  white-space: nowrap;
  color: #ffffff;
  text-shadow: 0 0.15rem 1.5rem rgba(0, 0, 0, 0.5);
}
.jb-hero-rule {
  height: 1px;
  border: 0;
  opacity: 1;
  margin: clamp(0.6rem, 1vw, 1.15rem) 0 clamp(0.85rem, 1.2vw, 1.5rem);
  background: linear-gradient(90deg, rgba(255, 255, 255, 0.75), rgba(255, 255, 255, 0));
}
.jb-hero-blurb {
  max-width: 34ch;
  color: var(--on-band-soft);
  font-size: clamp(0.9rem, 1vw, 1.05rem);
  line-height: 1.6;
  text-shadow: 0 0.1rem 0.9rem rgba(0, 0, 0, 0.55);
}

/* ================================================================= BANNER */
/* The source's `page-header min-height-400` strip at the top of every interior
   page: a photograph under a dark wash. It sits BEHIND the fixed bar, so it
   carries the bar's height as top padding whenever it has copy in it. */
.jb-banner {
  position: relative;
  isolation: isolate;
  display: flex;
  align-items: flex-end;
  min-height: 400px;
  overflow: hidden;
  background: var(--dark);
}
.jb-banner.is-tall {
  min-height: min(65vh, 620px);
}
.jb-banner-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.jb-banner-mask {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    color-mix(in srgb, var(--scrim) 62%, transparent) 0%,
    color-mix(in srgb, var(--scrim) 24%, transparent) 46%,
    color-mix(in srgb, var(--scrim) 72%, transparent) 100%
  );
}
.jb-banner-copy {
  position: relative;
  z-index: 1;
  padding-block: clamp(120px, 12vw, 150px) clamp(36px, 4vw, 60px);
}
.jb-banner-copy .jb-eyebrow {
  margin-bottom: 12px;
}
.jb-banner-h1 {
  color: #ffffff;
  font-variant-caps: small-caps;
  letter-spacing: 0.02em;
}
.jb-banner-lead {
  margin-top: 14px;
  max-width: 56ch;
  color: var(--on-band-soft);
}
/* With no copy the band is decoration only, and it still has to clear the bar
   — otherwise the capsule floats over a blank photograph and the page's first
   heading collides with it. */
.jb-banner:not(.has-copy) {
  min-height: 340px;
}

/* ============================================================== WHAT'S NEXT */
/* Three cards, each a cover plate over a completion bar and the pitch. The
   source gives them `shadow-lg blur h-100 border-radius-3xl`; the blur is
   dropped — it did nothing over an opaque white card — and the rest kept. */
.jb-card {
  display: flex;
  flex-direction: column;
  border-radius: var(--r-xl);
  background: var(--card);
  /* The source delineates its cards with the shadow alone. The hairline is the
     platform's addition and it earns its place on a re-tinted site: once
     `--card` rides the page ground, a shadow on a dark page is invisible and
     the card stops being a card. At the shipped palette it is a 12%-ink line
     around a white card on cream, which is barely there. */
  border: 1px solid var(--line);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.jb-card-media {
  display: block;
}
.jb-card-body {
  padding: 22px 24px 8px;
}
.jb-card-foot {
  margin-top: auto;
  padding: 12px 24px 26px;
}
.jb-card-text {
  margin-top: 14px;
  color: var(--ink-soft);
  font-size: 14.5px;
}
.jb-progress {
  height: 10px;
  border-radius: var(--r-pill);
  background: var(--linen);
  overflow: hidden;
}
.jb-progress-bar {
  display: block;
  height: 100%;
  border-radius: var(--r-pill);
  /* The lighter red: this is a FILL, so the 3:1 bar applies and the source's
     own value clears it. Its darker sibling carries the label below. */
  background: var(--rose-lit);
}
/* `--rose-ink`, falling back to the pinned rose. The rose is the source's
   "warning" hue and it is pinned to the progress bar it belongs to — but as
   LETTERING it lands on the page ground, and on a dark-page override a mid-red
   measures 3.19:1 there. `readableInk` in lib/brandOverrides.ts re-picks it
   against whatever ground a merchant chose, and emits nothing at all until one
   is set, so the shipped design keeps this exact red. */
.jb-progress-label {
  margin-top: 10px;
  color: var(--rose-ink, var(--rose));
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* ------------------------------------------------------------- cover plate */
/* The source's `.cover-plate`: a blank jacket mock-up with the title typeset
   over it between two rules. Used wherever a title has no cover art yet. */
.jb-plate {
  position: relative;
  display: block;
  overflow: hidden;
  border-radius: var(--r-lg);
  /* NO GROUND ON THE PLATE ITSELF. Every jacket in this theme is a transparent
     3-D render, so a fill here is a linen rectangle behind the book — visible
     on the white What's Next cards, on the dark quote band and on the book
     page. Only the blank mock-up below needs one. */
  /* The source sizes the plate's type in `cqw`, so the title scales with the
     card rather than the viewport — a plate in a three-up grid and the same
     plate filling a book page's cover column both get the right size. */
  container-type: inline-size;
}
/* The blank jacket IS the linen: `cover-blank.webp` is a kraft board whose
   edges fade out to alpha 36, so it needs the ground the artwork does not. */
.jb-plate.is-blank {
  background: var(--linen);
}
.jb-plate-art {
  width: 100%;
  height: auto;
}
/* THE BLANK MOCK-UP KEEPS ITS OWN LANDSCAPE RATIO rather than being cropped to
   a jacket's. The source uses it that way in all three places it appears — a
   banner across the top of a home card, the Forthcoming row, and the cover
   column of an unannounced book's page — and it never mixes plates and jackets
   inside one grid, so nothing goes ragged. Forcing 456:700 on it was worse in
   both directions: `cover` on a taller box throws away the left and right of
   the mock-up and leaves a 514px column of blank paper, which made the home
   cards half as tall again as the source's. */
/* NO SCRIM, AND DARK INK. The mock-up is a kraft-coloured blank jacket, and
   the source typesets straight onto its front board — `left: 7%; right: 27%`
   keeps the block off the spine down the right-hand quarter. Measured over the
   band the type actually lands on, #22201c reads 6.62:1 at the mean and 5.07:1
   at the darkest 1% of it. A white-on-scrim version was tried first and came in
   at 2.45:1: the artwork is too light to carry white, and darkening it enough
   to would have buried the jacket the plate exists to show. */
.jb-plate-inner {
  position: absolute;
  top: 50%;
  left: 7%;
  right: 27%;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  color: var(--plate-ink);
}
.jb-plate-rule {
  display: block;
  width: 100%;
  height: 2px;
  background: currentColor;
  opacity: 0.85;
}
.jb-plate-title {
  display: block;
  margin: 4.3cqw 0;
  /* The BODY face, not the display serif: the source sets its blank jackets in
     Montserrat 700 small-caps, which is what makes a plate read as a printer's
     placeholder rather than as a typeset cover. */
  font-family: var(--sans);
  font-weight: 700;
  font-variant-caps: small-caps;
  font-size: 10cqw;
  line-height: 1.12;
  letter-spacing: 0.005em;
}
.jb-plate-note {
  display: block;
  margin-top: 3cqw;
  font-family: var(--sans);
  font-weight: 500;
  font-size: 3.2cqw;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* ========================================================== FEATURED BOOK */
.jb-feature {
  position: relative;
  isolation: isolate;
  display: flex;
  align-items: center;
  min-height: min(72vh, 620px);
  overflow: hidden;
  background: var(--dark);
}
.jb-feature-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.jb-feature-mask {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    90deg,
    color-mix(in srgb, var(--scrim) 74%, transparent) 0%,
    color-mix(in srgb, var(--scrim) 44%, transparent) 52%,
    color-mix(in srgb, var(--scrim) 14%, transparent) 100%
  );
}
/* THE COPY COLUMN IS CAPPED, NOT THE BOX. This element carries `jb-container`
   too, so it is already the page's centred 1180px measure with an `--edge`
   gutter — and the width limit belongs on the copy inside it. Capping the BOX
   at 640px while padding it as though it spanned the viewport made the two
   fight: the gutter is `(100vw - 1180px)/2 + edge`, which is 178px a side at
   1440px and 348px at 1780px, so the 640px box was left with 284px of content
   and then with none at all — past about 1460px the padding exceeded the box
   and the heading collapsed to one word per line. */
.jb-feature-copy {
  position: relative;
  z-index: 1;
  padding-block: clamp(56px, 7vw, 96px);
}
/* The band's copy runs to a measure rather than the full container, which is
   what keeps it off the photograph's subject on a wide monitor. On the
   children, so the box itself stays the page's own gutter. */
.jb-feature-copy > * {
  max-width: 640px;
}
.jb-feature-h2 {
  color: #ffffff;
  font-size: clamp(2.1rem, 4vw, 3.4rem);
}
.jb-feature-lead {
  margin: 16px 0 30px;
  max-width: 46ch;
  color: var(--on-band-soft);
}

/* =============================================================== PULL QUOTE */
/* A flat band under a dark mask, the source's quote mark, a display line in
   the author's voice and the avatar row. The optional cover column is the home
   page's variant; a book page runs the quote full width. */
.jb-quote-band {
  position: relative;
  isolation: isolate;
  padding-block: clamp(52px, 6.5vw, 96px);
}
.jb-quote-mask {
  position: absolute;
  inset: 0;
  background: var(--band-mask);
}
.jb-quote-inner {
  position: relative;
  z-index: 1;
}
.jb-quote-inner.has-cover {
  display: grid;
  grid-template-columns: minmax(0, 300px) minmax(0, 1fr);
  gap: clamp(28px, 4vw, 64px);
  align-items: center;
}
.jb-quote-cover {
  display: grid;
  gap: 22px;
}
.jb-quote-glyph {
  display: block;
  margin-bottom: 18px;
  color: rgba(255, 255, 255, 0.55);
}
.jb-quote-text {
  font-family: var(--serif);
  font-size: clamp(1.6rem, 2.9vw, 2.5rem);
  font-weight: 400;
  line-height: 1.2;
  color: #ffffff;
}
.jb-quote-cite {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-top: clamp(26px, 3vw, 42px);
}
.jb-avatar {
  display: block;
  flex: none;
  width: 62px;
  height: 62px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.85);
  overflow: hidden;
  background: var(--linen);
}
.jb-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* The author cut-out is a full-length figure; a face crop is what a round
     avatar wants out of it. */
  object-position: 50% 8%;
}
.jb-quote-name {
  display: block;
  color: #ffffff;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.04em;
}
.jb-quote-source {
  display: block;
  margin-top: 2px;
  color: var(--on-band-mute);
  font-size: 13.5px;
}

/* ============================================================ GENRE MARQUEE */
/* The source duplicates its list so one full copy can translate and loop
   seamlessly. Same here, with the second copy aria-hidden. */
.jb-marquee {
  overflow: hidden;
  padding-block: clamp(28px, 3.4vw, 48px);
  border-block: 1px solid var(--line);
  background: var(--paper);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.jb-marquee-track {
  display: flex;
  width: max-content;
  animation: jb-marquee 38s linear infinite;
}
.jb-marquee-run {
  display: flex;
  align-items: center;
  gap: clamp(32px, 4vw, 64px);
  padding-right: clamp(32px, 4vw, 64px);
}
.jb-marquee-item {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  white-space: nowrap;
  font-family: var(--serif);
  font-size: clamp(1.5rem, 2.6vw, 2.3rem);
  font-weight: 400;
  color: var(--ink);
}
.jb-marquee-item img {
  width: auto;
  height: clamp(38px, 4.4vw, 58px);
  object-fit: contain;
}
@keyframes jb-marquee {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}
/* Paused inside the editor: a strip sliding under the drag layer cannot be
   grabbed. */
.jb-marquee.is-paused .jb-marquee-track {
  animation-play-state: paused;
}

/* ============================================================== AUTHOR STRIP */
.jb-about-grid {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: clamp(28px, 4vw, 64px);
  align-items: center;
}
.jb-about-grid.is-right {
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
}
.jb-about-grid.is-right .jb-about-media {
  order: 2;
}
.jb-about-media img {
  width: 100%;
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-lg);
}
.jb-about-copy .jb-eyebrow {
  margin-bottom: 12px;
}
.jb-about-copy .jb-h2 {
  margin-bottom: 22px;
}

/* ================================================================== BOOKS */
.jb-books .jb-section-head {
  max-width: 62ch;
}
.jb-book {
  display: flex;
  flex-direction: column;
}
.jb-book-cover {
  display: block;
}
.jb-book-cover .jb-plate {
  filter: var(--drop-md);
  transition: transform 0.24s ease;
}
/* The blank plate is a filled box, so it takes the box shadow it was drawn
   for — a filter on it would only soften its own faded edge. */
.jb-book-cover .jb-plate.is-blank {
  filter: none;
  box-shadow: var(--shadow-md);
}
.jb-book-cover:hover .jb-plate {
  transform: translateY(-4px);
}
.jb-book-copy {
  margin-top: 18px;
  text-align: center;
}
.jb-book-h {
  font-size: clamp(1.15rem, 1.5vw, 1.35rem);
  line-height: 1.25;
}
.jb-book-h a {
  color: var(--ink);
}
.jb-book-h a:hover {
  color: var(--accent);
}
.jb-book-genre {
  margin: 6px 0 14px;
  color: var(--ink-faint);
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}
/* The plate layout drops the title and genre — the plate already carries the
   title — so the button needs the margin the copy block would have given it. */
.jb-grid3.is-plates .jb-book-copy {
  margin-top: 22px;
}

/* ============================================================== BOOK DETAIL */
.jb-bookhero-grid {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  gap: clamp(28px, 4.5vw, 72px);
  align-items: start;
}
.jb-bookhero-copy .jb-h2 {
  margin-bottom: 18px;
}
/* THE WIDTH HAS TO BE DEFINITE, NOT A CAP. `max-width` plus `margin-inline:
   auto` takes a grid item out of `stretch` and sizes it to fit-content — and
   the plate inside is a `container-type: inline-size` box, whose contents by
   definition contribute nothing to its own inline size. The column measured
   fit-content of that as 0 and the jacket vanished on every book page. Giving
   the box a real width resolves the plate's `100%` against something, and the
   auto margins still centre whatever slack is left in the column. */
.jb-bookhero-cover {
  width: min(100%, 400px);
  margin-inline: auto;
}
.jb-bookhero-cover .jb-plate {
  filter: var(--drop-lg);
}
.jb-bookhero-cover .jb-plate.is-blank {
  filter: none;
  box-shadow: var(--shadow-lg);
}
.jb-status {
  margin-bottom: 20px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.08em;
}
.jb-status.is-available {
  color: var(--ink-mute);
}
/* The source paints this in its light red, which measures 3.84:1 — see the
   header note. */
.jb-status.is-progress {
  color: var(--rose-ink, var(--rose));
}
.jb-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 26px;
}
.jb-tag {
  padding: 6px 13px;
  border-radius: var(--r-pill);
  background: var(--linen);
  color: var(--ink-soft);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* ========================================================= CHARACTER CARDS */
/* The source hangs a monogram medallion over the top edge of each card, which
   needs the row to carry the overhang as top padding — otherwise the first row
   of medallions is clipped by the band. */
.jb-cast-head {
  margin-bottom: clamp(48px, 5vw, 72px);
}
.jb-cast .jb-grid2 {
  row-gap: clamp(56px, 6vw, 80px);
}
.jb-cast-card {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 52px 30px 30px;
  border-radius: var(--r-xl);
  background: var(--card);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-lg);
}
.jb-cast-monogram {
  position: absolute;
  top: -34px;
  left: 50%;
  transform: translateX(-50%);
  display: grid;
  place-items: center;
  width: 68px;
  height: 68px;
  border-radius: 50%;
  border: 2px solid var(--ink);
  background: var(--paper);
  font-family: var(--serif);
  font-size: 1.5rem;
  letter-spacing: 0.06em;
  color: var(--ink);
}
.jb-cast-name {
  text-align: center;
}
.jb-cast-first {
  font-size: clamp(1.35rem, 1.8vw, 1.6rem);
  line-height: 1.1;
}
.jb-cast-last {
  font-family: var(--serif);
  font-size: clamp(1.1rem, 1.5vw, 1.3rem);
  line-height: 1.2;
  color: var(--ink);
}
.jb-cast-role {
  margin-top: 8px;
  color: var(--ink-faint);
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}
.jb-cast-body {
  margin-top: 22px;
  color: var(--ink-soft);
  font-size: 14.5px;
}
/* The source opens each description on a drop cap. */
.jb-cast-body::first-letter {
  float: left;
  margin: 6px 8px 0 0;
  font-family: var(--serif);
  font-size: 3.1rem;
  line-height: 0.78;
  color: var(--accent);
}

/* ================================================================== PROSE */
.jb-prose-col {
  max-width: 74ch;
}
.jb-prose-col.is-wide {
  max-width: 92ch;
}
.jb-prose-col > .jb-h2 {
  margin-bottom: 26px;
}
.jb-prose-part + .jb-prose-part {
  margin-top: 30px;
}
.jb-prose-sub {
  margin-bottom: 12px;
  font-family: var(--sans);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--ink);
}
.jb-prose-list {
  margin: 14px 0 0;
  display: grid;
  gap: 10px;
}
.jb-prose-list li {
  position: relative;
  padding-left: 20px;
  color: var(--ink-soft);
}
.jb-prose-list li::before {
  content: "•";
  position: absolute;
  left: 2px;
  color: var(--accent);
}

/* ================================================================= PRAISE */
.jb-praise-card {
  display: flex;
  flex-direction: column;
  padding: 30px;
  border-radius: var(--r-xl);
  background: var(--card);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-md);
}
.jb-stars {
  margin-bottom: 12px;
  color: var(--accent);
  font-size: 15px;
  letter-spacing: 0.14em;
}
.jb-praise-text {
  flex: 1;
  font-family: var(--serif);
  font-size: clamp(1.15rem, 1.5vw, 1.35rem);
  line-height: 1.36;
  color: var(--ink);
}
.jb-praise-card footer {
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--line);
}
.jb-praise-name {
  color: var(--ink);
  font-size: 13.5px;
  font-weight: 600;
  letter-spacing: 0.03em;
}
.jb-praise-role {
  margin-top: 3px;
  color: var(--ink-faint);
  font-size: 12px;
}

/* ================================================================ JOURNAL */
.jb-post {
  display: flex;
  flex-direction: column;
  color: inherit;
}
.jb-post img {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-md);
  transition: transform 0.24s ease;
}
.jb-post:hover img {
  transform: translateY(-4px);
}
.jb-post-meta {
  margin-top: 18px;
  color: var(--ink-faint);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}
.jb-post-title {
  margin-top: 8px;
  font-family: var(--serif);
  font-size: clamp(1.25rem, 1.7vw, 1.5rem);
  line-height: 1.22;
  color: var(--ink);
}
.jb-post:hover .jb-post-title {
  color: var(--accent);
}
.jb-post-excerpt {
  margin-top: 10px;
  color: var(--ink-soft);
  font-size: 14px;
}

/* ============================================================== NEWSLETTER */
/* The source's plum band: a display line left, the input group right. */
.jb-news {
  position: relative;
  isolation: isolate;
  padding-block: clamp(48px, 6vw, 88px);
}
.jb-news-mask {
  position: absolute;
  inset: 0;
  background: var(--band-mask);
}
.jb-news-inner {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  gap: clamp(24px, 3.4vw, 56px);
  align-items: center;
}
.jb-news-h2 {
  color: #ffffff;
  font-size: clamp(1.8rem, 3vw, 2.6rem);
}
.jb-news-row {
  display: flex;
  gap: 0;
}
.jb-news-input {
  flex: 1;
  min-width: 0;
  padding: 14px 18px;
  border: 1px solid var(--on-band-line);
  border-right: 0;
  border-radius: var(--r-md) 0 0 var(--r-md);
  /* `--card`, not a white literal: the field carries `--ink`, and on a
     re-tinted page a pinned white fill strands light lettering on it. */
  background: var(--card);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 14px;
}
.jb-news-input::placeholder {
  color: var(--ink-mute);
}
.jb-news-input:focus-visible {
  outline: 2px solid #ffffff;
  outline-offset: 0;
}
.jb-news-btn {
  padding: 14px 24px;
  border: 0;
  border-radius: 0 var(--r-md) var(--r-md) 0;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--sans);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: var(--track);
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
}
.jb-news-btn:hover {
  background: #ffffff;
  color: var(--ink);
}
.jb-news-note,
.jb-news-err {
  margin-top: 12px;
  color: var(--on-band-mute);
  font-size: 12.5px;
  line-height: 1.6;
}
.jb-news-err {
  color: #ffffff;
}
.jb-news-note a {
  color: #ffffff;
  text-decoration: underline;
}
.jb-news-done {
  position: relative;
  z-index: 1;
  font-family: var(--serif);
  font-size: clamp(1.2rem, 1.8vw, 1.5rem);
  color: #ffffff;
}

/* ================================================================ PRODUCTS */
.jb-book-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: clamp(24px, 3vw, 40px);
}
.jb-book-card {
  display: flex;
  flex-direction: column;
  text-align: center;
}
.jb-book-media {
  display: block;
}
.jb-book-media img {
  width: 100%;
  aspect-ratio: 2 / 3;
  object-fit: cover;
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-md);
  transition: transform 0.24s ease;
}
.jb-book-media:hover img {
  transform: translateY(-4px);
}
.jb-book-title {
  margin-top: 16px;
  font-size: 1.2rem;
  line-height: 1.24;
}
.jb-book-title a {
  color: var(--ink);
}
.jb-book-title a:hover {
  color: var(--accent);
}
.jb-book-meta {
  margin: 6px 0 0;
  color: var(--ink-mute);
  font-size: 13px;
}
.jb-book-card .jb-btn {
  margin-top: 16px;
  align-self: center;
}

/* ========================================================= TEMPLATE NOTICE */
.jb-notice {
  padding-block: clamp(28px, 3vw, 40px);
  background: var(--linen);
  border-block: 1px solid var(--line);
}
.jb-notice-inner {
  max-width: 78ch;
}
.jb-notice .jb-eyebrow {
  margin-bottom: 8px;
}
.jb-notice-text {
  color: var(--ink-soft);
  font-size: 13.5px;
  line-height: 1.7;
}
.jb-notice-text a {
  border-bottom: 1px solid var(--line-strong);
}

/* ================================================================ ENQUIRY */
.jb-enquiry {
  padding-block: clamp(48px, 6vw, 88px);
  background: var(--paper);
}
.jb-enquiry-fine {
  margin-top: 26px;
  max-width: 74ch;
  color: var(--ink-mute);
  font-size: 13px;
  line-height: 1.7;
}

/* ============================================================ LEGAL PAGES */
/* richpage.css loses to the reset above on source order, so the prose margins
   are restored here rather than left to it. */
/* The legal pages open straight onto the Page block with no banner above it,
   and the bar is FIXED — without this the first heading starts under it. The
   banner pages pay for their own clearance in `.jb-banner-copy`. */
.jb-page {
  padding-top: calc(var(--bar-h) + 56px);
}
.jb-page .rich-page {
  color: var(--ink-soft);
}
.jb-page .rich-page :is(h1, h2, h3, h4) {
  margin: 1.6em 0 0.5em;
  font-family: var(--serif);
  color: var(--ink);
}
.jb-page .rich-page :is(h1, h2, h3, h4):first-child {
  margin-top: 0;
}
.jb-page .rich-page p {
  margin: 0 0 1.1em;
}
.jb-page .rich-page :is(ul, ol) {
  margin: 0 0 1.1em 1.3em;
  padding-left: 0;
  list-style: revert;
}
.jb-page .rich-page li {
  margin-bottom: 0.5em;
}
.jb-page .rich-page a {
  border-bottom: 1px solid var(--line-strong);
}

/* ================================================================== FOOTER */
.jb-footer {
  padding-block: clamp(56px, 6vw, 88px) 28px;
  background: var(--linen);
  border-top: 1px solid var(--line);
}
.jb-footer-grid {
  display: grid;
  grid-template-columns: minmax(0, 6fr) repeat(3, minmax(0, 2fr));
  gap: clamp(28px, 3.4vw, 56px);
}
.jb-footer-word {
  font-family: var(--serif);
  font-size: clamp(1.5rem, 2.2vw, 1.9rem);
  font-variant-caps: small-caps;
  letter-spacing: 0.04em;
  color: var(--ink);
}
.jb-footer-blurb {
  margin-top: 12px;
  max-width: 46ch;
  color: var(--ink-mute);
  font-size: 13.5px;
  line-height: 1.7;
}
.jb-footer-col {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.jb-footer-head {
  font-family: var(--sans);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink);
}
.jb-footer-col a {
  color: var(--ink-mute);
  font-size: 13.5px;
}
.jb-footer-col a:hover {
  color: var(--accent);
}
.jb-footer-base {
  margin-top: clamp(36px, 4vw, 56px);
  padding-top: 22px;
  border-top: 1px solid var(--line);
  text-align: center;
  color: var(--ink-faint);
  font-size: 12.5px;
}

/* =============================================================== LOCATIONS */
/* `.loc-section` and `.loc-detail` re-declare the --loc-* family on
   themselves, so these have to be set ON those selectors — an inherited value
   loses and the block falls back to the app-wide brown. */
.jb-site .loc-section,
.jb-site .loc-detail {
  --loc-heading-font: var(--serif);
  --loc-body-font: var(--sans);
  --loc-ink: var(--ink);
  --loc-accent: var(--accent);
  --loc-border: var(--line);
  --loc-muted: var(--ink-mute);
  --loc-radius: 12px;
  --loc-sticky-top: calc(var(--bar-h) + 32px);
  --loc-map-h: min(calc(100dvh - var(--loc-sticky-top) - 32px), 460px);
  /* `--surface`, NOT `--loc-surface`. The card's own rule is
     `background: var(--surface, #fff)` — there is no `--loc-` prefixed
     spelling of it, and a theme that sets the prefixed name sets nothing at
     all. At the shipped palette the fallback #fff is nearly the paper, so the
     card looks correct; under a dark-page override the ink goes light while
     the card stays white and every line in it collapses to ~2:1. */
  --surface: var(--card);
}
/* Two shared literals that only bite on a re-tinted site: both paint
   `color: #fff` on a `--loc-accent` ground, which is safe only while that
   token stays dark. `--on-accent` is the readableInk pair that already answers
   this question for every other accent fill in the theme. */
.jb-site .loc-filter button.is-active,
.jb-site .loc-nearme-btn.is-on {
  color: var(--on-accent);
}
.jb-loc-page {
  padding-top: calc(var(--bar-h) + 40px);
}

/* ============================================================== BREAKPOINTS */
@media (max-width: 1080px) {
  .jb-footer-grid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
  .jb-footer-brand {
    grid-column: 1 / -1;
  }
}

@media (max-width: 900px) {
  /* The bar's links collapse into the panel; the burger takes their place. */
  .jb-nav-links {
    display: none;
  }
  .jb-nav-toggle {
    display: block;
  }
  /* The desktop search sits in the action row beside the cart; the panel
     carries its own mount, so hiding the bar's one costs nothing.
     `:not(.jb-search-mobile)` IS LOAD-BEARING: the panel's mount is
     `class="jb-search jb-search-mobile"`, so an unqualified rule here hides it
     too and the mobile nav ships with no search at all — which is precisely
     what it did until this was measured. */
  .jb-site .jb-search:not(.jb-search-mobile) {
    display: none;
  }

  /* The layered hero is abandoned rather than shrunk (see the note above it). */
  .jb-hero {
    height: auto;
    min-height: 30rem;
    display: flex;
    align-items: flex-end;
  }
  .jb-hero-book,
  .jb-hero-figure {
    display: none;
  }
  .jb-hero-stage {
    height: auto;
  }
  .jb-hero-copy {
    position: relative;
    left: auto;
    top: auto;
    width: 100%;
    transform: none;
    padding: calc(var(--bar-h) + 72px) var(--edge) clamp(40px, 7vw, 64px);
  }
  .jb-hero-copy::before {
    inset: -10% -6% -12% -6%;
  }
  .jb-hero-name {
    /* The nowrap that keeps the name on one line beside the book has nothing
       to protect once the copy owns the frame, and it overflows a 390px
       viewport by ~40px. */
    white-space: normal;
  }
  .jb-hero-blurb {
    max-width: 46ch;
  }

  .jb-grid3 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .jb-about-grid,
  .jb-about-grid.is-right {
    grid-template-columns: minmax(0, 1fr);
  }
  .jb-about-grid.is-right .jb-about-media {
    order: 0;
  }
  .jb-about-media {
    max-width: 460px;
  }
  .jb-bookhero-grid {
    grid-template-columns: minmax(0, 1fr);
  }
  .jb-bookhero-cover {
    order: -1;
    max-width: 300px;
    margin-inline: 0;
  }
  .jb-quote-inner.has-cover {
    grid-template-columns: minmax(0, 1fr);
  }
  .jb-quote-cover {
    max-width: 260px;
  }
  .jb-news-inner {
    grid-template-columns: minmax(0, 1fr);
  }
  .jb-feature-copy {
    max-width: 100%;
    padding-inline: var(--edge);
  }
}

@media (max-width: 640px) {
  .jb-header {
    inset: 10px 0 auto;
  }
  .jb-bar-inner {
    min-height: 62px;
  }
  .jb-logo-img {
    /* A wide wordmark floors the brand flex item at its intrinsic width and
       shoves the burger clean off a 390px viewport. */
    max-height: 30px;
  }
  .jb-grid3,
  .jb-grid2 {
    grid-template-columns: minmax(0, 1fr);
  }
  .jb-book-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .jb-banner,
  .jb-banner:not(.has-copy) {
    min-height: 280px;
  }
  .jb-banner.is-tall {
    min-height: 340px;
  }
  .jb-news-row {
    flex-direction: column;
    gap: 10px;
  }
  .jb-news-input {
    border-right: 1px solid var(--on-band-line);
    border-radius: var(--r-md);
  }
  .jb-news-btn {
    border-radius: var(--r-md);
  }
  .jb-cast-card {
    padding-inline: 22px;
  }
}

/* A reader who has asked for less motion gets none of it. */
@media (prefers-reduced-motion: reduce) {
  html:has(.jb-site) {
    scroll-behavior: auto;
  }
  .jb-marquee-track {
    animation: none;
  }
  .jb-site *,
  .jb-site *::before,
  .jb-site *::after {
    transition: none !important;
  }
  .jb-book-cover:hover .jb-plate,
  .jb-post:hover img,
  .jb-book-media:hover img {
    transform: none;
  }
}
