/* Runo — the persistent player.
 *
 * Lives outside <main>, so it survives the soft navigation that swaps the
 * page. It is the one piece of the UI a listener touches with the screen
 * off in one hand, which is why every control here is a full touch target
 * and the transport row gets its own line on a phone.
 *
 * **Four shapes, one set of markup** (KAN-66, and the phone pair added in
 * the mobile pass):
 *
 *   .mode-window  a floating, draggable, resizable window (desktop)
 *   .mode-min     a strip in #player-dock under the topbar (desktop)
 *   .mode-mini    one row above the bottom nav (phone)
 *   .mode-sheet   the whole screen, Now Playing (phone)
 *
 * **Exactly one mode class is on the element at a time**, chosen by
 * `applyWindow()` in player.js, and every rule below keys off one of them.
 * The first version left `mode-window` on at phone width and relied on the
 * media query to override what mattered — but `.mode-window .panel-tab`
 * outranks `.panel-tab`, so the reading pane grew unbounded on a phone and
 * pushed the tabs off the top of the screen. A class that names the *shape*
 * cannot leak across layouts the way one that names the device does.
 *
 * The markup is not duplicated per shape. A second copy of the title or the
 * transport would be a second thing to keep in step with the audio, which is
 * the bug the one-file-per-chapter rewrite existed to remove. `.pb-controls`
 * places its four children by named grid areas, once per mode, which is what
 * makes that possible.
 *
 * Geometry comes from --pb-x/y/w/h, written by player.js and persisted; CSS
 * owns what the shapes *look* like and JS owns where the window *is*.
 */

@layer components {
  /* The parking space. Sticky in normal flow, so the minimized strip
   * reserves its own height instead of covering the page and still follows
   * you down it. Zero-height when the player is closed or floating — a
   * `position: fixed` child leaves nothing in flow. Below the topbar's
   * z-index: they never overlap, and if they ever do the topbar wins. */
  #player-dock {
    position: sticky;
    top: var(--topbar-h);
    z-index: 29;
  }

  /* Clears the fixed bar. The bottom nav's own offset is in layout.css;
   * with the player up, the player is the lower of the two. Only the phone
   * layout needs it — a window floats over the page and the minimized strip
   * takes its space from the dock. */
  #player-root {
    display: flex;
    flex-direction: column;
    min-height: 0;
  }

  /* --- the first-run tip -------------------------------------------------
   * The player does not appear on its own any more, so the one control that
   * summons it needs introducing exactly once. Anchored under the top bar
   * rather than centred as a modal: a tip that blocks the page to explain a
   * button is worse than the button being briefly unexplained.
   */
  .player-tip {
    position: fixed;
    top: calc(var(--topbar-h) + var(--space-2));
    right: var(--space-4);
    z-index: 55;
    width: min(22rem, calc(100vw - 2 * var(--space-4)));
    padding: var(--space-4);
    background: var(--surface-raised);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
  }
  /* The pointer at the control it is talking about. */
  .player-tip::before {
    content: "";
    position: absolute;
    top: -6px;
    right: 3.25rem;
    width: 11px;
    height: 11px;
    background: var(--surface-raised);
    border-top: 1px solid var(--border-strong);
    border-left: 1px solid var(--border-strong);
    rotate: 45deg;
  }
  .player-tip-title {
    margin-bottom: var(--space-2);
    font-weight: 650;
  }
  .player-tip-body {
    margin-bottom: var(--space-3);
    color: var(--text-muted);
    font-size: var(--text-sm);
    line-height: var(--leading-normal);
  }

  /* --- the window ------------------------------------------------------- */
  #player-root.mode-window {
    position: fixed;
    left: var(--pb-x, 50%);
    top: var(--pb-y, 20%);
    width: var(--pb-w, 34rem);
    height: var(--pb-h, 30rem);
    z-index: 50;
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
  }

  /* While a pointer is dragging or resizing: no transitions to fight the
   * pointer, and no text selection anywhere on the page. */
  body.pb-dragging { user-select: none; cursor: grabbing; }
  body.pb-dragging #player-root { transition: none; }

  #pb-titlebar {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex: none;
    padding: var(--space-1) var(--space-2) var(--space-1) var(--space-3);
    background: var(--surface-raised);
    border-bottom: 1px solid var(--border);
    cursor: grab;
  }
  body.pb-dragging #pb-titlebar { cursor: grabbing; }

  /* The grip: three short rules, drawn rather than typed, so it cannot land
   * as a tofu box on a system without the braille block. */
  .pb-grip {
    flex: none;
    width: 1.1rem;
    height: 0.55rem;
    background: repeating-linear-gradient(
      to bottom,
      var(--text-faint) 0 1px,
      transparent 1px 3px);
    border-radius: 1px;
  }

  .pb-window-title {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--text-muted);
    font-size: var(--text-xs);
  }

  /* Window chrome is smaller than a transport control: it is aimed with a
   * mouse, never a thumb, and it lives on a strip a thumb never reaches. */
  .pb-chrome {
    width: 1.75rem;
    height: 1.75rem;
    color: var(--text-muted);
    font-size: var(--text-md);
  }
  .pb-chrome:hover { color: var(--text); }

  /* Restore belongs to the minimized strip; the window has minimize. */
  #pb-restore { display: none; }
  /* The panel is always open in a window — collapsing it *is* minimizing,
   * and two ways to make the player small is one too many. */
  .mode-window #pb-expand { display: none; }

  /* --- the window body: rail on the left, content on the right ---------- */
  /* The first draft put the bottom bar inside a window frame, which is what
   * it looked like: a tab row with the voice and sleep controls adrift at
   * the end of it, and the mobile transport underneath. A window gets a
   * navigation rail, a content area, and a footer that belongs to it. */
  .mode-window #player-body {
    display: grid;
    grid-template-columns: minmax(7rem, 9.5rem) minmax(0, 1fr);
    flex: 1;
    min-height: 0;
  }

  #pb-rail {
    display: flex;
    min-width: 0;
    gap: var(--space-1);
  }
  .mode-window #pb-rail {
    flex-direction: column;
    padding: var(--space-3) var(--space-2);
    background: var(--surface-sunken);
    border-right: 1px solid var(--border);
    overflow-y: auto;
  }
  /* In the rail a tab is a row, not a column heading: left-aligned, marked
   * down its leading edge rather than underlined. */
  .mode-window .tab-btn {
    border-bottom: none;
    border-inline-start: 2px solid transparent;
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    text-align: start;
  }
  .mode-window .tab-btn.active {
    background: var(--surface-raised);
    border-inline-start-color: var(--accent);
  }
  /* The spacer sinks voice and sleep to the foot of the rail, where a
   * setting belongs — out of the way of the three things you click. */
  .mode-window .voice-label,
  .mode-window .sleep-label {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-1);
    padding: var(--space-2) var(--space-1) 0;
    border-top: 1px solid var(--border);
  }
  .mode-window .sleep-label { border-top: none; padding-top: 0; }
  .mode-window .voice-label select,
  .mode-window .sleep-label select { max-width: none; width: 100%; }

  /* --- the minimized strip ---------------------------------------------- */
  #player-root.mode-min {
    background: color-mix(in oklab, var(--surface) 94%, transparent);
    backdrop-filter: blur(12px);
    border-bottom: 1px solid var(--border);
    box-shadow: var(--shadow-sm);
  }
  .mode-min #pb-titlebar,
  .mode-min #player-body,
  .mode-min .pb-resize,
  .mode-min #pb-expand,
  .mode-min #pb-bookmark { display: none; }
  .mode-min #pb-restore { display: inline-grid; }
  /* Chapter stepping and ±10 s go; play, the clock and the seek bar stay.
   * A strip is for keeping your place, not for working the book. */
  .mode-min #pb-prev,
  .mode-min #pb-next,
  .mode-min #pb-back,
  .mode-min #pb-fwd,
  .mode-min #pb-stop { display: none; }
  .mode-min .pb-play { width: var(--tap-min); height: var(--tap-min); font-size: 1.05rem; }
  /* The strip has no title bar, so its close stays — and the bookmark's
     label would not earn its width in a one-line strip. */
  .mode-min .pb-labelled span { display: none; }
  /* Play, then what is playing, then the clock — with the seek as a full
   * width rule above them. The bar's own grid puts its flexible column
   * under the transport, which is nearly empty here, so it took all the
   * slack and left the title crushed against the play button. */
  .mode-min .pb-controls {
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas:
      "seek      seek   seek"
      "transport title  utility";
    gap: 0 var(--space-3);
    padding-block: 0 var(--space-1);
  }
  .mode-min .pb-transport { grid-area: transport; }
  .mode-min .pb-title { grid-area: title; cursor: default; }
  .mode-min .pb-utility { grid-area: utility; }
  .mode-min #pb-seek { grid-area: seek; }
  /* The bar tucks its slider up under the panel above it; the strip has the
   * topbar up there instead, and the negative margin drew the progress line
   * across it — and clipped the thumb against it. */
  .mode-min #pb-seek { margin: 2px 0 0; }
  /* Restore before close: it is the one people reach for, and putting the
   * destructive control last is the same order every window manager uses.
   * `order` rather than moving the markup, which the bar shares. */
  .mode-min #pb-restore { order: 0; }
  .mode-min #pb-close-bar { order: 1; }

  #player-bar {
    flex: none;
    background: color-mix(in oklab, var(--surface) 92%, transparent);
  }
  /* The bar is the whole thing on a phone and the foot of the window on a
   * desktop; only the phone wants a rule above it. */
  .mode-min #player-bar { background: none; }

  #pb-seek {
    display: block;
    width: 100%;
    height: 1.1rem;
    margin: -0.5rem 0 -0.2rem;
    accent-color: var(--accent);
    background: transparent;
    cursor: pointer;
  }

  .pb-controls {
    display: grid;
    grid-template-columns: auto minmax(8rem, 1fr) auto auto;
    align-items: center;
    gap: var(--space-3);
    max-width: var(--content-max);
    margin-inline: auto;
    padding: var(--space-2) var(--space-4) var(--space-3);
  }

  /* The window's footer: what is playing and the clock on one line, the
   * seek under them, the transport under that. Named areas rather than
   * source order, because the same four children make a phone's bar and a
   * minimized strip out of the same markup. */
  .mode-window .pb-controls {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "title  utility"
      "seek   seek"
      "transport transport";
    gap: var(--space-1) var(--space-3);
    max-width: none;
    padding: var(--space-2) var(--space-4) var(--space-3);
  }
  .mode-window .pb-title { grid-area: title; cursor: grab; }
  .mode-window .pb-utility { grid-area: utility; }
  .mode-window #pb-seek { grid-area: seek; }
  .mode-window .pb-transport {
    grid-area: transport;
    justify-self: center;
    gap: var(--space-2);
  }

  .pb-btn {
    display: inline-grid;
    place-items: center;
    flex: none;
    width: var(--tap-min);
    height: var(--tap-min);
    padding: 0;
    background: transparent;
    color: var(--text);
    border: none;
    border-radius: var(--radius-md);
    font-size: 1.05rem;
    line-height: 1;
    cursor: pointer;
    transition: background var(--duration-fast) var(--ease);
  }
  .pb-btn:hover { background: var(--surface-raised); }

  /* A transport button that says what it does. `.pb-btn` is a fixed square
   * because everything else in the row is a glyph; this one carries a word,
   * so it takes the width it needs and keeps the row's height. */
  .pb-labelled {
    width: auto;
    grid-auto-flow: column;
    gap: var(--space-2);
    padding-inline: var(--space-3);
    color: var(--text-muted);
    font-size: var(--text-sm);
  }
  .pb-labelled:hover { color: var(--text); }

  /* The window's own close lives in the title bar, where a window's close
   * belongs. A second one down beside the bookmark was just clutter. */
  .mode-window #pb-close-bar { display: none; }

  .pb-play {
    width: 3.25rem;
    height: 3.25rem;
    background: var(--accent);
    color: var(--accent-contrast);
    border-radius: var(--radius-full);
    font-size: 1.25rem;
  }
  .pb-play:hover { background: var(--accent-hover); }

  .pb-transport,
  .pb-utility {
    display: flex;
    align-items: center;
    gap: var(--space-1);
  }

  .pb-title {
    min-width: 0;
    overflow: hidden;
    cursor: pointer;
  }
  .pb-book,
  .pb-chapter {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .pb-book { color: var(--text-muted); font-size: var(--text-xs); }
  .pb-chapter { font-weight: 600; font-size: var(--text-md); }

  .pb-time {
    color: var(--text-muted);
    font-size: var(--text-sm);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
  }

  /* A chapter whose audio was thrown away by a re-narration: the transport
   * stays visible so the player does not appear to have crashed, but it is
   * plainly not going to play. */
  .mode-window .pb-chapter.is-gone,
  .mode-min .pb-chapter.is-gone { color: var(--text-faint); }

  /* --- resizing --------------------------------------------------------- */
  /* Eight strips rather than one corner grip. A player parks at the bottom
   * of the screen, where growing downwards has nowhere to go — being able
   * to take the top edge up is the difference between resizable and
   * theoretically resizable. */
  .pb-resize { position: absolute; z-index: 2; }
  .mode-min .pb-resize { display: none; }
  .pb-resize[data-resize="n"] { inset: 0 0.6rem auto; height: 0.4rem; cursor: ns-resize; }
  .pb-resize[data-resize="s"] { inset: auto 0.6rem 0; height: 0.4rem; cursor: ns-resize; }
  .pb-resize[data-resize="w"] { inset: 0.6rem auto 0.6rem 0; width: 0.4rem; cursor: ew-resize; }
  .pb-resize[data-resize="e"] { inset: 0.6rem 0 0.6rem auto; width: 0.4rem; cursor: ew-resize; }
  .pb-resize[data-resize="nw"] { inset: 0 auto auto 0; width: 0.7rem; height: 0.7rem; cursor: nwse-resize; }
  .pb-resize[data-resize="ne"] { inset: 0 0 auto auto; width: 0.7rem; height: 0.7rem; cursor: nesw-resize; }
  .pb-resize[data-resize="sw"] { inset: auto auto 0 0; width: 0.7rem; height: 0.7rem; cursor: nesw-resize; }
  .pb-resize[data-resize="se"] { inset: auto 0 0 auto; width: 0.7rem; height: 0.7rem; cursor: nwse-resize; }

  /* --- expandable panel ------------------------------------------------ */
  #player-panel {
    display: flex;
    flex-direction: column;
    min-height: 0;
    max-width: var(--content-max);
    margin-inline: auto;
    background: var(--surface);
    border: 1px solid var(--border);
    border-bottom: none;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    box-shadow: var(--shadow-lg);
  }
  /* Inside a window the panel is the content column beside the rail: it
   * fills what the window's height leaves and grows the scroller rather
   * than the window. */
  .mode-window #player-panel {
    min-width: 0;
    /* The base rule centres the panel with `margin-inline: auto`, which in
     * a flex or grid parent absorbs the free space and shrink-wraps the
     * item instead of filling it — the sheet's chapter list came out 198px
     * wide, centred, in a 390px sheet. */
    margin-inline: 0;
    border: none;
    border-radius: 0;
    box-shadow: none;
    max-width: none;
  }

  .tab-btn {
    padding: var(--space-2) var(--space-3);
    background: none;
    color: var(--text-muted);
    border: none;
    border-bottom: 2px solid transparent;
    font-size: var(--text-sm);
    font-weight: 550;
    cursor: pointer;
  }
  .tab-btn:hover { color: var(--text); }
  .tab-btn.active { color: var(--text); border-bottom-color: var(--accent); }

  .panel-spacer { flex: 1; }

  .voice-label,
  .sleep-label {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding-bottom: var(--space-1);
    color: var(--text-muted);
    font-size: var(--text-sm);
  }
  .voice-label select,
  .sleep-label select {
    max-width: 10rem;
    padding: var(--space-1) var(--space-2);
    background: var(--surface-sunken);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    font-size: var(--text-sm);
  }
  /* Unavailable voices stay visible but disabled — a list whose contents
     change as you move through a book is disorienting, and the greyed entry
     explains the absence before you walk into it. */
  .voice-label select option:disabled { color: var(--text-faint); font-style: italic; }

  .panel-tab {
    max-height: 45vh;
    overflow-y: auto;
    padding: var(--space-2) var(--space-3);
  }
  /* In a window the height is the user's choice, so the tab takes what is
     left rather than a fraction of the viewport. */
  .mode-window .panel-tab { max-height: none; flex: 1; min-height: 0; }
  .panel-empty { padding: var(--space-3); color: var(--text-muted); font-size: var(--text-sm); }

  .chapter-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    width: 100%;
    min-height: var(--tap-min);
    padding: var(--space-2) var(--space-3);
    background: none;
    color: var(--text);
    border: none;
    border-radius: var(--radius-md);
    font-size: var(--text-sm);
    text-align: left;
    cursor: pointer;
  }
  .chapter-row:hover { background: var(--surface-raised); }
  .chapter-row.current { color: var(--accent); font-weight: 600; }
  .chapter-row.unavailable { color: var(--text-faint); cursor: default; }
  .chapter-row.unavailable:hover { background: none; }
  .chapter-dur {
    color: var(--text-muted);
    font-size: var(--text-xs);
    font-variant-numeric: tabular-nums;
  }

  .bookmark-row {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-1) var(--space-2);
  }
  .bookmark-jump {
    min-height: var(--tap-min);
    background: none;
    color: var(--accent);
    border: none;
    font-size: var(--text-sm);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    text-align: left;
    cursor: pointer;
  }
  .bookmark-note {
    flex: 1;
    min-width: 0;
    padding: var(--space-2);
    background: var(--surface-sunken);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    font-size: var(--text-sm);
  }
  .bookmark-del { color: var(--text-muted); font-size: var(--text-sm); }

  /* --- follow-along text ------------------------------------------------
   * The pane shows the author's text, sliced where the narrated units fall
   * (`chunking.unit_spans`, served by /follow). It used to render the VTT
   * cues, which are the units as the *model* received them — one flat run
   * with stops added at bare line endings — and that was tolerable while it
   * was a dim grey strip. Blurring everything but the line being read makes
   * it the surface people look straight at, so it has to be the book.
   */
  .panel-tab-text {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    overflow: hidden;  /* the scroller is #follow-text, below */
  }

  /* The compact row is the phone's; a desktop window shows the settings
   * inline because it has the width for them. */
  .reader-bar { display: none; }
  /* Immersive reading exists only in the phone's sheet, so its way out does
   * too. This hide has to be **outside** the phone media query: with it
   * inside, nothing hid the button on a desktop, where it rendered as a
   * two-line lump at the foot of the text and did nothing when clicked —
   * `.reading` is a state only the sheet ever enters (Toni, 2026-08-11). */
  .reading-exit { display: none; }
  .reader-tools {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex: none;
    flex-wrap: wrap;
    color: var(--text-muted);
    font-size: var(--text-xs);
  }
  .reader-group { display: inline-flex; align-items: center; gap: var(--space-1); }
  .reader-label { padding-inline-end: var(--space-1); }
  .reader-step {
    width: 1.9rem;
    height: 1.9rem;
    color: var(--text-muted);
    border: 1px solid var(--border);
    font-size: var(--text-xs);
  }
  .reader-step:hover { color: var(--text); }
  .reader-step:disabled { opacity: 0.4; cursor: default; }
  .reader-toggle {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    cursor: pointer;
  }
  .reader-toggle input { accent-color: var(--accent); }

  #follow-text {
    position: relative;  /* offsetTop of units is pane-relative for auto-scroll */
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    /* The author's line breaks and blank lines are the point, and the spans
     * tile the text exactly — so the whitespace between them must survive
     * to the page rather than be collapsed by the default rules. */
    white-space: pre-wrap;
    font-size: var(--reader-pane, 0.9375rem);
    line-height: var(--leading-prose);
  }
  /* **Emphasis is clarity, not size** (Toni, 2026-08-11, on seeing it).
   * A narration unit is up to four sentences and routinely spans several
   * paragraphs, beginning and ending mid-line — so a font-size step on the
   * active one re-wraps the text around a ragged edge that cuts across
   * paragraph boundaries. It read as chaos. Blur and brightness say the
   * same thing and move nothing: with `--reader-lift` at its default of 0
   * the active unit is the same size as the page, so no line in the pane
   * ever re-wraps as the narration advances. The lift is kept as an opt-in
   * control for anyone who wants the size step back. */
  .follow-unit {
    color: var(--text-muted);
    cursor: pointer;
    transition: color var(--duration-base) var(--ease);
  }
  .follow-unit:hover { color: var(--text); }
  .follow-unit.active {
    color: var(--text);
    font-size: calc(var(--reader-pane, 0.9375rem) + var(--reader-lift, 0rem));
  }

  /* On by default: with the size step gone this *is* the emphasis, and the
   * clear line reads as deliberate rather than as a broken page. */
  .reader-blur .follow-unit { filter: blur(2px); opacity: 0.5; }
  .reader-blur .follow-unit.active { filter: none; opacity: 1; }
  .reader-blur .follow-unit:hover { filter: blur(0.8px); opacity: 0.85; }

  .reader-ease .follow-unit {
    transition: color var(--duration-base) var(--ease),
                filter var(--duration-base) var(--ease),
                opacity var(--duration-base) var(--ease),
                font-size var(--duration-base) var(--ease);
  }

  /* --- phone -------------------------------------------------------------
   *
   * Two shapes, and neither is the desktop's. A **mini** bar of one row
   * sits above the bottom navigation and costs the page almost nothing; a
   * **sheet** takes the whole screen when you want the book. That is what
   * every audio app on a phone does, and the reason is measurable: the old
   * bar was 191px of an 844px screen doing nothing, and its half-height
   * panel showed five chapters of a hundred.
   *
   * Nothing here is allowed to be a small target. This is the surface used
   * one-handed with the screen half-off.
   */
  @media (max-width: 48rem) {
    #player-dock { position: static; }

    /* --- mini ------------------------------------------------------------ */
    .mode-mini {
      position: fixed;
      inset: auto 0 calc(var(--mobile-nav-h) + env(safe-area-inset-bottom)) 0;
      z-index: 26;                      /* over the page, under the sheet */
      height: var(--mini-player-h);
      background: color-mix(in oklab, var(--surface-raised) 96%, transparent);
      backdrop-filter: blur(12px);
      border-top: 1px solid var(--border);
      box-shadow: var(--shadow-md);
      overflow: hidden;
    }
    .mode-mini #pb-titlebar,
    .mode-mini #player-body,
    .mode-mini .pb-resize,
    .mode-mini #pb-restore,
    .mode-mini #pb-expand,
    .mode-mini #pb-bookmark,
    .mode-mini #pb-prev,
    .mode-mini #pb-next,
    .mode-mini #pb-back,
    .mode-mini #pb-fwd,
    .mode-mini #pb-stop,
    .mode-mini .pb-time { display: none; }

    .mode-mini #player-bar { background: none; height: 100%; }
    /* Play, what is playing, and a way out. Closing pauses and puts the
     * player away; the topbar's control brings it back, exactly as on a
     * desktop. Without it the bar could only ever be maximised, never
     * dismissed (Toni, 2026-08-11). */
    .mode-mini .pb-controls {
      grid-template-columns: auto minmax(0, 1fr) auto;
      grid-template-areas: "transport title close";
      align-items: center;
      gap: var(--space-3);
      height: 100%;
      max-width: none;
      padding: 0 var(--space-4) 0 var(--space-3);
    }
    .mode-mini .pb-transport { grid-area: transport; }
    /* `display: contents` so the close can be placed in the grid on its
       own, without a second copy of the button in the markup. */
    .mode-mini .pb-utility { display: contents; }
    .mode-mini #pb-close-bar {
      grid-area: close;
      width: var(--tap-min);
      height: var(--tap-min);
      color: var(--text-muted);
    }
    .mode-mini .pb-play { width: var(--tap-min); height: var(--tap-min); font-size: 1.05rem; }
    /* The whole bar opens the sheet, so the title is a control, not a
       caption — and it is the biggest tap target on the screen. */
    .mode-mini .pb-title {
      grid-area: title;
      align-self: stretch;
      display: flex;
      flex-direction: column;
      justify-content: center;
      cursor: pointer;
    }
    /* Progress as a hairline across the top: the real seek needs a thumb's
       worth of room, and it gets it in the sheet. Inert here so a tap on
       the bar opens the sheet rather than scrubbing by accident. */
    .mode-mini #pb-seek {
      position: absolute;
      inset: 0 0 auto 0;
      height: 3px;
      margin: 0;
      padding: 0;
      pointer-events: none;
      appearance: none;
      background: var(--border);
      border-radius: 0;
    }
    .mode-mini #pb-seek::-webkit-slider-thumb { appearance: none; width: 0; height: 0; }
    .mode-mini #pb-seek::-moz-range-thumb { width: 0; height: 0; border: none; }
    .mode-mini #pb-seek::-webkit-slider-runnable-track { height: 3px; background: transparent; }
    .mode-mini #pb-seek::-moz-range-progress { height: 3px; background: var(--accent); }
    .mode-mini #pb-seek::-moz-range-track { height: 3px; background: var(--border); }

    /* --- sheet ------------------------------------------------------------ */
    .mode-sheet {
      position: fixed;
      inset: 0;
      z-index: 60;                      /* over the bottom nav as well */
      display: flex;
      flex-direction: column;
      height: 100dvh;
      background: var(--bg);
      padding-bottom: env(safe-area-inset-bottom);
      overflow: hidden;
      transform: translateY(var(--sheet-drag, 0px));
    }
    /* The page behind a full-screen sheet must not scroll under it. */
    body.sheet-open { overflow: hidden; }

    .mode-sheet .pb-resize,
    .mode-sheet #pb-expand,
    .mode-sheet #pb-restore,
    .mode-sheet #pb-close-bar,
    .mode-sheet .pb-book { display: none; }

    /* Handle and chrome first, then the transport, then the tabs: the
       controls sit in the bottom half where a thumb reaches, and the list
       you are reading gets the top. */
    .mode-sheet #pb-titlebar { order: 0; }
    .mode-sheet #player-bar { order: 1; }
    .mode-sheet #player-body { order: 2; }

    .mode-sheet #pb-titlebar {
      position: relative;
      flex: none;
      justify-content: space-between;
      padding: var(--space-4) var(--space-2) var(--space-1);
      background: none;
      border-bottom: none;
      cursor: grab;
      touch-action: none;               /* the drag-to-dismiss owns the gesture */
    }
    /* The grab handle every sheet has, centred over the row. */
    .mode-sheet .pb-grip {
      position: absolute;
      inset: var(--space-2) auto auto 50%;
      translate: -50% 0;
      width: 2.5rem;
      height: 4px;
      background: var(--border-strong);
      border-radius: var(--radius-full);
    }
    .mode-sheet .pb-window-title {
      font-size: var(--text-sm);
      text-align: center;
    }
    .mode-sheet .pb-chrome { width: var(--tap-min); height: var(--tap-min); }

    /* **Compact, and at the top.** The text is what this app is for, so the
     * controls take the rows they need and no more: chapter, then seek and
     * clock on one line, then the transport with the bookmark beside it.
     * The first version gave the transport a 4.5rem play button across four
     * separate rows, which left the reading pane a couple of lines tall on
     * an 844px screen (Toni, 2026-08-11).
     *
     * `display: contents` on `.pb-utility` is what makes it fit: it drops
     * the wrapper so the clock and the bookmark can be placed in the grid
     * individually, without a second copy of either in the markup. */
    .mode-sheet #player-bar { flex: none; background: none; }
    .mode-sheet .pb-controls {
      grid-template-columns: minmax(0, 1fr) auto;
      grid-template-areas:
        "title     title"
        "seek      time"
        "transport bookmark";
      align-items: center;
      gap: var(--space-1) var(--space-3);
      max-width: none;
      padding: 0 var(--space-4) var(--space-3);
    }
    .mode-sheet .pb-title { grid-area: title; cursor: default; text-align: center; }
    .mode-sheet .pb-chapter {
      font-size: var(--text-lg);
      font-family: var(--font-serif);
      white-space: normal;
    }
    .mode-sheet #pb-seek {
      grid-area: seek;
      height: var(--tap-min);           /* a thumb, not a hairline */
      margin: 0;
    }
    .mode-sheet .pb-utility { display: contents; }
    .mode-sheet .pb-time { grid-area: time; font-size: var(--text-sm); }
    .mode-sheet #pb-bookmark { grid-area: bookmark; }
    .mode-sheet #pb-bookmark span { display: none; }   /* the ribbon says it */
    .mode-sheet .pb-transport {
      grid-area: transport;
      justify-content: space-between;
      gap: var(--space-1);
    }
    .mode-sheet .pb-btn { width: var(--tap-min); height: var(--tap-min); font-size: 1.15rem; }
    .mode-sheet .reader-immerse span { font-size: var(--text-sm); }
    .mode-sheet .pb-play { width: 3.25rem; height: 3.25rem; font-size: 1.35rem; }
    .mode-sheet .pb-labelled { width: auto; }

    /* Tabs as a segmented control, then the content beneath. */
    .mode-sheet #player-body {
      display: flex;
      flex-direction: column;
      flex: 1;
      min-height: 0;
      border-top: 1px solid var(--border);
    }
    .mode-sheet #pb-rail {
      flex: none;
      flex-wrap: wrap;
      align-items: center;
      gap: var(--space-2);
      padding: var(--space-2) var(--space-3);
      background: none;
      border: none;
    }
    .mode-sheet .tab-btn {
      flex: 1;
      min-width: 5rem;
      min-height: var(--tap-min);
      background: var(--surface-sunken);
      border: 1px solid var(--border);
      border-radius: var(--radius-md);
      text-align: center;
    }
    .mode-sheet .tab-btn.active {
      background: var(--accent-subtle);
      border-color: var(--accent);
      color: var(--text);
    }
    /* Voice and sleep get their own line rather than being pushed off the
       end of the tab row, which is where they used to go — and they leave
       the screen entirely while the Text tab is up, because they are
       settings and a reader wants the rows back. */
    .mode-sheet .panel-spacer { flex-basis: 100%; height: 0; }
    .mode-sheet[data-tab="text"] .panel-spacer,
    .mode-sheet[data-tab="text"] .voice-label,
    .mode-sheet[data-tab="text"] .sleep-label { display: none; }
    .mode-sheet .voice-label,
    .mode-sheet .sleep-label {
      flex: 1;
      min-width: 8rem;
      gap: var(--space-2);
      padding: 0;
      border: none;
    }
    .mode-sheet .voice-label select,
    .mode-sheet .sleep-label select {
      flex: 1;
      max-width: none;
      min-height: var(--tap-min);
    }
    .mode-sheet #player-panel {
      flex: 1;
      min-height: 0;
      margin-inline: 0;
      background: none;
      border: none;
      border-radius: 0;
      box-shadow: none;
      max-width: none;
    }
    .mode-sheet .panel-tab {
      max-height: none;
      flex: 1;
      min-height: 0;
      padding-inline: var(--space-3);
    }
    .mode-sheet .chapter-row { min-height: 3.25rem; }
    .mode-sheet .bookmark-row { padding-block: var(--space-2); }

    /* Reading, immersively: a tap on the text hides everything that is not
       the book, and a tap brings it back. The controls are still in the
       DOM, so nothing has to be rebuilt when they return. */
    .mode-sheet.reading #pb-titlebar,
    .mode-sheet.reading #player-bar,
    .mode-sheet.reading #pb-rail { display: none; }
    .mode-sheet.reading #player-body { border-top: none; }
    .mode-sheet.reading .reader-tools { display: none; }
    .mode-sheet.reading .reading-exit {
      position: fixed;
      inset: auto 50% calc(env(safe-area-inset-bottom) + var(--space-4)) auto;
      z-index: 61;
      display: inline-grid;
      place-items: center;
      translate: 50% 0;
      width: auto;
      min-height: var(--tap-min);
      padding-inline: var(--space-4);
      background: color-mix(in oklab, var(--surface-raised) 92%, transparent);
      backdrop-filter: blur(8px);
      border: 1px solid var(--border-strong);
      border-radius: var(--radius-full);
      box-shadow: var(--shadow-md);
      font-size: var(--text-sm);
    }
    .mode-sheet .reader-bar {
      display: flex;
      align-items: center;
      gap: var(--space-2);
      flex: none;
    }
    /* Folded away until asked for. `hidden` would be the honest attribute,
       but the row is shared with the desktop window where it is always
       open, so the state lives on the root as a class instead. */
    .mode-sheet:not(.reader-open) .reader-tools { display: none; }
    .mode-sheet .reader-tools {
      flex-wrap: wrap;
      gap: var(--space-2) var(--space-3);
      padding-block: var(--space-2);
      border-block: 1px solid var(--border);
    }
    .reader-step { width: var(--tap-min); height: var(--tap-min); }
    .reader-toggle { min-height: var(--tap-min); }
    #reader-settings[aria-expanded="true"] {
      background: var(--accent-subtle);
      color: var(--text);
    }
  }
}
