/* =============================================================================
 * ALL BOOKS — templates/all-books.php            (Kate Book Manager 1.16.3)
 *
 * A faithful port of all-books-page-prototype.html (approved 10 Aug 2026), with
 * exactly three deliberate changes:
 *
 *   1. TYPE. The prototype declared six faces. Kate locked the system to THREE
 *      on 10 Aug 2026 and the names below are the brand kit's own
 *      (brand-kit/tokens.css), so editing that file changes this page:
 *
 *          --ka-display   'Oswald', Impact, sans-serif   every heading
 *          --ka-body      'Hanken Grotesk', sans-serif   all normal text
 *                                                        AND ALL BUTTONS
 *          --ka-script    'Caveat', cursive              the eyebrow ABOVE a
 *                                                        heading, and nothing
 *                                                        else on this page
 *
 *      Fjalla One, Cormorant Garamond, Poppins and Special Elite are gone. The
 *      italic voice the prototype gave Cormorant is now --ka-body: a lede is
 *      normal text, and "all normal text should be Hanken Grotesk" does not
 *      have an exception for the ones set in italics.
 *
 *   2. THE INTRO PARAGRAPH IS DELETED. "Ten books, four worlds, one sky…" is
 *      gone at Kate's request. The legend along the bottom rail stays, and it
 *      is now the only place the thread grammar is spelled out — which is why
 *      the rail is not optional furniture. At 768+ the counts move into the
 *      column the lede used to occupy so the two-column header still has two
 *      columns; see .cs-copy.
 *
 *   3. SCOPED. Everything is under `body.kbm-all-books`, because a plugin
 *      stylesheet that styles bare `body`, `img` or `a` leaks onto whatever
 *      else the theme renders. The prototype could be global; this cannot.
 *
 * The prototype ribbon, the "prototype control" panel and the comp footer are
 * not ported — they were furniture for the comp and said so.
 * ========================================================================== */

/* ══ tokens ═══════════════════════════════════════════════════════════════ */
body.kbm-all-books{

  /* ---- TYPE — the three locked faces. No fourth. ------------------------ */
  --ka-display: 'Oswald', Impact, sans-serif;
  --ka-body:    'Hanken Grotesk', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --ka-script:  'Caveat', cursive;

  /* Back-compat aliases, so a rule ported verbatim from the prototype still
     resolves. Every one of them points at one of the three faces above. */
  --ka-display-alt: var(--ka-display);
  --ka-sans:        var(--ka-body);
  --ka-serif:       var(--ka-body);
  --ka-typewriter:  var(--ka-body);

  /* ---- ground + accent ------------------------------------------------- */
  --ka-navy:      #020F24;
  --ka-navy-lift: #061A38;
  --ka-gold:      #F5D000;
  --ka-gold-2:    #FFE033;
  --ka-paper:     #EEF2F8;

  --ka-muted:     rgba(238,242,248,.44);
  --ka-muted-2:   rgba(238,242,248,.66);
  --ka-line:      rgba(238,242,248,.13);

  --ka-royal-blue:  #1B44B8;
  --ka-bright-blue: #3E6DF2;
  --ka-rose:        #FF4E86;
  --ka-creme:       #FDFCF8;

  /* ---- page-local layout ----------------------------------------------- */
  --ka-pad:  clamp(1.15rem, 4vw, 3.5rem);
  --ka-maxw: 1380px;

  /* ---- the fixed site header, and the hero's top padding ----------------
     #ka-site-header is `position: fixed`, so it takes no space in flow. This
     page's Astra overrides also zero #content's padding-top (the theme
     otherwise adds var(--ka-header-h) on every non-home page), which is
     correct here — the constellation is a full-height hero and wants to run
     under the nav — but it means the hero's own top padding is the ONLY thing
     keeping the H1 out from behind the nav bar. At 3.4rem (54px) against an
     84px header it was not enough, and the word "Everything" was sitting
     underneath the menu on the live page.

     Stated as a token rather than a magic number so the two .cs-frame rules
     that need it cannot drift apart, and mirrored from the child theme's own
     --ka-header-h / --ka-header-h-mobile. It is NOT read from the theme
     variable: the theme's mobile value is set on `.ka-site-header` itself, not
     on an ancestor of this page's markup, so `var(--ka-header-h)` resolved
     here would return the desktop 84px at every width. */
  --kbm-hh: 84px;
  --kbm-hero-top: calc(var(--kbm-hh) + 2.2rem);
}
@media (max-width:1024px){
  body.kbm-all-books{ --kbm-hh: 70px; }
}

/* ══ the page ground ══════════════════════════════════════════════════════ */
body.kbm-all-books{
  background: var(--ka-navy);
  color: var(--ka-paper);
  font-family: var(--ka-body);
  font-size: 17px;
  font-weight: 400;
  line-height: 1.7;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Astra (or any theme) puts the page in a narrow article column and prints a
   page title. Same override set the other two KBM templates use. */
body.kbm-all-books #page,
body.kbm-all-books .site,
body.kbm-all-books #content,
body.kbm-all-books .site-content,
body.kbm-all-books #primary,
body.kbm-all-books .site-main,
body.kbm-all-books .ast-container,
body.kbm-all-books .ast-container-fluid,
body.kbm-all-books .ast-article-single,
body.kbm-all-books article.page,
body.kbm-all-books .entry-content{
  max-width: 100% !important;
  width: 100% !important;
  margin: 0 !important;
  padding: 0 !important;
  background: transparent !important;
}
body.kbm-all-books .entry-header,
body.kbm-all-books .ast-single-post-order,
body.kbm-all-books .page-title,
body.kbm-all-books .ast-featured-img{ display: none !important; }

/* ── THE RESET, AND THE BUG IT CAUSED (fixed 1.16.1) ──────────────────────
   This was written as:

       body.kbm-all-books .kbm-ab *,
       body.kbm-all-books .kbm-ab *::before,
       body.kbm-all-books .kbm-ab *::after{ margin:0; padding:0; ... }

   which is a specificity of (0,2,1) — one element, two classes. The prototype
   this file was ported from could reset globally with a bare `*` at (0,0,0),
   and every `.cs-frame { padding: … }` style rule below, at (0,1,0), then won
   normally. Adding the scope to keep the plugin's CSS off the rest of the site
   also lifted the reset ABOVE every single-class rule in this stylesheet.

   The result on the live page was that EVERY padding declared in this file was
   silently dead: .cs-frame, .ab-section, .ab-inner, .ab-empty, the fallback
   cover, all of it computed to 0px. The hero and the sections sat flush
   against both edges of the window, the H1 started at y=0 underneath the fixed
   header, and elements with a negative offset (.sh-num, .ab-cv) pushed a few
   pixels past the right edge and gave the phone a horizontal scrollbar. It
   looked like a dozen separate layout bugs and it was one selector.

   :where() is the fix, not a longer selector list. :where() contributes ZERO
   specificity, so the element rule below is (0,0,0) — exactly the prototype's
   bare `*` — while the descendant match still confines it to this page's
   markup. Every padding and margin in this file is live again, and nothing had
   to be restated or fought with !important.

   The pseudo-elements need their own rule rather than riding inside the
   :where() list: :where() takes a complex-selector-list, which may not contain
   a pseudo-element, and because :where() is forgiving an invalid item is
   dropped SILENTLY — `:where(*, *::before, *::after)` would quietly reset only
   real elements and look like it worked. Split out, it lands at (0,0,1) for
   the pseudo-element, still below every class rule in this file. */
:where(body.kbm-all-books .kbm-ab) :where(*){
  margin:0; padding:0; box-sizing:border-box;
}
:where(body.kbm-all-books .kbm-ab) :where(*)::before,
:where(body.kbm-all-books .kbm-ab) :where(*)::after{
  margin:0; padding:0; box-sizing:border-box;
}
/* box-sizing on the container itself too — the reset above only reaches its
   descendants, and .kbm-ab takes no padding of its own either way. */
body.kbm-all-books .kbm-ab{ box-sizing:border-box; }

/* ══ THE SITE HEADER OVER THIS PAGE'S HERO ═══════════════════════════════
   Kate: "the header should be transparent until you scroll."

   WHAT IS ACTUALLY ON THIS PAGE, because there is a known trap here. The
   documented "backwards transparency" bug on this site was an Elementor
   background Opacity Scrolling Effect on header section 7729, and the rule of
   thumb from that round is: do not fight an Elementor motion effect with CSS,
   switch the effect off in Elementor. That trap is NOT present here. Checked
   live on https://kateangelo.com/books/ : there is no element 7729 in the DOM,
   no `.elementor-location-header`, and no element anywhere on the page with a
   `data-settings` containing a sticky or opacity scrolling effect. (post-7729
   .css is still ENQUEUED — Elementor prints its kit CSS globally — which is
   why a grep for 7729 looks alarming and the DOM does not.)

   The header on this page is the child theme's own `<header id="ka-site-header">`
   from kate-angelo-child/header.php: position:fixed, 84px tall (70 ≤1024), and
   `background-color: var(--ka-navy) !important` set through the ID selector.

   So the only thing to beat is that base rule, and the child theme's author
   already documented the exact way to do it — the same file does precisely
   this for the home page (`body.home #ka-site-header`). !important here is not
   fighting a motion effect; it is the one lever that outranks an ID-plus-
   !important declaration in another stylesheet whose load order is not ours to
   control. body + two classes + id beats id alone, deterministically.

   TWO body classes drive this, both written by all-books.js:

     .kbm-ab-hero      there IS a full-height constellation on this page for
                       the nav to float over. Nothing here fires without it.
                       Kate can switch the constellation section OFF in "All
                       Books — Page Sections", and then the page opens on the
                       mood shelf — a transparent nav over that is just an
                       unreadable nav. Gating on this also makes no-JS safe:
                       neither class exists, none of these rules match, and the
                       header stays exactly as solid as it is on every other
                       page rather than being stranded transparent forever.
     .kbm-ab-scrolled  the reader has left the hero.

   Deliberately NOT the site-wide `body.kbm-scrolled`: that one is toggled only
   by the front-page script in kate-book-manager.php and is read by
   `body.home.kbm-scrolled` rules, and borrowing it would couple this page to
   the home page's hero threshold. */
body.kbm-all-books.kbm-ab-hero #ka-site-header,
body.kbm-all-books.kbm-ab-hero .ka-site-header{
  background-color:transparent !important;
  box-shadow:none;
  transition:background-color .35s ease, box-shadow .35s ease;
}
body.kbm-all-books.kbm-ab-hero.kbm-ab-scrolled #ka-site-header,
body.kbm-all-books.kbm-ab-hero.kbm-ab-scrolled .ka-site-header{
  background-color:var(--ka-navy) !important;
  /* !important on the shadow too, and it was measured, not assumed. Without it
     the computed box-shadow stays `none` even though this selector is
     body + 3 classes + an id. The stale Elementor stylesheet the child theme
     documents declares `.ka-site-header, .ka-site-header.elementor-sticky
     --active { background-color: transparent !important; box-shadow: none
     !important; }` — the theme's own comment reads that as only firing on the
     sticky variant, but bare `.ka-site-header` is the first item in the
     selector list, so the !important applies here unconditionally and no
     amount of specificity beats it. (This is why the home page's scrolled
     header has no shadow either; that is the child theme's to fix, not ours.) */
  box-shadow:0 6px 30px rgba(0,0,0,.35) !important;
}
/* Transparent over a star field means the logo and the menu are the only
   things separating the nav from the sky behind it. A soft top-down scrim,
   drawn behind the header's own content, keeps them legible without reading as
   a bar. It fades out with the scrolled state, where the solid navy takes
   over. pointer-events:none so it can never eat a click on the menu. */
body.kbm-all-books.kbm-ab-hero #ka-site-header::before{
  content:''; position:absolute; inset:0; z-index:-1; pointer-events:none;
  background:linear-gradient(180deg, rgba(2,15,36,.72), rgba(2,15,36,0));
  transition:opacity .35s ease;
}
body.kbm-all-books.kbm-ab-hero.kbm-ab-scrolled #ka-site-header::before{ opacity:0; }
@media (prefers-reduced-motion: reduce){
  body.kbm-all-books.kbm-ab-hero #ka-site-header,
  body.kbm-all-books.kbm-ab-hero .ka-site-header,
  body.kbm-all-books.kbm-ab-hero #ka-site-header::before{ transition:none; }
}

body.kbm-all-books .kbm-ab img{ max-width:100%; display:block; }
body.kbm-all-books .kbm-ab a{ color:inherit; }
body.kbm-all-books ::selection{ background:var(--ka-gold); color:var(--ka-navy); }
body.kbm-all-books .kbm-ab :focus-visible{
  outline:3px solid var(--ka-gold); outline-offset:3px; border-radius:2px;
}

@media (prefers-reduced-motion: reduce){
  body.kbm-all-books .kbm-ab *,
  body.kbm-all-books .kbm-ab *::before,
  body.kbm-all-books .kbm-ab *::after{
    animation-duration:.001ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.001ms !important;
  }
}

.ab-skip{
  position:absolute; left:-9999px; top:0; z-index:999;
  background:var(--ka-gold); color:var(--ka-navy);
  font-family:var(--ka-body); font-weight:600;
  font-size:.72rem; letter-spacing:.16em; text-transform:uppercase;
  padding:.9rem 1.4rem;
}
.ab-skip:focus{ left:0; }

.ab-vh{
  position:absolute !important; width:1px; height:1px;
  overflow:hidden; clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap;
}

/* ── shared type ─────────────────────────────────────────────────────────
   ONE micro-label style. In the prototype this existed to replace twenty-six
   Special Elite labels; with the three-face system it is simply the label. */
.u-label{
  font-family:var(--ka-body); font-weight:500;
  font-size:.66rem; letter-spacing:.19em; text-transform:uppercase;
  line-height:1.5;
}

/* 1.16.3 — `.ab-kicker .n` is deleted with the numbers it styled ("01 THE
   CONSTELLATION", Kate: "you have 01 the constellation still showing on the
   books page"). templates/all-books.php no longer emits the span at all, in any
   section, so the rule had nothing left to select. The kicker itself survives on
   the mood and Every book sections, where it is the only eyebrow they have. */
.ab-kicker{
  font-family:var(--ka-body); font-weight:500;
  font-size:.7rem; letter-spacing:.28em; text-transform:uppercase;
  color:var(--ka-gold); margin:0 0 1rem;
}

/* ── HEADINGS MUST STATE THEIR OWN COLOUR ON THIS SITE ────────────────────
   1.16.1, and this is the "fix the title" bug.

   Astra prints a dynamic inline rule:

       h1, h2, h3, h4, h5, h6,
       .entry-content :where(h1, h2, h3, h4, h5, h6) { color: #10253A; }

   #10253A is a dark navy for a light page. This page's ground is #020F24, so
   any heading that does not name a colour of its own renders dark-navy-on-
   navy — technically visible, practically not. It was NOT enough for
   body.kbm-all-books to set `color: var(--ka-paper)`: that is inherited, and a
   direct declaration on the element beats an inherited value no matter how
   specific the ancestor's selector is.

   The prototype never had to say this — it was a standalone page with no
   theme underneath it — which is exactly how the port arrived with the hole.
   On the live page it meant "EVERYTHING" and "WHAT ARE YOU" were invisible
   and only the gold half of each heading ("she's written", "in the mood for")
   could be read. Kate saw a heading with its first line missing.

   Fixed by naming the colour on the three heading rules that lacked one
   (.ab-h2, .cs-title, .cs-d-title) rather than with one blanket override:
   every OTHER heading on this page already sets its own colour deliberately
   (.ab-empty h3 and .cn-list-group h3 are gold), and a blanket rule specific
   enough to outrank Astra would also have outranked those and flattened them
   to paper. */
.ab-h2{
  font-family:var(--ka-display);
  /* 1.17.5 — was 500. Kate: "you didn't use the right font for 'browse by mood'
     you aren't keeping consistent". It IS the right family — Oswald, same as every
     heading — but at weight 500 against the homepage's 900 it reads as a different
     typeface. .books-h, .sp-h, .qz-bg-text and .mk-h are all 900. Only the weight
     changes here; the deliberately-scoped colour above is untouched. */
  font-weight:900;
  font-size:clamp(2.4rem, 6.6vw, 5.2rem); line-height:.88;
  text-transform:uppercase; letter-spacing:-.02em;
  color:var(--ka-paper);
  margin:0 0 1.1rem;
}
.ab-h2 em{ font-style:normal; color:var(--ka-gold); }

.ab-lede{
  font-family:var(--ka-body); font-weight:300;
  font-size:clamp(1.05rem, 2.2vw, 1.35rem); line-height:1.6;
  color:rgba(238,242,248,.72); max-width:56ch;
  border-left:1.5px solid rgba(245,208,0,.42); padding-left:1.1rem;
}

.ab-section{
  position:relative;
  padding:clamp(3.6rem, 8vw, 8rem) var(--ka-pad);
  border-top:1px solid var(--ka-line);
}
/* NO HERO ON THIS PAGE. body.kbm-ab-nohero is written by the template when the
   constellation section is switched off (or there are no books), and then the
   page's first element in flow is an ordinary section under a solid fixed
   header. Its own padding is smaller than the header at phone sizes, so the
   first one gets the header's height added. Only the first: the sections below
   it scroll under a header that is already opaque. */
body.kbm-ab-nohero .kbm-ab > .ab-section:first-of-type,
body.kbm-ab-nohero .kbm-ab > .ab-skip + .ab-section{
  padding-top:calc(var(--kbm-hh) + clamp(3.6rem, 8vw, 8rem));
}
.ab-inner{ max-width:var(--ka-maxw); margin:0 auto; }
.ab-head{ margin:0 0 clamp(2rem, 4.5vw, 3.4rem); }

/* ── the empty states ────────────────────────────────────────────────────
   A page that renders nothing is a page nobody can debug. Every section can
   say "there is nothing here yet" in its own voice instead of collapsing. */
.ab-empty{
  border:1px dashed rgba(245,208,0,.35); border-radius:3px;
  padding:clamp(1.4rem, 4vw, 2.4rem);
  background:rgba(245,208,0,.04);
  max-width:56ch;
}
.ab-empty h3{
  font-family:var(--ka-display); font-weight:500; text-transform:uppercase;
  font-size:1.15rem; line-height:1.05; letter-spacing:.03em;
  color:var(--ka-gold); margin:0 0 .5rem;
}
.ab-empty p{
  font-family:var(--ka-body); font-weight:300;
  font-size:.95rem; line-height:1.6; color:rgba(238,242,248,.7); margin:0;
}

/* ══ shared cover component (real art + CSS-drawn fallback) ═══════════════
   The fallback is not a nicety. A book with no cover uploaded yet renders no
   <img> at all, and this draws a cover from the title and byline so the grid
   keeps its shape instead of collapsing to a coloured rectangle. */
.ab-cv{
  position:relative; display:block;
  width:100%; aspect-ratio:2 / 3;
  border-radius:2px; overflow:hidden;
  background:linear-gradient(158deg, var(--a1,#274063) 0%, var(--a2,#07182f) 100%);
}
.ab-cv img{
  position:absolute; inset:0; z-index:2;
  width:100%; height:100%; object-fit:cover; display:block;
}
.ab-cv img.is-dead{ display:none; }
.ab-cv-fb{
  position:absolute; inset:0; z-index:1;
  display:flex; flex-direction:column; justify-content:flex-end;
  padding:9% 8% 8%;
  background:
    radial-gradient(120% 60% at 50% 10%, rgba(255,255,255,.14), transparent 62%),
    linear-gradient(180deg, transparent 42%, rgba(2,15,36,.72) 100%);
}
.ab-cv-fb::before{
  content:''; position:absolute; left:0; top:0; bottom:0; width:6%;
  background:rgba(0,0,0,.3);
}
.ab-cv-fbt{
  font-family:var(--ka-display);
  font-weight:500; text-transform:uppercase;
  font-size:clamp(.62rem, 9cqw, 1.1rem); line-height:1.02; letter-spacing:.01em;
  color:#F4F7FC;
}
.ab-cv-fba{
  margin-top:.55em; padding-top:.5em;
  border-top:2px solid rgba(245,208,0,.85);
  font-family:var(--ka-body); font-weight:500;
  font-size:clamp(.42rem, 5.5cqw, .66rem); letter-spacing:.2em; text-transform:uppercase;
  color:rgba(244,247,252,.85);
}

/* ╔══════════════════════════════════════════════════════════════════╗
   ║  SECTION 1 — THE CONSTELLATION, AS A FULL-PAGE HERO               ║
   ║                                                                   ║
   ║  Three deliberately different builds of the same content:         ║
   ║    < 768px   stacked per-series rows, hero framing intact         ║
   ║    768–1199  banded sky, copy above it, aside below it            ║
   ║    ≥ 1200px  free composition, copy and aside float over the sky  ║
   ║                                                                   ║
   ║  Thread grammar has to read with no caption:                      ║
   ║    solid  = ordered series, read in order                         ║
   ║    dotted = shared world, start anywhere                          ║
   ║    dashed = the next book, not out yet                            ║
   ╚══════════════════════════════════════════════════════════════════╝ */
.cs{
  position:relative; isolation:isolate;
  min-height:100dvh;
  display:flex; flex-direction:column;
  overflow:hidden;
  background:
    radial-gradient(120% 78% at 74% -6%, rgba(245,208,0,.10), transparent 56%),
    radial-gradient(78% 62% at 8% 40%, rgba(27,68,184,.20), transparent 62%),
    radial-gradient(70% 60% at 96% 92%, rgba(74,78,124,.20), transparent 64%),
    var(--ka-navy);
}
/* Opening the list un-pins the hero — a long list must be allowed to grow. */
.cs.is-list{ min-height:0; overflow:visible; }

.cs-frame{
  position:relative; z-index:2; flex:1 1 auto;
  display:flex; flex-direction:column; gap:clamp(1.6rem,4vw,2.4rem);
  /* top = clear the fixed nav; see --kbm-hero-top in the token block */
  padding:var(--kbm-hero-top) var(--ka-pad) 1.6rem;
}

/* ── the copy block: kicker, signature, title ───────────────────────
   Both of the things that used to share this block with the heading are gone:
   the lede paragraph (Kate, 10 Aug) and then the counts strip that had been
   moved into the column the lede vacated (Kate, 1.16.1).

   So the two-column grid this block grew at 768+ is gone with them. A grid
   whose second column has nothing in it is not a composition, it is a hole —
   it would have left the heading squeezed into 55% of the width with an empty
   half-row beside it. One column, heading at full measure, at every
   breakpoint. .cs-copy is a plain block again and the grid-area assignments
   the children carried are deleted rather than left dangling. */
.cs-copy{ position:relative; z-index:6; max-width:34rem; }
.cs-script{
  /* Caveat. Once, above the heading. That is the whole brief for this face. */
  font-family:var(--ka-script);
  font-weight:400;
  font-size:clamp(1.6rem, 4.2vw, 2.6rem); line-height:1;
  color:var(--ka-gold); margin:0 0 .12em;
}
.cs-title{
  font-family:var(--ka-display);
  font-weight:500;
  font-size:clamp(3.2rem, 12vw, 8rem); line-height:.82;
  text-transform:uppercase; letter-spacing:-.035em;
  /* Not decorative. Without it Astra's h1 rule paints "EVERYTHING" #10253A on
     a #020F24 hero — see the note above .ab-h2. */
  color:var(--ka-paper);
  margin:0 0 1rem;
}
/* Direct children only — the per-word spans the heading animation inserts
   must not each land on their own line. */
.cs-title > span{ display:block; }
.cs-title .y{ color:var(--ka-gold); }
/* .cs-meta (the counts strip) is deleted in 1.16.1 along with the markup that
   carried it. Nothing else on the page used the class. */

/* ── the sky ───────────────────────────────────────────────────────── */
.cs-sky{
  display:none;                 /* phone build has no sky — see .cs-rows */
  position:relative;
  container-type:inline-size;
  --nw:8.6;                     /* node width, in cqw, at scale 1 */
}
/* Layer order in the sky, back to front:
     0  particles   drifting dots, a <canvas>, pointer-events:none
     2  svg         halos, series threads, node dots
     3  labels      cluster labels (empty in the date layout, kept for reuse)
     4  nodes       the covers themselves — the only layer that takes a pointer */
.cs-particles,.cs-svg{
  position:absolute; inset:0; width:100%; height:100%;
  pointer-events:none;
}
/* The particle field must never eat a pointer event: the covers on top of it
   are draggable, and a canvas that swallowed the press would break them. */
.cs-particles{ z-index:0; pointer-events:none !important; }
.cs-svg{ z-index:2; }
.cs-labels{ position:absolute; inset:0; z-index:3; pointer-events:none; }
.cs-nodes{ position:absolute; inset:0; z-index:4; }

/* Parallax layers. Compositor-only transforms, one shared pair of custom
   properties, written once per rAF frame. */
.cs-particles{ transform:translate3d(calc(var(--mx,0) * 6px), calc(var(--my,0) * 4px), 0); }
.cs-svg   { transform:translate3d(calc(var(--mx,0) * 12px), calc(var(--my,0) * 8px), 0); }
.cs-labels{ transform:translate3d(calc(var(--mx,0) * 15px), calc(var(--my,0) * 10px), 0); }
.cs-nodes { transform:translate3d(calc(var(--mx,0) * 20px), calc(var(--my,0) * 13px), 0); }

.cs-clabel{
  position:absolute; transform:translate(-50%,-50%);
  font-family:var(--ka-display);
  font-weight:500; text-transform:uppercase;
  font-size:clamp(.6rem, 1.35cqw, 1.05rem); line-height:1;
  letter-spacing:.34em; white-space:nowrap;
  color:rgba(245,208,0,.5);
}

/* a node: an absolutely-placed slot, and a button inside it that GSAP owns.
   The slot centres itself with the standalone `translate` property, NOT with
   `transform` — Draggable writes `transform` on this element. */
.cs-slot{
  position:absolute;
  width:calc(var(--nw) * 1cqw * var(--d,1));
  translate:-50% -50%;
  container-type:inline-size;
}
.cs.can-drag .cs-slot{ cursor:grab; }
.cs.can-drag .cs-slot.is-dragging{ cursor:grabbing; }
.cs-slot.is-dragging .ab-cv{
  box-shadow:0 26px 48px rgba(0,0,0,.78), inset 0 0 0 1px rgba(245,208,0,.5);
}
.cs-slot.is-dragging .cs-node{ transition:none; }
/* 1.16.1 — .cs-node is an <a href> to the book's permalink, not a <button>.
   Three properties exist only because of that change:

     text-decoration:none   an anchor is underlined by default and a cover with
                            an underlined caption is not a cover.
     -webkit-user-drag:none native HTML5 link dragging. Belt and braces with
                            the draggable="false" attribute the JS writes: the
                            attribute is what actually stops it in every
                            browser, this is what stops WebKit starting the
                            drag image before the attribute is consulted. If a
                            cover ever stops being draggable, this pair is the
                            first place to look.
     cursor                 stays `pointer`, and .cs.can-drag .cs-slot puts
                            `grab` over it where dragging is live. */
.cs-node{
  display:block; width:100%; margin:0; padding:0; border:0;
  background:none; color:inherit; text-align:left; cursor:pointer;
  text-decoration:none;
  -webkit-user-drag:none; user-drag:none;
  font-family:var(--ka-body);
  -webkit-appearance:none; appearance:none;
  transition:transform .5s cubic-bezier(.22,.72,.24,1);
}
.cs-node img{ -webkit-user-drag:none; user-drag:none; }

/* ── the "i": the detail card's own way in ─────────────────────────────
   The detail card used to open on the cover's click. The cover navigates now,
   so the card needs an affordance of its own — small, on the cover's top
   corner, and quiet until the cover is hovered or something inside the slot
   takes focus.

   HIDDEN ON A TOUCH POINTER, deliberately. It is revealed on hover; a touch
   screen has no hover, so it would either sit there permanently as eleven
   dots of noise over a designed hero, or be undiscoverable. And the thing it
   competes with — a tap that opens the book's own page — already shows
   everything the card does, in more detail. `@media (hover: hover)` is the
   whole gate. */
.cs-info{
  position:absolute; top:0; right:0; z-index:12;
  display:none;
  width:1.55rem; height:1.55rem; padding:0;
  border:1px solid rgba(245,208,0,.55); border-radius:50%;
  background:rgba(2,15,36,.86); color:var(--ka-gold);
  font-family:var(--ka-display); font-weight:500;
  font-size:.72rem; line-height:1; cursor:pointer;
  -webkit-appearance:none; appearance:none;
  opacity:0; transform:translate(35%,-35%) scale(.82);
  transition:opacity .22s ease, transform .22s ease, background-color .22s ease;
}
@media (hover: hover) and (pointer: fine){
  .cs-info{ display:grid; place-items:center; }
}
.cs-slot:hover .cs-info,
.cs-slot:focus-within .cs-info,
.cs-info.is-on{ opacity:1; transform:translate(35%,-35%) scale(1); }
/* 1.16.3 — the resting navy, restated for :focus. Astra's `button:focus`
   (0,1,1) beats `.cs-info` (0,1,0) and would paint a focused info dot solid
   blue; see the audit box on .cs-toggle. Placed BEFORE the gold rule below so
   an OPEN dot stays gold while it is focused. */
.cs-info:focus,
.cs-info:active{
  background-color:rgba(2,15,36,.86); color:var(--ka-gold);
  border-color:rgba(245,208,0,.55);
}
/* border-color is named here as well as background: the theme's rule sets all
   three, and .cs-info's own gold ring lives in the `border:` shorthand at
   (0,1,0), which loses the same way the background did. Measured — the dot's
   ring was rendering #057dcd blue on hover on the live page. */
.cs-info:hover,
.cs-info.is-on{ background:var(--ka-gold); color:var(--ka-navy); border-color:var(--ka-gold); }
/* Focus has to be visible even when the dot is otherwise at opacity 0 — the
   :focus-within rule above covers the slot, this covers the button itself so a
   keyboard user tabbing straight onto it never lands on an invisible control. */
.cs-info:focus-visible{ opacity:1; transform:translate(35%,-35%) scale(1); }
@media (prefers-reduced-motion: reduce){
  .cs-info{ transition:none; }
}
.cs-node .ab-cv{
  box-shadow:0 12px 26px rgba(0,0,0,.62), inset 0 0 0 1px rgba(238,242,248,.1);
  transition:box-shadow .35s ease;
}
.cs-node:hover{ transform:scale(1.07); }
.cs-node:hover .ab-cv{ box-shadow:0 18px 34px rgba(0,0,0,.7), inset 0 0 0 1px rgba(238,242,248,.3); }
.cs-node.is-sel{ transform:scale(1.12); }
.cs-node.is-sel .ab-cv{ box-shadow:0 0 0 2px var(--ka-gold), 0 22px 36px rgba(0,0,0,.66); }
.cs-node:focus-visible{ outline:none; }
.cs-node:focus-visible .ab-cv{ box-shadow:0 0 0 3px var(--ka-gold), 0 14px 26px rgba(0,0,0,.6); }

/* ── THE CAPTION UNDER A COVER ────────────────────────────────────────
   1.16.3. This is the title, and on an upcoming book two gold words under it.
   That is the whole caption.

   It used to be the title PLUS a second line reading "<SERIES NAME> · <YEAR>",
   and on "THE BINGEABLE ELITE GUARDIANS · 2025" that second line wrapped to
   four — taller than the cover it hung under. Kate: "I think we should get rid
   of the series title, series number, and the year so it doesn't look so
   crowded." The series name, the number and the date all still appear in full
   in the detail card, in "Show all as a list" and in the Every book index. */
.cs-nt{
  display:block; margin-top:.42rem;
  font-family:var(--ka-display);
  font-weight:500; text-transform:uppercase;
  font-size:clamp(.5rem, 11cqw, .82rem); line-height:1.06; letter-spacing:.02em;
  color:rgba(238,242,248,.9);
}
/* ── NOT OUT YET ──────────────────────────────────────────────────────
   Four signals, none of them a chart: a gold-framed cover, a breathing gold
   halo, "Coming soon" in gold under it, and a dashed thread up from the last
   published book in the series.

   .cs-ny is ONLY emitted on an upcoming book now (see capHTML in all-books.js),
   so it is gold at the base rather than grey-with-a-gold-override — there is no
   longer such a thing as a published book's second caption line to be grey. It
   deliberately carries NO YEAR: "COMING 2026" is wrong from 1 January 2027
   onwards and nobody would notice, where "Coming soon" cannot go stale. If Kate
   wants the pre-order signal gone as well, deleting the `.cs-ny` branch in
   capHTML() is the whole change. */
.cs-ny{
  display:block; margin-top:.2rem;
  font-family:var(--ka-body); font-weight:600;
  font-size:clamp(.44rem, 7.5cqw, .58rem); letter-spacing:.15em;
  text-transform:uppercase; color:rgba(245,208,0,.86);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.cs-node.is-soon .ab-cv{
  box-shadow:0 12px 26px rgba(0,0,0,.62), inset 0 0 0 1.5px rgba(245,208,0,.8);
}
.cs-node.is-soon:hover .ab-cv{
  box-shadow:0 18px 34px rgba(0,0,0,.7), inset 0 0 0 1.5px rgba(245,208,0,1);
}
.cs-glow{
  position:absolute; left:50%; top:32%; width:210%; height:160%;
  transform:translate(-50%,-50%); z-index:0; pointer-events:none;
  background:radial-gradient(closest-side, rgba(245,208,0,.42), rgba(2,15,36,0) 72%);
  animation:csPulse 4.2s ease-in-out infinite;
}
@keyframes csPulse{
  0%,100%{ opacity:.3; transform:translate(-50%,-50%) scale(.92); }
  50%    { opacity:.78; transform:translate(-50%,-50%) scale(1.06); }
}
@media (prefers-reduced-motion: reduce){
  /* No pulse, but the halo stays — reduced motion is a request to stop moving,
     not to stop informing. */
  .cs-glow{ animation:none; opacity:.58; }
  .cs-node{ transition:none; }
  .cs-particles,.cs-svg,.cs-labels,.cs-nodes{ transform:none !important; }
}

/* ── the aside: the book detail card, and nothing else ─────────────────
   1.16.1 — .cs-worldnote and .wn-tag are deleted. They styled the shared-world
   blurb ("Elite Guardians / A shared world of elite protection specialists…"
   plus a "Shared world · no reading order" tag) which Kate does not want on
   this page. The markup and the JS that built it are gone too, so these rules
   had nothing left to select. The aside is now only the detail card, and it is
   `hidden` until a reader asks for one — so with nothing selected the aside
   occupies no space at all. */
.cs-aside{ position:relative; z-index:6; max-width:34rem; }

.cs-detail{
  padding:1.2rem 1.35rem 1.35rem;
  border:1px solid rgba(245,208,0,.3); border-radius:3px;
  background:linear-gradient(200deg, rgba(6,22,52,.94), rgba(2,15,36,.94));
  backdrop-filter:blur(6px);
  display:grid; grid-template-columns:1fr auto; gap:.7rem 1.4rem; align-items:start;
}
.cs-detail[hidden]{ display:none; }
.cs-d-series{
  font-family:var(--ka-body); font-weight:500;
  font-size:.62rem; letter-spacing:.2em; text-transform:uppercase;
  color:rgba(245,208,0,.88); margin:0 0 .45rem;
}
.cs-d-title{
  font-family:var(--ka-display);
  font-weight:500; text-transform:uppercase;
  font-size:clamp(1.25rem, 2.4vw, 1.85rem); line-height:1; letter-spacing:.01em;
  color:var(--ka-paper);              /* Astra's h2 rule — see .ab-h2 above */
  margin:0 0 .5rem;
}
.cs-d-tag{
  font-family:var(--ka-body); font-weight:300;
  font-size:1.02rem; line-height:1.5; color:rgba(238,242,248,.7);
  margin:0 0 .5rem; max-width:46ch;
}
.cs-d-byline{
  font-family:var(--ka-body); font-weight:400; font-size:.8rem;
  line-height:1.45; color:rgba(238,242,248,.58); margin:0 0 .55rem;
}
.cs-d-byline .co{ color:rgba(245,208,0,.78); }
.cs-d-meta{
  font-family:var(--ka-body); font-weight:400;
  font-size:.62rem; letter-spacing:.17em; text-transform:uppercase;
  color:var(--ka-muted); margin:0;
}
.cs-d-side{ display:flex; flex-direction:column; gap:.65rem; align-items:flex-end; }
.cs-d-close{
  -webkit-appearance:none; appearance:none; cursor:pointer;
  background:none; border:1px solid rgba(238,242,248,.32); color:var(--ka-paper);
  border-radius:999px; width:30px; height:30px; line-height:1; font-size:.9rem;
  font-family:var(--ka-body);            /* BUTTON → --ka-body */
}
/* The × never declared a background for any state — `background:none` at
   (0,1,0) — so Astra's `button:hover` / `button:focus` (0,1,1) turned it into a
   solid blue circle the moment the pointer touched it. It stays transparent in
   all four states; only the ring and the glyph go gold. See the audit box on
   .cs-toggle. */
.cs-d-close:hover,
.cs-d-close:focus-visible{ background-color:transparent; border-color:var(--ka-gold); color:var(--ka-gold); }
.cs-d-close:focus,
.cs-d-close:active{ background-color:transparent; }

/* ── the bottom rail: legend · scroll cue · list escape hatch ──────────
   With the intro paragraph deleted, this legend is the ONLY place the thread
   grammar is written down. It is load-bearing copy now, not decoration. */
.cs-rail{
  position:relative; z-index:7; margin-top:auto;
  display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between;
  gap:1rem 1.4rem;
  padding-top:1rem; border-top:1px solid var(--ka-line);
}
.cs-legend{
  display:flex; flex-wrap:wrap; gap:.35rem 1.3rem;
  font-family:var(--ka-body); font-weight:400;
  font-size:.6rem; letter-spacing:.17em; text-transform:uppercase;
  color:var(--ka-muted);
}
.cs-legend span{ display:inline-flex; align-items:center; gap:.5rem; }
.cs-legend i{ display:inline-block; width:24px; height:2px; background:var(--ka-gold); opacity:.6; }
.cs-legend i.dotted{ background:none; height:0; border-top:2px dotted rgba(245,208,0,.8); }
.cs-legend i.dash{ background:none; height:0; border-top:2px dashed rgba(245,208,0,.75); }
/* 1.16.2 — the world tether's swatch. Deliberately the SAME dotted pattern as
   .dotted above and deliberately fainter (.28 against .8): the tether is drawn
   at opacity .2 where a shared-world thread is drawn at .58, and the legend has
   to show that difference or the two marks read as one. */
.cs-legend i.tether{ background:none; height:0; border-top:2px dotted rgba(245,208,0,.28); }
.cs-legend i.dot{ width:7px; height:7px; border-radius:50%; background:rgba(238,242,248,.4); }

/* ╔══════════════════════════════════════════════════════════════════════╗
   ║  EVERY BUTTON ON THIS PAGE STATES ITS OWN :hover AND :focus  1.16.3   ║
   ║                                                                       ║
   ║  Kate's screenshot: "Reset the sky" rendering as a SOLID BLUE button   ║
   ║  while "Show all as a list" beside it — the same .cs-toggle class —    ║
   ║  is correctly outlined.                                               ║
   ║                                                                       ║
   ║  THE RULE THAT WAS WINNING. Not the Astra stylesheet; Astra's dynamic  ║
   ║  inline CSS, printed into the head as #astra-theme-css-inline-css and  ║
   ║  read off the live page:                                              ║
   ║                                                                       ║
   ║      button:focus, .menu-toggle:hover, button:hover, .ast-button:hover,║
   ║      … { color:#ffffff; background-color:#057dcd; border-color:#057dcd }║
   ║                                                                       ║
   ║  #057dcd is the blue. The first item in that selector list is          ║
   ║  `button:focus` — one type and one pseudo-class, specificity (0,1,1).  ║
   ║  `.cs-toggle` is one class, (0,1,0). (0,1,1) beats (0,1,0), so the     ║
   ║  moment a button on this page TAKES FOCUS the theme repaints it solid  ║
   ║  blue, and it stays that way until focus moves. Kate had clicked       ║
   ║  "Reset the sky"; she had not clicked the one next to it. Nothing      ║
   ║  about the two buttons differed — only which one had been used.        ║
   ║                                                                       ║
   ║  Same family as the 1.16.1 heading-colour bug: a direct declaration    ║
   ║  from the theme beating a value we thought we owned.                   ║
   ║                                                                       ║
   ║  WHY :hover WAS ALREADY FINE, which is the tell. `.cs-toggle:hover` is ║
   ║  a class plus a pseudo-class, (0,2,0), and that DOES beat the theme's  ║
   ║  `button:hover` at (0,1,1). Every control on this page whose hover was ║
   ║  written out was already immune; every control whose FOCUS was not     ║
   ║  written out was not. That is the whole shape of the bug and it is why ║
   ║  the audit below is four controls and not one.                         ║
   ║                                                                       ║
   ║  NO !important ANYWHERE. (0,2,0) is enough, so the fix is to say what  ║
   ║  each control looks like when it is focused — which is something the   ║
   ║  stylesheet should have been saying regardless. The one thing it must  ║
   ║  NOT do is add a blanket `body.kbm-all-books .kbm-ab button` rule: at  ║
   ║  (0,2,2) that would outrank .cs-toggle, .cs-info, .cs-d-close and      ║
   ║  .mt-tab all at once and flatten four deliberately different controls  ║
   ║  into one, which is exactly the trap called out in the .ab-h2 note.    ║
   ║                                                                       ║
   ║  THE AUDIT — every <button> this page renders:                         ║
   ║    .cs-toggle / .cs-reset   bottom rail        fixed here              ║
   ║    .cs-info                 the "i" on a cover fixed at its own rule   ║
   ║    .cs-d-close              the detail card's × fixed at its own rule  ║
   ║    .mt-tab                  the mood tabs      fixed at its own rule   ║
   ║  The mood tabs were the worst of them and nobody had reported it: they ║
   ║  set `background:none` at (0,1,0), so `button:hover` painted a solid   ║
   ║  blue pill under the pointer on EVERY tab, and clicking one left it    ║
   ║  blue underneath the travelling gold indicator.                        ║
   ║                                                                       ║
   ║  Colours are the brand-kit values (brand-kit/tokens.css) by way of the ║
   ║  --ka-* tokens at the top of this file. Nothing new is introduced.     ║
   ╚══════════════════════════════════════════════════════════════════════╝ */
.cs-toggle{
  -webkit-appearance:none; appearance:none; cursor:pointer;
  padding:.6rem 1.05rem .56rem;
  border:1px solid rgba(238,242,248,.32); border-radius:2px;
  background:rgba(238,242,248,.05); color:var(--ka-paper);
  font-family:var(--ka-body); font-weight:600;   /* BUTTON → --ka-body */
  font-size:.62rem; letter-spacing:.17em; text-transform:uppercase;
  transition:background-color .25s ease, border-color .25s ease, color .25s ease;
}
.cs-toggle[hidden]{ display:none; }
/* "Reset the sky" only exists where dragging does, so it stays hidden until
   initDrag() has actually created the draggables. */
.cs-reset{ border-color:rgba(245,208,0,.42); color:rgba(245,208,0,.9); }

/* The resting look, restated for the states the theme claims. (0,2,0) each, so
   they outrank `button:focus` (0,1,1) without a single !important. A plain
   :focus is a mouse click that landed — it must look like the button did a
   moment ago. Keyboard focus is :focus-visible, and that gets the gold below
   AND the page's 3px gold outline from the shared :focus-visible rule. */
.cs-toggle:focus,
.cs-toggle:active{
  background-color:rgba(238,242,248,.05);
  border-color:rgba(238,242,248,.32);
  color:var(--ka-paper);
}
/* …and the reset button's gold variant of the same, AFTER the rule above so
   its border and text survive being focused. Equal specificity, later wins. */
.cs-reset:focus,
.cs-reset:active{
  background-color:rgba(238,242,248,.05);
  border-color:rgba(245,208,0,.42);
  color:rgba(245,208,0,.9);
}
/* Last, so it is the answer for BOTH buttons in both states. This is the brand
   hover Kate already had; it is only moved down the file and given
   :focus-visible as a second trigger. */
.cs-toggle:hover,
.cs-toggle:focus-visible{
  background-color:rgba(245,208,0,.14);
  border-color:var(--ka-gold);
  color:var(--ka-gold);
}
.cs-draghint[hidden]{ display:none; }
.cs-legend .grab{
  width:auto; height:auto; background:none;
  font-size:.72rem; line-height:1; opacity:.85; transform:translateY(1px);
}

/* the scroll cue — the hero's handoff into section 02 */
.cs-cue{
  display:inline-flex; align-items:center; gap:.7rem;
  text-decoration:none; color:var(--ka-muted);
  font-family:var(--ka-body); font-weight:600;
  font-size:.6rem; letter-spacing:.22em; text-transform:uppercase;
  transition:color .25s ease;
}
.cs-cue:hover{ color:var(--ka-gold); }
.cs-cue-line{
  position:relative; display:block; width:1px; height:34px;
  background:linear-gradient(180deg, rgba(245,208,0,.05), rgba(245,208,0,.5));
  overflow:hidden;
}
.cs-cue-line i{
  position:absolute; left:-1px; top:0; width:3px; height:9px; border-radius:2px;
  background:var(--ka-gold);
  animation:csCue 2.4s cubic-bezier(.6,0,.3,1) infinite;
}
@keyframes csCue{
  0%   { transform:translateY(-10px); opacity:0; }
  30%  { opacity:1; }
  75%  { transform:translateY(34px); opacity:0; }
  100% { transform:translateY(34px); opacity:0; }
}
@media (prefers-reduced-motion: reduce){ .cs-cue-line i{ animation:none; top:12px; } }

/* ── stacked per-series rows — the phone build of the constellation ── */
.cs-rows{ display:block; }
.cn-group{ margin:0 0 2.2rem; }
.cn-group-h{
  display:flex; align-items:baseline; gap:.75rem; flex-wrap:wrap; margin:0 0 .8rem;
}
.cn-group-h h3{
  font-family:var(--ka-display);
  font-weight:500; text-transform:uppercase;
  font-size:1.05rem; line-height:1; letter-spacing:.03em; color:var(--ka-gold); margin:0;
}
.cn-group-h .cnt{
  font-family:var(--ka-body); font-weight:400;
  font-size:.6rem; letter-spacing:.17em; text-transform:uppercase; color:var(--ka-muted);
}
/* .cn-world-blurb is deleted in 1.16.1 — the phone build's copy of the same
   shared-world blurb the desktop aside carried. Both went together; a rule
   left behind here would have kept styling a paragraph that is never emitted
   and hidden the fact that the phone had its own duplicate of the sentence. */
.cn-strip{
  position:relative; display:flex; gap:.8rem; list-style:none; margin:0; padding:0;
}
.cn-group.has-thread .cn-strip::before{
  content:''; position:absolute; left:8%; right:8%; top:32%;
  height:1.5px; background:rgba(245,208,0,.42); z-index:0;
}
.cn-group.is-world.has-thread .cn-strip::before{
  height:0; background:none; border-top:2px dotted rgba(245,208,0,.55);
}
.cn-group.has-thread.to-soon .cn-strip::after{
  content:''; position:absolute; right:8%; width:26%; top:32%;
  height:0; border-top:1.5px dashed rgba(245,208,0,.6); z-index:0;
}
.cn-strip li{
  position:relative; z-index:1; flex:1 1 0; min-width:0; max-width:150px;
  container-type:inline-size;
}
/* 1.16.1 — an <a> to the book, not a <button>. See .cs-node for the full
   reasoning; the same three anchor-only properties apply here. */
.cn-node2{
  display:block; width:100%; text-align:left; cursor:pointer;
  background:none; border:0; padding:0; color:inherit;
  text-decoration:none;
  -webkit-user-drag:none; user-drag:none;
  font-family:var(--ka-body);
  -webkit-appearance:none; appearance:none;
}
.cn-node2 img{ -webkit-user-drag:none; user-drag:none; }
.cn-node2 .ab-cv{ box-shadow:0 10px 20px rgba(0,0,0,.55), inset 0 0 0 1px rgba(238,242,248,.1); }
.cn-node2.is-sel .ab-cv{ box-shadow:0 0 0 2px var(--ka-gold), 0 14px 24px rgba(0,0,0,.6); }
.cn-node2.is-soon .ab-cv{ box-shadow:0 10px 20px rgba(0,0,0,.55), inset 0 0 0 1px rgba(245,208,0,.55); }
.cn-node2 .cs-nt{ font-size:clamp(.5rem, 10cqw, .8rem); }
.cn-node2 .cs-ny{ font-size:clamp(.44rem, 7.5cqw, .6rem); }

/* ── list escape hatch ─────────────────────────────────────────────── */
.cs-list{ margin-top:1.2rem; }
.cs-list[hidden]{ display:none; }
.cn-list-group{ margin:0 0 1.5rem; }
.cn-list-group h3{
  font-family:var(--ka-display);
  font-weight:500; text-transform:uppercase;
  font-size:1rem; letter-spacing:.04em; color:var(--ka-gold); margin:0 0 .1rem;
}
.cn-list-group p.sub{
  font-family:var(--ka-body); font-weight:400;
  font-size:.6rem; letter-spacing:.17em; text-transform:uppercase;
  color:var(--ka-muted); margin:0 0 .7rem;
}
.cs-list ol{ list-style:none; margin:0; padding:0; }
.cs-list li{ border-top:1px solid var(--ka-line); }
.cs-list li:last-child{ border-bottom:1px solid var(--ka-line); }
.cs-list a{
  display:flex; align-items:baseline; gap:.9rem; flex-wrap:wrap;
  padding:.75rem .3rem; text-decoration:none;
}
.cs-list a:hover{ background:rgba(245,208,0,.07); }
.cs-list .lt{
  font-family:var(--ka-display);
  font-weight:500; text-transform:uppercase;
  font-size:.95rem; letter-spacing:.02em; color:var(--ka-paper);
}
.cs-list a:hover .lt{ color:var(--ka-gold); }
/* Only rendered when a real series number exists. Never "Book ". */
.cs-list .ln{
  font-family:var(--ka-body); font-weight:600; font-size:.72rem;
  color:rgba(245,208,0,.8); min-width:1.4em;
}
.cs-list .lb{
  font-family:var(--ka-body); font-weight:400; font-size:.76rem;
  color:rgba(238,242,248,.52);
}
.cs-list .ly{
  font-family:var(--ka-body); font-weight:400;
  font-size:.6rem; letter-spacing:.16em; text-transform:uppercase;
  color:var(--ka-muted); margin-left:auto;
}

/* ── constellation, 768–1199: banded sky, copy above, aside below ────
   The copy block used to go two-column here, because the counts strip lived in
   the right-hand column. With the counts gone (1.16.1) there is nothing to put
   there, so it is a single-column block: kicker, signature, heading, and the
   sky gets the rest of the viewport. The grid, its two column tracks and the
   four grid-area assignments are deleted, not overridden — leaving them and
   adding `display:block` would have left four rules pointing at a grid that no
   longer exists.

   The heading keeps the same clamp it had, so nothing about its size changes:
   only the box it sits in got wider. */
@media (min-width:768px){
  .cs-frame{ padding:var(--kbm-hero-top) var(--ka-pad) 1.4rem; gap:clamp(1.2rem,2.6vw,1.8rem); }
  .cs-sky{ display:block; flex:1 1 auto; min-height:470px; --nw:7.2; }
  .cs-rows{ display:none; }
  .cs-copy{ max-width:none; }
  .cs-title{ margin-bottom:0; font-size:clamp(2.4rem, 6vw, 4.2rem); }
  .cs-aside{ max-width:64rem; }
}
/* Tablet captions leave container-query sizing and take a fixed legible size,
   line-clamped so a long TITLE can never grow a node into the space below it.
   1.16.3 drops the second line's clamp from three to one: it is "Coming soon"
   now, not a series name, and it is emitted with white-space:nowrap anyway —
   the clamp is belt and braces for a translated string, not a wrap the real
   copy can reach. */
@media (min-width:768px) and (max-width:1199px){
  .cs-sky{ --nw:9.2; min-height:560px; }
  .cs-nodes .cs-nt,
  .cs-nodes .cs-ny{
    display:-webkit-box; -webkit-box-orient:vertical; overflow:hidden;
  }
  .cs-nodes .cs-nt{ font-size:.6rem; -webkit-line-clamp:3; line-clamp:3; }
  .cs-nodes .cs-ny{
    font-size:.5rem; letter-spacing:.1em;
    -webkit-line-clamp:1; line-clamp:1;
  }
}
/* A short tablet — iPad landscape at 1024×768 — cannot hold covers AND copy in
   one viewport. Rather than crush them it takes the phone build. `is-tight` is
   set by the fit check in JS, which measures the real leftover height instead
   of guessing at a media-query breakpoint. */
.cs.is-tight .cs-sky{ display:none; }
.cs.is-tight .cs-rows{ display:block; }

/* ── constellation, ≥1200 ──────────────────────────────────────────────
   The heading sits ABOVE the star field, the way the Mood section's header
   sits above its shelf.

   The aside still floats bottom-RIGHT. The reason given here used to be that
   the date layout ran the books bottom-left to top-right and reliably left
   that corner empty; 1.16.3 replaced the date layout with a scatter, so that
   is no longer true and is not claimed here any more. The corner is still the
   right place for it — it is furthest from the heading, and the card is
   `hidden` until a reader presses an "i", so for almost the whole life of the
   page it occupies nothing at all. When it IS open it is an opaque bordered
   panel at z-index 8 and it reads as a panel over the sky, which is what it
   is. Nothing is reserved for it: reserving a quarter of the sky for a card
   that is usually not there would give back the room the caption cut just
   won. */
@media (min-width:1200px){
  /* The bottom padding is the rail's height, reserved: the rail floats over
     the sky, so the sky has to stop above it. */
  .cs-frame{ padding:var(--kbm-hero-top) var(--ka-pad) 5.6rem; gap:clamp(.9rem,1.8vw,1.4rem); }
  /* the .cs-copy grid tracks went with the counts strip — see the 768 block */
  .cs-title{ font-size:clamp(2.6rem, 4.4vw, 4.6rem); }
  .cs-sky{ --nw:5.6; min-height:clamp(400px, 48vh, 620px); }
  .cs-aside{
    position:absolute; right:var(--ka-pad); bottom:5.4rem;
    width:min(27rem, 34%); max-width:none; z-index:8;
  }
  .cs-rail{
    position:absolute; left:var(--ka-pad); right:var(--ka-pad); bottom:1.3rem;
    margin-top:0; flex-wrap:wrap;
  }
  .cs-legend{ order:1; flex:1 1 20rem; min-width:0; }
  .cs-cue{ order:2; }
  .cs-reset{ order:3; }
  .cs-toggle{ order:4; }
  .cs-list{ display:none; }
  /* the list un-pins everything and returns the frame to normal flow */
  .cs.is-list .cs-frame{
    display:flex; flex-direction:column; gap:2rem;
    padding:var(--kbm-hero-top) var(--ka-pad) 3rem;
  }
  .cs.is-list .cs-aside,
  .cs.is-list .cs-rail{ position:static; width:auto; max-width:44rem; }
  .cs.is-list .cs-rail{ max-width:none; }
  .cs.is-list .cs-sky{ display:none; }
  .cs.is-list .cs-list{ display:block; }
}
/* short laptops: stop the composition collapsing into itself */
@media (min-width:1200px) and (max-height:640px){
  .cs{ min-height:680px; }
}

/* ╔══════════════════════════════════════════════════════════════════╗
   ║  SCROLL-TRIGGERED HEADING ANIMATION                               ║
   ║                                                                   ║
   ║  NEITHER mode touches the text content. The full heading is in the ║
   ║  DOM from first paint; the reveal is clip-path only. A typewriter  ║
   ║  built by pushing characters into textContent reads as a stream of ║
   ║  interruptions to a screen reader, breaks copy-and-paste and hides ║
   ║  the heading from find-in-page.                                    ║
   ╚══════════════════════════════════════════════════════════════════╝ */
.hw{ display:inline-block; position:relative; }
/* The padding/negative-margin pair costs nothing in layout and buys a clip box
   that actually contains the glyphs: these headings run at line-height .82–.88,
   so the line box is SHORTER than the type. */
.hw-ink{
  display:inline-block;
  padding:.2em .07em .26em;
  margin:-.2em -.07em -.26em;
}
.hw-car{
  position:absolute; top:0; height:.9em; left:var(--c,100%);
  width:.055em; min-width:2px;
  background:var(--ka-gold); opacity:0; pointer-events:none;
}
.hw.is-typing .hw-car{ opacity:1; animation:hwBlink .62s steps(1,end) infinite; }
@keyframes hwBlink{ 0%,49%{ opacity:1; } 50%,100%{ opacity:0; } }

/* TYPEWRITER. `--c` drives the clip and the caret from the same number, so
   they cannot drift. Default 100%: with no JS, a heading is just a heading. */
[data-anim-h].m-type .hw-ink{
  clip-path:inset(0 calc(100% - var(--c,100%)) 0 0);
}

/* GRADIENT SWEEP. A per-word mask reveal from below, with a gold gradient
   running across the whole heading as it settles. */
[data-anim-h].m-sweep .hw-ink{
  clip-path:inset(0 0 var(--r,0%) 0);
  transform:translateY(var(--ty,0));
}
[data-anim-h].is-gold .hw-ink{
  -webkit-background-clip:text; background-clip:text;
  -webkit-text-fill-color:transparent; color:transparent;
  background-repeat:no-repeat;
}
[data-anim-h].is-anim .hw-ink{ will-change:clip-path, transform; }

@media (prefers-reduced-motion: reduce){
  .hw-car{ display:none; }
  [data-anim-h] .hw-ink{ clip-path:none !important; transform:none !important; }
}

/* ╔══════════════════════════════════════════════════════════════════╗
   ║  SECTION 2 — BROWSE BY MOOD                                       ║
   ║  A segmented tab control with a travelling indicator. Real         ║
   ║  role=tablist semantics, roving tabindex, arrow-key navigation.   ║
   ╚══════════════════════════════════════════════════════════════════╝ */
#kbm-ab-mood{
  background:
    radial-gradient(110% 70% at 12% 0%, rgba(27,68,184,.16), transparent 58%),
    radial-gradient(80% 60% at 92% 86%, rgba(245,208,0,.06), transparent 62%),
    var(--ka-navy);
}

/* the rail is the scroll container on phones; the track holds the tabs */
.mt-rail{ position:relative; margin:0 0 1.1rem; }
.mt-scroll{
  overflow-x:auto; overflow-y:hidden;
  scroll-snap-type:x proximity;
  -webkit-overflow-scrolling:touch;
  scrollbar-width:none;
  padding:.28rem;
  border:1px solid var(--ka-line); border-radius:999px;
  background:rgba(238,242,248,.045);
}
.mt-scroll::-webkit-scrollbar{ display:none; }

.mt-track{
  position:relative;
  display:flex; gap:.15rem;
  width:max-content; min-width:100%;
}
/* the travelling indicator — one absolutely-positioned pill, moved by GSAP */
.mt-ind{
  position:absolute; left:0; top:0; width:0; height:0;
  border-radius:2px; background:var(--ka-gold);   /* 1.17.5 — squared, see .mt-tab */
  pointer-events:none; z-index:0;
  box-shadow:0 6px 18px rgba(245,208,0,.24);
}
.mt-tab{
  position:relative; z-index:1;
  -webkit-appearance:none; appearance:none; cursor:pointer;
  flex:0 0 auto; scroll-snap-align:center;
  /* 1.17.5 — SQUARED OFF. Kate: "I don't really like the pills you have in the
     mood section." She rejected pill shapes on /hello for the same reason, and
     they were genuinely off-system here: every other control on this site uses a
     2-3px radius (.cs-toggle above is 2px, .sh-buy is square), so a 999px pill was
     the only rounded thing on the page.
     Now a flat tab with a 2px radius, and the type moves to the display face in
     caps so the rail reads as part of the same editorial system as the headings
     rather than as a widget. The travelling .mt-ind indicator is squared to match
     — it still slides, it just is not a lozenge any more. */
  border:0; background:none; border-radius:2px;
  padding:.7rem 1.05rem .62rem;
  font-family:var(--ka-display); font-weight:600;
  font-size:.82rem; line-height:1.15; letter-spacing:.06em; text-transform:uppercase;
  color:rgba(238,242,248,.62);
  white-space:nowrap;
  transition:color .28s ease;
}
/* 1.16.3 — a mood tab is a transparent pill sliding over the travelling gold
   indicator, and it has to STAY transparent. `.mt-tab{background:none}` is
   (0,1,0) and Astra's dynamic `button:hover, button:focus` is (0,1,1), so every
   tab was painting itself a solid blue pill under the pointer and staying blue
   after it was clicked — on top of the indicator. Nobody had reported this; it
   is the same leak as "Reset the sky" and it is fixed the same way, by naming
   the resting background in the states the theme claims. See the audit box on
   .cs-toggle. */
.mt-tab:hover,
.mt-tab:focus,
.mt-tab:focus-visible,
.mt-tab:active{ background-color:transparent; }
/* …and the text colour with it. `.mt-tab` is border:0 so the theme's blue
   border-color has nothing to draw, but its `color:#ffffff` does: a tab that
   had been clicked was rendering full white against the other tabs' 62% white,
   which reads as a second kind of "selected" next to the real one. */
.mt-tab:focus,
.mt-tab:active{ color:rgba(238,242,248,.62); }
.mt-tab:hover,
.mt-tab:focus-visible{ color:var(--ka-paper); }
.mt-tab[aria-selected="true"]{ color:var(--ka-navy); font-weight:600; }
/* Restated after the selected rule, because a selected tab is the one a reader
   just clicked and is therefore the one holding focus. Without this the theme's
   white-on-blue would land on the tab sitting over the gold indicator — the
   most visible control in the section. */
.mt-tab[aria-selected="true"]:hover,
.mt-tab[aria-selected="true"]:focus,
.mt-tab[aria-selected="true"]:active{ background-color:transparent; color:var(--ka-navy); }
.mt-tab .n{
  display:inline-block; margin-left:.5em;
  font-variant-numeric:tabular-nums; opacity:.5; font-weight:400;
}
.mt-tab[aria-selected="true"] .n{ opacity:.62; }
.mt-tab:focus-visible{ outline:3px solid var(--ka-gold); outline-offset:2px; }
.mt-tab[aria-selected="true"]:focus-visible{ outline-color:var(--ka-paper); outline-offset:-4px; }

/* edge fades, only while there is actually more rail in that direction */
.mt-rail::before,.mt-rail::after{
  content:''; position:absolute; top:2px; bottom:2px; width:44px;
  pointer-events:none; z-index:2; opacity:0; transition:opacity .25s ease;
  border-radius:2px;   /* 1.17.5 — squared with the tabs */
}
.mt-rail::before{ left:0;  background:linear-gradient(90deg, #050f24 22%, rgba(5,15,36,0)); }
.mt-rail::after { right:0; background:linear-gradient(270deg, #050f24 22%, rgba(5,15,36,0)); }
.mt-rail.can-l::before{ opacity:1; }
.mt-rail.can-r::after { opacity:1; }

.mt-note{
  font-family:var(--ka-body); font-weight:300;
  font-size:1.05rem; line-height:1.5; color:rgba(238,242,248,.68);
  min-height:3.2em; margin:.2rem 0 1.9rem; max-width:62ch;
}
.mt-note b{ color:var(--ka-gold); font-weight:600; }
.mt-note .cnt{
  display:block; margin-top:.4rem;
  font-family:var(--ka-body); font-weight:400;
  font-size:.62rem; letter-spacing:.19em; text-transform:uppercase; color:var(--ka-muted);
}

.mood-field{
  display:flex; flex-wrap:wrap; align-items:flex-start;
  gap:.7rem; padding:0; margin:0; list-style:none;
}
.mood-bk{
  position:relative; container-type:inline-size;
  width:100px; flex:0 0 auto;
  will-change:transform, width;
}
.mood-bk .ab-cv{
  box-shadow:0 12px 26px rgba(0,0,0,.5), 0 0 0 1px rgba(238,242,248,.1);
  transition:box-shadow .35s ease;
}
.mood-lk{ display:block; text-decoration:none; color:inherit; }
.mood-cap{
  display:block; margin-top:.5rem;
  font-family:var(--ka-display);
  font-weight:500; text-transform:uppercase;
  font-size:.62rem; line-height:1.12; letter-spacing:.02em;
  color:rgba(238,242,248,.84);
}
.mood-sub{
  display:block; margin-top:.18rem;
  font-family:var(--ka-body); font-weight:400;
  font-size:.55rem; letter-spacing:.13em; text-transform:uppercase; color:var(--ka-muted);
}
/* Nothing ever disappears. Hits grow, misses desaturate and step back. */
.mood-bk.is-hit{ width:124px; }
.mood-bk.is-hit .ab-cv{ box-shadow:0 20px 38px rgba(0,0,0,.6), 0 0 0 2px var(--ka-gold); }
.mood-bk.is-miss{ width:64px; }
.mood-bk.is-miss .ab-cv{
  filter:grayscale(1) brightness(.6) contrast(.85);
  opacity:.32;
  box-shadow:0 6px 14px rgba(0,0,0,.4), 0 0 0 1px rgba(238,242,248,.06);
}
.mood-bk.is-miss .mood-cap{ color:rgba(238,242,248,.4); }
.mood-bk.is-miss .mood-sub{ color:rgba(238,242,248,.26); }

.mood-badge{
  position:absolute; left:0; top:.65rem; z-index:4;
  padding:.28rem .5rem .24rem .45rem; border-radius:0 2px 2px 0;
  background:var(--ka-gold); color:var(--ka-navy);
  font-family:var(--ka-body); font-weight:600;
  font-size:.44rem; line-height:1; letter-spacing:.09em; text-transform:uppercase;
}
.mood-badge--soon{
  background:rgba(2,15,36,.88); color:var(--ka-paper);
  box-shadow:inset 0 0 0 1px rgba(238,242,248,.5);
}

/* no-Flip / reduced-motion fallback: CSS transition instead of travel */
#kbm-ab-mood-panel.is-noflip .mood-bk{ transition:width .45s cubic-bezier(.4,0,.2,1); }
#kbm-ab-mood-panel.is-noflip .mood-bk .ab-cv{
  transition:filter .45s ease, opacity .45s ease, box-shadow .35s ease;
}
@media (prefers-reduced-motion: reduce){
  #kbm-ab-mood-panel.is-noflip .mood-bk,
  #kbm-ab-mood-panel.is-noflip .mood-bk .ab-cv{ transition:none; }
}

/* mood, 768+: the rail stops scrolling and wraps to as many rows as it needs —
   the indicator then travels in x AND y. */
@media (min-width:768px){
  .mt-rail{ margin-bottom:1.3rem; }
  .mt-scroll{ overflow:visible; padding:.3rem; border-radius:22px; }
  .mt-track{ flex-wrap:wrap; width:auto; gap:.15rem; }
  .mt-rail::before,.mt-rail::after{ display:none; }
  .mood-field{ gap:1rem; }
  .mood-bk{ width:118px; }
  .mood-bk.is-hit{ width:142px; }
  .mood-bk.is-miss{ width:78px; }
  .mood-cap{ font-size:.66rem; }
  .mood-sub{ font-size:.58rem; }
  .mood-badge{ font-size:.5rem; letter-spacing:.11em; }
}
@media (min-width:1200px){
  .mt-rail{ max-width:1040px; }
  .mood-field{ gap:1.25rem; max-width:1120px; }
  .mood-bk{ width:150px; }
  .mood-bk.is-hit{ width:186px; }
  .mood-bk.is-miss{ width:88px; }
}

/* ╔══════════════════════════════════════════════════════════════════╗
   ║  SECTION 3 — EVERY BOOK                                           ║
   ╚══════════════════════════════════════════════════════════════════╝ */
#kbm-ab-shelf{ background:var(--ka-navy); }

.sh-group{ margin:0 0 clamp(3rem, 6vw, 5rem); }
.sh-group-h{
  display:flex; align-items:baseline; gap:1rem; flex-wrap:wrap;
  padding-bottom:.8rem; margin:0 0 2.6rem;
  border-bottom:1px solid rgba(245,208,0,.3);
}
.sh-group-h h3{
  font-family:var(--ka-display);
  font-weight:500; text-transform:uppercase;
  font-size:clamp(1.4rem, 3.4vw, 2.4rem); line-height:1; letter-spacing:-.01em;
  margin:0; color:var(--ka-paper);
}
.sh-group-h .cnt{
  font-family:var(--ka-body); font-weight:500;
  font-size:.62rem; letter-spacing:.19em; text-transform:uppercase;
  color:var(--ka-gold); margin-left:auto;
}

.sh-grid{
  display:grid; grid-template-columns:repeat(2, 1fr);
  gap:clamp(1.4rem, 3vw, 2.6rem) clamp(1.1rem, 2.4vw, 2rem);
  list-style:none; margin:0; padding:0;
}
.sh-item{ position:relative; display:flex; flex-direction:column; gap:.85rem; container-type:inline-size; }
.sh-num{
  position:absolute; top:-1rem; left:-.2rem; z-index:1;
  font-family:var(--ka-display);
  font-weight:500; font-size:3rem; line-height:1;
  color:var(--ka-gold); opacity:.13; transition:opacity .3s ease; pointer-events:none;
}
.sh-item:hover .sh-num{ opacity:.34; }
.sh-cvlink{ position:relative; z-index:2; display:block; text-decoration:none; }
.sh-cvlink .ab-cv{
  box-shadow:0 16px 44px rgba(0,0,0,.8);
  transition:transform .4s cubic-bezier(.22,1,.36,1), box-shadow .4s ease;
}
.sh-item:nth-child(2n+1) .ab-cv{ transform:rotate(-1.6deg); }
.sh-item:nth-child(2n+2) .ab-cv{ transform:rotate(1.5deg); }
.sh-cvlink:hover .ab-cv{
  transform:rotate(0deg) translateY(-8px);
  box-shadow:0 28px 66px rgba(0,0,0,.9), 0 0 0 1px rgba(245,208,0,.16);
}
@media (prefers-reduced-motion: reduce){
  .sh-item .ab-cv{ transform:none !important; transition:none; }
}
.sh-body{ display:flex; flex-direction:column; gap:.3rem; }
.sh-title{
  font-family:var(--ka-display);
  font-weight:500; text-transform:uppercase;
  font-size:clamp(.95rem, 11cqw, 1.25rem); line-height:1.02; letter-spacing:.02em;
  color:var(--ka-paper); text-decoration:none; transition:color .2s ease;
}
.sh-cvlink:hover ~ .sh-body .sh-title, .sh-title:hover{ color:var(--ka-gold); }
.sh-sub{
  font-family:var(--ka-body); font-weight:400;
  font-size:.6rem; letter-spacing:.13em; text-transform:uppercase;
  color:var(--ka-muted); line-height:1.5;
}
.sh-sub .sv{ color:rgba(245,208,0,.8); }
.sh-sub .sw{ color:rgba(238,242,248,.5); }
.sh-byline{
  font-family:var(--ka-body); font-weight:400; font-size:.76rem;
  line-height:1.45; color:rgba(238,242,248,.52);
}
.sh-byline .co{ color:rgba(245,208,0,.74); }
.sh-date{
  font-family:var(--ka-body); font-weight:300; font-size:.8rem;
  color:rgba(238,242,248,.58); letter-spacing:.01em;
}
/* THE BUY BUTTON. --ka-body, like every other button on the site. */
.sh-buy{
  align-self:flex-start; margin-top:.55rem; text-decoration:none;
  font-family:var(--ka-body); font-weight:600;
  font-size:.62rem; letter-spacing:.14em; text-transform:uppercase;
  padding:.66rem 1.05rem .62rem;
  background:var(--ka-gold); color:var(--ka-navy); border:1px solid var(--ka-gold);
  transition:background-color .25s ease, color .25s ease;
}
.sh-buy:hover{ background:var(--ka-gold-2); border-color:var(--ka-gold-2); }

/* ── SHELF ACTIONS (1.16.6) ───────────────────────────────────────────────────
   The buy button and "Learn more", side by side under each shelf entry.

   .sh-buy carries `align-self:flex-start` because it used to be a direct child of
   the .sh-body column; it is now wrapped, so the WRAPPER owns that job and the
   button must stop claiming it or it fights the row. Hence the reset below —
   removing it from .sh-buy itself would change the constellation panel, which
   reuses that class outside this row. */
.sh-actions{
  display:flex; flex-wrap:wrap; align-items:center; gap:.5rem;
  align-self:flex-start; margin-top:.55rem;
}
.sh-actions .sh-buy{ align-self:auto; margin-top:0; }

/* Ghost twin of .sh-buy. Same type scale and padding so the pair reads as one
   control with two options; only the fill differs. Deliberately quieter than
   .sh-buy.is-pre (which is gold-on-transparent) so a pre-order book still shows a
   clear primary — two gold-ish outlines side by side would read as a tie. */
.sh-more{
  text-decoration:none;
  font-family:var(--ka-body); font-weight:600;
  font-size:.62rem; letter-spacing:.14em; text-transform:uppercase;
  padding:.66rem 1.05rem .62rem;
  background:transparent; color:rgba(238,242,248,.82);
  border:1px solid rgba(238,242,248,.32);
  transition:background-color .25s ease, color .25s ease, border-color .25s ease;
}
.sh-more:hover{ background:rgba(238,242,248,.1); color:#fff; border-color:rgba(238,242,248,.55); }
.sh-more:focus-visible{ outline:2px solid var(--ka-gold); outline-offset:2px; }

/* A book with no live retailer already falls back to a "Book details" button that
   goes to the same place as Learn more (see kbm_all_books_buy() in
   inc/all-books-data.php). Showing both would be two buttons, one destination —
   so the fallback hides its twin. */
.sh-buy.is-info + .sh-more{ display:none; }

@media (max-width:480px){
  /* Stacked and full width so neither is a thumb-sized target. */
  .sh-actions{ flex-direction:column; align-items:stretch; width:100%; }
  .sh-actions .sh-buy, .sh-more{ text-align:center; }
}
.sh-buy.is-pre{ background:transparent; color:var(--ka-gold); }
.sh-buy.is-pre:hover{ background:rgba(245,208,0,.14); }
.sh-buy.is-info{ background:transparent; color:rgba(238,242,248,.82); border-color:rgba(238,242,248,.35); }
.sh-buy.is-info:hover{ background:rgba(238,242,248,.1); }

.sh-flag{
  position:absolute; left:0; top:.8rem; z-index:4;
  padding:.3rem .55rem .26rem .48rem; border-radius:0 2px 2px 0;
  background:rgba(2,15,36,.9); color:var(--ka-gold);
  box-shadow:inset 0 0 0 1px rgba(245,208,0,.6);
  font-family:var(--ka-body); font-weight:600;
  font-size:.5rem; line-height:1; letter-spacing:.11em; text-transform:uppercase;
}
.sh-flag.is-start{ background:var(--ka-gold); color:var(--ka-navy); box-shadow:none; }

@media (min-width:768px){
  .sh-grid{ grid-template-columns:repeat(3, 1fr); }
  .sh-num{ font-size:4.2rem; top:-1.35rem; }
  .sh-item:nth-child(3n+1) .ab-cv{ transform:rotate(-1.8deg); }
  .sh-item:nth-child(3n+2) .ab-cv{ transform:rotate(1.4deg); }
  .sh-item:nth-child(3n+3) .ab-cv{ transform:rotate(-1.1deg); }
}
@media (min-width:1200px){
  .sh-grid{ grid-template-columns:repeat(4, 1fr); }
  .sh-item:nth-child(4n+1) .ab-cv{ transform:rotate(-1.8deg); }
  .sh-item:nth-child(4n+2) .ab-cv{ transform:rotate(1.4deg); }
  .sh-item:nth-child(4n+3) .ab-cv{ transform:rotate(-1.1deg); }
  .sh-item:nth-child(4n+4) .ab-cv{ transform:rotate(1.9deg); }
}

/* ══ ORDER NOW POPUP (1.17.1) ═════════════════════════════════════════════════
   Opens from any buy button on this page whose book has 2+ retailers. Styled to
   match the book page's own Order Now modal — same tile grid, same ink-height
   logo sizing — because it is the same job and a reader may well see both. */
.ab-modal{position:fixed;inset:0;z-index:10000;display:flex;align-items:center;justify-content:center;padding:1.25rem;opacity:0;transition:opacity .25s ease}
.ab-modal[hidden]{display:none}
.ab-modal.is-open{opacity:1}
.ab-modal__backdrop{position:absolute;inset:0;background:rgba(2,15,36,.82);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px)}
.ab-modal__card{
  position:relative;width:100%;max-width:660px;max-height:86vh;overflow-y:auto;
  background:var(--ka-navy,#020F24);border:1px solid rgba(255,255,255,.14);border-radius:14px;
  padding:clamp(1.5rem,4vw,2.25rem);
  box-shadow:0 30px 90px rgba(0,0,0,.65);
  transform:translateY(14px) scale(.98);transition:transform .3s cubic-bezier(.22,.61,.36,1)
}
.ab-modal.is-open .ab-modal__card{transform:none}
.ab-modal__close{
  position:absolute;top:.75rem;right:.75rem;width:40px;height:40px;
  background:none;border:1px solid rgba(255,255,255,.2);border-radius:50%;
  color:rgba(238,242,248,.75);font-size:.9rem;cursor:pointer;
  transition:background-color .2s,color .2s,border-color .2s
}
.ab-modal__close:hover{background:rgba(255,255,255,.1);color:#fff;border-color:#fff}
.ab-modal__kicker{font-family:var(--ka-body);font-size:.6rem;font-weight:700;letter-spacing:.28em;text-transform:uppercase;color:var(--ka-gold,#F5D000);margin:0 0 .5rem}
.ab-modal__title{font-family:var(--ka-display);font-weight:900;text-transform:uppercase;font-size:clamp(1.4rem,4.5vw,2rem);line-height:1;color:#EEF2F8;margin:0 0 .4rem}
.ab-modal__sub{font-family:var(--ka-body);font-size:.95rem;color:rgba(238,242,248,.62);margin:0 0 1.5rem}
.ab-modal__grid{list-style:none;margin:0;padding:0;display:grid;gap:.6rem;grid-template-columns:1fr}
@media (min-width:560px){.ab-modal__grid{grid-template-columns:repeat(3,1fr)}}

/* Tile: identical treatment to .kbm-modal__logo-btn on the book page, including
   the --kbm-ih ink-height formula. See kbm_retailer_logo_ink_height() for why a
   single uniform box over these 18 wordmarks does not work. */
.ab-tile{
  display:flex;align-items:center;justify-content:center;
  min-height:64px;padding:1rem .75rem;text-decoration:none;
  background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.14);border-radius:8px;
  transition:border-color .2s,background-color .2s,transform .18s
}
.ab-tile:hover{border-color:rgba(245,208,0,.55);background:rgba(245,208,0,.08);transform:translateY(-2px)}
.ab-tile:focus-visible{outline:2px solid var(--ka-gold,#F5D000);outline-offset:2px}
.ab-tile__logo{
  --ab-logo-h:18px;--ab-logo-box:34px;
  display:block;width:100%;height:var(--ab-logo-box);
  background-repeat:no-repeat;background-position:center;
  background-size:max( min(100%, calc(var(--ab-logo-box) * 2.5)),
                       min(100%, calc(var(--ab-logo-h) * 300 / var(--kbm-ih, 48))) ) auto;
  filter:brightness(0) invert(1);
  overflow:hidden;text-indent:-9999px
}
/* Text fallback for a retailer with no bundled mark (and for Kindle Unlimited,
   which deliberately has none). */
.ab-tile__text{
  display:flex;align-items:center;justify-content:center;width:100%;min-height:34px;
  font-family:var(--ka-body);font-size:.78rem;font-weight:700;letter-spacing:.05em;
  text-transform:uppercase;color:rgba(238,242,248,.92);text-align:center;line-height:1.2
}
.ab-tile--ku{border-color:rgba(245,208,0,.38);background:rgba(245,208,0,.08)}
.ab-tile--ku .ab-tile__text{color:var(--ka-gold,#F5D000)}
@media (prefers-reduced-motion:reduce){
  .ab-modal,.ab-modal__card{transition:none}
  .ab-tile{transition:none}
}
