/* reader-mobile.css — the foundation review, shaped like a reader.

   WHY THIS IS A SEPARATE SHEET, AND WHY EVERY RULE IS INSIDE A MEDIA QUERY

   The document surfaces grew desktop-first and were walked backwards through
   ~30 max-width patches spread over four stylesheets (380, 460, 480, 520, 560,
   600, 620, 640, 680, 700, 720, 760, 820, 900, 1100, 1101, 1299, 1300). Editing
   those in place to fix a phone means reasoning about what every one of them
   does at every other width, on a surface that is also the input to the PDF
   renderer. So this sheet does not edit them: it loads last, and every rule it
   carries lives inside `@media (max-width: 700px)` (or a coarse-pointer query).
   Above 700px this file contributes nothing at all, which is the guarantee that
   the desktop layout — and the four-pass PDF render, which prints the report
   page at a 1280px viewport — cannot regress because of anything in here.

   700px is the phone/tablet line, chosen to sit under the 720 and 760 patches
   that already exist so this sheet never half-applies inside one of them.

   THE MODEL: ONE READING BAR, ONE ACTION SLOT, NEVER A STACK

   On a phone the reader had two persistent bars — the sticky chapter nav (73px)
   riding above the fixed approval gate (94–115px). Measured on an iPhone 14
   that is 188px of 844, and on a 320px SE 190px of 568: a third of the screen
   arguing with the document it is meant to be showing. The gate ALSO carries
   the feedback controls and, once queries exist, a payment card lands inline in
   the flow, so up to seven controls compete for the bottom of the screen.

   The layout here is two fixed strips and a rule about what may occupy them:

     ┌──────────────────────────────┐
     │  chapter content             │
     ├──────────────────────────────┤
     │  ONE action, or nothing      │ 52px  ← the slot (#approval-bar)
     ├──────────────────────────────┤
     │  ≡   ←   4/10      Next →    │ 56px  ← the reading bar (.chapter-nav)
     └──────────────────────────────┘

   The reading bar is constant and never changes height, so the document never
   reflows under the reader's thumb. The slot holds exactly one action, chosen
   by the ladder in dockPhone() (foundation/index.html), and collapses to
   nothing when there is no action — which is the common case, and gives the
   read the whole screen.

   The big commitments are NOT crammed into the 52px strip. The reader already
   ends in "Next" and "Approved" chapters, and the pay card already falls inline
   on mobile; those are full-width decision surfaces that cost no chrome at all.
   The slot is the persistent shortcut for someone who has already decided.

   WHAT IS DELIBERATELY NOT HERE
   Per-item feedback stays inline in the content (the ✎ on each .fnote-host).
   It is where the reader already is when they have an opinion, and it scales to
   any number of items without spending a pixel of chrome. */

/* ─────────────────────────────────────────────────────────────────────────
   Motion. Not width-scoped: this is about the input device, not the layout.
   scroll-behavior: smooth is set globally on <html> and is a nice touch on a
   desktop. On a phone a contents tap animates the reader through the entire
   document — and the repo's own UI-audit harness already has to force `auto`
   to take an honest measurement, which is the tell that it is in the way.
   ───────────────────────────────────────────────────────────────────────── */
@media (pointer: coarse) {
  html { scroll-behavior: auto; }
}

@media (max-width: 700px) {

  /* The two strips, as one budget. Height lives here rather than in each rule
     so the document's bottom clearance below can be derived from it instead of
     re-guessing it — the 110px magic number in syncBarClearance() is what
     happens when it is guessed. --rdr-slot-h is set to 0 by dockPhone() when
     the slot is empty; the fallback covers first paint before it runs. */
  body.booklet-body {
    --rdr-nav-h: 56px;
    --rdr-slot-h: 0px;
    --rdr-safe-b: env(safe-area-inset-bottom, 0px);
    --rdr-chrome: calc(var(--rdr-nav-h) + var(--rdr-slot-h) + var(--rdr-safe-b));
  }

  /* ── Top chrome ────────────────────────────────────────────────────────
     .booklet-topnav is fixed and had no safe-area padding — only .rlv__bar in
     redesign.css ever got one — so on a notched iPhone it ran under the status
     bar. */
  .booklet-topnav {
    padding-top: env(safe-area-inset-top, 0px);
    padding-left: max(0px, env(safe-area-inset-left, 0px));
    padding-right: max(0px, env(safe-area-inset-right, 0px));
  }
  /* 44px targets in the top row. Measured at 36×31 (share), 34×34 (theme) and
     31×36 (close) — every control in this bar was under the minimum. */
  .booklet-topnav__right .booklet-btn,
  .booklet-topnav__right .theme-toggle {
    min-width: 44px; min-height: 44px;
    display: inline-flex; align-items: center; justify-content: center;
  }
  /* Any author `display` outranks the UA stylesheet's `[hidden] { display: none }`,
     so the rule above put the Download button back on screen for every reader
     whose PDF has not been rendered — a dead control, which is worse than no
     control because they press it. This repo carries eight of these guards for
     exactly that reason and the audit harness flagged a ninth the moment the
     rule above was written. */
  .booklet-topnav__right .booklet-btn[hidden],
  .booklet-topnav__right .theme-toggle[hidden] { display: none; }
  /* Four 44px controls plus a wordmark do not fit 390px, and the wordmark was
     the thing that lost — clipped at the left edge. Shrink the mark (it is a
     home link, not a brand moment, on a document someone is reading) and let
     the row breathe. The breadcrumb is redundant with the Close button beside
     it and goes. */
  /* THE DOWNLOAD HAS TO READ AS AN ACTION, NOT A FORMAT.
     `.rl-hide-sm` drops the word "Download" below 640px, so the control renders
     as the bare string "PDF" sitting between a share glyph and a theme glyph —
     a label among icons, which is why it reads as a caption and gets skipped.
     On this document that is the expensive one to miss: the rendered file is
     the only way a phone can keep or forward a foundation, since there is no
     print dialog behind any other route (see download-doc.js).

     A glyph in front of the word puts it in the same language as the controls
     either side of it. Phone-only and drawn in CSS, so the desktop button —
     which still says "Download PDF" in full — is untouched. */
  #download::before {
    content: "⤓";
    /* Same glyph, with empty alt text so a screen reader gets the anchor's own
       "Download PDF" and not "down arrow with stop" in front of it. Declared
       twice on purpose: the alt-text syntax is Chromium/Safari-17+, and a
       browser that does not understand the second line keeps the first rather
       than dropping the glyph entirely. */
    content: "⤓" / "";
    font-size: 1.05em;
    line-height: 1;
    margin-right: 0.28em;
  }
  #download { padding-inline: 0.6rem; gap: 0; }

  .site-nav__inner { gap: 4px; padding-inline: 8px; }
  .site-nav__logo {
    min-height: 44px; display: inline-flex; align-items: center;
    flex: 0 1 auto; min-width: 0;
  }
  .site-nav__logo img { max-width: 104px; height: auto; }
  .booklet-crumbs { display: none; }
  .booklet-topnav__right { gap: 4px; margin-left: auto; }

  /* ── Contents ──────────────────────────────────────────────────────────
     THE HANDLE WAS SITTING ON THE WORDS. deliverable.css moves it to
     `top: 3.5rem` below 1101px, with a comment saying that drops it into the
     gap between the chapter kicker and the chapter title. It does not: the
     handle occupies y 120–160 and the kicker runs y 116–134, so at 320, 390 and
     430 alike the handle covers the first character of both lines — "CHAPTER 1
     OF 10" renders as "HAPTER 1 OF 10", and "What buyers evaluate" as "hat
     buyers evaluate". The earlier fix was aimed at the right bug and landed 4px
     short of it.

     Rather than nudge it again, take it out of the reading column entirely: on
     a phone the contents belong in the reading bar, in the thumb's reach, not
     floating over the prose at the top of the screen. */
  /* .chapter-toc is `position: fixed; z-index: 40`, which makes it a stacking
     context — so a z-index on the handle INSIDE it is resolved against its
     siblings, not against the reading bar, and the handle painted under a bar
     it was 16 levels "above". The container is what has to move. */
  .chapter-toc { z-index: 56; }
  .chapter-toc__handle {
    position: fixed;
    top: auto;
    left: max(10px, env(safe-area-inset-left, 0px));
    /* Rides above the reading bar where there is one (the foundation), and sits
       on the floor where there is not (the query explorer's buyers rail). */
    bottom: calc(var(--rdr-safe-b) + 6px);
    width: 44px; height: 44px;
    padding: 0;
    justify-content: center;
    border-radius: 12px;
    z-index: 56;                    /* over the reading bar (55) */
    /* SOLID, NOT GHOST. Transparent-on-content was legible enough beside the
       foundation's reading bar and nearly invisible on the query explorer,
       which has no bar behind it — and worse in the Letterpress theme, where a
       muted border on warm white all but disappears. It is the only way into
       the contents, so it gets a real surface. Still not the accent: on the
       foundation it sits beside Next, and two lime buttons in one row means
       neither reads as the forward action. */
    background: var(--paper, var(--bg-card, #141416));
    color: var(--text);
    border: 1px solid var(--border-light);
    box-shadow: 0 6px 18px -6px rgba(0, 0, 0, 0.45);
  }
  /* IT DOES NOT MOVE WHEN IT OPENS. It used to jump from the left edge to the
     right so the open drawer would not cover it, which meant the control you
     just pressed was no longer where you pressed it — reported as confusing
     from a phone. It stays put and rises above the panel instead, so the same
     spot opens and closes the drawer. */
  .chapter-toc.is-open .chapter-toc__handle {
    left: max(10px, env(safe-area-inset-left, 0px));
    right: auto;
    z-index: 62;                      /* over the drawer panel */
    background: var(--accent-fill, var(--accent, #c8ff00));
    color: var(--accent-ink, #09090b);
    border-color: transparent;
  }

  /* THE COMMERCE BAR BURIED THE CONTENTS BUTTON.
     The rule above puts the handle 6px off the floor, on the stated reasoning
     that the query explorer has no bottom bar. It has one in every pre-crawl
     state: .qxl-bar is fixed at bottom 0, 104px (142px while confirming), at
     z-index 60. The handle sat at z-index 56 ENTIRELY inside that band —
     measured at y 794–838 against the bar's 740–844 — so it was painted over
     and unhittable. That is the only way into the contents, and therefore the
     only way to move between personas, missing from the one screen whose whole
     job is to get 150 questions read before they are locked in. Reported from
     a phone, 2026-08-29.

     Lift it clear of the bar's MEASURED height (--qxl-bar-h, republished by the
     bar's ResizeObserver on every state change and rotation) and above its
     layer. The bar's own padding-bottom is max(0.7rem, safe-area-inset-bottom),
     so the inset is already inside that height — adding --rdr-safe-b here would
     count it twice and float the handle off the bar. */
  body.has-qxl .chapter-toc__handle {
    bottom: calc(var(--qxl-bar-h, 104px) + 10px);
    z-index: 61;                      /* over the commerce bar (60) */
  }
  /* Still over the drawer panel once open — the open state's z-62 already wins,
     but it is restated here so the two rules can't drift apart. */
  body.has-qxl .chapter-toc.is-open .chapter-toc__handle { z-index: 62; }

  .chapter-toc__list {
    width: min(88vw, 320px);
    /* 100vh is mobile Safari's LARGE viewport, so with the URL bar showing the
       list was taller than the screen and its last chapters were unreachable.
       account.css moved to dvh; the booklet sheets never followed. */
    max-height: calc(100dvh - var(--rl-topnav-h, 64px) - 2rem);
    overscroll-behavior: contain;   /* scrolling the drawer must not scroll the doc */
    -webkit-overflow-scrolling: touch;
  }
  /* A contents list is the one place navigation IS the content, and its rows
     were 24–29px. */
  .toc-item { min-height: 44px; font-size: 0.95rem; }

  /* ── The reading bar ───────────────────────────────────────────────────
     Fixed, not sticky. Sticky was correct while the bar had to settle above a
     second bar of unpredictable height; with the slot on a fixed budget the
     nav can simply be the floor of the screen, which means it never moves under
     a thumb that is reaching for it. */
  .chapter-nav {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 55;
    margin: 0;
    height: calc(var(--rdr-nav-h) + var(--rdr-safe-b));
    padding: 0 12px calc(var(--rdr-safe-b));
    /* 44px for the contents handle + 10px gutter + 8px gap. */
    padding-left: 66px;
    display: flex; align-items: center; gap: 8px;
    flex-wrap: nowrap;              /* the ≤1299px block allows wrapping; a fixed
                                       bar with a fixed height must not wrap */
    row-gap: 0;
    border: 0;
    border-top: 1px solid var(--border);
    border-radius: 0;
    background: rgba(var(--panel-rgb), 0.98);
    -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
    box-shadow: none;
  }
  /* HOW FAR THROUGH THE DOCUMENT, WITHOUT SPENDING A PIXEL ON SAYING SO.
     "4 / 10" answers "where am I" only if the reader also remembers how long
     the book is. A hairline riding the bar's own top border answers it at a
     glance and costs no height — the bar already draws that border, this just
     colours the part behind the reader. --rdr-progress is set by showChapter().
     Not an animation the reader watches: it moves on a turn, in step with the
     chapter change, so reduced-motion has nothing extra to suppress. */
  .chapter-nav::before {
    content: "";
    position: absolute; top: -1px; left: 0;
    width: calc(var(--rdr-progress, 0) * 100%);
    height: 2px;
    background: var(--accent, #c8ff00);
    transition: width .28s ease;
    pointer-events: none;
  }
  .chapter-nav { position: fixed; }   /* re-stated: ::before is positioned to it */
  @media (prefers-reduced-motion: reduce) {
    .chapter-nav::before { transition: none; }
  }

  .chapter-nav .booklet-btn {
    min-height: 44px;
    padding-inline: 0.85rem;
    flex: 0 0 auto;
    white-space: nowrap;
  }
  /* Back loses its label and keeps its target: the arrow is unambiguous next to
     a position counter, and the 86×39 pair was both too small to hit and too
     wide to leave the counter any room. */
  #ch-prev { min-width: 44px; padding-inline: 0.6rem; font-size: 0; }
  #ch-prev::before { content: "←"; font-size: 1rem; }
  #ch-next { margin-left: auto; }
  .chapter-nav__count {
    flex: 0 0 auto;
    margin: 0;
    padding: 0;
    font-size: 0.78rem;
    font-variant-numeric: tabular-nums;
    color: var(--text-muted);
  }
  /* The notes dock is hoisted into the slot by dockPhone() when it has
     something to say. It must never re-enter this row: five controls in a
     56px bar is the overflow the ≤1299px block wraps to escape, and a fixed
     bar cannot wrap. */
  .chapter-nav .fnote-dock { display: none; }

  /* ── The action slot ───────────────────────────────────────────────────
     One row, one action, one line of explanation. The gate's prose is what made
     this bar 94–115px: every renderApproval() branch writes a full sentence or
     three into .fnd-approval__msg. On a phone the sentence is the part that
     collapses — clamped to a single line — and the action keeps its full size,
     because the action is why the bar exists. */
  .fnd-approval,
  .fnd-approval--min {
    position: fixed;
    left: 0; right: 0;
    bottom: calc(var(--rdr-nav-h) + var(--rdr-safe-b));
    top: auto;
    /* The base rule centres the bar with `left: 50%; translateX(-50%)`. Setting
       left/right alone leaves the transform, which then shifts the full-width
       strip half a viewport off the left edge. */
    transform: none;
    z-index: 50;
    width: 100%; max-width: none;
    height: var(--rdr-slot-h);
    padding: 0 12px;
    display: flex; align-items: center; gap: 10px;
    /* The base rule wraps, which is right for a bar that grows to fit its
       message and wrong for a strip on a fixed 52px budget: the action wrapped
       to a second line and was clipped by the slot's own overflow. */
    flex-wrap: nowrap;
    border: 0;
    border-top: 1px solid var(--border);
    border-radius: 0;
    background: rgba(var(--panel-rgb), 0.98);
    -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
    box-shadow: none;
    overflow: hidden;
  }
  .fnd-approval[hidden] { display: none; }

  .fnd-approval__msg {
    flex: 1 1 auto; min-width: 0;
    margin: 0;
    font-size: 0.82rem; line-height: 1.3;
    color: var(--text-secondary);
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 1;
    overflow: hidden;
  }
  /* Inside one clamped line, only the lead clause survives — so let the <strong>
     carry it and drop the trailing explanation rather than truncating mid-word
     on a random character. The full text stays in the DOM for screen readers
     and for the ≥701px layouts. */
  .fnd-approval__msg > *:not(strong) { display: none; }
  .fnd-approval__msg strong { color: var(--text); font-weight: 600; }
  /* The operator banner is a second line by construction and cannot fit a
     one-line slot; it is also irrelevant to the customer whose phone this is. */
  .fnd-approval__op { display: none; }

  .fnd-approval__actions { flex: 0 0 auto; display: flex; gap: 8px; margin: 0; }
  .fnd-approval__actions .booklet-btn {
    min-height: 40px;
    padding-inline: 0.9rem;
    font-size: 0.86rem;
    white-space: nowrap;
  }
  /* An action with nothing to say beside it takes the row. */
  .fnd-approval:not(:has(.fnd-approval__msg)) .fnd-approval__actions,
  .fnd-approval__actions:only-child { flex: 1 1 auto; }
  .fnd-approval__actions:only-child .booklet-btn { flex: 1 1 auto; justify-content: center; }

  /* The gate's two icon-buttons are one decision with two answers, so they are
     a single occupant of the slot: request-changes compact on the left, the
     forward action taking the rest of the row. */
  .fnd-icons {
    flex: 1 1 auto; display: flex; gap: 8px; align-items: center;
    min-width: 0; justify-content: stretch;
  }
  .fnd-icon { min-height: 40px; flex: 0 0 auto; padding: 0 0.75rem; }
  .fnd-icon--go { flex: 1 1 auto; justify-content: center; }

  /* ONE PRIMARY ON SCREEN. With the gate in the slot, "Looks right" and
     "Next →" were both accent-filled and vertically adjacent — two lime
     buttons 60px apart, so neither read as the thing to press. The commitment
     outranks the pagination, so while the slot holds a real action Next steps
     down to the ghost treatment and the accent is spent once.

     Keyed to the two rungs that carry an action, not to the presence of the
     gate markup: .fnd-icons stays in the bar in every rung (placement is
     deterministic — see dockPhone), so testing for it would also demote Next
     under a plain status message, where Next IS the only thing to press. */
  body:has(#approval-bar[data-slot="gate"]) .chapter-nav #ch-next,
  body:has(#approval-bar[data-slot="notes"]) .chapter-nav #ch-next,
  body:has(#approval-bar[data-slot="message"] .booklet-btn--primary) .chapter-nav #ch-next {
    background: transparent;
    color: var(--text-secondary);
    border: 1px solid var(--border-light);
  }
  .fnd-icon__l { font-size: 0.86rem; }
  /* Feedback collapses to its glyph — the label is redundant beside a primary
     that names the alternative, and the count badge still reads. */
  #appr-fb .fnd-icon__l { display: none; }
  #appr-fb.fnd-icon--pending .fnd-icon__l { display: inline; font-size: 0.8rem; }

  /* ── The ladder, expressed as CSS ──────────────────────────────────────
     dockPhone() writes one attribute; these four rules are what it means. The
     losing rungs are hidden by a rule that stops matching the moment the
     attribute changes, so no state can outlive the branch that set it. */
  #approval-bar[data-slot="notes"] > :not(.fnote-dock) { display: none; }
  #approval-bar[data-slot="gate"]  > :not(.fnd-icons)  { display: none; }
  #approval-bar[data-slot="message"] > .fnote-dock,
  #approval-bar[data-slot="message"] > .fnd-icons { display: none; }
  #approval-bar[data-slot="none"] { display: none; }

  /* Hoisted notes (priority 1 in the ladder). */
  .fnd-approval .fnote-dock {
    position: static; flex: 1 1 auto; display: flex; gap: 8px;
    left: auto; bottom: auto; min-width: 0;
  }
  .fnd-approval .fnote-review,
  .fnd-approval .fnote-fab {
    min-height: 40px; justify-content: center;
    box-shadow: none; white-space: nowrap;
  }
  /* Send is the rung; composing another note is the secondary beside it. The
     launcher keeps its glyph and its 40px target and gives up its label, the
     same trade #appr-fb makes in the gate — stated here rather than left to
     whichever one happens to overflow first. */
  .fnd-approval .fnote-fab { flex: 0 0 auto; padding-inline: 0.75rem; }
  .fnd-approval .fnote-fab__l { display: none; }
  .fnd-approval .fnote-review { flex: 1 1 auto; }

  /* The minimize affordance has no meaning in a one-line slot. */
  .fnd-approval__min,
  .fnd-approval__reopen,
  .fnd-approval__more { display: none !important; }

  /* The collapsible status card (changes_requested / reprocessing) is its own
     shape — .fnd-min__toggle plus a .fnd-min__body — rather than the msg +
     actions pair the other branches build, so it overflowed the slot. Its own
     comment says the toggle line "is the ENTIRE message" when collapsed and
     carries the outcome, which is exactly what a one-line slot needs: force
     the collapsed presentation and drop the expander, since there is nowhere
     in a fixed 52px strip to expand into. Nothing is lost — the body restates
     the toggle, and this state has no action to reach. */
  .fnd-approval--min .fnd-min__body { display: none; }
  .fnd-approval--min .fnd-min__x { display: none; }
  .fnd-min__toggle {
    flex: 1 1 auto; min-width: 0;
    padding: 0; text-align: left;
    display: flex; align-items: center; gap: 8px;
  }
  .fnd-min__t {
    min-width: 0; font-size: 0.82rem;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }

  /* ── Clearance ─────────────────────────────────────────────────────────
     One source of truth for the bottom inset, derived from the two strips
     rather than from syncBarClearance()'s 110px guess. */
  /* Clearance is for the foundation's fixed reading bar, so it is conditional
     on that bar existing. The query explorer loads this same sheet and has no
     docked furniture; without the guard it reserved 80px of dead space under a
     list that ends in nothing. */
  body:has(.chapter-nav) .doc,
  body:has(.chapter-nav) .report {
    padding-bottom: calc(var(--rdr-chrome) + 1.5rem);
  }
  /* Anchored jumps have to clear the fixed top bar. */
  :target { scroll-margin-top: calc(var(--rl-topnav-h, 64px) + 12px); }
  .doc [id] { scroll-margin-bottom: calc(var(--rdr-chrome) + 1rem); }

  /* Floating furniture that hangs off --fnd-bar-h has to move to the new
     budget, or it lands behind the strips. */
  .fnote-toast { bottom: calc(var(--rdr-chrome) + 14px); }

  /* ── Content ───────────────────────────────────────────────────────────
     Capability rows put the name and the strength pill in one row, which left
     the name ~55% of the width: "Clinical evidence", "Dosage transparency" and
     "Third-party testing" all wrapped to two lines beside a pill with room to
     spare. Stack them — the pill reads fine as a second line and the name gets
     the full measure. */
  .kg-row__head {
    display: flex; flex-direction: column; align-items: flex-start; gap: 6px;
  }
  .kg-row__name { font-size: 1rem; line-height: 1.3; }
  .kg-strength { flex: 0 0 auto; }

  /* A NUMBERED row is not a capability row and must not inherit the stack.
     The column above is right for a name + strength pill; applied to the query
     list it gave a 2-character handle its own line on all 150 rows, and left
     the number reading as a heading over the question rather than a label on
     it. Grid instead: the number in a gutter, the question beside it, and the
     note button under the question where the stack meant to put it. */
  .kg-row__head:has(.kg-row__n) {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: baseline;
    gap: 6px 8px;
  }
  .kg-row__head:has(.kg-row__n) .kg-row__n { grid-column: 1; }
  .kg-row__head:has(.kg-row__n) .kg-row__name,
  .kg-row__head:has(.kg-row__n) .qxl-notebtn { grid-column: 2; }
  .kg-row__head:has(.kg-row__n) .qxl-notebtn { justify-self: start; }

  /* The running head has to survive 320px, where the client name and the
     document title cannot both fit on one line. The name is the half that
     anchors — "AI Visibility Foundation Review" is already the eyebrow on the
     cover and the page title in the tab — so the document half goes and the
     name keeps the room. */
  .doc-runhead { margin-bottom: 1.1rem; padding-bottom: 0.6rem; }
  .doc-runhead__name {
    font-size: 0.88rem; min-width: 0;
    overflow: hidden; text-overflow: ellipsis;
  }
  .doc-runhead__doc { display: none; }

  /* The cover's facts are a flex row with a 1.5rem gap. At 390px every item
     wraps onto its own line and that gap becomes 24px of ROW spacing, so three
     short strings — the host, the page count, the issue date — spread over a
     third of the first screen and read as three unrelated statements rather
     than one caption. They are one caption. */
  .ch__cover-meta { gap: 0.45rem 1.25rem; margin-bottom: 1.75rem; }

  /* Mono labels bottom out at 9–11px across the document surfaces and hold
     those sizes unchanged from 1440 down to 320. A 12px floor is still small
     for a caption and is the largest step that changes no line breaks. */
  .chapter-kicker { font-size: 0.75rem; }

  /* ─────────────────────────────────────────────────────────────────────
     QUERY EXPLORER, BEFORE THE CRAWL (?qstate) — the review-and-lock state
     A different render of the same page: no answers yet, so no filter bar (the
     rules below it never match here), and instead a fixed commerce bar and a
     "Suggest a change" control on every one of 214 questions.

     THE NOTE BUTTON IS THE WHOLE INTERACTION AND IT WAS 29px TALL.
     .qxl-notebtn is 0.74rem in 0.25rem padding — 140×29 — and it is repeated
     per question, which is what took this surface to 218 of 228 tap targets
     under 44px. This is the state where the customer is asked to correct the
     question set before paying for a crawl to run on it; a control they have to
     aim at is a correction we do not receive.
     ───────────────────────────────────────────────────────────────────────── */
  .qxl-notebtn {
    min-height: 44px;
    padding: 0 0.85rem;
    font-size: 0.8rem;
    display: inline-flex; align-items: center;
  }
  .qxl-step__acts .qxl-notebtn { min-height: 44px; padding: 0 0.85rem; font-size: 0.8rem; }
  .qxl-upload, .qxl-upload__chip { min-height: 44px; display: inline-flex; align-items: center; }

  /* THE CLEARANCE WAS SHORT BY THE AMOUNT THE BAR ACTUALLY GROWS.
     body.has-qxl .doc reserves a flat 5.5rem (88px), and the bar measures 104px
     on a phone — the title wraps to two lines beside a 230px CTA and the
     subline clamps to two. So the last 16px of the document sits under a fixed
     commerce bar, which is where the final question and the lock-in step live.
     Derived from the bar's real height instead of a constant that was right for
     one width. */
  body.has-qxl .doc {
    padding-bottom: calc(var(--qxl-bar-h, 104px) + 1.5rem + env(safe-area-inset-bottom, 0px));
  }

  /* At 320 the bar plus the top nav is 168px of a 568px screen — 30%, which is
     the one width where this reads as too much. Trimming the gap and the CTA's
     side padding buys back a line without touching the subline, which is the
     order's case ("$0 today — charged on delivery") and must not ellipsize. */
  /* The order button was 41–44px depending on width — the one control on this
     surface a customer MUST hit, sized by its padding rather than by a floor. */
  .qxl-bar__cta { min-height: 44px; }

  /* THE CONFIRM STATE HAS TWO BUTTONS, AND THEY LEFT THE QUESTION NO ROOM.
     "Lock the set and start the crawl?" beside "Keep tuning" and "Yes — lock in
     & run →" on a 390px row wrapped to one word per line — seven lines of
     heading down the left edge — and truncated the sub to "no more…". Only this
     state stacks: the single-CTA states fit their row and stacking them would
     spend height for nothing. */
  .qxl-bar:has(.qxl-bar__cta ~ .qxl-bar__cta) { flex-wrap: wrap; row-gap: 0.7rem; }
  .qxl-bar:has(.qxl-bar__cta ~ .qxl-bar__cta) .qxl-bar__txt { flex: 1 0 100%; }
  .qxl-bar:has(.qxl-bar__cta ~ .qxl-bar__cta) .qxl-bar__cta {
    flex: 1 1 0; min-width: 0; justify-content: center; margin-left: 0;
  }
  /* The subline is the terms of the thing being confirmed — it must not clip. */
  .qxl-bar:has(.qxl-bar__cta ~ .qxl-bar__cta) .qxl-bar__txt span {
    -webkit-line-clamp: 3;
  }
  /* And in the confirmation itself, nothing is trimmed at all: that subline is
     what the customer is agreeing to. Declared after the rule above because the
     two have equal specificity and this file loads last. The bar grows, which
     is fine — its height is measured (--qxl-bar-h), so the document clearance
     and the contents handle both follow it. */
  .qxl-bar.is-fulltext .qxl-bar__txt span {
    display: block; overflow: visible; -webkit-line-clamp: none;
  }

  @media (max-width: 360px) {
    .qxl-bar { gap: 0.6rem; }
    .qxl-bar__cta { padding: 0.6rem 0.8rem; font-size: 0.85rem; }
  }

  /* ─────────────────────────────────────────────────────────────────────
     QUERY EXPLORER — the filters stop being the page
     .qxa-bar is four view pills, five engine pills and a search field in a
     wrapping grid, and it is sticky. At 390px it wraps to 253px and at 320px
     to 289px, so with the top bar it held 317px of an 844px iPhone (38%) and
     353px of a 568px SE (62%) — while the reader scans 150 questions through
     what is left. Worse, nothing they came for was on the first screen at all:
     the feed started 1,275px down.

     dockFilters() moves the controls into a sheet and leaves one 56px row that
     says what is currently filtered. Everything stays one tap away and nothing
     hides behind a horizontal scroll.
     ───────────────────────────────────────────────────────────────────────── */
  .qxa-bar[hidden] { display: none; }

  .qxa-mini {
    position: sticky; top: var(--rl-secbar-h, 0px); z-index: 6;
    display: flex; align-items: center; gap: 10px;
    margin: 1rem 0 0.75rem; padding: 0.5rem 0;
    background: var(--bg, #09090b);
    border-bottom: 1px solid var(--border);
  }
  .qxa-mini[hidden] { display: none; }
  .qxa-mini__b {
    flex: 1 1 auto; min-width: 0; min-height: 44px;
    display: flex; align-items: center; gap: 0.5rem;
    padding: 0 0.9rem; cursor: pointer;
    background: var(--card, var(--bg-card)); color: var(--text-secondary);
    border: 1px solid var(--border); border-radius: 999px;
    font: inherit; font-size: 0.88rem; text-align: left;
  }
  /* A filtered list must not look like an unfiltered one. */
  .qxa-mini.is-filtered .qxa-mini__b {
    color: var(--accent);
    border-color: rgba(var(--accent-rgb, 200,255,0), .5);
    background: rgba(var(--accent-rgb, 200,255,0), .08);
  }
  .qxa-mini__i { flex: 0 0 auto; font-size: 0.95rem; line-height: 1; }
  .qxa-mini__l { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .qxa-mini__n {
    flex: 0 0 auto; font-family: var(--mono, monospace); font-size: 0.78rem;
    color: var(--text-muted); font-variant-numeric: tabular-nums;
  }

  .qxa-sheet { position: fixed; inset: 0; z-index: 70; }
  .qxa-sheet[hidden] { display: none; }
  .qxa-sheet__scrim {
    position: absolute; inset: 0; border: 0; padding: 0; cursor: pointer;
    background: rgba(9, 9, 11, 0.62);
    opacity: 0; transition: opacity .2s ease;
  }
  .qxa-sheet.is-open .qxa-sheet__scrim { opacity: 1; }
  .qxa-sheet__panel {
    position: absolute; left: 0; right: 0; bottom: 0;
    max-height: 82dvh; overflow-y: auto; overscroll-behavior: contain;
    background: var(--paper, var(--bg-card, #141416));
    border-top: 1px solid var(--border);
    border-radius: 18px 18px 0 0;
    padding: 0.5rem 1rem calc(1rem + env(safe-area-inset-bottom, 0px));
    transform: translateY(100%); transition: transform .22s ease;
  }
  .qxa-sheet.is-open .qxa-sheet__panel { transform: translateY(0); }
  .qxa-sheet__grab {
    width: 38px; height: 4px; border-radius: 2px; margin: 0.35rem auto 0.9rem;
    background: var(--border-light, #3f3f46);
  }
  .qxa-sheet__body { display: grid; gap: 1rem; }
  /* Two filter groups — what you're looking for, and which engine — read as one
     undifferentiated field of pills when only a gap divides them. A hairline
     says "these are separate questions" without spending a line on a heading
     that would repeat what the pills already say. */
  .qxa-sheet__body > * + * { border-top: 1px solid var(--border); padding-top: 1rem; }
  /* In the bar these groups sit on one row and wrap; in the sheet each is its
     own block with room, which is what buys the 44px targets. */
  .qxa-sheet__body .qxa-seg { flex-wrap: wrap; gap: 0.45rem; }
  .qxa-sheet__body .qxa-bar__row { flex-direction: column; align-items: stretch; gap: 0.75rem; }
  .qxa-sheet__body .qxa-seg__b { min-height: 44px; padding: 0 1rem; font-size: 0.9rem; }
  .qxa-sheet__body .qxa-search { min-height: 44px; flex: 1 1 auto; font-size: 1rem; }
  .qxa-sheet__done {
    width: 100%; min-height: 46px; margin-top: 1rem; cursor: pointer;
    background: var(--accent-fill, var(--accent, #c8ff00)); color: var(--accent-ink, #09090b);
    border: 0; border-radius: 12px; font: inherit; font-weight: 600; font-size: 0.95rem;
  }
  @media (prefers-reduced-motion: reduce) {
    .qxa-sheet__panel, .qxa-sheet__scrim { transition: none; }
  }

  /* The pay card falls inline on mobile (the status rail is display:contents
     below 1300px), which is right — it is a full-width decision surface that
     costs no chrome. Give it the room that implies. */
  .paygate-host { margin: 1.75rem 0; }
  .paygate-host .booklet-btn,
  .paygate-host button[type="submit"] { min-height: 48px; width: 100%; }
}

/* Between the phone bar model and the docked-bar model there is one width band
   — 641 to 700 — where booklet.css's ≤1299px block docks the gate INTO the
   chapter nav while this sheet is also fixing the nav to the floor. Both are
   trying to own the same row. dockPhone() owns ≤700, so tell the docked-gate
   rules to stand down for that band rather than leaving the two to race. */
@media (min-width: 641px) and (max-width: 700px) {
  .chapter-nav .fnd-icons { display: none; }
}
