/* Ebook Correction UI — imports shared variables/fonts from style.css */

/* === Upload screen === */

.upload-screen {
  flex: 1;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  min-height: 0;
  overflow: hidden;
}

.upload-row {
  flex: 0 0 auto;
  display: flex;
  justify-content: center;
}

/* Two-column dashboard: LEFT = drop-zone + "My books" stacked; RIGHT =
   updates feed. No visible divider between columns — separation comes
   from the gap. Below the breakpoint, the columns collapse to a single
   stack (drop-zone → my-books → updates) so the user's own books are
   the first thing they see on mobile, and the public-library section
   below stays full-width on every viewport. */
.dashboard-row {
  flex: 1;
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  justify-content: center;
  gap: 32px;
  padding: 32px;
  min-height: 0;
  overflow-y: auto;
}

.dashboard-col-left {
  flex: 1 1 480px;
  max-width: 480px;
  display: flex;
  flex-direction: column;
  gap: 24px;
  min-height: 0;
}

@media (max-width: 720px) {
  .dashboard-row {
    flex-direction: column;
    align-items: center;
    padding: 16px;
    gap: 24px;
  }
  .dashboard-row .dashboard-col-left,
  .dashboard-row .messages-section {
    flex: 0 0 auto;
    width: 100%;
    max-width: 480px;
  }
  /* `.session-list-section` is nested inside `.dashboard-col-left`; at
     mobile widths its desktop `flex: 1 1 480px` makes it greedily fill
     the column with empty space below the book list. Snap it to natural
     height so the stack flows tight (upload box → my books → updates). */
  .dashboard-row .session-list-section {
    flex: 0 0 auto;
    width: 100%;
    max-width: 480px;
  }
  .upload-box {
    padding: 32px 16px;
  }
}

/* === Messages feed === */

/* Sits as a top-level column of `.dashboard-row` alongside
   `.dashboard-col-left` — same width budget as the left column so the
   two halves balance at desktop sizes. The mobile media-query above
   collapses it to a full-width row in the stacked layout. */
.messages-section {
  flex: 1 1 480px;
  max-width: 480px;
  display: flex;
  flex-direction: column;
  min-height: 0;
  gap: 8px;
}

.messages-heading {
  font-size: 13px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  margin: 0 0 4px 0;
}

.messages-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-height: 0;
}

.messages-empty {
  color: var(--text-muted);
  font-size: 13px;
  font-style: italic;
  padding: 12px 0;
}

/* Vertical stack: header row (handle + time, right-justified) on top,
 * body row below. Header keeps tight padding; body gets extra horizontal
 * room so long messages aren't forced to manually wrap around metadata. */
.messages-item {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-pane);
}

.messages-item-publication {
  cursor: pointer;
  transition: border-color 0.12s, background 0.12s;
}

.messages-item-publication:hover,
.messages-item-publication:focus {
  border-color: var(--accent);
  outline: none;
}

.messages-item-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
  font-size: 11px;
  color: var(--text-muted);
  min-width: 0;
}

.messages-item-handle {
  font-weight: 500;
  color: var(--text);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.messages-item-time {
  font-style: italic;
  color: var(--text-muted);
  flex: 0 0 auto;
}

/* Publication body sits beside the cover thumb; admin body is just the
 * sanitised HTML. Both get the same horizontal padding bump so the
 * message text has breathing room within the card. */
.messages-item-body-row {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: 10px;
  padding: 0 6px;
  min-width: 0;
}

.messages-item-cover {
  width: 32px;
  height: 48px;
  object-fit: cover;
  border-radius: 2px;
  flex: 0 0 auto;
  background: var(--bg);
}

.messages-item-cover-placeholder {
  width: 32px;
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  border-radius: 2px;
  flex: 0 0 auto;
  color: var(--text-muted);
}
.messages-item-cover-placeholder .shave-icon {
  width: 22px;
  height: 22px;
}

.messages-item-body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.messages-item-title {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.messages-item-author {
  font-size: 12px;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The single-line "{handle} shared" message in the Updates feed. Handle
 * is a bold inline span. */
.messages-item-message {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  font-size: 14px;
  color: var(--text);
  min-width: 0;
}

.messages-item-message .messages-item-handle {
  font-weight: 600;
}

.messages-item-admin .messages-item-body {
  padding: 0 6px;
}

.messages-item-admin .messages-item-html {
  font-size: 13px;
  color: var(--text);
  line-height: 1.4;
}

.messages-item-admin .messages-item-html > *:first-child { margin-top: 0; }
.messages-item-admin .messages-item-html > *:last-child { margin-bottom: 0; }

.messages-more {
  align-self: flex-start;
  font-size: 12px;
  color: var(--link);
  text-decoration: none;
  padding: 4px 0;
}

.messages-more:hover {
  text-decoration: underline;
}

.upload-box {
  text-align: center;
  padding: 60px 80px;
  border: 2px dashed var(--border);
  border-radius: 12px;
  background: var(--bg-pane);
  transition: border-color 0.15s, background 0.15s;
  flex: 0 0 auto;
  max-width: 100%;
  box-sizing: border-box;
}

/* Offline: the upload box (needs the engine), the Updates feed and the
   Library strip (needs the server) have no offline function — hide
   them so My Books' downloaded shelf is the whole surface. */
body.offline .upload-box,
body.offline .messages-section,
body.offline .library-footer-section {
  display: none !important;
}

.upload-box.drag-over {
  border-color: var(--accent);
  background: rgba(79, 70, 229, 0.04);
}

.upload-icon {
  font-size: 48px;
  margin-bottom: 16px;
}

.upload-box h2 {
  font-size: 20px;
  margin-bottom: 8px;
  color: var(--text);
}

.upload-box p {
  color: var(--text-muted);
  margin-bottom: 24px;
}

.upload-label {
  display: inline-block;
  cursor: pointer;
}

.upload-status {
  margin-top: 20px;
  font-size: 14px;
  color: var(--text-muted);
}

/* === Buttons === */

.btn-primary {
  padding: 8px 20px;
  background: var(--accent);
  color: white;
  border: none;
  border-radius: var(--radius-sm);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  font-family: inherit;
}

.btn-primary:hover {
  background: var(--accent-hover);
}

/* A confirm button is given BOTH classes when the action is destructive
   (ShaveConfirmModal's `danger`). This file loads after style.css, so its
   .btn-primary would otherwise repaint every such button accent-blue and a
   destructive action would wear the colour of a safe one. */
.btn-primary.btn-danger {
  background: #dc2626;
}

.btn-primary.btn-danger:hover {
  background: #b91c1c;
}

.btn-primary:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.btn-secondary {
  padding: 8px 16px;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: 14px;
  cursor: pointer;
  font-family: inherit;
}

.btn-secondary:hover {
  background: var(--bg-pane-alt);
}

.btn-secondary:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* === Toolbar extras === */

.toolbar-cover-wrap {
  position: relative;
  flex-shrink: 0;
}

/* Read-only badge — soft amber pill so the user notices but it
   doesn't shout. Shown next to the book title when the session
   belongs to a different client. */
.toolbar-readonly-badge {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(217, 119, 6, 0.18);
  color: #d97706;
  flex-shrink: 0;
}

.toolbar-cover {
  width: 24px;
  height: 32px;
  object-fit: cover;
  border-radius: 3px;
  vertical-align: middle;
  cursor: pointer;
  display: block;
}

/* Reader mode for a non-owner: no popover actions exist, so the thumb
   stays visible but inert. JS short-circuits the click; this rule drops
   the affordance so the cursor doesn't promise interactivity. */
body.reader-cover-inert .toolbar-cover {
  cursor: default;
}

.cover-popover {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  background: var(--bg-toolbar);
  border: 1px solid rgba(255,255,255,0.15);
  border-radius: var(--radius-sm);
  padding: 12px;
  z-index: 200;
  box-shadow: 0 8px 24px rgba(0,0,0,0.5);
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 180px;
}

.cover-preview {
  width: 160px;
  height: auto;
  border-radius: 4px;
  display: block;
}

.toolbar-sep {
  opacity: 0.3;
}

.toolbar-book-meta {
  display: flex;
  flex-direction: row;
  align-items: baseline;
  gap: 8px;
  min-width: 0;
}

.toolbar-book-sep {
  opacity: 0.3;
  font-size: 18px;
  font-weight: 300;
}

.toolbar-book-title {
  font-size: 20px;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--text-toolbar);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: min(320px, 30vw);
  outline: none;
  border-radius: 3px;
  padding: 0 2px;
  cursor: default;
}

.toolbar-book-author {
  font-size: 12px;
  font-style: italic;
  font-weight: 400;
  opacity: 0.8;
  color: var(--text-toolbar);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: min(200px, 20vw);
  outline: none;
  border-radius: 3px;
  padding: 0 2px;
  cursor: default;
}

/* Mobile: long titles + author names overflow the single-row toolbar
   layout. Stack title above author and clamp each to one row with
   ellipsis — total budget is two rows. Hides the inter-element dot
   so the stacked layout reads as a heading + byline (iter DF / #268).
   Iter DF-fix / #283: the clamp originally sat on .toolbar-book-title /
   .toolbar-book-author, but those are inline children of an intermediate
   .toolbar-meta-wrap span, so the `-webkit-box` clamp had no constrained
   parent and never fired. Apply the clamp to the wrap instead — that's
   the element whose width the column stack actually constrains. */
@media (max-width: 720px) {
  .toolbar-book-meta {
    flex-direction: column;
    align-items: flex-start;
    gap: 0;
    /* `min-width: 0` is essential — without it the parent flex item
       refuses to shrink and the ellipsis never triggers. */
    min-width: 0;
    max-width: 100%;
  }
  .toolbar-book-sep {
    display: none;
  }
  .toolbar-meta-wrap {
    /* Clamp host: the wrap is the element that owns the per-line width
       budget on mobile. `-webkit-box` + `-webkit-line-clamp` is the
       cross-browser (Chrome/Safari/Firefox-since-68) way to clip to
       N rows with ellipsis. `min-width: 0` + `max-width: 100%` let
       the wrap shrink inside its column-stacked parent. */
    display: -webkit-box;
    -webkit-line-clamp: 1;
    -webkit-box-orient: vertical;
    overflow: hidden;
    min-width: 0;
    max-width: 100%;
  }
  .toolbar-book-title,
  .toolbar-book-author {
    /* The wrap clamps; the title/author themselves must NOT cap their
       own width on mobile or the desktop `max-width: 320px / 200px`
       leaks in and clips before the wrap's ellipsis can run. */
    max-width: 100%;
  }
  /* Smaller type on mobile so two stacked rows fit the toolbar height
     without growing the header. */
  .toolbar-book-title {
    font-size: 15px;
  }
  .toolbar-book-author {
    font-size: 11px;
  }
}

.toolbar-progress {
  font-size: 13px;
  opacity: 0.7;
}

.toolbar-tab {
  transition: background 0.1s;
}

.toolbar-tab.active {
  background: rgba(255, 255, 255, 0.2);
}

/* === View containers === */

.view {
  flex: 1;
  display: flex;
  min-height: 0;
  overflow: hidden;
}

/* === Review layout === */

.review-layout {
  position: relative;
  display: flex;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

/* Left: issue list panel. Width is user-resizable via the adjacent
   splitter; JS sets flex-basis when the user drags. min-width is
   intentionally low so the user can squish the panel down hard
   without it bouncing back. */
.issue-list-panel {
  position: relative;
  flex: 0 0 30%;
  min-width: 80px;
  display: flex;
  flex-direction: column;
  background: var(--bg-pane);
}

/* Collapsed-sidebar rail. Hidden by default; replaces the panel + its
   splitter when body.sidebar-collapsed is set.
   padding-top matches .issue-list-header's expanded chevron Y so the
   icon doesn't jump vertically on collapse/expand — the header's
   align-items: center pulls the chevron to ~10px from the header top
   (chevron 22px, header 42px), and the rail sits in the same flex row
   so they share a top reference. */
.sidebar-rail {
  display: none;
  flex: 0 0 28px;
  background: var(--bg-pane);
  border-right: 1px solid var(--border);
  align-items: flex-start;
  justify-content: center;
  padding-top: 10px;
}
body.sidebar-collapsed .issue-list-panel,
body.sidebar-collapsed #splitter-sidebar { display: none; }
body.sidebar-collapsed .sidebar-rail { display: flex; }

/* Mobile: the issue navigator becomes a popover that overlays the
   content panes. The ribbon (.sidebar-rail) flips role: when the
   popover is OPEN (body NOT collapsed) we hide the ribbon so the
   popover covers the viewport; when CLOSED (body.sidebar-collapsed)
   we show the ribbon so it acts as the re-open trigger, matching
   desktop-collapsed semantics.
   z-index 600 sits above the editor widget (500, see #word-popup) so
   the popover covers the widget when opened, but stays well below the
   modal stack (≥1000) and chrome banners. */
@media (max-width: 720px) {
  .sidebar-rail {
    display: none;
  }
  body.sidebar-collapsed .sidebar-rail {
    display: flex;
  }
  .issue-list-panel {
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    z-index: 600;
    width: min(90vw, 320px);
    flex: 0 0 auto;
    box-shadow: 4px 0 12px rgba(0, 0, 0, 0.15);
  }
  #splitter-sidebar {
    display: none;
  }
  /* Narrow panel: the search field drops to its own full-width row beneath
     the title + action buttons (order 3 puts it last; 100% basis forces the
     wrap) rather than competing for space on the header's first line. */
  .issue-text-filter {
    order: 3;
    flex-basis: 100%;
    max-width: 100%;
  }
}

.issue-list-filters {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
}

.issue-list-filters .threshold-control {
  font-size: 12px;
}

.issue-list-filters .threshold-control input[type="range"] {
  flex: 1;
  width: auto;
}

/* Free-text issue filter. Lives in the header row, left of the action
   buttons; matches source (Latin) or target (Shavian) text. The magnifier
   adornment shows only while the field is empty (:placeholder-shown).
   flex: 1 with min-width:0 lets it shrink gracefully so it never crowds
   out the title/count; max-width caps it on a roomy sidebar. When the
   header wraps (narrow panel) it takes a full-width row of its own. */
.issue-text-filter {
  position: relative;
  display: flex;
  align-items: center;
  box-sizing: border-box;
  flex: 1 1 120px;
  min-width: 0;
  max-width: 200px;
  /* Between the title-group (order 0) and the action buttons (order 2)
     so it sits inline on the left of them on a wide sidebar. */
  order: 1;
}

.issue-text-filter input[type="text"] {
  box-sizing: border-box;
  width: 100%;
  max-width: 100%;
  font-size: 12px;
  /* The header sets uppercase + letter-spacing for its label text; reset
     both here so the field's placeholder/value read as normal input text. */
  text-transform: none;
  letter-spacing: normal;
  padding: 3px 7px 3px 22px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg-pane);
  color: var(--text);
}

.issue-text-filter input[type="text"]::placeholder {
  color: var(--text-muted);
}

.issue-text-filter input[type="text"]:focus {
  outline: none;
  border-color: var(--text-muted);
}

/* Incomplete/invalid regex: the list is frozen on the last valid pattern, so
   tint the border red (subtle, not a fill) to explain why results aren't
   updating. --danger reads as red in both themes. Wins over :focus. */
.issue-text-filter input[type="text"].is-invalid-regex,
.issue-text-filter input[type="text"].is-invalid-regex:focus {
  border-color: var(--danger, #c0392b);
}

.issue-text-filter-icon {
  position: absolute;
  left: 6px;
  display: inline-flex;
  align-items: center;
  color: var(--text-muted);
  pointer-events: none;
}

.issue-text-filter-icon .shave-icon {
  width: 12px;
  height: 12px;
}

/* Hide the magnifier once the user has typed — the text carries the
   affordance from then on. */
.issue-text-filter input[type="text"]:not(:placeholder-shown) + .issue-text-filter-icon {
  display: none;
}

.type-filter-row {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.type-filter-pill {
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 2px 7px;
  border-radius: 8px;
  border: none;
  cursor: pointer;
  background: var(--border);
  color: var(--text-muted);
  opacity: 0.45;
}
.type-filter-pill.active {
  opacity: 1;
}

.issue-list-header {
  /* Zero left padding — the .sidebar-collapse-slot owns the leftmost
     column and aligns with the .sidebar-rail's left edge in the
     collapsed state, so the chevron's X is identical in both states. */
  padding: 10px 16px 10px 0;
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}

/* Pin the collapse chevron to a fixed-width leftmost slot. Width and
   centring match .sidebar-rail so the chevron sits at identical X/Y
   whether the panel is expanded (this slot) or collapsed (the rail). */
.sidebar-collapse-slot {
  flex: 0 0 28px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.issue-list-title-group {
  flex: 1 1 auto;
  display: flex;
  align-items: baseline;
  gap: 6px;
}

.issue-count {
  font-weight: 400;
  font-size: 12px;
}

.issue-list-header-actions {
  display: flex;
  gap: 4px;
  align-items: center;
  /* After the search field (order 1) on a wide sidebar. */
  order: 2;
}

.issue-list {
  flex: 1;
  overflow-y: auto;
  padding: 4px 0;
}

.issue-item {
  padding: 8px 16px;
  display: flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
  font-size: 14px;
  border-left: 3px solid transparent;
  transition: background 0.1s;
}

.issue-item:hover {
  background: var(--bg);
}

.issue-item.active {
  background: rgba(79, 70, 229, 0.08);
  border-left-color: var(--accent);
}

/* Accepted items stay fully interactive — the green check is the review
   marker, not a dim-out. */
.issue-item.accepted .issue-word {
  color: var(--text-muted, #6b7280);
}

/* Dirty: user has typed something here but not committed. The pencil
   glyph + amber tint signal "in progress" — lighter than the green
   check but heavier than an unedited issue's coloured dot. */
.issue-item.dirty .issue-word {
  color: #a16207;
  font-style: italic;
}

.issue-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}

.issue-dot.green { background: #22c55e; }
.issue-dot.yellow { background: #eab308; }
.issue-dot.red { background: #ef4444; }
.issue-dot.orange { background: #f97316; }

/* Orphan section: corrections whose issueId no longer appears in the
 * current events list. Derived on the client; no persistent flag. */
.orphan-section-header {
  color: var(--text-muted);
}
.issue-item.orphan-item {
  color: var(--text-muted);
  cursor: pointer;
  /* No accepted/active states — orphans are read-only. */
}
.issue-item.orphan-item:hover {
  background: var(--bg-pane-alt);
}
.issue-dot.orphan-dot {
  background: var(--text-muted);
}

.orphan-context {
  padding: 16px;
  background: var(--bg-pane-alt);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
}
.orphan-context-title {
  font-weight: 600;
  margin-bottom: 8px;
}
.orphan-context-body {
  margin-bottom: 12px;
}
.orphan-context-kv {
  font-family: monospace;
  font-size: 12px;
  margin: 4px 0;
}
.orphan-context-k {
  color: var(--text-muted);
}

.issue-check {
  color: #22c55e;
  font-size: 14px;
  flex-shrink: 0;
  width: 8px;
  text-align: center;
}

.issue-pencil {
  color: #ca8a04;
  font-size: 13px;
  flex-shrink: 0;
  width: 8px;
  text-align: center;
}

.issue-word {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.issue-count-badge {
  font-size: 11px;
  color: var(--text-muted);
  background: var(--border);
  border-radius: 8px;
  padding: 0 5px;
  flex-shrink: 0;
  margin-left: 2px;
}

.issue-conf {
  font-size: 12px;
  color: var(--text-muted);
  flex-shrink: 0;
}

/* Small pill showing the issue kind, since the sidebar is now grouped
   by chapter rather than by type. */
.issue-type-badge {
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 1px 6px;
  border-radius: 8px;
  background: var(--border);
  color: var(--text-muted);
  flex-shrink: 0;
  margin-left: auto;
  margin-right: 4px;
}
.issue-type-badge.badge-punct,
.type-filter-pill.badge-punct    { background: rgba(45, 212, 191, 0.18); color: #0d9488; }
.issue-type-badge.badge-unknown,
.type-filter-pill.badge-unknown  { background: rgba(249, 115, 22, 0.18); color: #c2410c; }
.issue-type-badge.badge-affix,
.type-filter-pill.badge-affix    { background: rgba(168, 85, 247, 0.18); color: #7e22ce; }
.issue-type-badge.badge-homo,
.type-filter-pill.badge-homo     { background: rgba(79, 70, 229, 0.18); color: #4338ca; }
.issue-type-badge.badge-namer-dot,
.type-filter-pill.badge-namer-dot { background: rgba(236, 72, 153, 0.18); color: #be185d; }
.issue-type-badge.badge-comment,
.type-filter-pill.badge-comment  { background: rgba(253, 224, 71, 0.5); color: #854d0e; }
.issue-type-badge.badge-annotation-highlight,
.type-filter-pill.badge-annotation-highlight { background: rgba(132, 204, 22, 0.2); color: #4d7c0f; }
.issue-type-badge.badge-annotation-comment { background: rgba(14, 165, 233, 0.18); color: #0369a1; }
.issue-type-badge.badge-annotation-shared-comment { background: rgba(244, 63, 94, 0.16); color: #be123c; }
.issue-type-badge.badge-source-edit,
.type-filter-pill.badge-source-edit { background: rgba(100, 116, 139, 0.2); color: #334155; }

/* Handle-style pill on resolved patches authored by another contributor. */
.contributor-badge {
  font-size: 10px;
  font-weight: 600;
  padding: 2px 6px;
  border-radius: 3px;
  background: rgba(79, 70, 229, 0.12);
  color: var(--accent);
  margin-left: 6px;
  flex-shrink: 0;
}

/* Progress band — the fill sits behind the label so the bar reads as a
   filling-up shape under the text rather than a thin line above it. */
.issue-progress-band {
  position: relative;
  border-top: 1px solid var(--border);
  background: var(--bg-pane);
  overflow: hidden;
}

.issue-progress-fill {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  width: 0%;
  background: rgba(34, 197, 94, 0.18);
  transition: width 0.3s ease, background 0.2s ease;
}

.issue-progress-fill.processing {
  background: rgba(59, 130, 246, 0.20);
}

.issue-progress-label {
  position: relative;
  padding: 10px 16px;
  font-size: 12px;
  color: var(--text-muted);
  text-align: center;
}

/* Right: main review area */
.review-main {
  position: relative;
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  overflow: hidden;
}

/* Side by side (paneOrientation.js): the source/target splitter stands
   upright. Its handle sits high, below the title bars and the find bar, not
   low as on the sidebar's splitter, where the floating toolbar covers it at
   narrow widths. */
.review-main[data-orientation="side-by-side"] {
  flex-direction: row;
}
.review-main[data-orientation="side-by-side"] > .splitter-v {
  width: 1px;
  height: auto;
  cursor: ew-resize;
}
.review-main[data-orientation="side-by-side"] > .splitter-v .splitter-handle {
  top: 108px;
  bottom: auto;
  left: -11px;
  right: auto;
  cursor: ew-resize;
}
.review-main[data-orientation="side-by-side"] > .splitter-v .splitter-handle::before {
  content: '↔';
}
@media (pointer: coarse) {
  .review-main[data-orientation="side-by-side"] > .splitter-v .splitter-handle {
    left: -14px;
  }
}

/* Context panes (source + shavian paragraph). They share whatever
   vertical space the correction column doesn't claim — the column
   sizes to its content, so the panes get the rest.
   The pane paints --bg-pane so the .context-content mask-fade reveals
   matching paper, not the body's chrome backdrop. See style.css for
   the paper-keeps-light override that keeps this in lockstep with the
   .context-content paint when the user opts paper out of dark mode. */
.context-pane {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  background: var(--bg-pane);
  border-bottom: 1px solid var(--border);
  min-height: 0;
  overflow: hidden;
}

.pane-header {
  display: flex;
  align-items: center;
  height: 40px;
  padding: 0 24px;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  background: var(--bg-pane-alt);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
  /* Chapter titles (#331) can overflow narrow panes — truncate cleanly. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A pane title bar's TOC label (paneTitleBars.js). An editable label is
   marked with an underline rather than on hover, so touch shows it too. */
.pane-title-label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  /* Room below the text, which the overflow would otherwise clip the
     underline off. */
  padding-block: 4px;
}
.pane-title-label.is-editable {
  cursor: text;
  text-decoration: underline dotted;
  text-underline-offset: 3px;
}
/* The bar under the source/target splitter keeps clear of its handle, which
   sits on the bar's top edge, 18px in from the right and up to 28px wide. */
.review-main[data-orientation="stacked"] > .splitter-v + .context-pane > .pane-header {
  padding-right: 56px;
}
/* 16px keeps iOS Safari from zooming the page when the field takes focus. */
.pane-title-input {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 16px;
  font-weight: 400;
  text-transform: none;
  letter-spacing: normal;
  padding: 2px 6px;
  border: 1px solid var(--accent);
  border-radius: 4px;
  background: var(--bg-pane);
  color: var(--text);
}
.pane-title-reset {
  flex: 0 0 auto;
  margin-left: 8px;
  min-height: 32px;
  padding: 0 10px;
  font-size: 12px;
  text-transform: none;
  letter-spacing: normal;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg-pane);
  color: var(--text);
  cursor: pointer;
}
/* On touch the bar is 44px, and its label and Reset each take its height. */
@media (pointer: coarse) {
  .pane-header {
    height: 44px;
  }
  .pane-title-label {
    align-self: stretch;
    padding-block: 0;
    line-height: 44px;
  }
  .pane-title-reset {
    align-self: stretch;
    min-height: 0;
  }
}

.context-content {
  padding: 17vh 24px 0 24px;
  /* The pane's text: the book's medium, 16px, times the pane's scale
     (paneTextScale.js), which brings the book's body text to the pane's own
     size, --shave-pane-text. */
  --shave-pane-text: 20px;
  font-size: calc(16px * var(--shave-pane-scale, 1));
  overflow-wrap: break-word;
  line-height: 1.75;
  overflow-y: auto;
  background: var(--bg-pane);
  flex: 1;
  min-height: 0;
  /* Top/bottom fade so surrounding text dissolves toward the edges. */
  mask-image: linear-gradient(to bottom,
    transparent 0, black 28%, black 72%, transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom,
    transparent 0, black 28%, black 72%, transparent 100%);
}

/* A full viewport of breathing room past the last paragraph — runway
   for the cross-chapter "scroll off the end" animation. Implemented
   as a pseudo-element rather than padding-bottom because the latter
   interacted badly with flex sizing and pushed the pane's box past
   the viewport (mask fade clipped, scrollbar disappeared). */
.context-content::after {
  content: '';
  display: block;
  height: 50vh;
}

/* A spine item with another before or after it: past its text, a zone the
   reader scrolls on into to move to that one (chapterSnap.js, which sets
   --spine-zone). */
.context-content[data-spine-prev] {
  padding-top: calc(17vh + var(--spine-zone));
}
.context-content[data-spine-next]::after {
  height: calc(50vh + var(--spine-zone));
}

/* The OS default selection colour matches --bg-pane in dark mode, so a
   drag-select in the editor panes was invisible against the dark paper.
   Paint selection with the annotation yellow (the same warm highlight the
   reader uses for marks) so it reads clearly on both light and dark paper. */
.context-content ::selection {
  background: var(--ann-hl-y);
  color: inherit;
}
.context-content ::-moz-selection {
  background: var(--ann-hl-y);
  color: inherit;
}

/* Round 107 / #311: at wide viewports, give the context panes book-page-
   style horizontal margins. Mirrors the breathing room a printed page's
   side margins give to body text. Mobile-first: the base 24px stays put
   below 1024px; this only adds extra horizontal padding when there's
   room. clamp() floors at the base 24px so the rule never SHRINKS the
   margin, scales with viewport in the middle, and caps at 96px so the
   text column doesn't collapse on ultra-wide displays. */
@media (min-width: 1024px) {
  .context-content {
    padding-left: max(var(--block-gutter), clamp(24px, 8vw, 96px));
    padding-right: clamp(24px, 8vw, 96px);
  }
}
/* Side by side, each pane has about half the width to give. */
@media (min-width: 1024px) {
  .review-main[data-orientation="side-by-side"] .context-content {
    padding-left: max(var(--block-gutter), clamp(24px, 4vw, 48px));
    padding-right: clamp(24px, 4vw, 48px);
  }
}
/* The left gutter holds, from the pane's edge, the comment markers (6px in,
   30px wide), the divider's + and the block bars, one column of them, 13px
   clear of the text (`.block-handle`, `.block-gap`): 84px. A touch screen's
   pane, as narrow as a tablet's side by side, keeps the 54px gutter of a
   narrow window, its bars 11px clear of the text (`.block-gap` below). */
.context-content {
  --block-gutter: 84px;
}
@media (pointer: coarse) {
  .context-content {
    --block-gutter: 54px;
  }
}

/* Splitter rules live in style.css (shared between text and ebook). */

/* Disable hyperlink navigation in the chapter panes — accidental clicks
   following links is annoying when reviewing. Highlight spans nested
   inside <a> still get clicks (pointer-events: auto restores them). */
.context-content a {
  pointer-events: none;
  color: inherit;
  text-decoration: none;
  cursor: text;
}
.context-content a span,
.context-content a .hl-current,
.context-content a .hl-other {
  pointer-events: auto;
}

/* Pin block margins in pixels so the two panes match regardless of
   the source's stylesheet or the target's larger font size. */
.context-content p,
.context-content div,
.context-content blockquote,
.context-content li {
  margin-top: 0;
  margin-bottom: 12px;
}

/* Cap embedded chapter imagery to the pane width — otherwise a large
   figure forces horizontal scrolling on mobile. Scoped to .context-content
   so toolbar covers, card thumbs and chrome icons stay sized by their
   own rules. */
.context-content img {
  max-width: 100%;
  height: auto;
}
.context-content h1,
.context-content h2,
.context-content h3,
.context-content h4,
.context-content h5,
.context-content h6 {
  margin-top: 18px;
  margin-bottom: 12px;
}

/* Target pane: larger still (matches text-mode view). Font inherits
   from body — one face across the app. */
#target-context.context-content {
  --shave-pane-text: 22px;
  line-height: 1.8;
  /* Override .shavian-text's pre-wrap; block margins govern spacing. */
  white-space: normal;
  position: relative;
}
/* The comment markers are placed in the scrolled content, in each pane's
   left gutter. */
#source-context.context-content { position: relative; }
.context-content .ann-margin-box {
  left: 6px;
  right: auto;
}
/* A gutter wide enough for a comment marker and the block bars beside it;
   the wider layouts' margins already are. */
@media (max-width: 1023px) {
  .context-content {
    padding-left: 54px;
  }
}

/* Active word the user is parked on. The word OFTEN already carries an
   event fill (red unknown, green accepted, yellow comment...), so a
   competing fill just blends back in. The active cue is therefore
   ORTHOGONAL to fill: a solid accent ring (box-shadow, so it never
   reflows the glyphs) plus bold weight, stacked on top of whatever fill
   the span already has. Indigo is distinct from every event hue, so the
   ring reads as "focused" even over a red event span. */
.context-content .hl-current {
  background: rgba(79, 70, 229, 0.18);
  border-radius: 2px;
  padding: 1px 2px;
  box-shadow: 0 0 0 2px var(--accent);
  font-weight: 700;
}

/* Dark-mode tone-shift for the active ring — same dual-selector recipe
   as the signal pills below: explicit dark theme OR OS dark (without a
   light override). Lift to the bright end of the indigo so the ring
   reads on a dark pane. */
:root[data-theme="dark"] .context-content .hl-current {
  box-shadow: 0 0 0 2px #818cf8;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .context-content .hl-current {
    box-shadow: 0 0 0 2px #818cf8;
  }
}

/* Non-active highlights for every other filtered issue in the chapter.
   Tints match the issue-list dot colours so the user can scan a pane
   and see roughly how many low-confidence spots are in it. */
.context-content .hl-other {
  border-radius: 2px;
  padding: 1px 2px;
}
.context-content .hl-other.green { background: rgba(34, 197, 94, 0.14); }
.context-content .hl-other.yellow { background: rgba(234, 179, 8, 0.16); }
.context-content .hl-other.red { background: rgba(239, 68, 68, 0.16); }
.context-content .hl-other.orange { background: rgba(249, 115, 22, 0.16); }

.context-content .hl-comment {
  background: rgba(253, 224, 71, 0.45);
  border-radius: 2px;
  padding: 1px 2px;
  text-decoration: underline;
  text-decoration-color: rgba(202, 138, 4, 0.7);
  text-decoration-thickness: 2px;
  cursor: pointer;
}
.context-content .hl-comment.hl-current {
  background: rgba(253, 224, 71, 0.7);
}

/* Notes (highlights and comments) lie under the issue and correction marks
   (spliceSpansIntoHtml's underMarks) and stand taller than them. Where a note
   is nested in a mark, the mark's colour shows through its middle and the
   note's colour in bands above and below, so the issue stays legible and the
   overlap shows. */
.context-content {
  --note-band: 0.22em;
}
.context-content .ann-hl {
  padding-block: var(--note-band);
}
.context-content .hl-comment {
  --note-tint: rgba(253, 224, 71, 0.45);
  padding-block: var(--note-band);
}
.context-content .ann-hl[data-ann-color="y"] { --note-tint: var(--ann-hl-y); }
.context-content .ann-hl[data-ann-color="g"] { --note-tint: var(--ann-hl-g); }
.context-content .ann-hl[data-ann-color="p"] { --note-tint: var(--ann-hl-p); }
.context-content .ann-hl[data-ann-color="b"] { --note-tint: var(--ann-hl-b); }
.context-content [data-issue-key] .ann-hl,
.context-content [data-issue-key] .hl-comment {
  padding-inline: 0;
  background:
    linear-gradient(var(--note-tint), var(--note-tint)) top / 100% var(--note-band) no-repeat,
    linear-gradient(var(--note-tint), var(--note-tint)) bottom / 100% var(--note-band) no-repeat;
}

.context-content .correction-accepted {
  background: rgba(34, 197, 94, 0.2);
  border-radius: 2px;
  padding: 1px 2px;
  color: #16a34a;
}

.context-content .hl-current.correction-accepted {
  background: rgba(34, 197, 94, 0.35);
}

/* Revised source text: marked as accepted corrected text is, without the
   padding, as it is drawn text node by text node inside other spans. */
.context-content .source-revised {
  background: rgba(34, 197, 94, 0.2);
  border-radius: 2px;
  color: #16a34a;
}

/* A deletion's place: an empty mark, a narrow highlighted bar with a
   caret under it (the owner: "make it and its highlight slightly wider"),
   all chrome, no text; on a touch screen, with a 44px tap target around it
   that takes no room in the line. */
/* A line break's symbol, chrome beside the text, taking no width. */
.context-content .source-br-mark {
  position: relative;
}
.context-content .source-br-mark::before {
  content: '\23CE';
  position: absolute;
  left: 2px;
  bottom: 0;
  color: var(--text-muted);
  font-size: 0.8em;
  pointer-events: none;
}
.context-content .source-deletion {
  position: relative;
  /* Above the blocks after it, each a containing block (the structural
     edits' reset rule, below), which would otherwise be drawn over its tap
     target. */
  z-index: 1;
  border-left: 2px solid #16a34a;
  background: none;
  /* Wider than the caret it is, and taking no more room in the line. */
  box-shadow: -3px 0 0 1px rgba(34, 197, 94, 0.35);
  cursor: pointer;
}
.context-content .source-deletion::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: -6px;
  transform: translateX(-50%);
  border: 4px solid transparent;
  border-top: none;
  border-bottom: 5px solid #16a34a;
}
@media (pointer: coarse) {
  .context-content .source-deletion::before {
    content: '';
    position: absolute;
    top: 50%;
    left: -12px;
    width: 24px;
    height: 44px;
    transform: translateY(-50%);
  }
}

/* Structural edits (blockEdits.js): a frame laid over each block, in it, so
   that it is laid out with the block; its handle in the pane's left margin,
   and its marks over its text. None of it holds text: a label is drawn from
   its data-label. Every block element of a pane is a containing block, so
   that its frame is laid out over it, always: toggling it with the frames
   restyled the whole pane. It is in the reset layer, below the book's
   sheets, so a book's own absolute, fixed or sticky block stands as the book
   places it, and still holds its frame; one a book's sheet makes static has
   its frame anchored to its edges (blockEdits.js, wholePlacement). */
@layer reset {
  .context-content :is(p, div, li, h1, h2, h3, h4, h5, h6, blockquote, dd, dt, figcaption, pre,
    address, section, article, aside, header, footer, figure, caption, td, th) {
    position: relative;
  }
  /* A list's markers stand inside the text column, as a browser's own
     sheet puts them, not in the margin where the bars are; a book's sheet
     may still place them. */
  .context-content :is(ul, ol) {
    padding-inline-start: 40px;
  }
}
/* A frame makes no box of its own, so what it holds is laid out over its
   block's element: a box for every block of a large chapter, in each pane,
   costs its layout. One of lines inside an element, anchored to them, or
   drawn waiting makes one. */
.context-content .block-frame {
  display: contents;
}
.context-content .block-frame.is-lines {
  display: block;
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.context-content .block-frame.is-lines {
  inset: auto 0;
}
.context-content .block-frame > * {
  pointer-events: auto;
}
/* The handle's bar is its background, not a box of its own: a large
   chapter has a handle for every block in each pane. Every handle stands in
   one column, its frame shifted by `--bar-shift` from its block's left edge
   to the pane's text start (blockEdits.js, alignToColumn), whatever the
   block's indent, nesting or alignment. Beside a block shorter than 12px
   (a rule, an empty element) it is 12px, reaching up into the pane's block
   margin above it, where no other bar is. */
.context-content .block-handle {
  --bar: var(--border);
  --bar-left: 5px;
  --bar-width: 3px;
  position: absolute;
  top: min(0px, calc(100% - 12px));
  bottom: 0;
  right: calc(100% - var(--bar-shift, 0px));
  width: 14px;
  margin-right: 8px;
  padding: 0;
  border: none;
  background: linear-gradient(var(--bar), var(--bar)) calc(14px - var(--bar-left) - var(--bar-width)) 2px /
    var(--bar-width) calc(100% - 4px) no-repeat;
  cursor: pointer;
  /* Above the blocks after its own, every one positioned: a paragraph
     beside a floated figure lays its box under the figure's. */
  z-index: 1;
}
/* A figure floated beside a paragraph has its handle in the paragraph's
   margin: the figure's, the smaller, stands over the paragraph's there. */
.context-content .block-handle[data-media] {
  z-index: 2;
}
.context-content .block-handle:hover {
  --bar: var(--text-muted);
}
.context-content .block-frame.is-inserted .block-handle {
  --bar: #16a34a;
}
.context-content .block-frame.is-selected .block-handle {
  --bar: var(--accent);
  --bar-left: 3px;
  --bar-width: 7px;
}
@media (pointer: coarse) {
  /* A 44px target, its bar the block's full height, as on every device (the
     owner: "the paragraph bars are smaller on my tablet"); beside a short
     block the target reaches past the block, invisibly, to 44px tall. */
  .context-content .block-handle::before {
    content: '';
    position: absolute;
    top: min(0px, calc(50% - 22px));
    bottom: min(0px, calc(50% - 22px));
    left: 0;
    right: 0;
  }
}
/* A marker between blocks stands half the pane's 12px block margin from
   the text beside it. */
.context-content .block-marker {
  position: absolute;
  left: 0;
  right: 0;
  height: 0;
}
.context-content .block-marker.at-start {
  top: -6px;
}
.context-content .block-marker.at-end {
  top: calc(100% + 6px);
}
.context-content .block-marker-line {
  position: absolute;
  left: 0;
  right: 0;
  height: 0;
  border-top: 2px dashed #16a34a;
  pointer-events: none;
}
.context-content .block-marker-line.is-waiting {
  border-top-color: var(--text-muted);
}
.context-content .block-marker-pill {
  position: absolute;
  left: 100%;
  min-width: 16px;
  height: 16px;
  margin: -8px 0 0 2px;
  padding: 0 6px;
  border: 1px solid #16a34a;
  border-radius: 8px;
  background: var(--bg-pane);
  color: #16a34a;
  font: 600 10px/14px system-ui, -apple-system, sans-serif;
  white-space: nowrap;
  cursor: pointer;
  transform: translateX(-100%);
}
.context-content .block-marker-pill::after {
  content: attr(data-label);
}
.context-content .block-marker-pill[aria-disabled="true"] {
  border-color: var(--border);
  color: var(--text-muted);
  cursor: default;
}
@media (pointer: coarse) {
  .context-content .block-marker-pill {
    height: 32px;
    margin-top: -16px;
    line-height: 30px;
    border-radius: 16px;
  }
  /* A 44px target, as the deletion caret's, over the pill's own 32px. */
  .context-content .block-marker-pill::before {
    content: '';
    position: absolute;
    top: 50%;
    left: 0;
    right: 0;
    height: 44px;
    transform: translateY(-50%);
  }
}

/* The selection's toolbar: the correction toolbar's family (.cw-pick-btn
   pictograms in a small strip), floating beside the first block selected. */
.block-toolbar {
  position: fixed;
  z-index: 1200;
  padding: 4px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.18);
}
.block-toolbar[hidden] {
  display: none;
}
.block-toolbar-actions {
  display: flex;
  gap: 4px;
}
.block-class {
  max-width: 180px;
  height: 28px;
  padding: 0 4px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg);
  color: var(--text);
  font: 12px system-ui, -apple-system, sans-serif;
}
@media (pointer: coarse) {
  .block-class {
    height: 44px;
  }
}
.block-tool:disabled {
  opacity: 0.45;
  cursor: default;
}
.block-tool:disabled:hover {
  background: var(--bg);
  color: var(--text);
}
@media (pointer: coarse) {
  .block-tool {
    width: 44px;
    height: 44px;
  }
}

/* The divider in the gap above a block, or below the last, while a pointer
   is near it, once a tap was on it, or above and below a selection: in the
   removal marker's language, a line across the text and a round + at its
   start in the left margin, beyond the block bars, that adds a block there.
   It fades in (150 ms) and out (200 ms), taking no press while hidden.
   Shown, it is 44px tall about the pane's 12px block margin and reaches into
   the margin, so a finger or the pointer keeps it on its way to the +. */
.context-content .block-gap {
  position: absolute;
  top: -28px;
  left: calc(-46px + var(--bar-shift, 0px));
  right: 0;
  height: 44px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 200ms ease-out;
}
.context-content .block-gap.is-shown {
  opacity: 1;
  pointer-events: auto;
  transition: opacity 150ms ease-in;
}
.context-content .block-gap.at-end {
  top: calc(100% - 16px);
}
.context-content .block-gap::before {
  content: '';
  position: absolute;
  top: 21px;
  left: calc(46px - var(--bar-shift, 0px));
  right: 0;
  border-top: 2px dashed var(--accent);
  pointer-events: none;
}
.context-content .block-gap-add {
  position: absolute;
  top: 50%;
  left: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border: 1px solid var(--accent);
  border-radius: 50%;
  background: var(--bg-pane);
  color: var(--accent);
  cursor: pointer;
  transform: translateY(-50%);
}
.context-content .block-gap-add .shave-icon {
  width: 12px;
  height: 12px;
  display: block;
}
.context-content .block-gap-add:disabled {
  border-color: var(--border);
  color: var(--text-muted);
  cursor: default;
}
@media (pointer: coarse) {
  /* A 44px target over the button's own 32px. */
  .context-content .block-gap-add {
    width: 32px;
    height: 32px;
  }
  .context-content .block-gap-add::before {
    content: '';
    position: absolute;
    inset: -6px;
  }
}
@media (pointer: coarse), (max-width: 1023px) {
  /* The 54px gutter holds the comment markers and the block bars only: the
     bars, 14px out from the text, take a 44px target reaching over its
     start, and the divider's + stands at the text's start, opaque, over
     it. */
  .context-content .block-handle {
    width: 44px;
    margin-right: -30px;
    background-position-x: 0;
  }
  .context-content .block-gap {
    left: var(--bar-shift, 0px);
  }
  .context-content .block-gap::before {
    left: calc(40px - var(--bar-shift, 0px));
  }
  .context-content .block-gap-add {
    left: 0;
    z-index: 2;
  }
}

/* A new block typed in place (blockEdits.js): an element of its kind, so it
   takes the book's look and the editor's block margins, holding a textarea
   that reads as its text; a refusal is drawn from data-error. In the target
   pane its placeholder stands at the same place, until the server's
   transliteration fills it. */
.context-content .block-new {
  position: relative;
}
.context-content .block-new-text {
  display: block;
  width: 100%;
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  border: none;
  outline: 2px dashed var(--accent);
  outline-offset: 4px;
  background: transparent;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-align: inherit;
  text-indent: inherit;
  text-transform: inherit;
  resize: none;
  overflow: hidden;
}
.context-content .block-new[data-error]::after {
  content: attr(data-error);
  display: block;
  margin-top: 8px;
  color: var(--text-error);
  font: 400 13px/1.3 system-ui, -apple-system, sans-serif;
  text-indent: 0;
}
.context-content .block-new.is-mirror {
  min-height: 1lh;
  border-radius: 3px;
  background: repeating-linear-gradient(135deg, rgba(127, 127, 127, 0.18) 0 6px, transparent 6px 12px);
}
/* A block added, waiting for the server: shown as typed, its text drawn
   from data-text (the pane holds no text of its own), and an empty block at
   its place in the target pane, which the server's transliteration fills. */
.context-content .block-new.is-pending::before {
  content: attr(data-text);
  white-space: pre-wrap;
}
.context-content .block-new.is-revisable {
  cursor: text;
}
.context-content .block-new.is-mirror.is-pending {
  background: none;
}
/* A removal waiting for the server: its blocks gone at once, in both panes;
   lines of a block, which stay, are covered. */
.context-content :is(p, div, li, h1, h2, h3, h4, h5, h6, blockquote, dd, dt, figcaption, pre, address,
  section, article, aside, header, footer, figure, caption, tr):has(> .block-frame.is-removing:not(.is-lines)),
.context-content tr:has(> td:last-child > .block-frame.is-removing),
.context-content tr:has(> th:last-child > .block-frame.is-removing) {
  display: none !important;
}
.context-content .block-frame.is-lines.is-removing {
  background: var(--bg-pane);
}
.block-kind {
  font: 600 12px/1 system-ui, -apple-system, sans-serif;
}
.block-kind[aria-pressed="true"] {
  background: var(--accent);
  color: white;
}

/* The app's btn-primary and btn-secondary, sized for a title bar or a
   popover. */
.block-edit-btn {
  flex: 0 0 auto;
  min-height: 28px;
  padding: 0 10px;
  font-size: 12px;
}
.block-edit-btn .is-short {
  display: none;
}
@media (pointer: coarse) {
  .block-edit-btn {
    min-width: 44px;
    min-height: 44px;
  }
}
@media (max-width: 720px) {
  .block-edit-btn .is-long {
    display: none;
  }
  .block-edit-btn .is-short {
    display: inline;
  }
}

/* The section mark (blockEdits.js addSectionMark), in the bars' one column
   just above the spine item's first block, in both panes: its tap opens the
   section menu (sectionEdits.js). Its glyph is CSS, so the pane holds no
   text of its own. */
.context-content .block-section-mark {
  position: absolute;
  bottom: calc(100% + 4px);
  right: calc(100% - var(--bar-shift, 0px));
  width: 20px;
  height: 20px;
  margin-right: 5px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg-pane);
  color: var(--text-muted);
  font: 600 13px/18px system-ui, -apple-system, sans-serif;
  cursor: pointer;
  z-index: 2;
}
.context-content .block-section-mark.at-after {
  bottom: auto;
  top: calc(100% + 4px);
}
.context-content .block-section-mark::before {
  content: '\00A7';
}
.context-content .block-section-mark:hover,
.context-content .block-section-mark[aria-expanded="true"] {
  color: var(--text);
  border-color: var(--text-muted);
}
@media (pointer: coarse) {
  /* A 44px target over the mark's own 20px. */
  .context-content .block-section-mark::after {
    content: '';
    position: absolute;
    inset: -12px;
  }
}
/* The editor of a new block, and a removal's box: one floating box. */
.block-edit-popover {
  position: fixed;
  z-index: 1200;
  box-sizing: border-box;
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg-pane);
  color: var(--text);
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);
  font-size: 14px;
}
.block-edit-popover[hidden] {
  display: none;
}
.block-insert-form {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.block-insert-tag,
.block-insert-input,
.block-insert-text {
  font: inherit;
  font-size: 16px;
  color: var(--text);
  background: var(--bg-pane);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 6px 8px;
}
.block-insert-text {
  resize: vertical;
  min-height: 4.5em;
}
.block-insert-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}
.block-insert-error {
  margin: 0;
  color: var(--text-error);
  font-size: 13px;
}
.block-removal-title {
  margin: 0 0 4px;
  font-weight: 600;
}
.block-removal-box.cw-root {
  gap: 8px;
  background: none;
}
.block-removal-box .cw-word {
  font-size: 14px;
}
.block-removal-box .cw-type {
  font-size: 11px;
  padding: 1px 6px;
}

/* Dirty: user has a draft replacement here that DOES apply at export,
   but hasn't been committed. Visually distinct from accepted (solid
   green) and from the active cursor (blue). Dashed outline conveys
   "draft". */
.context-content .correction-dirty {
  background: rgba(234, 179, 8, 0.16);
  border: 1px dashed rgba(202, 138, 4, 0.7);
  border-radius: 2px;
  padding: 0 2px;
  color: #a16207;
}

.context-content .hl-current.correction-dirty {
  background: rgba(234, 179, 8, 0.32);
  border-color: #ca8a04;
}

.context-content .correction-pending {
  background: rgba(79, 70, 229, 0.12);
  border-radius: 2px;
  padding: 1px 2px;
  color: #6366f1;
  font-style: italic;
}

/* Active inline-edit surface: the `.hl-current` target span turned into a
   contentEditable typing surface, which reads as the text around it, with a
   caret and nothing else (the owner: "no longer do any styling changes in the
   context panel where the editing is happening: just having a cursor and
   making it look and feel like wysiwyg editing"). It drops `.hl-current`'s
   ring, fill, bold and padding, and the confidence/correction colour, italic
   and draft border, taking the surrounding text's own look; the other
   issues' highlights stay. The caret is the text's own ink, in either theme.
   Scoped to `.hl-current.editing` so it outranks `.hl-current.correction-*`
   and `.hl-comment.hl-current` by source order. */
.context-content .hl-current.editing {
  background: transparent;
  color: inherit;
  font-weight: inherit;
  font-style: inherit;
  padding: 0;
  border: none;
  outline: none;
  box-shadow: none;
  caret-color: var(--text);
}

/* Emptied, the surface is an inline-block at the line's normal height: an
   empty inline had WebKit draw the caret below its line, the
   owner's "if I delete all the text in my selection, the cursor jumps down"
   (blocks2.8). It is a pixel wide, where WebKit paints the caret, so a mark
   that follows it (a deletion's place) stands where it stood. */
.context-content .hl-current.editing:empty {
  display: inline-block;
  line-height: normal;
  min-width: 1px;
}

/* Over the dark theme's ring, which outranks the rule above. */
:root[data-theme="dark"] .context-content .hl-current.editing {
  box-shadow: none;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .context-content .hl-current.editing {
    box-shadow: none;
  }
}

/* Attribute-sourced issue context (<img alt="...">, etc.) */
.context-content .alt-text-banner {
  display: inline-block;
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: #7c3aed;
  background: rgba(124, 58, 237, 0.10);
  border: 1px solid rgba(124, 58, 237, 0.25);
  border-radius: 4px;
  padding: 2px 8px;
  margin-bottom: 10px;
}

.context-content .alt-text-body {
  font-style: italic;
  color: var(--text-secondary, #4b5563);
}

.context-content .alt-text-missing {
  color: #b91c1c;
  font-style: italic;
}

/* Floating editor popup. The widget itself (cw-compact) provides position,
   sizing, padding, border, shadow — we only override z-index so the
   popup stacks above the cover popover (200) but below the modal
   backdrop (1000). */
#word-popup {
  z-index: 500;
}

.threshold-control {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--text-muted);
}

.threshold-control input[type="range"] {
  width: 120px;
}


.metadata-hint {
  font-size: 13px;
  color: var(--text-muted);
  margin-bottom: 16px !important;
}

.metadata-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 16px;
}

.metadata-field label {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.metadata-input {
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
  font-size: 16px;
  width: 100%;
  box-sizing: border-box;
}

.metadata-input:focus {
  outline: none;
  border-color: var(--accent, #4a90d9);
}

/* === No-issue state === */

.no-issues-msg {
  text-align: center;
  padding: 40px 20px;
  color: var(--text-muted);
  font-size: 15px;
}

.cancel-queued-btn {
  margin-top: 16px;
  padding: 6px 14px;
  font-size: 13px;
  background: transparent;
  color: var(--text-muted);
  border: 1px solid var(--border);
  border-radius: 4px;
  cursor: pointer;
}
.cancel-queued-btn:hover:not(:disabled) {
  background: rgba(239, 68, 68, 0.08);
  color: #dc2626;
  border-color: #dc2626;
}
.cancel-queued-btn:disabled {
  opacity: 0.5;
  cursor: wait;
}

/* === Session list === */

.session-list-section {
  flex: 1 1 480px;
  max-width: 480px;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.session-list-header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 12px;
}

.session-list-heading {
  font-size: 13px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  margin: 0;
  flex: 1 1 auto;
}

.session-list-filter-toggle {
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  font-size: 14px;
  line-height: 1;
  padding: 4px 6px;
  cursor: pointer;
}

.session-list-filter-toggle:hover {
  background: var(--bg-pane);
  border-color: var(--border);
  color: var(--text);
}

.session-list-filter-toggle[aria-expanded="true"] {
  background: var(--bg-pane);
  border-color: var(--border);
  color: var(--text);
}

.session-list-filters {
  display: flex;
  gap: 8px;
  margin-bottom: 12px;
  flex-wrap: wrap;
  align-items: center;
}

.session-list-filters input,
.session-list-filters select {
  padding: 6px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-pane);
  color: var(--text);
  font-size: 13px;
  font-family: inherit;
}

.session-list-filters input {
  flex: 1 1 140px;
  min-width: 120px;
}

.session-list-filters select {
  flex: 0 0 auto;
}

.session-list-filters input:focus,
.session-list-filters select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.15);
}

.session-list-filter-clear {
  padding: 4px 10px;
  font-size: 16px;
  line-height: 1;
}

.session-list-filter-input-wrap {
  position: relative;
  display: inline-flex;
  flex: 1 1 140px;
  min-width: 120px;
}

.session-list-filter-input-wrap input {
  flex: 1 1 auto;
  padding-right: 26px;
}

.session-list-filter-input-clear {
  position: absolute;
  right: 4px;
  top: 50%;
  transform: translateY(-50%);
  background: transparent;
  border: 0;
  padding: 2px 6px;
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  color: var(--text-muted);
}

.session-list-filter-input-clear:hover {
  color: var(--text);
}

.session-list-empty-filtered {
  font-size: 13px;
  color: var(--text-muted);
  padding: 12px 4px;
}

.session-list-offline-note,
.session-list-partial-note {
  font-size: 13px;
  color: var(--text-muted);
  padding: 0 4px 8px;
}

/* Flows inline in the already-scrollable `.dashboard-row` parent — no
   independent inner scroll (mirrors `.messages-list`). The "More…"
   affordance (`.session-list-more`) caps the visible tile count instead. */
.session-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex: 0 0 auto;
}

/* Matches `.messages-more` — the sibling feed's "More…" affordance. */
.session-list-more {
  align-self: flex-start;
  font-size: 12px;
  color: var(--link);
  text-decoration: none;
  padding: 4px 0;
}

.session-list-more:hover {
  text-decoration: underline;
}

.session-card {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  background: var(--bg-pane);
  border: 1px solid var(--border);
  border-radius: 8px;
  transition: border-color 0.15s, box-shadow 0.15s;
}

.session-card:hover {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.08);
}

.session-card-cover-wrap {
  position: relative;
  flex-shrink: 0;
  width: 48px;
  height: 64px;
}

.session-card-cover {
  width: 48px;
  height: 64px;
  object-fit: contain;
  border-radius: 4px;
  flex-shrink: 0;
  cursor: pointer;
  background: var(--border);
}

.session-card-borrowed-badge {
  position: absolute;
  top: -6px;
  right: -6px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: #facc15;
  color: #1c1c1c;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
}
.session-card-borrowed-badge .shave-icon {
  width: 13px;
  height: 13px;
}

/* Finished mark. Bottom corner so it clears the borrowed badge above it —
   a borrowed book you have finished carries both. Green because the mark
   is an achievement, not a status the app assigned. */
.session-card-finished-badge {
  position: absolute;
  bottom: -6px;
  right: -6px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: #16a34a;
  color: #ffffff;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
}

.session-card-finished-badge .shave-icon {
  width: 13px;
  height: 13px;
}

/* Coverless tile: the badge joins the title-row pill cluster, so it flows
   with the text instead of hanging off a corner that isn't there. */
.session-card-finished-badge-inline {
  position: static;
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
}

.session-card-finished-badge-inline .shave-icon {
  width: 11px;
  height: 11px;
}

.session-card-user {
  font-size: 11px;
  color: var(--text-muted);
  font-style: italic;
  margin-top: 2px;
}

.session-card-info {
  flex: 1;
  cursor: pointer;
  min-width: 0;
}

.session-card-title-row {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  flex-wrap: wrap;
}

.session-card-title {
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1 1 auto;
  min-width: 0;
}

.dialect-badge {
  display: inline-flex;
  align-items: center;
  box-sizing: border-box;
  height: 16px;
  font-size: 10px;
  line-height: 1;
  font-weight: 700;
  letter-spacing: 0.5px;
  padding: 0 6px;
  border-radius: 3px;
  background: var(--badge-bg, rgba(127, 127, 127, 0.15));
  color: var(--text-muted);
  flex: 0 0 auto;
}

/* Script-direction pill modifier. Same chrome as the base
   .dialect-badge (font, padding, border-radius) — only the colour
   tokens change so the pill reads as a distinct accent. Uses theme
   variables so dark mode picks up the right palette automatically. */
.dialect-badge-script {
  background: var(--script-badge-bg, rgba(79, 70, 229, 0.18));
  color: var(--script-badge-fg, #4f46e5);
}

/* Dialect pill modifier (GB/US). Yellow palette — visually distinct
   from the script (indigo) and published (green) pills it sits beside
   in the title-row cluster. */
.dialect-badge-gb,
.dialect-badge-us {
  background: rgba(234, 179, 8, 0.18);
  color: #854d0e;
}

/* Merged published+access-mode pill: green background, eye (read-only)
 * or pencil (editable) icon. Absence of this pill on a book means the
 * book is private — the gating logic lives in publishedBadge.js.
 * Inherits the base .dialect-badge box (padding, border-radius) so it
 * sits at the same height as the text-bearing pills in the cluster.
 * The icon is sized to match the cap-height of `font-size: 10px`
 * text and centred via the flex layout. */
.dialect-badge-published {
  display: inline-flex;
  align-items: center;
  background: rgba(34, 197, 94, 0.18);
  color: #16a34a;
}

.published-badge-icon {
  width: 12px;
  height: 12px;
  flex: 0 0 auto;
  color: currentColor;
}

.session-card-meta-text {
  min-width: 0;
}

.session-card-meta {
  font-size: 13px;
  color: var(--text-muted);
  margin-top: 2px;
}

.session-card-bar {
  margin-top: 4px;
  display: flex;
  align-items: center;
  gap: 8px;
}

.session-card-bar-track {
  flex: 1 1 auto;
  height: 4px;
  background: rgba(127, 127, 127, 0.18);
  border-radius: 2px;
  overflow: hidden;
}

.session-card-bar-fill {
  height: 100%;
  background: var(--accent);
  transition: width 200ms ease;
}

.session-card-bar-label {
  flex: 0 0 auto;
  font-size: 12px;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* The accent reads as "work you have done to the book"; reading position
   is not that, so it takes the muted fill. */
.session-card-reading .session-card-bar-fill {
  background: var(--text-muted);
}

.session-card-state {
  display: inline-block;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 1px 6px;
  border-radius: 3px;
  margin-top: 4px;
}

.session-card-state-transliterating {
  background: rgba(234, 179, 8, 0.15);
  color: #a16207;
}

.session-card-state-queued {
  background: rgba(100, 116, 139, 0.15);
  color: #475569;
}

.session-card-state-error {
  background: rgba(239, 68, 68, 0.12);
  color: #dc2626;
}

.session-card-state-unknown {
  background: var(--border);
  color: var(--text-muted);
}

.session-card-delete {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  border: none;
  background: none;
  color: var(--text-muted);
  font-size: 18px;
  cursor: pointer;
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.15s, background 0.1s;
}

.session-card:hover .session-card-delete {
  opacity: 1;
}

.session-card-delete:hover {
  background: rgba(239, 68, 68, 0.1);
  color: #dc2626;
}

.session-card[data-card-kind="borrowed-tombstone"] {
  opacity: 0.55;
  font-style: italic;
}

.session-card[data-card-kind="borrowed-tombstone"]:hover {
  border-color: var(--border);
  box-shadow: none;
}

.session-card[data-card-kind="borrowed-tombstone"] .session-card-delete {
  opacity: 1;
}

/* === Library footer (other users' published books) === */

.library-footer-section {
  flex: 0 0 auto;
  border-top: 1px solid var(--border);
  background: var(--bg-pane);
}

.library-footer-header {
  position: static;
}

.library-footer-section .library-strip {
  display: none;
}

body.library-expanded #library-strip-section .library-strip {
  display: flex;
  gap: 12px;
  overflow-x: auto;
  padding: 12px 16px 16px 16px;
  scroll-snap-type: x proximity;
}

/* /library focused mode — the public-library section takes over the
   dashboard's visible area. The dashboard row (your-books + upload box)
   is hidden entirely so the filter row sits flush against the toolbar
   with no dead vertical space; the collapsible "Library" header
   band belongs to the carousel mode only. */
body.library-mode .dashboard-row {
  display: none;
}
body.library-mode .library-footer-header {
  display: none;
}
body.library-mode #library-strip-section {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
  border-top: 0;
}
/* Fixed 3-column centered grid: cards keep their natural width, the
   group as a whole centers in the viewport. Collapses to 2/1 columns
   on narrower viewports — never exceeds the column's width budget. */
body.library-mode #library-strip-section .library-strip {
  display: grid;
  grid-template-columns: repeat(3, 200px);
  justify-content: center;
  gap: 16px;
  overflow-x: visible;
  overflow-y: auto;
  padding: 16px;
  scroll-snap-type: none;
}
body.library-mode #library-strip-section .library-strip .library-card {
  flex: 0 0 auto;
  width: 100%;
}
.library-expanded-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 8px 16px 4px 16px;
  align-items: center;
  justify-content: center;
}
.library-expanded-filters input[type="search"],
.library-expanded-filters select {
  font: inherit;
  padding: 4px 8px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg);
  min-width: 0;
}
.library-expanded-filters input[type="search"] {
  flex: 0 1 200px;
  max-width: 240px;
}
@media (max-width: 720px) {
  body.library-mode #library-strip-section .library-strip {
    grid-template-columns: repeat(2, 176px);
    padding: 12px;
  }
  .library-expanded-filters {
    padding: 8px 12px 4px 12px;
  }
  .library-expanded-filters input[type="search"] {
    flex: 1 1 100%;
    max-width: none;
  }
}
@media (max-width: 380px) {
  body.library-mode #library-strip-section .library-strip {
    grid-template-columns: 176px;
  }
}

.library-card {
  flex: 0 0 144px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 8px;
  background: var(--bg-pane);
  border: 1px solid var(--border);
  border-radius: 6px;
  cursor: pointer;
  scroll-snap-align: start;
  transition: border-color 0.15s, box-shadow 0.15s;
}

.library-card:hover {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.08);
}

.library-card-link {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  color: inherit;
  text-decoration: none;
}

.library-card-cover-wrap {
  width: 100%;
}

.library-card-cover {
  width: 100%;
  height: 160px;
  object-fit: contain;
  border-radius: 4px;
  background: var(--border);
  display: block;
}

.library-card-cover-placeholder {
  width: 100%;
  height: 160px;
  border-radius: 4px;
  background: var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-muted);
}
.library-card-cover-placeholder .shave-icon {
  width: 48px;
  height: 48px;
}

.library-card-title-row {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.library-card-title {
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1 1 auto;
  min-width: 0;
}

.library-card-author {
  font-size: 11px;
  color: var(--text-muted);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.library-card-user {
  font-size: 10px;
  color: var(--text-muted);
  font-style: italic;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Co-located pill cluster (dialect, script, merged published) at the
   bottom-right of the card chrome. Lives in the card body, not on the
   cover artwork — keeps the cover image clean. flex-wrap so the pills
   stack gracefully on narrow tiles. */
.library-card-pills {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 4px;
  margin-top: auto;
}

/* Cloud-download control that rides the tile's tag/pill row (My Books +
   Library) and the details-dialog badge row. Icon-only, sized to sit inline
   with the pills; hidden entirely once the book is downloaded (absence = done).
   The tile control paints its own display:none, so this is the visible state. */
.session-card-offline {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  padding: 0;
  border: none;
  background: none;
  color: var(--text-muted);
  cursor: pointer;
  border-radius: 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.session-card-offline:hover:not(:disabled) {
  background: var(--hover-bg, rgba(0, 0, 0, 0.06));
  color: var(--accent);
}

.session-card-offline:disabled {
  cursor: default;
}

.session-card-offline .shave-icon {
  width: 16px;
  height: 16px;
}

/* Out-of-sync badge, a sibling of the download control in the same pill row.
   It shares that control's box so the two read as one cluster, and differs
   only in colour — which is what carries the two states apart at a glance:
   'queued' is muted (transient, clears itself, nothing to do) while 'blocked'
   is the warning colour (needs the user, will not clear on its own). */
.session-card-sync {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  border-radius: 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text-muted);
  position: relative;
}

.session-card-sync .shave-icon {
  width: 16px;
  height: 16px;
}

.session-card-sync:hover {
  background: var(--hover-bg, rgba(0, 0, 0, 0.06));
}

.session-card-sync[data-state="blocked"],
.session-card-sync[data-state="unfit"] {
  color: var(--warning, #d97706);
}

/* The blocked badge is the one tile affordance that must not be missed, so it
   also carries a dot — colour alone fails for the colour-blind and washes out
   on a bright screen. Pinned inside the button box (not offset out of it, which
   clips against the 22px bounds) and ringed in the tile background so it stays
   legible where it overlaps the glyph beneath. */
.session-card-sync[data-state="blocked"]::after,
.session-card-sync[data-state="unfit"]::after {
  content: "";
  position: absolute;
  top: 1px;
  right: 1px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--warning, #d97706);
  box-shadow: 0 0 0 1.5px var(--bg-pane);
}

/* Determinate progress ring shown while a tile download runs. The track is a
   faint full circle; the arc fills clockwise as the download progresses. */
.offline-tile-ring {
  display: inline-flex;
  width: 16px;
  height: 16px;
}

.offline-tile-ring .shave-icon {
  width: 16px;
  height: 16px;
}

.offline-tile-ring-track {
  fill: none;
  stroke: var(--border, rgba(0, 0, 0, 0.15));
  stroke-width: 2;
}

.offline-tile-ring-arc {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linecap: round;
  transition: stroke-dashoffset 0.2s ease;
}

.library-more {
  flex: 0 0 auto;
  align-self: center;
  display: inline-flex;
  align-items: center;
  padding: 8px 16px;
  margin-left: 8px;
  background: var(--accent);
  border: none;
  border-radius: var(--radius-sm);
  color: white;
  text-decoration: none;
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
  scroll-snap-align: start;
}

.library-more:hover {
  background: #4338ca;
}

.library-empty {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  padding: 0 12px;
  color: var(--text-muted);
  font-size: 13px;
  font-style: italic;
}

.library-signin-prompt {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding: 12px 16px;
}

.library-signin-prompt p {
  margin: 0;
  color: var(--text-muted);
  font-size: 14px;
}

.session-list-empty {
  padding: 16px;
  color: var(--text-muted);
  font-size: 13px;
  font-style: italic;
  text-align: center;
}

.list-notice-retry {
  margin-left: 8px;
  padding: 4px 12px;
  font-size: 13px;
  font-style: normal;
}

/* === Library viewer (read-only published book) === */

/* The correction column and word popup stay visible for read-only viewers
 * — the widget itself self-gates editing via book.readOnly. We only hide
 * controls that would mutate the book (e.g. the dialect picker). */
body.library-viewer .toolbar-radio-group {
  display: none !important;
}

.toolbar-readonly-badge.collab {
  background: rgba(22, 163, 74, 0.18);
  color: #15803d;
}

/* === Share modal === */

.share-modal {
  max-width: 460px;
}

.share-busy,
.share-error {
  color: var(--text-muted);
}

.share-error {
  color: #dc2626;
}

.share-url-row {
  display: flex;
  gap: 8px;
  margin: 12px 0;
}

.share-url-input {
  flex: 1;
  padding: 6px 10px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
}

.share-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 12px;
}

.share-allow-edits,
.share-publish-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 8px 0;
  font-size: 13px;
  cursor: pointer;
}

/* === Issue section headers === */

.issue-section-header {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px 6px calc(10px + 12px * var(--depth, 0));
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  user-select: none;
  position: sticky;
  top: 0;
  z-index: 1;
}

.issue-section-header:hover {
  /* Use a theme token — the hardcoded #eeeef4 produced a bright
     near-white flash on dark backgrounds (issue-list section dividers
     AND the Public-library header which reuses this class). */
  background: var(--bg-pane-alt);
}

.issue-toc-attention {
  padding: 6px 12px 6px 10px;
  font-size: 12px;
  color: var(--text-muted);
  border-bottom: 1px solid var(--border);
}

.issue-section-chevron {
  font-size: 9px;
  flex-shrink: 0;
  opacity: 0.6;
}

.issue-section-label {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.issue-section-count {
  flex-shrink: 0;
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  opacity: 0.7;
}

.issue-section-hide-btn {
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg-pane);
  color: var(--text-muted);
  font-size: 12px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  padding: 0;
  font-family: inherit;
  transition: background 0.1s, color 0.1s, border-color 0.1s;
}

.issue-section-hide-btn:hover {
  background: var(--border);
}

.issue-section-hide-btn.active {
  background: rgba(34, 197, 94, 0.12);
  border-color: #86efac;
  color: #16a34a;
}

/* === Spinner === */

.spinner {
  display: inline-block;
  width: 20px;
  height: 20px;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.6s linear infinite;
  vertical-align: middle;
  margin-right: 8px;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

.toolbar-btn.saved {
  background: var(--accent);
  color: white;
}

/* ─── Comment widget ─── */
.cmt-root {
  display: flex;
  flex-direction: column;
  gap: 10px;
  background: var(--bg-pane);
}
.cmt-root.cmt-compact {
  position: fixed;
  z-index: 100;
  min-width: 280px;
  max-width: 380px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 6px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
/* An annotation's comment: a speech bubble in the highlight yellow, its tail
   at --cmt-tail-x pointing at the highlight from above or below. A
   correction's comment keeps the plain box. */
.cmt-root.cmt-annotation {
  --cmt-bubble: linear-gradient(var(--ann-hl-y), var(--ann-hl-y)), var(--bg-pane);
  background: var(--cmt-bubble);
  border-color: var(--ann-hl-y);
  border-radius: 14px;
}
.cmt-annotation.cmt-tail-up::before,
.cmt-annotation.cmt-tail-down::before {
  content: '';
  position: absolute;
  left: calc(var(--cmt-tail-x) - 7px);
  width: 12px;
  height: 12px;
  background: var(--cmt-bubble);
  border: 1px solid var(--ann-hl-y);
  transform: rotate(45deg);
}
.cmt-annotation.cmt-tail-up::before { top: -7px; border-right: 0; border-bottom: 0; }
.cmt-annotation.cmt-tail-down::before { bottom: -7px; border-left: 0; border-top: 0; }
/* A highlight's note has nothing to resolve, and no buttons: it is saved
   when it closes or loses focus. */
.cmt-annotation .cmt-resolve-row,
.cmt-annotation .cmt-actions { display: none; }
/* A comment shared with the book's editors: the link indigo the theme sets,
   in place of the yellow, on the bubble, its tail and its label. */
.cmt-root.cmt-shared {
  --cmt-shared-wash: color-mix(in srgb, var(--link) 12%, transparent);
  --cmt-bubble: linear-gradient(var(--cmt-shared-wash), var(--cmt-shared-wash)), var(--bg-pane);
  background: var(--cmt-bubble);
  border-color: var(--link);
}
.cmt-shared.cmt-tail-up::before,
.cmt-shared.cmt-tail-down::before { border-color: var(--link); }
.cmt-shared .cmt-label {
  background: color-mix(in srgb, var(--link) 18%, transparent);
  color: var(--link);
}
.cmt-share-toggle {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 11px;
  color: var(--text-muted);
  cursor: pointer;
}
.cmt-share-toggle[hidden] { display: none; }
.cmt-share-checkbox {
  margin: 0;
  width: 12px;
  height: 12px;
  accent-color: var(--link);
}
.cmt-shared .cmt-share-toggle { color: var(--link); }
.cmt-header {
  display: flex;
  align-items: baseline;
  gap: 10px;
}
.cmt-label {
  font-size: 11px;
  color: var(--text-muted);
  background: rgba(45, 212, 191, 0.18);
  color: #0d9488;
  padding: 1px 6px;
  border-radius: 3px;
}
.cmt-location {
  font-size: 12px;
  color: var(--text-muted);
  margin-left: auto;
}
.cmt-textarea {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 4px;
  font-size: 14px;
  font-family: inherit;
  resize: vertical;
  outline: none;
  background: var(--bg-pane);
  box-sizing: border-box;
}
.cmt-textarea:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.12);
}
.cmt-notice {
  color: var(--text-muted);
  font-size: 12px;
}
.cmt-error {
  padding: 6px 10px;
  border-radius: 4px;
  background: #fee2e2;
  color: #991b1b;
  font-size: 12px;
  border: 1px solid #fca5a5;
}
.cmt-resolve-row {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--text-muted);
  cursor: pointer;
}
.cmt-resolve-checkbox { margin: 0; }
.cmt-actions {
  display: flex;
  gap: 6px;
  align-items: center;
}
.cmt-btn-delete { margin-left: auto; }
.cmt-readonly .cmt-textarea {
  background: var(--bg, #f3f4f6);
  color: var(--text-muted, #6b7280);
  cursor: default;
}

/* ─── Comment issue-list rendering ─── */
.issue-comment-glyph {
  display: inline-block;
  width: 14px;
  text-align: center;
  margin-right: 4px;
  font-size: 14px;
  line-height: 1;
  flex-shrink: 0;
}
.comment-badge {
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 1px 6px;
  border-radius: 8px;
  background: rgba(45, 212, 191, 0.18);
  color: #0d9488;
  flex-shrink: 0;
  margin-left: auto;
  margin-right: 4px;
}
.issue-item.comment-resolved .issue-word {
  text-decoration: line-through;
  color: var(--text-muted);
}
.issue-item.comment-resolved .comment-badge {
  opacity: 0.6;
}

/* ─── Highlights and comments in the issue list ─── */
.issue-ann-swatch {
  width: 12px;
  height: 12px;
  border-radius: 3px;
  flex-shrink: 0;
}
.issue-ann-swatch[data-ann-color="y"] { background: var(--ann-hl-y); }
.issue-ann-swatch[data-ann-color="g"] { background: var(--ann-hl-g); }
.issue-ann-swatch[data-ann-color="p"] { background: var(--ann-hl-p); }
.issue-ann-swatch[data-ann-color="b"] { background: var(--ann-hl-b); }
.annotation-item .issue-word,
.source-edit-item .issue-word,
.issue-source-original,
.issue-ann-note {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.issue-ann-note {
  flex: 1;
  color: var(--text-muted);
  font-style: italic;
}
.issue-source-original {
  flex: 1;
  color: var(--text-muted);
  text-decoration: line-through;
}

/* ─── Book metadata modal ─── */
.modal.book-meta-modal {
  max-width: 640px;
  width: 90vw;
}
#book-meta-body {
  gap: 16px;
}
.book-meta-loading,
.book-meta-error {
  text-align: center;
  color: var(--text-muted);
  margin: 24px 0;
}
.book-meta-error { color: var(--danger, #c0392b); }

.book-meta-header {
  gap: 12px;
}
.book-meta-header h2 {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 15px;
  font-weight: 500;
  color: var(--text-muted);
}
.book-meta-icon-toolbar {
  display: flex;
  gap: 4px;
  flex: 0 0 auto;
}
.book-meta-icon-btn {
  background: transparent;
  border: 1px solid transparent;
  border-radius: 4px;
  cursor: pointer;
  padding: 4px 8px;
  font-size: 16px;
  line-height: 1;
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
.book-meta-icon-svg,
.book-meta-icon-btn .shave-icon {
  width: 18px;
  height: 18px;
  display: block;
}

/* Editor-widget icons (correction widget, issue panel) sit in tighter
   chrome than the metadata toolbar; render them at 14px. */
.cw-icon-btn .shave-icon,
.cw-pick-btn .shave-icon,
.issue-section-hide-btn .shave-icon,
.issue-section-chevron .shave-icon,
.issue-check .shave-icon,
.issue-pencil .shave-icon,
.issue-comment-glyph .shave-icon {
  width: 14px;
  height: 14px;
  display: block;
}
.cw-pick-btn .shave-icon {
  width: 18px;
  height: 18px;
}
.issue-section-chevron .shave-icon {
  width: 11px;
  height: 11px;
}
.book-meta-icon-btn:hover {
  background: var(--bg-pane, #fafafa);
  border-color: var(--border, #e5e5e5);
}
.book-meta-icon-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.book-meta-icon-btn-danger:hover {
  background: rgba(192, 57, 43, 0.12);
  border-color: rgba(192, 57, 43, 0.4);
  color: var(--danger, #c0392b);
}

/* A toggle that is currently on. The check is the same glyph either way,
   so the state has to be carried by colour — matching the tile badge, so
   pressing this and finding that on the tile are visibly one thing. */
.book-meta-icon-btn-on {
  background: rgba(22, 163, 74, 0.12);
  border-color: rgba(22, 163, 74, 0.4);
  color: #16a34a;
}

.book-meta-nav-chevron {
  background: transparent;
  border: 1px solid transparent;
  border-radius: 4px;
  cursor: pointer;
  padding: 4px;
  line-height: 0;
  color: var(--text-muted);
  flex: 0 0 auto;
}
.book-meta-nav-chevron .shave-icon {
  width: 18px;
  height: 18px;
  display: block;
}
.book-meta-nav-chevron:hover {
  color: var(--text);
  background: var(--bg-pane, #fafafa);
  border-color: var(--border, #e5e5e5);
}

.book-meta-split {
  display: flex;
  gap: 20px;
  align-items: flex-start;
}
.book-meta-right {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.book-meta-cover {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}
.book-meta-cover img {
  max-width: 220px;
  max-height: 330px;
  border-radius: 4px;
  box-shadow: 0 4px 16px rgba(0,0,0,0.18);
}
.book-meta-cover-placeholder {
  width: 220px;
  height: 310px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 72px;
  color: var(--text-muted);
  background: var(--bg-pane, #fafafa);
  border: 1px solid var(--border, #e5e5e5);
  border-radius: 4px;
  text-transform: uppercase;
}

.book-meta-headline {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.book-meta-headline-title {
  font-size: 32px;
  font-weight: 600;
  line-height: 1.1;
  color: var(--text);
  min-height: 1.1em;
}
.book-meta-headline-author {
  font-size: 20px;
  color: var(--text-muted);
  min-height: 1.2em;
}

.book-meta-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}

@media (max-width: 720px) {
  .book-meta-split {
    flex-direction: column;
    align-items: center;
  }
  .book-meta-right {
    width: 100%;
    align-items: stretch;
  }
  .book-meta-headline,
  .book-meta-badges,
  .book-meta-uploaded,
  .book-meta-modified {
    text-align: center;
  }
  .book-meta-badges {
    justify-content: center;
  }
  .book-meta-headline-title {
    font-size: 26px;
  }
  .book-meta-headline-author {
    font-size: 17px;
  }
}
.book-meta-owner {
  font-size: 12px;
  color: var(--text-muted);
}
.book-meta-sharing-public,
.book-meta-sharing-shared,
.book-meta-sharing-private {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.3px;
  padding: 2px 6px;
  border-radius: 3px;
  flex: 0 0 auto;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
.book-meta-sharing-public {
  background: rgba(34, 197, 94, 0.15);
  color: #16a34a;
}
.book-meta-sharing-shared {
  background: rgba(59, 130, 246, 0.15);
  color: #2563eb;
}
.book-meta-sharing-private {
  background: rgba(127, 127, 127, 0.15);
  color: var(--text-muted);
}
.book-meta-uploaded,
.book-meta-modified {
  font-size: 12px;
  color: var(--text-muted);
}

.book-meta-note {
  margin: 0;
}
.book-meta-note h3 {
  margin: 0 0 6px 0;
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}
.book-meta-note-body {
  padding: 10px 12px;
  border: 1px solid var(--border, #e5e5e5);
  border-radius: 4px;
  background: var(--bg-pane, #fafafa);
  min-height: 60px;
}
.book-meta-note.is-readonly .book-meta-note-body {
  padding: 0;
  border: 0;
  background: transparent;
  min-height: 0;
  color: var(--text-muted);
  font-size: 13px;
  font-style: italic;
}
.book-meta-note-collapsed { margin: 0; }
.book-meta-note-add {
  background: none;
  border: 1px dashed var(--border, #cfcfcf);
  border-radius: 4px;
  color: var(--text-muted);
  cursor: pointer;
  font: inherit;
  padding: 6px 10px;
}
.book-meta-note-add:hover {
  background: var(--bg-pane, #fafafa);
  color: var(--text, inherit);
}

.book-meta-transliteration-progress h3,
.book-meta-transliteration-error h3 {
  margin: 0 0 6px 0;
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}
.book-meta-transliteration-progress p { margin: 0; }
.book-meta-transliteration-error p { margin: 0 0 4px 0; }
.book-meta-transliteration-error { color: var(--danger, #c0392b); }
.book-meta-transliteration-error-hint {
  color: var(--text-muted);
  font-size: 12px;
}

.book-meta-admin-public {
  margin-right: auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--text-muted);
}

/* The long admin label plus both CTAs overflow at iPad-portrait width; let
   the footer wrap and give the toggle its own row so the CTAs stay reachable. */
#book-meta-footer:has(.book-meta-admin-public) {
  flex-wrap: wrap;
}
@media (max-width: 480px) {
  .book-meta-admin-public {
    flex-basis: 100%;
    margin-right: 0;
  }
}

/* Owner-only editable note hint (read-only viewers see plain text). */
.book-meta-note-body.editable:focus {
  background: var(--bg-pane, #fafafa);
  outline: 2px solid var(--accent, #2563eb);
  outline-offset: 2px;
  border-radius: 3px;
}
.book-meta-note-body.editable.empty::before {
  content: attr(data-placeholder);
  color: var(--text-muted);
  font-style: italic;
  pointer-events: none;
}

@media (max-width: 600px) {
  .modal.book-meta-modal {
    width: calc(100vw - 20px);
    min-width: 0;
    max-width: none;
    height: calc(100vh - 20px);
    max-height: calc(100vh - 20px);
    border: 1px solid var(--border, #e2e4e8);
    border-radius: var(--radius-md, 8px);
  }
  .book-meta-cover img,
  .book-meta-cover-placeholder {
    max-width: 160px;
    max-height: 240px;
  }
  .book-meta-cover-placeholder {
    width: 160px;
    height: 220px;
    font-size: 56px;
  }
  .book-meta-headline-title { font-size: 22px; }
  .book-meta-headline-author { font-size: 16px; }
  .book-meta-header h2 { font-size: 13px; }
  .book-meta-icon-btn { padding: 4px 6px; font-size: 14px; }
}

/* === Dark-mode tone-shifts for signal pills + tinted highlights ===
   Translucent tints over a dark background read as muddy mid-greys, so
   lift the alphas and switch text to the bright end of each hue. These
   rules ONLY fire under explicit dark theme or OS dark (without an
   override). Paper-keeps-light surfaces inherit the light cascade so
   tints rendered inside paper need no change. */
:root[data-theme="dark"] .issue-type-badge.badge-punct,
:root[data-theme="dark"] .type-filter-pill.badge-punct {
  background: rgba(45, 212, 191, 0.22);
  color: #5eead4;
}
:root[data-theme="dark"] .issue-type-badge.badge-unknown,
:root[data-theme="dark"] .type-filter-pill.badge-unknown {
  background: rgba(249, 115, 22, 0.22);
  color: #fdba74;
}
:root[data-theme="dark"] .issue-type-badge.badge-affix,
:root[data-theme="dark"] .type-filter-pill.badge-affix {
  background: rgba(168, 85, 247, 0.24);
  color: #d8b4fe;
}
:root[data-theme="dark"] .issue-type-badge.badge-homo,
:root[data-theme="dark"] .type-filter-pill.badge-homo {
  background: rgba(99, 102, 241, 0.28);
  color: #c7d2fe;
}
:root[data-theme="dark"] .issue-type-badge.badge-comment,
:root[data-theme="dark"] .type-filter-pill.badge-comment {
  background: rgba(253, 224, 71, 0.28);
  color: #fde68a;
}
:root[data-theme="dark"] .issue-type-badge.badge-source-edit,
:root[data-theme="dark"] .type-filter-pill.badge-source-edit {
  background: rgba(148, 163, 184, 0.28);
  color: #e2e8f0;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .issue-type-badge.badge-punct,
  :root:not([data-theme="light"]) .type-filter-pill.badge-punct {
    background: rgba(45, 212, 191, 0.22);
    color: #5eead4;
  }
  :root:not([data-theme="light"]) .issue-type-badge.badge-unknown,
  :root:not([data-theme="light"]) .type-filter-pill.badge-unknown {
    background: rgba(249, 115, 22, 0.22);
    color: #fdba74;
  }
  :root:not([data-theme="light"]) .issue-type-badge.badge-affix,
  :root:not([data-theme="light"]) .type-filter-pill.badge-affix {
    background: rgba(168, 85, 247, 0.24);
    color: #d8b4fe;
  }
  :root:not([data-theme="light"]) .issue-type-badge.badge-homo,
  :root:not([data-theme="light"]) .type-filter-pill.badge-homo {
    background: rgba(99, 102, 241, 0.28);
    color: #c7d2fe;
  }
  :root:not([data-theme="light"]) .issue-type-badge.badge-comment,
  :root:not([data-theme="light"]) .type-filter-pill.badge-comment {
    background: rgba(253, 224, 71, 0.28);
    color: #fde68a;
  }
  :root:not([data-theme="light"]) .issue-type-badge.badge-source-edit,
  :root:not([data-theme="light"]) .type-filter-pill.badge-source-edit {
    background: rgba(148, 163, 184, 0.28);
    color: #e2e8f0;
  }
}

/* Dark-mode tone-shift for the script-direction pill — same recipe as
   the other signal pills above (lift the indigo alpha, switch text to
   the bright end of the hue) so it reads on a dark surface. */
:root[data-theme="dark"] .dialect-badge-script {
  background: rgba(99, 102, 241, 0.28);
  color: #c7d2fe;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .dialect-badge-script {
    background: rgba(99, 102, 241, 0.28);
    color: #c7d2fe;
  }
}

:root[data-theme="dark"] .dialect-badge-gb,
:root[data-theme="dark"] .dialect-badge-us {
  background: rgba(250, 204, 21, 0.25);
  color: #fde047;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .dialect-badge-gb,
  :root:not([data-theme="light"]) .dialect-badge-us {
    background: rgba(250, 204, 21, 0.25);
    color: #fde047;
  }
}

/* === Editor floating toolbar (#119) ===
   Mirrors the reader's `.reader-floating-toolbar` (gui/reader.css). Visual
   intent: the same icon strip in the same bottom-RIGHT corner of both
   views, so the editor↔reader transition feels continuous. Position is
   viewport-fixed (the reader uses absolute-inside-fixed which has the
   same effect once the chrome layer is mounted at full viewport).

   Side choice — RIGHT (NOT left): #119 swap. The reader's progress chip
   moved to bottom-LEFT in the matching reader.css change so the
   editor's chrome layout (no progress chip on the left) mirrors it. */
.editor-floating-toolbar {
  display: none;
}
/* The toolbar's place and size, set once here; its height is one button
   plus its padding and border.

   Its bottom matches the reader toolbar's VISUAL height, not just its 16px
   inset (#93 / #51 follow-up). The reader toolbar is position:absolute
   inside `.reader-root`, whose `bottom: 24px` reserves the always-visible
   `.app-footer` band (height 24px); its 16px inset therefore clears the
   footer, landing 40px above the viewport bottom. This toolbar is
   position:FIXED against the layout viewport, so a bare `bottom: 16px`
   overlaps that same 24px footer and sits 24px lower than the reader's.
   Add the footer band back explicitly (24px, matching `.reader-root`'s
   bottom + `.app-footer`'s height) so both toolbars sit at one height; at
   narrow widths only the inset drops, to 4px, as in reader.css's 720px
   block. `env(safe-area-inset-bottom)` mirrors `.reader-loading`
   (reader.css): it is 0 under the app's default viewport-fit, and tracks
   the iPad home-indicator band if viewport-fit=cover is ever adopted. */
body.editor-mode {
  --editor-toolbar-bottom: calc(24px + 16px + env(safe-area-inset-bottom, 0px));
  --editor-toolbar-pad: 6px;
  --editor-toolbar-button: 28px;
  --editor-toolbar-height: calc(var(--editor-toolbar-button) + 2 * var(--editor-toolbar-pad) + 2px);
}
@media (pointer: coarse) {
  body.editor-mode {
    --editor-toolbar-button: 44px;
  }
}
@media (max-width: 720px) {
  body.editor-mode {
    --editor-toolbar-bottom: calc(24px + 4px + env(safe-area-inset-bottom, 0px));
    --editor-toolbar-pad: 4px;
  }
}
/* A phone's rounded corners clip a bar this low; 10px up clears them. */
@media (max-width: 720px) and (pointer: coarse) {
  body.editor-mode {
    --editor-toolbar-bottom: calc(24px + 4px + 10px + env(safe-area-inset-bottom, 0px));
  }
}
body.editor-mode .editor-floating-toolbar {
  position: fixed;
  bottom: var(--editor-toolbar-bottom);
  right: 16px;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  box-sizing: border-box;
  block-size: var(--editor-toolbar-height);
  padding: var(--editor-toolbar-pad) 8px;
  background: var(--bg-pane);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  color: var(--text);
  /* Above the panes' splitter (49, its handle 50), which side by side runs
     down behind the toolbar. */
  z-index: 51;
  pointer-events: auto;
  margin: 0;
}
/* Narrow viewports — tighter insets so the toolbar doesn't crowd the
   sidebar-rail / mobile chrome. Mirrors the reader.css 720px block. */
@media (max-width: 720px) {
  body.editor-mode .editor-floating-toolbar {
    right: 4px;
    padding: var(--editor-toolbar-pad) 6px;
    gap: 2px;
  }
}
.editor-floating-btn {
  background: transparent;
  border: 1px solid transparent;
  border-radius: 4px;
  padding: 4px 6px;
  cursor: pointer;
  color: inherit;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.editor-floating-btn:hover {
  background: var(--bg-pane-alt);
  border-color: var(--border);
}
.editor-floating-btn .shave-icon {
  width: 18px;
  height: 18px;
  display: block;
}
@media (pointer: coarse) {
  .editor-floating-btn {
    min-width: var(--editor-toolbar-button);
    min-height: var(--editor-toolbar-button);
  }
}


/* The style panel's button is offered only to a book's owner, and only
   while the book takes edits of its text. */
.editor-floating-btn[hidden] {
  display: none;
}
.editor-floating-btn[aria-pressed="true"] {
  background: var(--bg-pane-alt);
  border-color: var(--border);
}

/* === Style panel (stylePanel.js) ===
   It takes the issue list's place, over it in its column: the list stays laid
   out beneath it, hidden, so its scroll and selection are as they were when
   the panel closes, and neither pane changes width as it opens or closes.
   With the issue list collapsed to its rail, the column comes back as a
   popover over the panes, holding only the panel. On a phone the panel is a
   sheet over the lower half, the full width, with the panes laid out in the
   half above it, so the text can still be tapped and a sheet's edits are
   seen as they are typed; its lists end clear of the floating toolbar, which
   then sits over it. */
.style-panel {
  --style-panel-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
  --style-panel-end: 0px;
  position: absolute;
  inset: 0;
  /* A column squeezed narrower than this is overhung, over the panes. */
  min-width: min(300px, 90vw);
  z-index: 2;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  background: var(--bg-pane);
  border-right: 1px solid var(--border);
  color: var(--text);
  font-size: 13px;
}
.style-panel[hidden] {
  display: none;
}
@media (min-width: 721px) {
  body.sidebar-collapsed .issue-list-panel:has(> .style-panel:not([hidden])) {
    display: flex;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: min(420px, 36%);
    z-index: 52;
    box-shadow: 4px 0 12px rgba(0, 0, 0, 0.15);
  }
}
@media (max-width: 720px) {
  .review-layout:has(.style-panel:not([hidden])) > .review-main {
    align-self: flex-start;
    height: 50%;
  }
  /* A rule being edited: the pane it was listed from takes that half alone. */
  .review-layout:has(.style-panel[data-sheet="shavian"]:not([hidden])) .context-pane[data-script="latin"],
  .review-layout:has(.style-panel[data-sheet="latin"]:not([hidden])) .context-pane[data-script="shavian"],
  .review-layout:has(.style-panel[data-sheet]:not([hidden])) #splitter-panes {
    display: none;
  }
  /* The issue list, popover or collapsed, gives way to the sheet. */
  body .issue-list-panel:has(> .style-panel:not([hidden])) {
    display: contents;
  }
  .issue-list-panel:has(> .style-panel:not([hidden])) > :not(.style-panel) {
    display: none;
  }
  .style-panel {
    --style-panel-end: calc(var(--editor-toolbar-height) + 24px);
    inset: auto 0 0 0;
    height: 50%;
    width: 100%;
    max-width: 100%;
    min-width: 0;
    z-index: 600;
    border-right: none;
    border-top: 1px solid var(--border);
    box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.15);
  }
}
.style-panel-header {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 42px;
  padding: 0 8px 0 12px;
  border-bottom: 1px solid var(--border);
}
.style-panel-title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
}
.style-panel-btn {
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: inherit;
  cursor: pointer;
  font-size: 18px;
  line-height: 1;
  min-width: 28px;
  min-height: 28px;
}
.style-panel-btn:hover {
  background: var(--bg-pane-alt);
  border-color: var(--border);
}
@media (pointer: coarse) {
  .style-panel-btn {
    min-width: 44px;
    min-height: 44px;
  }
}
.style-panel-rules {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 8px 12px;
}
.style-panel-rules[hidden] {
  display: none;
}
.style-panel-message {
  color: var(--text-muted);
  margin: 12px 0;
}
.style-sheet-group + .style-sheet-group {
  margin-top: 12px;
}
.style-sheet-name {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted);
  margin: 8px 0 4px;
  overflow-wrap: anywhere;
}
.style-rules {
  list-style: none;
  margin: 0;
  padding: 0;
}
.style-rule {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 6px 8px;
  margin: 0 0 6px;
}
.style-rule-top {
  display: flex;
  gap: 6px;
  align-items: baseline;
}
.style-rule-head {
  display: flex;
  flex: 1;
  min-width: 0;
  gap: 8px;
  align-items: baseline;
  padding: 2px 0;
}
.style-rule-selector {
  flex: 1;
  min-width: 0;
  font-family: var(--style-panel-mono);
  font-weight: 600;
  overflow-wrap: anywhere;
}
.style-rule-line,
.style-rule-on {
  color: var(--text-muted);
  font-size: 12px;
}
.style-rule-line {
  white-space: nowrap;
}
.style-rule-on {
  display: block;
  overflow-wrap: anywhere;
}
.style-decls {
  list-style: none;
  margin: 4px 0 0;
  padding: 0 0 0 12px;
}
.style-decl code {
  font-family: var(--style-panel-mono);
  overflow-wrap: anywhere;
}
.style-decl.is-not-shown code {
  text-decoration: line-through;
  color: var(--text-muted);
}
.style-decl-note {
  margin-left: 6px;
  font-size: 11px;
  font-style: italic;
  color: var(--text-muted);
}
.style-rule-add {
  display: block;
  width: 100%;
  margin: 4px 0 8px;
  padding: 6px 8px;
  font: inherit;
  text-align: left;
  color: var(--accent);
  background: transparent;
  border: 1px dashed var(--border);
  border-radius: var(--radius-sm);
  cursor: pointer;
  overflow-wrap: anywhere;
}
.style-rule-add:hover {
  background: var(--bg-pane-alt);
}
@media (pointer: coarse) {
  .style-rule-add {
    min-height: 44px;
  }
}
.style-rule-edit {
  flex: none;
  font: inherit;
  font-size: 12px;
  color: var(--accent);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 1px 8px;
  cursor: pointer;
}
.style-rule-edit:hover {
  background: var(--bg-pane-alt);
}
@media (pointer: coarse) {
  .style-rule-edit {
    min-width: 44px;
    min-height: 44px;
  }
}
/* While a rule is edited the rest of the listing waits for its Done. */
.style-rule[inert] {
  opacity: 0.5;
}
.style-rule.is-editing .style-rule-edit,
.style-rule.is-editing .style-rule-on,
.style-rule.is-editing .style-decls {
  display: none;
}
.style-rule-editor {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
  margin-top: 6px;
}
.style-rule-text {
  width: 100%;
  box-sizing: border-box;
  resize: vertical;
  margin: 0;
  padding: 6px 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-pane);
  color: var(--text);
  font-family: var(--style-panel-mono);
  font-size: 12px;
  line-height: 18px;
  tab-size: 2;
  white-space: pre;
  overflow: auto;
}
.style-panel-edit[hidden] {
  display: none;
}
.style-panel-edit {
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
.style-panel-edit-name {
  margin: 0;
  padding: 6px 12px 0;
  color: var(--text-muted);
  font-size: 12px;
  overflow-wrap: anywhere;
}
.style-panel-edit-name[hidden] {
  display: none;
}
/* A status mark is a button only so a tap can tell what it means. */
.style-status {
  font: inherit;
  padding: 0;
  background: none;
  border: none;
  cursor: help;
}
.style-panel-sheet-state[title]:not([title=""]) {
  cursor: help;
}
.style-sheet-edited {
  margin-left: 6px;
  font-weight: 500;
  color: var(--accent);
}
.style-sheet-edited::before {
  content: "\00b7\00a0";
}
.style-panel-sheet-bar,
.style-panel-conflict {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
}
.style-panel-conflict[hidden] {
  display: none;
}
.style-panel-sheet-state {
  flex: 1 1 140px;
  min-width: 0;
  color: var(--text-muted);
  font-size: 12px;
}
.style-panel-conflict {
  background: var(--bg-pane-alt);
}
.style-panel-conflict p {
  flex: 1 1 100%;
  margin: 0;
}
.style-panel-action {
  padding: 4px 10px;
  min-height: 28px;
}
.style-panel-action[hidden] {
  display: none;
}
@media (pointer: coarse) {
  .style-panel-action {
    min-height: 44px;
  }
}
/* A field under 16px makes iOS zoom the page when it is focused. */
@media (pointer: coarse) {
  .style-rule-text {
    font-size: 16px;
    line-height: 24px;
  }
}

/* === Find in text (editorFind.js) ===
   The Cmd/Ctrl+F bar, top right over the panes and their splitter
   (.review-main is its containing block), under the top title bars: 4px
   below their 40px, 44px on touch. */
.editor-find {
  position: absolute;
  top: 44px;
  right: 16px;
  z-index: 51;
  display: flex;
  align-items: center;
  gap: 2px;
  box-sizing: border-box;
  max-width: calc(100% - 32px);
  padding: 2px 4px 2px 8px;
  background: var(--bg-pane);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  color: var(--text);
}
.editor-find[hidden] {
  display: none;
}
.editor-find-icon {
  display: inline-flex;
  color: var(--text-muted);
}
.editor-find-icon .shave-icon,
.editor-find-btn .shave-icon {
  width: 16px;
  height: 16px;
  display: block;
}
/* 16px keeps iOS Safari from zooming the page when the field takes focus. */
.editor-find-input {
  flex: 1 1 12em;
  min-width: 0;
  font-size: 16px;
  padding: 4px 6px;
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  color: inherit;
}
.editor-find-input:focus {
  outline: none;
  border-color: var(--accent);
}
.editor-find-input.is-invalid,
.editor-find-input.is-invalid:focus {
  border-color: var(--danger, #c0392b);
}
.editor-find-count {
  flex: 0 0 auto;
  padding: 0 4px;
  font-size: 12px;
  color: var(--text-muted);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.editor-find-btn {
  flex: 0 0 auto;
  min-width: 32px;
  min-height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  color: inherit;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}
.editor-find-btn:hover:not(:disabled) {
  background: var(--bg-pane-alt);
  border-color: var(--border);
}
.editor-find-btn:disabled {
  opacity: 0.35;
  cursor: default;
}
@media (pointer: coarse) {
  .editor-find {
    top: 48px;
  }
  .editor-find-btn {
    min-width: 44px;
    min-height: 44px;
  }
}
@media (max-width: 720px) {
  .editor-find {
    left: 8px;
    right: 8px;
    max-width: none;
  }
}
.context-content .find-match {
  background: rgba(250, 204, 21, 0.5);
  border-radius: 2px;
}

/* A publisher's page break, shown in both panes as the removal marker is
   drawn: a dashed line with a pill at its end, here labelled "page break".
   CSS alone, so the panes' text is the chapter's. Block-level ones only:
   an inline one is the print edition's page number, mid-sentence. */
.context-content :is([epub\:type~="pagebreak"], [role="doc-pagebreak"], [class~="pagebreak"],
    [class~="page-break"]):not(span, a) {
  border-top: 2px dashed var(--text-muted);
}
.context-content :is([epub\:type~="pagebreak"], [role="doc-pagebreak"], [class~="pagebreak"],
    [class~="page-break"]):not(span, a)::before {
  content: 'page break';
  display: block;
  width: max-content;
  margin: -9px 0 0 auto;
  padding: 0 6px;
  border: 1px solid var(--text-muted);
  border-radius: 8px;
  background: var(--bg-pane);
  color: var(--text-muted);
  font: 600 10px/14px system-ui, -apple-system, sans-serif;
  white-space: nowrap;
}
