/*
  Every chrome colour is a variable, and the theme is one attribute on <html>. The palette
  blocks also colour the appearance swatches, so the previews cannot drift from the page.
  `:root` deliberately carries NO colours: a
  document without data-theme therefore has no palette at all, which is why
  reader/index.html ships the attribute inline instead of waiting for the app to write it.

  The dark, black and sepia values are duplicated in reader/theme.ts, which injects them into the
  book's own documents — that pairing is what makes the page and the frame around it match.
  Change one, change the other.
*/
:root {
  /*
    The book is serif, the instrument is sans. The reading face carries no font payload:
    on Android WebView this list resolves to Noto Serif, which is already on the device.
  */
  --book-font: "Literata", "Bookerly", "Noto Serif", Georgia, serif;
  --ui-font: system-ui, "Roboto", sans-serif;

  /*
    #topbar and #controls are overlays, so nothing in normal flow can measure them. Their
    heights are stated here instead, because the always-visible rows at the bottom are
    positioned against them.
  */
  --topbar-h: 3rem;
  --controls-h: 3.25rem;
  --progress-h: 1.7rem;

  --slide: 220ms;
  /* ease-out-quart: decelerates into place. No overshoot — bars are not toys. */
  --ease: cubic-bezier(.22, 1, .36, 1);

  /*
    Capacitor's SystemBars plugin publishes --safe-area-inset-* on documentElement, and
    only makes native env() resolve on a WebView of version 140 or newer AND a page that
    declares viewport-fit=cover (reader/index.html:11-18). The custom property is
    therefore the source of truth on Android and env() is the fallback for the web build.
  */
  --safe-t: var(--safe-area-inset-top, env(safe-area-inset-top, 0px));
  --safe-b: var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px));
  --safe-l: var(--safe-area-inset-left, env(safe-area-inset-left, 0px));
  --safe-r: var(--safe-area-inset-right, env(safe-area-inset-right, 0px));

  /*
    The inset the READING AREA is laid out against, and the reason a centre tap can now
    raise the system bars without moving the book.

    --safe-t above is LIVE. Capacitor derives it from
    `insets.getInsets(systemBars() | displayCutout())`, which is VISIBILITY-dependent
    (SystemBars.java:168-174), so it grows the instant the status bar appears. Feeding
    that straight into #reader's padding resized #host, tripped the paginator's
    ResizeObserver and teleported the page on every tap — the defect db1815e fixed by
    refusing to toggle the bars at all.

    reader/insets.ts latches this one to the value seen while the bars are HIDDEN and
    freezes it while they are shown, so the reading box is the same box in both states.
    The fallback is --safe-t itself, which is correct for the web build: nothing there
    ever republishes it.
  */
  --reader-safe-t: var(--safe-t);
}
:root[data-theme="light"],
.theme-swatch[data-theme="light"] {
  --bg: #ffffff;
  --surface: #f6f4ef;
  --fg: #1a1a1a;
  --muted: #666666;
  --rule: #dddddd;
  --hover: #f0f0f0;
  --accent: #1d4ed8;
  --accent-fg: #f8fafc;
  --scrim: rgb(26 26 26 / .38);
  --notice-bg: #fef3c7;
  --notice-fg: #422006;
  color-scheme: light;
}
:root[data-theme="sepia"],
.theme-swatch[data-theme="sepia"] {
  --bg: #f4ecd8;
  --surface: #ede2c9;
  --fg: #493b2a;
  --muted: #75634b;
  --rule: #d3c4a5;
  --hover: #e3d5b7;
  --accent: #795124;
  --accent-fg: #fff8e9;
  --scrim: rgb(49 37 22 / .38);
  --notice-bg: #f5df9f;
  --notice-fg: var(--fg);
  color-scheme: light;
}
:root[data-theme="dark"],
.theme-swatch[data-theme="dark"] {
  --bg: #14161a;
  --surface: #1b1e24;
  --fg: #e6e1d9;
  --muted: #9aa0a6;
  --rule: #2a2f36;
  --hover: #22262d;
  --accent: #8ab4f8;
  --accent-fg: #0f1b33;
  --scrim: rgb(6 7 9 / .55);
  --notice-bg: #3a2f12;
  --notice-fg: #f5e7c1;
  color-scheme: dark;
}
/* True black for AMOLED: an unlit pixel, not a very dark grey. --surface is what lifts
   the chrome off the page, since the page itself has nowhere darker to go. */
:root[data-theme="black"],
.theme-swatch[data-theme="black"] {
  --bg: #000000;
  --surface: #0b0b0c;
  --fg: #e4dfd6;
  --muted: #8a857e;
  --rule: #212124;
  --hover: #17171a;
  --accent: #b7c8f9;
  --accent-fg: #12213f;
  --scrim: rgb(0 0 0 / .62);
  --notice-bg: #2a2411;
  --notice-fg: #f0e4bd;
  color-scheme: dark;
}

/*
  One z-order, stated once, because every layer here exists for a reason:
    0  #reader              the book
    3  #edge                must outrank the book or the renderer eats the drawer swipe
    4  #topbar, #controls   the two bars that come and go
    5  #statusbar, #progress, #next-chapter   never covered by the bars, so they draw
                            over them rather than under. Equal rank, so DOM order breaks
                            the tie: #next-chapter paints over a status message and
                            #progress paints over #next-chapter.
    6  #scrim
    7  #drawer
*/

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  /*
    100lvh, never 100vh or 100dvh. lvh is the LARGE viewport — the height with the system
    bars retracted — and it is the only one of the three that does not move when they come
    in. 100dvh shrinks by the bar insets, which resized #host and re-paginated the book on
    every centre tap; 100vh is the same value as lvh here but says nothing about why.
    Nothing retractable overlays this document (an APK and an Electron window have no URL
    bar), so the large viewport is always the visible one, and the always-visible bottom
    rows clear the navigation bar by riding up rather than by shortening the page.
  */
  height: 100lvh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--bg);
  color: var(--fg);
  font: 15px/1.5 var(--ui-font);
  /* Every page turn is a tap; a blue flash on each one is intolerable. :active states
     below give the deliberate controls their feedback back. */
  -webkit-tap-highlight-color: transparent;
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ---------- top bar ---------- */

#topbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 4;
  display: flex;
  align-items: center;
  gap: .25rem;
  height: calc(var(--topbar-h) + var(--safe-t));
  padding: var(--safe-t) calc(.35rem + var(--safe-r)) 0 calc(.35rem + var(--safe-l));
  background: var(--surface);
  border-bottom: 1px solid var(--rule);
  transform: translateY(-100%);
  /*
    visibility, not just the transform: a bar parked off-screen is still in the tab order,
    and tabbing into invisible controls is worse than having no keyboard support. The 0s
    transition delays the flip until the slide has finished, so the exit stays visible.
  */
  visibility: hidden;
  transition: transform var(--slide) var(--ease), visibility 0s linear var(--slide);
}
body[data-chrome="shown"] #topbar {
  transform: none;
  visibility: visible;
  transition: transform var(--slide) var(--ease), visibility 0s;
}
#nav-chapter {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  color: var(--fg);
  font-family: var(--book-font);
  font-size: .95rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

#topbar button,
#controls button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 44px: the smallest target a thumb hits reliably. */
  min-width: 44px;
  min-height: 44px;
  padding: 0 .55rem;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--fg);
  cursor: pointer;
  font: inherit;
}
#topbar button:active,
#controls button:active {
  background: var(--hover);
}
#sync-now {
  color: var(--muted);
  font-size: .85rem;
}
#nav-left,
#nav-right {
  font-size: 1.6rem;
  line-height: 1;
}
/* A toggle, so its state has to be visible and not only announced. Pressed reads as the
   accent chip the drawer's pill buttons use, so "on" looks the same everywhere. */
#tap-turn {
  font-size: .8rem;
  color: var(--muted);
  white-space: nowrap;
}
#tap-turn[aria-pressed="true"] {
  background: var(--accent);
  color: var(--accent-fg);
}

/* ---------- banners ---------- */

#alert {
  flex: none;
  margin: 0;
  /* A latched inset keeps a visible storage-failure alert from reflowing the book
     when Android raises its system bars. The transform below may use live insets. */
  padding: calc(.55rem + var(--reader-safe-t)) calc(.9rem + var(--safe-r)) .55rem
    calc(.9rem + var(--safe-l));
  transition: transform var(--slide) var(--ease);
}
#alert[hidden] {
  display: none;
}
/*
  A banner sits in normal flow, so #topbar would slide straight over the top of it. It
  rides down out of the way instead of the page reflowing: every reflow re-paginates the
  book under the reader's eyes, and a banner is not worth losing your line over.
*/
body[data-chrome="shown"] #alert {
  transform: translateY(calc(var(--topbar-h) + var(--safe-t)));
}
/* Loud on purpose: this banner only ever reports data being lost right now. These two
   literals are deliberately outside the palette blocks — a data-loss banner must look
   identical in every theme, and four themed copies of one red is four chances to drift. */
#alert {
  display: block;
  background: #7f1d1d;
  color: #fff;
  font-weight: 600;
}

/* ---------- the page ---------- */

#reader {
  position: relative;
  z-index: 0;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  /*
    Edge-to-edge horizontally, but never underneath the display cutout: prose sliding
    under the punch-hole camera is unreadable, and the inset is zero on the web anyway.
    LATCHED, not live — see --reader-safe-t. Once the bars are shown they are covered by
    #topbar, which is opaque and taller than the status bar, so the reading area needs no
    extra room for them and must not take any.
  */
  padding-top: var(--reader-safe-t);
}
#host {
  /* Outranks the preview underneath it, and keeps its own stacking context: the curl
     applies a filter to #host, and a filtered element paints as one unit. */
  position: relative;
  z-index: 1;
  flex: 1;
  min-height: 0;
  /*
    No visible scrollbar on the book, in either flow. The scroller is #container
    inside the paginator's CLOSED shadow root, so no selector and no
    non-inherited property (scrollbar-width) can reach it — but scrollbar-color IS
    inherited, and inheritance crosses the shadow boundary. Transparent thumb and
    track keep scrolling (wheel, touch, keyboard) while removing the bar entirely.
    Scoped to #host so the drawer and dialog scrollbars keep theirs. The gutter
    width itself cannot be reclaimed from outside; that dead strip in scrolled
    flow is the price. Verified by screenshot: bar present before, absent after.
  */
  scrollbar-color: transparent transparent;
  /*
    OPAQUE, so the destination page can stay painted underneath between turns without
    showing through. The paginator paints its own background from the content document
    (paginator.js:189-194), but that is the BOOK's background and may be transparent, and
    the page-padding ring around the iframe is this element's own area either way.
  */
  background: var(--bg);
}
/*
  The destination page of a turn in progress, and the lifted paper over it.

  Both are absolutely positioned to the SAME box as #host: #reader is a flex column whose
  only in-flow child is #host and whose only padding is --reader-safe-t, so this inset is
  that box exactly. Asserted at runtime rather than assumed — reader/app.ts compares the
  two rects before the first turn and falls back to an instant turn if they disagree,
  because a preview one pixel out of register would paginate differently and reveal the
  wrong page.

  #under-host is NEVER hidden while it exists, and that is deliberate rather than sloppy: a
  `visibility: hidden` subtree is not PAINTED, so the first frames of a turn could composite
  it before its raster for the page it had been moved to existed — which reads as the old
  page, or two duplicate pages, for a moment. It stays painted behind #host instead, which
  is opaque for exactly this reason. `display: none` would be worse still: the host would
  measure 0x0 and the preview would re-paginate, landing on a different page.
*/
#under-host,
#curl-fx {
  position: absolute;
  inset: var(--reader-safe-t) 0 0 0;
  pointer-events: none;
}
#under-host {
  z-index: 0;
  scrollbar-color: transparent transparent;
}
#under-host foliate-view {
  display: block;
  width: 100%;
  height: 100%;
}
#curl-fx {
  z-index: 2;
}

/* ---------- page-turn grips ---------- */

/*
  40px each, for the same two reasons #edge is 40px: it must outrank the book's iframe so
  a drag on it reaches the host document at all, and it must match the band the Android
  activity asks the platform to release (reader/app.ts calls setGestureExclusions with
  these bounds).

  touch-action: none is load-bearing, exactly as it is on #edge: without it Chromium
  claims the horizontal drag as its own pan and delivers pointercancel instead of
  pointermove, so the curl would work under a mouse and do nothing under a finger.

  Only present in curl mode, and #edge yields the left strip to them there — one strip
  cannot both open the drawer and turn the page back.
*/
#grip-left,
#grip-right {
  position: fixed;
  top: 0;
  bottom: 0;
  z-index: 3;
  width: 40px;
  touch-action: none;
  display: none;
}
#grip-left {
  left: 0;
}
#grip-right {
  right: 0;
}
body[data-turn="book"] :is(#grip-left, #grip-right) {
  display: block;
}
body[data-turn="book"] #edge {
  pointer-events: none;
}
/* foliate-view is a custom element, so it defaults to `display: inline` and collapses
   to 0x0 inside the flex host. The book then "opens" but renders nothing until some
   later navigation happens to force a layout. Found by driving the real UI: #host
   measured 940x753 while the view inside it measured 0x0. */
#host foliate-view {
  display: block;
  width: 100%;
  height: 100%;
}

/* ---------- status line ---------- */

#statusbar {
  position: relative;
  z-index: 5;
  flex: none;
  background: var(--bg);
  /*
    Lifted clear of the navigation bar whenever there is one, in BOTH chrome states, so
    the row cannot end up underneath it — including after a stray edge swipe restores the
    bars behind the app's back (SystemBars never sets
    BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE, platform/native.ts:138-143). A transform,
    because #progress below is lifted the same way and shortening either one would resize
    #host instead.
  */
  transform: translateY(calc(-1 * var(--safe-b)));
  transition: transform var(--slide) var(--ease);
}
/*
  Rides up over #controls when the bars come in, for the same reason the banners ride
  down: moving one line of chrome is free, reflowing the book is not. It takes --surface
  with it on the way, because MEASURED at 412x915 the translated line lands on top of the
  prose, and muted grey over serif body text is not readable; wearing the bar's own colour
  also closes the seam between this line and the bar it now sits on.

  --safe-b stays in the sum: #controls reserves --progress-h + --safe-b of bottom padding,
  so the row has to clear the navigation bar as well as the control bar to land in it.
*/
body[data-chrome="shown"] #statusbar {
  background: var(--surface);
  transform: translateY(calc(-1 * (var(--controls-h) + var(--safe-b))));
}
#status {
  display: block;
  padding: .3rem calc(.9rem + var(--safe-r)) .3rem calc(.9rem + var(--safe-l));
  color: var(--muted);
  font-size: .8rem;
}
/* All of the padding lives on #status, so an empty message collapses the row to nothing
   and #statusbar needs to know nothing about whether there is a message. */
#status:empty {
  display: none;
}

/* ---------- progress row ---------- */

#progress {
  position: relative;
  z-index: 5;
  flex: none;
  display: grid;
  /* 1fr auto 1fr, so the book percentage is centred on the SCREEN rather than in
     whatever space the other two values happen to leave. */
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  /*
    Height is a CONSTANT and the navigation bar is cleared with a transform. Both were
    once folded into the box — `height: calc(--progress-h + --safe-b)` with matching
    bottom padding — and because this row is a flex sibling of #reader, growing it by the
    inset shortened #host by the same amount and re-paginated the book. A transform moves
    the row without telling the layout.
  */
  height: var(--progress-h);
  padding: 1px calc(.9rem + var(--safe-r)) 0 calc(.9rem + var(--safe-l));
  transform: translateY(calc(-1 * var(--safe-b)));
  transition: transform var(--slide) var(--ease);
  background: var(--bg);
  color: var(--muted);
  /* tabular-nums so the digits hold their columns instead of jittering as they tick. */
  font-size: .78rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: .04em;
}
#progress-rule {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: var(--rule);
}
/*
  Divider and progress bar in one element: the only piece of chrome that never hides is
  also the one that says where you are. --book is a percentage written on <html> by the
  app; the 0% fallback means an unopened book shows an empty rule rather than a full one.
*/
#progress-rule::after {
  content: "";
  display: block;
  width: var(--book, 0%);
  height: 100%;
  background: var(--accent);
}
#pct-book {
  text-align: center;
}
#clock {
  text-align: end;
}

/* Page chrome lives outside the EPUB and never changes the reading box or catches taps. */
#page-number {
  display: none;
  position: absolute;
  bottom: .15rem;
  right: calc(.7rem + var(--safe-r));
  z-index: 2;
  padding: 1px 3px;
  font: 10px/1.1 Georgia, serif;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  background: var(--bg);
  pointer-events: none;
  user-select: none;
}
body[data-flow="paginated"][data-page-number="corner"] #page-number {
  display: block;
}
body[data-flow="paginated"][data-page-number="corner"] #pct-chapter {
  /* Retain the grid cell so book percentage and clock never shift. */
  visibility: hidden;
}
body[data-flow="scrolled"] #page-number-settings {
  display: none;
}

/* ---------- next chapter ---------- */

/*
  The reward for finishing a chapter, so it is built as a destination and not a hint:
  full width inside the same reading margins #progress uses, a 52px target, and the
  --accent fill with --accent-fg text that the drawer's pill buttons wear, so the two
  read as one family rather than two unrelated widgets.

  FIXED rather than laid out in flow, for the reason recorded at #next-chapter in
  reader/index.html: an in-flow row arriving at chapter end would shrink #host and
  re-paginate the book mid-sentence, and under scrolled flow it would oscillate, because
  a shorter viewport is no longer scrolled to the bottom. Nothing in the reading area
  moves when this button appears.

  It clears #progress by .4rem instead of resting on it: #progress-rule::after is filled
  with --accent as well, and an accent pill sitting flush on an accent bar merges into
  one shape.
*/
#next-chapter {
  position: fixed;
  z-index: 5;
  left: calc(.9rem + var(--safe-l));
  right: calc(.9rem + var(--safe-r));
  bottom: calc(var(--progress-h) + var(--safe-b) + .4rem);
  display: block;
  min-height: 52px;
  /* Symmetric block padding centres the label, which a line-height could not do for a
     chapter name long enough to be ellipsised. */
  padding: .95rem 1rem;
  border: 0;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-fg);
  cursor: pointer;
  font: inherit;
  font-size: .9rem;
  font-weight: 600;
  /* A chapter name is arbitrarily long and this label carries a whole one. */
  overflow: hidden;
  text-align: center;
  text-overflow: ellipsis;
  white-space: nowrap;
  /*
    Fades rather than slides, like #scrim: it sits in the middle of the screen edge and
    has nowhere to slide in from. visibility, not opacity alone — a zero-opacity button
    is still tabbable and still hit-tests, and tabbing into an invisible control is worse
    than having no keyboard support. The parked bars and the drawer all hide this way.
  */
  opacity: 0;
  visibility: hidden;
  transition: transform var(--slide) var(--ease), opacity var(--slide) var(--ease),
    visibility 0s linear var(--slide);
}
body[data-at-chapter-end="yes"] #next-chapter {
  opacity: 1;
  visibility: visible;
  transition: transform var(--slide) var(--ease), opacity var(--slide) var(--ease),
    visibility 0s;
}
/*
  Rides up over #controls when the bars come in, exactly as #statusbar does and for the
  same reason: this row outranks the bars in z-order, so standing still would mean
  covering the page-turn arrows and reading controls. Only the transform is set here, so it
  composes with the opacity above instead of needing one rule per pair of states.
*/
body[data-chrome="shown"] #next-chapter {
  transform: translateY(calc(-1 * var(--controls-h)));
}
/* Instant on press, like every other :active in this file: the fade above is for the
   button arriving, not for a thumb. The at-chapter-end prefix is REQUIRED — without it
   the shown rule's `opacity: 1` outranks this, (1,1,1) against (1,1,0), and the press
   would have no feedback at all. */
body[data-at-chapter-end="yes"] #next-chapter:active {
  opacity: .78;
  transition: opacity 0s;
}

/* ---------- control bar ---------- */

#controls {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 4;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .5rem;
  /*
    Its own row plus the always-visible rows it must not cover. #progress outranks it in
    z-order and draws across that bottom padding, so the bar can slide a clean 100% and
    land completely out of frame.
  */
  height: calc(var(--controls-h) + var(--progress-h) + var(--safe-b));
  padding: 0 calc(.35rem + var(--safe-r)) calc(var(--progress-h) + var(--safe-b))
    calc(.35rem + var(--safe-l));
  background: var(--surface);
  border-top: 1px solid var(--rule);
  transform: translateY(100%);
  visibility: hidden;
  transition: transform var(--slide) var(--ease), visibility 0s linear var(--slide);
}
body[data-chrome="shown"] #controls {
  transform: none;
  visibility: visible;
  transition: transform var(--slide) var(--ease), visibility 0s;
}
#controls .centre {
  display: flex;
  align-items: center;
  gap: .35rem;
}
#flow {
  min-height: 44px;
  padding: 0 .7rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: var(--bg);
  color: var(--fg);
  font: inherit;
  font-size: .85rem;
}
#controls #appearance-open {
  font-family: var(--book-font);
  font-size: 1.25rem;
}

/* Six touch targets fit one row at 320px without shrinking the page-turn arrows. */
@media (max-width: 400px) {
  #controls,
  #controls .centre {
    gap: 0;
  }
  #flow {
    width: 4.2rem;
    padding-inline: .3rem;
  }
  #controls #tap-turn {
    width: 3.5rem;
    padding-inline: .2rem;
    font-size: .75rem;
    line-height: 1.15;
    white-space: normal;
  }
}

/* ---------- swipe edge ---------- */

/*
  Transparent, and 40px of it — the same 40 as EDGE_STRIP_DP in the Android activity's
  setSystemGestureExclusionRects call, so the system back gesture releases exactly this
  strip and nothing else. See #edge in reader/index.html: its first job is to outrank the
  book's iframe so the drawer swipe reaches the host document at all.

  touch-action: none is the second job, and it is the whole reason the gesture works on a
  phone. Without it Chromium reads a horizontal drag here as a pan it owns, takes the
  pointer, and fires pointercancel instead of pointerup — so the swipe reader/app.ts is
  assembling never completes. MEASURED: the identical code opened the drawer under a
  mouse on the desktop and did nothing whatsoever under a finger, because a mouse drag is
  never a candidate for browser panning and so was never stolen. The failure is therefore
  invisible on every machine a developer tests on and total on the device people read on.

  Nothing else belongs in this rule. No background, no pointer-events, no border: any of
  them turns a strip that exists only to be swiped into a strip that eats taps meant for
  the page.
*/
#edge {
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  z-index: 3;
  width: 40px;
  touch-action: none;
}

/* ---------- drawer ---------- */

#scrim {
  position: fixed;
  inset: 0;
  z-index: 6;
  background: var(--scrim);
  opacity: 0;
  visibility: hidden;
  /* The one layer that fades rather than slides: a scrim covers the whole screen, so it
     has nowhere to slide in from. */
  transition: opacity var(--slide) var(--ease), visibility 0s linear var(--slide);
}
body[data-drawer="open"] #scrim {
  opacity: 1;
  visibility: visible;
  transition: opacity var(--slide) var(--ease), visibility 0s;
}
#drawer {
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  z-index: 7;
  /*
    ONE design at every width: an overlay on the phone and on the desktop alike. 86vw so
    the page still shows at the edge and the drawer reads as temporary, capped at 360px so
    a wide window does not stretch a list of chapter titles across half the screen.
  */
  width: 86vw;
  max-width: 360px;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border-right: 1px solid var(--rule);
  padding: var(--safe-t) 0 var(--safe-b) var(--safe-l);
  transform: translateX(-100%);
  visibility: hidden;
  transition: transform var(--slide) var(--ease), visibility 0s linear var(--slide);
}
body[data-drawer="open"] #drawer {
  transform: none;
  visibility: visible;
  transition: transform var(--slide) var(--ease), visibility 0s;
}
#drawer-head {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .9rem .5rem .7rem 1.1rem;
  border-bottom: 1px solid var(--rule);
}
#drawer-title {
  flex: 1;
  min-width: 0;
  margin: 0;
  overflow: hidden;
  font-family: var(--book-font);
  font-size: 1.45rem;
  font-weight: 500;
  letter-spacing: -.01em;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#drawer-close {
  min-width: 44px;
  min-height: 44px;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--muted);
  cursor: pointer;
  font-size: 1.75rem;
  line-height: 1;
}
#drawer-close:active {
  background: var(--hover);
  color: var(--fg);
}

#tabs {
  display: flex;
  padding: 0 .6rem;
  border-bottom: 1px solid var(--rule);
}
#tabs button {
  flex: 1;
  min-height: 44px;
  padding: .5rem .25rem;
  border: 0;
  border-bottom: 2px solid transparent;
  background: none;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: .85rem;
}
#tabs button[aria-selected="true"] {
  color: var(--fg);
  border-bottom-color: var(--accent);
}
#drawer > [role="tabpanel"] {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: .4rem .6rem;
}
/* The panels are flex children, and `flex: 1` above would otherwise win against the
   hidden attribute that reader/app.ts sets — leaving a panel both unselected and on
   screen. This override is the only thing making `hidden` mean anything here. */
#drawer > [role="tabpanel"][hidden] {
  display: none;
}

/* ---------- library home and compact novel picker ---------- */

/* Home overlays the still-laid-out renderer. Never collapse #reader or #host: doing
   so would repaginate the open EPUB and discard the box its CFI was resolved in. */
#home {
  position: fixed;
  inset: 0;
  z-index: 2;
  overflow-y: auto;
  overscroll-behavior-y: contain;
  scrollbar-color: var(--rule) var(--bg);
  background: var(--bg);
}
body[data-screen="reader"] #home {
  display: none;
}
body[data-screen="library"] :is(#reader, #topbar, #controls, #statusbar, #progress, #next-chapter, #edge, #grip-left, #grip-right, #drawer, #scrim) {
  visibility: hidden;
  pointer-events: none;
  transition: none;
}
/* A real storage failure must outrank the shelf, irrespective of chrome state. */
body[data-screen="library"] #alert {
  position: relative;
  z-index: 8;
  transform: none;
  transition: none;
}
.home-content {
  width: min(100%, 1120px);
  margin-inline: auto;
  padding: calc(1.25rem + var(--safe-t)) calc(1rem + var(--safe-r))
    calc(3rem + var(--safe-b)) calc(1rem + var(--safe-l));
}
.home-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
}
#home-title {
  margin: 0;
  font: 500 clamp(2rem, 4vw, 3rem)/1.15 var(--book-font);
  letter-spacing: -.04em;
}
.home-actions {
  display: flex;
  flex-wrap: wrap;
  gap: .6rem;
}
#home #home-settings {
  display: grid;
  place-items: center;
  width: 44px;
  padding: 0;
}
:is(#home, .library-dialog) button,
#all-novels {
  min-height: 44px;
  padding: .65rem 1rem;
  border: 1px solid var(--rule);
  border-radius: 10px;
  background: var(--surface);
  color: var(--fg);
  cursor: pointer;
  font: inherit;
  font-size: .85rem;
}
:is(#home, .library-dialog) button:hover,
#all-novels:hover {
  background: var(--hover);
}
:is(#home, .library-dialog) .library-primary {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--accent-fg);
  font-weight: 650;
}
:is(#home, .library-dialog) .library-primary:hover {
  background: color-mix(in srgb, var(--accent) 88%, var(--fg));
}
:is(#home, #library, .library-dialog) button:disabled {
  opacity: .5;
  cursor: not-allowed;
}
#library-status,
.import-notice {
  margin: 1rem 0 0;
  color: var(--muted);
  font-size: .85rem;
  overflow-wrap: anywhere;
}
#library-status:empty {
  display: none;
}
#continue-section,
.home-shelf {
  margin-top: 1.5rem;
}
#home h2 {
  margin: 0;
  font: 500 1.3rem/1.4 var(--book-font);
  letter-spacing: -.015em;
}
#home #continue-title {
  margin-bottom: .65rem;
}
.shelf-heading {
  display: flex;
  align-items: baseline;
  gap: .75rem;
}
#library-count,
#catalog-count {
  color: var(--muted);
  font-size: .8rem;
  font-variant-numeric: tabular-nums;
}
.shelf-toolbar {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: end;
  gap: .65rem;
  margin: .75rem 0 1rem;
}
.shelf-toolbar > label {
  min-width: 0;
}
.library-search {
  grid-column: 1 / -1;
}
.library-field-label {
  display: block;
  margin-bottom: .4rem;
  color: var(--muted);
  font-size: .75rem;
}
.shelf-toolbar .library-field-label {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
:is(#home, .library-dialog) select,
#library-query,
#switch-query,
#catalog-query {
  display: block;
  width: 100%;
  min-width: 0;
  min-height: 44px;
  padding: .65rem .75rem;
  border: 1px solid var(--rule);
  border-radius: 10px;
  background: var(--surface);
  color: var(--fg);
  font: inherit;
  font-size: .85rem;
}
:is(#library-query, #switch-query, #catalog-query)::placeholder {
  color: var(--muted);
  opacity: 1;
}
#library-view[aria-pressed="true"] {
  border-color: var(--accent);
  color: var(--accent);
}
.downloaded-filter {
  display: flex;
  align-items: center;
  gap: .5rem;
  min-height: 44px;
  color: var(--muted);
  font-size: .8rem;
  cursor: pointer;
}
#library-downloaded,
#catalog-hide-added {
  flex: none;
  width: 18px;
  height: 18px;
  margin: 0;
  accent-color: var(--accent);
}
#shelf {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: start;
  gap: 1.5rem 1rem;
}
:is(#home, #library, #catalog-list) .book-card {
  position: relative;
  min-width: 0;
  border-radius: 12px;
}
:is(#home, #library, #catalog-list) .book-open {
  display: flex;
  flex-direction: column;
  gap: .8rem;
  width: 100%;
  min-width: 0;
  min-height: 44px;
  padding: 0 0 44px;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: var(--fg);
  cursor: pointer;
  font: inherit;
  text-align: start;
}
:is(#home, #library, #catalog-list) .book-open:hover {
  background: var(--surface);
}
:is(#home, #library, #catalog-list) .book-cover {
  display: grid;
  place-items: center;
  width: 100%;
  min-width: 0;
  aspect-ratio: 2 / 3;
  overflow: hidden;
  border: 1px solid var(--rule);
  border-radius: 9px;
  background: var(--surface);
}
:is(#home, #library, #catalog-list) .book-cover img {
  display: block;
  width: 100%;
  height: 100%;
  min-height: 0;
  object-fit: contain;
}
:is(#home, #library, #catalog-list) .cover-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  padding: 1rem .75rem;
  border-inline-start: 5px solid color-mix(in srgb, var(--accent) 35%, var(--surface));
  background: linear-gradient(145deg,
    color-mix(in srgb, var(--accent) 18%, var(--surface)), var(--surface) 75%);
  color: var(--fg);
  font: 500 clamp(.9rem, 2.5vw, 1.2rem)/1.5 var(--book-font);
  text-align: center;
  overflow-wrap: anywhere;
}
:is(#home, #library, #catalog-list) .book-copy {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: .3rem;
  min-width: 0;
  width: 100%;
}
:is(#home, #library, #catalog-list) .book-title {
  color: var(--fg);
  font: 550 .95rem/1.45 var(--book-font);
  overflow-wrap: anywhere;
}
:is(#home, #library, #catalog-list) .book-author,
.book-uploader {
  color: var(--muted);
  font-size: .78rem;
  line-height: 1.45;
  overflow-wrap: anywhere;
}
:is(#home, #library, #catalog-list) .book-progress {
  margin-top: .2rem;
  color: var(--fg);
  font-size: .75rem;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
:is(#home, #library, #catalog-list) .book-availability {
  color: var(--muted);
  font-size: .72rem;
  overflow-wrap: anywhere;
}
:is(#home, #library, #catalog-list) .book-more {
  position: absolute;
  right: 0;
  bottom: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: .5rem;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
}
:is(#home, #library, #catalog-list) .book-more:hover {
  background: var(--hover);
  color: var(--fg);
}
:is(#home, #library, #catalog-list) .book-card[data-active="true"] .book-cover {
  border-color: var(--accent);
}
#home .continue-card {
  max-width: 42rem;
  border: 1px solid var(--rule);
  background: var(--surface);
}
#home .continue-card .book-open {
  flex-direction: row;
  align-items: center;
  gap: 1rem;
  padding: .85rem 1rem;
}
#home .continue-card .book-cover {
  flex: none;
  width: clamp(64px, 16vw, 96px);
}
#home .continue-card .book-title {
  font-size: .95rem;
}
.continue-label {
  color: var(--muted);
  font-size: .68rem;
  font-weight: 650;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.continue-action {
  margin-top: .45rem;
  color: var(--accent);
  font-size: .85rem;
  font-weight: 650;
}
#home .continue-card .continue-action {
  padding-right: 32px;
}
#shelf[data-view="list"] {
  grid-template-columns: minmax(0, 1fr);
  gap: .35rem;
}
#shelf[data-view="list"] .book-card {
  border-bottom: 1px solid var(--rule);
  border-radius: 0;
}
#shelf[data-view="list"] .book-open,
#library .book-open {
  flex-direction: row;
  align-items: center;
  gap: .85rem;
  padding: .75rem 44px .75rem .5rem;
}
#shelf[data-view="list"] .book-cover {
  flex: none;
  width: 64px;
}
#shelf[data-view="list"] .book-more,
#library .book-more {
  top: 50%;
  bottom: auto;
  transform: translateY(-50%);
}
#library-empty,
#library-no-results,
#catalog-empty,
#catalog-no-results {
  padding: 2.5rem 1rem;
  border: 1px dashed var(--rule);
  border-radius: 16px;
  text-align: center;
}
#library-empty .empty-book {
  display: grid;
  place-items: center;
  width: 88px;
  height: 88px;
  margin: 0 auto 1.5rem;
  border-radius: 50%;
  background: var(--surface);
  color: var(--muted);
}
#library-empty h3 {
  margin: 0;
  font: 500 1.65rem/1.3 var(--book-font);
}
#library-empty p {
  max-width: 26rem;
  margin: .85rem auto 1.5rem;
  color: var(--muted);
  font-size: .9rem;
}
#library-empty button {
  margin: .3rem;
}
#library-no-results {
  color: var(--muted);
}
#home [hidden],
.library-dialog [hidden] {
  display: none;
}
#switch-query {
  margin: .5rem 0;
  background: var(--bg);
}
#library .book-cover {
  flex: none;
  width: 44px;
  border-radius: 4px;
}
#library .cover-fallback {
  padding: .25rem;
  border-inline-start-width: 2px;
  font-size: .55rem;
  line-height: 1.25;
}
#library .book-title {
  font-size: .85rem;
}
#library .book-copy {
  gap: .15rem;
}
#library .book-author,
#library .book-progress,
#library .book-availability {
  font-size: .7rem;
}
#library .book-card[data-active="true"] {
  background: var(--hover);
}
#library .book-open:hover {
  background: var(--hover);
}
#all-novels {
  width: 100%;
  margin: .75rem 0;
}
.library-dialog {
  position: fixed;
  inset: var(--safe-t) var(--safe-r) var(--safe-b) var(--safe-l);
  width: min(28rem, calc(100vw - var(--safe-l) - var(--safe-r) - 2rem));
  max-height: calc(100dvh - var(--safe-t) - var(--safe-b) - 2rem);
  margin: auto;
  padding: 1.5rem;
  overflow-y: auto;
  overscroll-behavior-y: contain;
}
.library-dialog h2 {
  margin: 0 0 1rem;
  font: 500 1.5rem/1.4 var(--book-font);
  overflow-wrap: anywhere;
}
.library-dialog p {
  color: var(--muted);
  font-size: .85rem;
  overflow-wrap: anywhere;
}
.library-dialog p:empty {
  display: none;
}
.library-dialog label {
  display: block;
  margin-bottom: .4rem;
  color: var(--muted);
  font-size: .8rem;
}
.library-dialog-actions {
  display: grid;
  gap: .6rem;
  margin: 1.25rem 0;
}
.library-dialog menu {
  margin: 1.5rem 0 0;
  padding-top: 1rem;
  border-top: 1px solid var(--rule);
}
#catalog {
  width: min(52rem, calc(100vw - var(--safe-l) - var(--safe-r) - 2rem));
}
.catalog-toolbar {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: center;
  gap: .65rem;
}
.library-dialog .downloaded-filter {
  display: flex;
  grid-column: auto;
  margin: 0;
}
#catalog .library-search {
  grid-column: 1 / -1;
}
#catalog-list {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: .85rem;
}
#catalog-list .book-card {
  padding: .85rem;
  border: 1px solid var(--rule);
}
#catalog-list .book-open {
  flex-direction: row;
  align-items: flex-start;
  padding: 0;
  cursor: auto;
}
#catalog-list .book-open:hover {
  background: transparent;
}
#catalog-list .book-cover {
  flex: none;
  width: 64px;
}
#catalog-list .cover-fallback {
  padding: .3rem;
  font-size: .65rem;
}
#catalog-list .book-title {
  margin: 0;
}
#catalog-list :is(.book-add, .book-unpublish) {
  width: 100%;
  margin-top: .65rem;
}
#catalog-list .book-progress {
  color: var(--accent);
}
@media (min-width: 560px) {
  .catalog-toolbar {
    grid-template-columns: minmax(0, 1fr) auto;
  }
  #catalog-list {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (min-width: 560px) {
  .home-content {
    padding-inline: calc(1.75rem + var(--safe-l)) calc(1.75rem + var(--safe-r));
  }
  #shelf {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    column-gap: 1.5rem;
  }
}
@media (min-width: 800px) {
  .home-content {
    padding-top: calc(3rem + var(--safe-t));
  }
  .shelf-toolbar {
    grid-template-columns: minmax(0, 1fr) 9rem 10rem auto;
    column-gap: .75rem;
  }
  .library-search {
    grid-column: auto;
  }
  .downloaded-filter {
    grid-column: 1 / -1;
  }
  #shelf {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}
@media (min-width: 1040px) {
  #shelf {
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }
}

/* ---------- table of contents ---------- */

/* The vendored tree widget builds bare <ol>/<li>/<a>; all of its looks are ours. */
#toc ol {
  list-style: none;
  margin: 0;
  padding: 0;
}
/*
  Block padding only. The widget writes padding-inline-start as an INLINE style to show
  nesting depth (vendor/foliate-js/ui/tree.js:23), which beats anything in this file, so
  the indent is left to it and only the vertical rhythm is set here. A `padding` shorthand
  would look like it worked and quietly lose its inline-start value on every row.
*/
#toc a,
#toc span[role="treeitem"] {
  display: block;
  min-height: 48px;
  padding-block: .8rem;
  padding-inline-end: .6rem;
  border-radius: 8px;
  color: inherit;
  cursor: pointer;
  font-family: var(--book-font);
  text-decoration: none;
}
#toc a:hover,
#toc span[role="treeitem"]:hover {
  background: var(--hover);
}
/* This is how the reader sees which chapter is open (tree.js:109 sets it), so it is
   marked three ways — fill, colour and weight — not by a background alone. */
#toc [aria-current="page"] {
  background: var(--hover);
  color: var(--accent);
  font-weight: 600;
}
#toc [role="treeitem"] svg {
  fill: currentColor;
  opacity: .55;
  margin-inline-end: .35rem;
  vertical-align: middle;
}
#toc [aria-expanded="false"] + ol {
  display: none;
}
#toc [aria-expanded="false"] svg {
  transform: rotate(-90deg);
}
#toc-empty {
  padding: .6rem;
  color: var(--muted);
}

/* ---------- search ---------- */

#search-form {
  display: flex;
  margin-bottom: .35rem;
}
#search-query {
  flex: 1;
  min-height: 44px;
  padding: .5rem .8rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: var(--bg);
  color: var(--fg);
  font: inherit;
}
#search-status {
  margin: .35rem .2rem;
  color: var(--muted);
  font-size: .8rem;
}
#search-results .chapter {
  margin: .8rem .2rem .2rem;
  color: var(--muted);
  font-size: .72rem;
  letter-spacing: .08em;
  text-transform: uppercase;
}
#search-results button {
  display: block;
  width: 100%;
  min-height: 48px;
  padding: .6rem;
  border: 0;
  border-radius: 8px;
  background: none;
  color: inherit;
  cursor: pointer;
  font: inherit;
  font-family: var(--book-font);
  font-size: .88rem;
  text-align: left;
}
#search-results button:hover {
  background: var(--hover);
}
#search-results .match {
  color: var(--accent);
  font-weight: 700;
}

/* ---------- drawer foot ---------- */

#drawer-foot {
  display: flex;
  gap: .6rem;
  padding: .7rem 1rem 1rem;
  border-top: 1px solid var(--rule);
}
#drawer-foot button {
  flex: 1;
  min-height: 44px;
  border: 0;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-fg);
  cursor: pointer;
  font: inherit;
  font-size: .9rem;
  font-weight: 600;
}
#drawer-foot button:active {
  opacity: .78;
}

/* ---------- dialogs ---------- */

dialog {
  max-width: 34rem;
  background: var(--surface);
  color: var(--fg);
  border: 1px solid var(--rule);
  border-radius: 12px;
}
dialog::backdrop {
  background: var(--scrim);
}
dialog menu {
  display: flex;
  gap: .5rem;
  justify-content: flex-end;
  padding: 0;
}
dialog menu button {
  min-height: 44px;
  padding: 0 .9rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: none;
  color: var(--fg);
  cursor: pointer;
  font: inherit;
}
#conflict-yes {
  border-color: transparent;
  background: var(--accent);
  color: var(--accent-fg);
  font-weight: 600;
}

/* The modal is an overlay: changing its controls never resizes the reading viewport.
   Only [open] gets a layout display, preserving native dialog hiding and focus semantics. */
#appearance {
  position: fixed;
  inset: auto 0 0;
  width: 100%;
  max-width: 32.5rem;
  max-height: min(78dvh, calc(100dvh - var(--safe-t) - 4rem));
  margin: 0 auto;
  padding: 0;
  overflow: hidden;
  border-bottom: 0;
  border-radius: 22px 22px 0 0;
}
#appearance[open] {
  display: flex;
  flex-direction: column;
}
.appearance-head {
  display: flex;
  flex: none;
  align-items: center;
  justify-content: space-between;
  gap: .5rem;
  padding: .6rem max(1rem, var(--safe-r)) .6rem max(1rem, var(--safe-l));
  border-bottom: 1px solid var(--rule);
}
#appearance-title {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 650;
}
#appearance-close {
  flex: none;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--fg);
  cursor: pointer;
  font: 1.65rem/1 var(--ui-font);
}
#appearance-close:active,
#appearance-reset:active {
  background: var(--hover);
}
.appearance-body {
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: .8rem max(1rem, var(--safe-r)) calc(1rem + var(--safe-b))
    max(1rem, var(--safe-l));
  scrollbar-gutter: stable;
}
.appearance-hint {
  margin: 0 0 1.2rem;
  color: var(--muted);
  font-size: .8rem;
}
.appearance-group {
  min-width: 0;
  margin: 0 0 1.2rem;
  padding: 0;
  border: 0;
}
.appearance-group legend,
.appearance-range-heading label {
  padding: 0;
  font-size: .9rem;
  font-weight: 600;
}
.appearance-group legend {
  margin-bottom: .6rem;
}
.theme-options,
.font-options,
.alignment-options {
  display: grid;
  gap: .5rem;
}
.theme-options {
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: .3rem;
}
.font-options {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.alignment-options {
  grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
}
.appearance-choice {
  display: flex;
  align-items: center;
  gap: .5rem;
  min-width: 0;
  min-height: 48px;
  padding: .6rem;
  border: 1px solid var(--rule);
  border-radius: 12px;
  background: var(--bg);
  cursor: pointer;
  font-size: .85rem;
}
.appearance-choice:has(input:checked) {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.appearance-choice:has(input:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
.appearance-choice input[type="radio"] {
  flex: none;
  width: 16px;
  height: 16px;
  margin: 0;
  accent-color: var(--accent);
}
.theme-choice {
  flex-direction: column;
  gap: .35rem;
  padding: .45rem .15rem;
  font-size: .7rem;
}
.theme-swatch {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border: 1px solid var(--rule);
  border-radius: 50%;
  background: var(--bg);
  color: var(--fg);
  font: 1rem var(--book-font);
}
.theme-system {
  border-style: dashed;
  background: linear-gradient(135deg, var(--bg) 50%, var(--hover) 50%);
}
.font-choice {
  min-height: 56px;
  font-size: .95rem;
}
.font-choice[data-font="serif"] {
  font-family: var(--book-font);
}
.font-choice[data-font="sans"] {
  font-family: system-ui, "Noto Sans", Arial, sans-serif;
}
.font-choice[data-font="mono"] {
  font-family: ui-monospace, "Noto Sans Mono", Consolas, monospace;
}
.appearance-range {
  margin-bottom: 1rem;
}
.appearance-range-heading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .75rem;
}
.appearance-number {
  display: inline-flex;
  flex: none;
  align-items: baseline;
  gap: .3rem;
  color: var(--accent);
  font-size: .85rem;
  font-variant-numeric: tabular-nums;
}
/* The exact value, editable: type past the slider max instead of dragging to it.
   44px tall like every other touch target here; spinners kept, they are useful. */
.appearance-number input[type="number"] {
  width: 4.5rem;
  min-height: 44px;
  padding: 0 .45rem;
  border: 1px solid var(--rule);
  border-radius: 8px;
  background: var(--bg);
  color: var(--fg);
  font: inherit;
  text-align: end;
}
.appearance-number input[type="number"]:focus-visible {
  border-color: var(--accent);
  outline: none;
}
.appearance-range input[type="range"] {
  appearance: none;
  display: block;
  width: 100%;
  height: 44px;
  margin: .15rem 0 0;
  padding: 0;
  background: transparent;
  cursor: pointer;
  accent-color: var(--accent);
}
.appearance-range input[type="range"]::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 999px;
  background: linear-gradient(
    to right,
    var(--accent) var(--fill, 0%),
    var(--rule) var(--fill, 0%)
  );
}
/* The thumb's transparent border is part of its 44px hit area, not a larger drawing. */
.appearance-range input[type="range"]::-webkit-slider-thumb {
  appearance: none;
  box-sizing: border-box;
  width: 44px;
  height: 44px;
  margin-top: -20px;
  border: 10px solid transparent;
  border-radius: 50%;
  background: var(--accent);
  background-clip: padding-box;
}
.appearance-range input[type="range"]::-moz-range-track {
  height: 4px;
  border-radius: 999px;
  background: var(--rule);
}
.appearance-range input[type="range"]::-moz-range-progress {
  height: 4px;
  border-radius: 999px;
  background: var(--accent);
}
.appearance-range input[type="range"]::-moz-range-thumb {
  box-sizing: border-box;
  width: 44px;
  height: 44px;
  border: 10px solid transparent;
  border-radius: 50%;
  background: var(--accent);
  background-clip: padding-box;
}
#appearance-message {
  margin: 0 0 .75rem;
  font-size: .85rem;
}
#appearance-message:empty {
  display: none;
}
#appearance-reset {
  min-height: 44px;
  padding: 0 1rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: none;
  color: var(--fg);
  cursor: pointer;
  font: inherit;
  font-size: .85rem;
}
@media (min-width: 600px) and (min-height: 600px) {
  #appearance {
    inset: 0;
    width: calc(100% - 3rem);
    max-height: min(48rem, calc(100dvh - var(--safe-t) - var(--safe-b) - 3rem));
    margin: auto;
    border-bottom: 1px solid var(--rule);
    border-radius: 22px;
  }
}
/*
  Sign-in. Comfortably reachable on a phone rather than compact: this is the first thing a
  new device shows, and it is the only place in the app where anything is typed. 44px is
  the minimum touch target used throughout this file, and the width is capped so the
  fields do not stretch across a desktop window.
*/
#login {
  width: min(26rem, calc(100vw - 2rem));
  padding: 1.25rem;
  max-height: calc(100dvh - var(--safe-t) - var(--safe-b) - 2rem);
  overflow-y: auto;
  overscroll-behavior-y: contain;
}
#login h2 {
  margin: 0 0 .35rem;
  font-size: 1.15rem;
}
#login-intro {
  margin: 0 0 1.1rem;
  color: var(--muted);
  font-size: .85rem;
  line-height: 1.45;
}
#login label {
  display: block;
  margin-bottom: .3rem;
  color: var(--muted);
  font-size: .78rem;
  letter-spacing: .02em;
  text-transform: uppercase;
}
#login input:not([type="checkbox"]) {
  width: 100%;
  min-height: 44px;
  margin-bottom: .9rem;
  padding: 0 .7rem;
  border: 1px solid var(--rule);
  border-radius: 8px;
  background: var(--bg);
  color: var(--fg);
  font: inherit;
}
#login input:focus-visible {
  border-color: var(--accent);
  outline: none;
}
#login input:disabled {
  opacity: .55;
}
#login .downloaded-filter {
  display: flex;
  margin-bottom: .9rem;
  text-transform: none;
  letter-spacing: normal;
}
#login-register {
  flex: none;
  width: 18px;
  height: 18px;
  margin: 0;
  accent-color: var(--accent);
}
#login [hidden] {
  display: none;
}
/*
  role="alert" and hidden until there is something to say. Every rejection the sign-in can
  produce lands here — a malformed URL, a colon in the username, a 401, an unreachable
  host — because the dialog stays open on failure and this line is the only report.
*/
#login-error {
  margin: 0 0 .9rem;
  padding: .55rem .7rem;
  border-radius: 8px;
  background: var(--notice-bg);
  color: var(--notice-fg);
  font-size: .85rem;
  line-height: 1.4;
}
#login-error[hidden] {
  display: none;
}
#login-submit {
  border-color: transparent;
  background: var(--accent);
  color: var(--accent-fg);
  font-weight: 600;
}
#login-submit:disabled {
  opacity: .6;
}

/*
  Every transition declared in this file is listed here. The bars are meant to slide
  rather than pop, but "meant to" is not a reason to move the screen for somebody who
  asked the system not to. Killing the transition also makes the visibility flip
  immediate, which is exactly right: no slide to stay visible for.
*/
@media (prefers-reduced-motion: reduce) {
  #alert,
  #topbar,
  body[data-chrome="shown"] #topbar,
  #statusbar,
  #progress,
  #controls,
  body[data-chrome="shown"] #controls,
  #next-chapter,
  body[data-at-chapter-end="yes"] #next-chapter,
  body[data-at-chapter-end="yes"] #next-chapter:active,
  #scrim,
  body[data-drawer="open"] #scrim,
  #drawer,
  body[data-drawer="open"] #drawer {
    transition: none;
  }
}

#sync-history-list {
  display: grid;
  gap: 10px;
  margin-block: 16px;
}
.sync-history-entry {
  display: grid;
  gap: 6px;
  text-align: start;
  width: 100%;
  padding: 14px;
  white-space: normal;
}
.sync-history-entry span {
  font-size: 0.85em;
  opacity: 0.8;
}
