/*
app.css — project-wide custom styles that extend Bootstrap 5.3.3.
Loaded in <head> after the framework stylesheet so it can override/extend
Bootstrap utilities and component classes. Keep this file small and
component-oriented; prefer Bootstrap utilities in markup over adding rules
here. Custom properties follow Bootstrap's --bs-* token convention.
*/

:root {
  /* btn-icon: fixed square size for icon-only action buttons. Named as a
     design token so all icon buttons share one knob and stay in sync. The
     value matches the natural .btn-lg height (3.5rem). */
  --bs-btn-icon-size: 3.5rem;
}

/* btn-icon: square icon-only action button with explicit, font-independent
   sizing. Bootstrap ships no icon-button utility, and Bootstrap Icons glyphs
   have differing advance widths (circle vs bar glyphs), so a fixed width and
   height decouple the button box from the glyph. display:inline-flex centers
   the glyph; padding:0 neutralizes .btn-lg's 0.5/1rem padding so the box is
   exact; line-height:1 prevents the inline-block glyph baseline from adding
   vertical depth. Compose with .btn .btn-{color} .btn-lg in markup. */
.btn-icon {
  width: var(--bs-btn-icon-size);
  height: var(--bs-btn-icon-size);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  line-height: 1;
}

/* btn-icon-subtle: light tint for the active-log secondary icon buttons
   (pause / forward / note / resume). Uses Bootstrap's 200-step palette
   tokens — --bs-{color}-bg-subtle (the "200" tint) for the fill and
   --bs-{color}-text-emphasis (the "900" ink) for the glyph — so the icons
   read as one weight step lighter than the solid abort (danger) and end
   (success) buttons, and adapt to dark mode for free. Compose with
   .btn .btn-lg .btn-icon plus a per-color modifier (.btn-icon-{warning,
   primary, teal}) that maps the --bs-btn-subtle-* vars to the right
   Bootstrap tokens; see below. */
.btn-icon-subtle {
  --bs-btn-font-weight: 400;
  --bs-btn-color: var(--bs-btn-subtle-ink, var(--bs-secondary-text-emphasis));
  --bs-btn-bg: var(--bs-btn-subtle-fill, var(--bs-secondary-bg-subtle));
  --bs-btn-border-width: var(--bs-border-width);
  --bs-btn-border-color: var(--bs-btn-subtle-border, var(--bs-secondary-border-subtle));
  --bs-btn-hover-color: var(--bs-btn-subtle-ink, var(--bs-secondary-text-emphasis));
  --bs-btn-hover-bg: var(--bs-btn-subtle-fill-hover, var(--bs-secondary-bg-subtle));
  --bs-btn-hover-border-color: var(--bs-btn-subtle-border, var(--bs-secondary-border-subtle));
  --bs-btn-focus-shadow-rgb: var(--bs-btn-subtle-rgb, 108, 117, 125);
  --bs-btn-active-color: var(--bs-btn-subtle-ink, var(--bs-secondary-text-emphasis));
  --bs-btn-active-bg: var(--bs-btn-subtle-fill, var(--bs-secondary-bg-subtle));
  --bs-btn-active-border-color: var(--bs-btn-subtle-border, var(--bs-secondary-border-subtle));
  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
  --bs-btn-disabled-color: var(--bs-btn-subtle-ink, var(--bs-secondary-text-emphasis));
  --bs-btn-disabled-bg: var(--bs-btn-subtle-fill, var(--bs-secondary-bg-subtle));
  --bs-btn-disabled-border-color: var(--bs-btn-subtle-border, var(--bs-secondary-border-subtle));
}
/* Per-color modifiers: map --bs-btn-subtle-* to Bootstrap's 200/900 tokens,
   which auto-flip in dark mode ([data-bs-theme=dark]). */
.btn-icon-warning {
  --bs-btn-subtle-ink: var(--bs-warning-text-emphasis);
  --bs-btn-subtle-fill: var(--bs-warning-bg-subtle);
  --bs-btn-subtle-border: var(--bs-warning-border-subtle);
  --bs-btn-subtle-rgb: var(--bs-warning-rgb);
}
.btn-icon-primary {
  --bs-btn-subtle-ink: var(--bs-primary-text-emphasis);
  --bs-btn-subtle-fill: var(--bs-primary-bg-subtle);
  --bs-btn-subtle-border: var(--bs-primary-border-subtle);
  --bs-btn-subtle-rgb: var(--bs-primary-rgb);
}
.btn-icon-info {
  --bs-btn-subtle-ink: var(--bs-info-text-emphasis);
  --bs-btn-subtle-fill: var(--bs-info-bg-subtle);
  --bs-btn-subtle-border: var(--bs-info-border-subtle);
  --bs-btn-subtle-rgb: var(--bs-info-rgb);
}
/* Two-tone notebook icon: the bookmark gets an orange fill so it reads as a
   distinct accent against the cyan cover+rings (which use currentColor = the
   button's --bs-info-text-emphasis ink). Light mode uses --bs-orange
   (#fd7e14); dark mode lightens to #ffb07c for contrast on dark bg.
   The cover <rect> normally follows currentColor too, but in dark mode that
   resolves to the bright info-text-emphasis (#6edff6), which overpowers the
   icon.    --note-cover-fill (dark-only) dims the cover to a muted cyan
   (#1a86a0) so the cover softens while the binding+rings stay bright; light
   mode is unchanged via the var()'s currentColor fallback. */
:root { --note-bookmark-fill: var(--bs-orange); }
[data-bs-theme="dark"] { --note-bookmark-fill: #ffb07c; }
[data-bs-theme="dark"] { --note-cover-fill: #1a86a0; }
/* Teal is not a Bootstrap theme color (no --bs-teal-* tokens), so its 200/900
   pair is hardcoded and mirrored for dark mode. */
.btn-icon-teal {
  --bs-btn-subtle-ink: #0a5042;
  --bs-btn-subtle-fill: #d1f0e8;
  --bs-btn-subtle-border: #9fe6d3;
  --bs-btn-subtle-rgb: 32, 201, 151;
}
[data-bs-theme="dark"] .btn-icon-teal {
  --bs-btn-subtle-ink: #5ae5b5;
  --bs-btn-subtle-fill: #0e3b32;
  --bs-btn-subtle-border: #1a6b56;
}
.btn-icon-success {
  --bs-btn-subtle-ink: var(--bs-success-text-emphasis);
  --bs-btn-subtle-fill: var(--bs-success-bg-subtle);
  --bs-btn-subtle-border: var(--bs-success-border-subtle);
  --bs-btn-subtle-rgb: var(--bs-success-rgb);
}
.btn-icon-danger {
  --bs-btn-subtle-ink: var(--bs-danger-text-emphasis);
  --bs-btn-subtle-fill: var(--bs-danger-bg-subtle);
  --bs-btn-subtle-border: var(--bs-danger-border-subtle);
  --bs-btn-subtle-rgb: var(--bs-danger-rgb);
}
:root {
  --resume-ring-fill: var(--bs-primary);
  --skip-forward-fill: #20c997;
  --reorder-handle-fill: #f59e0b; /* amber-500 (Tailwind) / yellow-500 (Material) */
}
[data-bs-theme="dark"] {
  --skip-forward-fill: #5ae5b5;
  --reorder-handle-fill: #fbbf24; /* amber-400: lighter for contrast on dark bg */
}

/* Series-view reorder handle (bi-arrow-down-up, hidden via d-none until reorder
   mode is toggled): the 1em glyph is far too small for a touch target, so
   font-size enlarges the icon and padding widens the hit area to a
   finger-sized box while negative margins keep the row height unchanged.
   touch-action:none guarantees the browser never interprets a touch on the
   handle as a scroll, so Sortable always claims the drag; touches starting
   on the rest of the row still scroll normally (the Sortable handle guard
   rejects them before a drag ever starts). Colored amber/yellow-500
   (--reorder-handle-fill, lightened in dark mode) to signal the drag
   affordance; distinct from the primary Blue of the Reorder button so the
   handle and its toggle button read as two different affordances.
   font-weight needs !important because .bi::before hard-pins 400 with
   !important, which beats any inherited weight. */
.reorder-handle {
  cursor: grab;
  color: var(--reorder-handle-fill);
  font-size: 1.5rem;
  font-weight: 700 !important;
  padding: 0.75rem;
  margin: -0.75rem;
  touch-action: none;
}

/* Char-level diff highlighting (inline, within changed TOC fields).
   Uses Bootstrap RGB tokens so tints adapt to dark mode. */
ins { text-decoration: none; background-color: rgba(var(--bs-success-rgb), 0.15); }
del { text-decoration: line-through; background-color: rgba(var(--bs-danger-rgb), 0.15); }

/* Line-level diff rows (raw-text overview). */
.diff-line { padding: 0 0.5rem; white-space: pre-wrap; font-family: monospace; }
.diff-equal { color: var(--bs-body-color); }
.diff-del { background-color: rgba(var(--bs-danger-rgb), 0.1); color: var(--bs-danger-text-emphasis); }
.diff-ins { background-color: rgba(var(--bs-success-rgb), 0.1); color: var(--bs-success-text-emphasis); }

/* Readboard spine title styling:
   - Normal leaf entries: no bold, no italic (default)
   - Non-leaf (branch) entries, non-actionable: italic via .toc-title
   - Actionable entries (leaf or branch): bold via .slide-confirm-title
     (actionable branches override the branch italic — single source of
     emphasis for interactive rows) */
.slide-confirm-title { font-weight: 600; }
.focused-spine-row--branch .toc-title { font-style: italic; }

/* Float actionable spine rows above their neighbors with a soft drop-shadow.
   :has(> .slide-confirm-group) scopes the effect to <li> rows that directly
   contain a slider group, so non-actionable rows, rows whose nested descendants
   have sliders, and future-scheduled reviews (no slider rendered) stay flat.
   The negative margin pulls the row up by 6px and the compensating padding-top
   keeps the slider track at its original vertical position; z-index + position
   ensure the elevated row sits above the rows it overlaps. The negative margin
   covers the previous sibling's bottom border, so :not(:first-child) adds a
   border-top on the floated row itself — its own separator, visible at its
   elevated z-index. Guarded by :not(:first-child) to avoid a double line when
   the floated row is the first child (the <ul> border-top already covers that
   case). Browser support: Chrome 105+, Safari 15.4+, Firefox 121+. */
.focused-spine-list > .list-group-item:not(:first-child):has(> .slide-confirm-group) {
  margin-top: -0.375rem;
  padding-top: 0.625rem;
  z-index: 1;
  position: relative;
  border-top: var(--bs-list-group-border-width) solid var(--bs-list-group-border-color);
}

/* Consistent separator borders. Bootstrap's list-group-flush only assigns
   bottom borders, so the 1st child inside a nested <ul> and the first
   visible row of a book's spine have no top separator. Two rules:
   1. border-top on the <ul> itself — draws a line above the entire list
      regardless of which child is first visible (spine windowing hides
      early rows with display:none, so :first-child may be hidden).
   2. :first-child on each nested <ul>'s first <li> — creates a separator
      between parent content (slider/toc-toggle) and its first child.
   Scoped to .focused-spine-list so other list-groups on the page are
   unaffected. */
.focused-spine-list,
.focused-spine-list .list-group-item > ul.list-group-flush > .list-group-item:first-child {
  border-top: var(--bs-list-group-border-width) solid var(--bs-list-group-border-color);
}

/* Embla Carousel base (v8.6.0). Only these three rules are required by the
   library. Inert on pages that don't use Embla. Kept for future carousel
   clusters (readboard, etc.); the change-queue now uses slide-confirm
   below instead. overflow:hidden on the viewport is mandatory;
   touch-action on the container lets the browser handle vertical scroll
   natively while Embla owns the horizontal drag. */
.embla__viewport { overflow: hidden; }
.embla__container { display: flex; touch-action: pan-y pinch-zoom; }
.embla__slide { flex: 0 0 100%; min-width: 0; }

/* Slide-to-confirm bi-directional slider. The .slide-confirm-group
   wrapper starts as a plain d-flex gap-2 button row (no-JS fallback).
   slide-confirm.js appends a .slide-confirm track with a centered
   draggable handle and hides the original forms. Drag the handle right
   past 80% to approve, left past 80% to reject. Release before the
   threshold and the handle snaps back to center. */
.slide-confirm {
  position: relative; width: 100%; height: 3rem; border-radius: 1.5rem;
  overflow: hidden; touch-action: none; cursor: grab;
  border: 1px solid var(--bs-border-color);
  background: var(--bs-body-secondary-bg);
  user-select: none;
}
.slide-confirm:focus-visible { outline: 0.15rem solid var(--bs-primary); outline-offset: 0.15rem; }
.slide-confirm:active { cursor: grabbing; }
.slide-confirm-zone {
  position: absolute; top: 0; bottom: 0;
  display: flex; align-items: center; font-size: 0.85rem; font-weight: 500;
  pointer-events: none; transition: opacity 0.2s ease;
}
.slide-confirm-zone-reject {
  left: 0.75rem; color: var(--bs-danger-text-emphasis); opacity: 0.5;
}
.slide-confirm-zone-approve {
  right: 0.75rem; color: var(--bs-success-text-emphasis); opacity: 0.5;
}
.slide-confirm-handle {
  position: absolute; top: 50%; left: 50%; width: 2.5rem; height: 2.5rem;
  margin-top: -1.25rem; margin-left: -1.25rem;
  border-radius: 50%; background: var(--bs-body-bg);
  box-shadow: 0 1px 3px rgba(0,0,0,0.2);
  display: flex; align-items: center; justify-content: center;
  z-index: 2; will-change: transform;
}
.slide-confirm-handle::after {
  content: "\2194"; font-size: 1rem; color: var(--bs-secondary-color);
}
.slide-confirm-start .slide-confirm-handle::after { content: none; }
.slide-confirm.is-approving {
  background: rgba(var(--bs-success-rgb), 0.25);
}
.slide-confirm.is-approving .slide-confirm-zone-approve { opacity: 1; }
.slide-confirm.is-approving .slide-confirm-handle {
  background: var(--bs-success); box-shadow: 0 1px 3px rgba(0,0,0,0.2), 0 0 0 3px rgba(var(--bs-success-rgb), 0.25);
}
.slide-confirm.is-rejecting {
  background: rgba(var(--bs-danger-rgb), 0.25);
}
.slide-confirm.is-rejecting .slide-confirm-zone-reject { opacity: 1; }
.slide-confirm.is-rejecting .slide-confirm-handle {
  background: var(--bs-danger); box-shadow: 0 1px 3px rgba(0,0,0,0.2), 0 0 0 3px rgba(var(--bs-danger-rgb), 0.25);
}
.slide-confirm.is-committed { pointer-events: none; opacity: 0.6; }

/* Uni-directional start slider (readboard spine). The handle starts at the
   left edge and drags right past 80% to commit. A title overlay sits inside
   the track (right-aligned, dimmed at rest); the zone label ("Start"/"Review")
   cross-fades in as the title fades out on commit. The handle is tinted by
   the data-accent attribute (warning/primary/success). Disabled sliders are
   non-interactive and visually muted. */
.slide-confirm-start {
  height: 2.5rem; border-radius: 1.25rem; cursor: default;
  background: var(--bs-body-bg);
  /* Override the .slide-confirm base border: the layered box-shadow below
     provides the floating-above visual cue. The 1px outline ring at low
     opacity keeps the track edge visible against light/dark surfaces
     without the weight of a real border. */
  border: 0;
  box-shadow:
    0 0.5rem 1rem -0.25rem rgba(0, 0, 0, 0.18),
    0 0 0 1px rgba(0, 0, 0, 0.04);
}
.slide-confirm-start:focus-visible { outline: 0.15rem solid var(--bs-primary); outline-offset: 0.15rem; }

/* Branch rows with a direct slider need a gap below the track so the drop
   shadow has room to fade without overlapping the first child row beneath.
   Leaf rows (no children) are unaffected. */
.focused-spine-row--branch > .slide-confirm-group .slide-confirm-start {
  margin-bottom: 0.375rem;
}
.slide-confirm-title {
  position: absolute; top: 0; bottom: 0; left: 3rem; right: 0.5rem;
  display: flex; align-items: center;
  font-size: 1rem; color: var(--bs-body-color);  /* match .toc-title */
  opacity: 1; pointer-events: none; overflow: hidden;
  white-space: nowrap; text-overflow: ellipsis;
  transition: opacity 0.2s ease;
}
.slide-confirm-title--toggle {
  pointer-events: auto; cursor: pointer;
}
.slide-confirm-zone-start {
  right: 0.75rem; color: var(--bs-success-text-emphasis); opacity: 0;
}
.slide-confirm-start .slide-confirm-handle {
  top: 50%; left: 0.25rem; margin-top: -1.1rem; margin-left: 0;
  width: 2.2rem; height: 2.2rem; cursor: grab;
  background: var(--bs-success);
}
.slide-confirm-start[data-accent="warning"] .slide-confirm-handle { background: var(--bs-warning); }
.slide-confirm-start[data-accent="primary"] .slide-confirm-handle { background: var(--bs-primary); }
.slide-confirm-start[data-accent="success"] .slide-confirm-handle { background: var(--bs-success); }
/* Teal variant: used by the Continue slider on the active reading page.
   Teal is not a Bootstrap theme color, so the values are hardcoded from
   the .btn-icon-teal definition (rgb 32, 201, 151 / ink #0a5042). */
/* Continue slider: "sink below" — recessed into the card surface.
   Overrides the .slide-confirm-start floating-above look. The track
   background matches the card (--bs-body-tertiary-bg) so it blends in,
   and an inset shadow replaces the drop shadow so the track looks
   pressed into the surface rather than floating above it. The End
   slider keeps the floating-above look, creating a clear hierarchy:
   live session floats, next part sinks. The is-committing background
   override below still wins (higher specificity) so the track lights
   up teal during drag. */
.slide-confirm-start[data-accent="teal"] {
  background: var(--bs-body-tertiary-bg);
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.08), 0 0 0 1px rgba(0, 0, 0, 0.04);
}
.slide-confirm-start[data-accent="teal"] .slide-confirm-handle { background: #20c997; }
.slide-confirm-start[data-accent="teal"] .slide-confirm-zone-start { color: #0a5042; }
.slide-confirm-start[data-accent="teal"].is-committing { background: rgba(32, 201, 151, 0.15); }
.slide-confirm-start[data-accent="teal"].is-committing .slide-confirm-handle {
  box-shadow: 0 1px 3px rgba(0,0,0,0.2), 0 0 0 3px rgba(32, 201, 151, 0.25);
}
.slide-confirm-start .slide-confirm-handle:active { cursor: grabbing; }
.slide-confirm-start .slide-confirm-handle i,
.slide-confirm-start .slide-confirm-handle .bi {
  color: var(--bs-body-bg); font-size: 1rem;
}

/* Draggable-direction affordance: a right-pointing chevron on the handle
   that "marches" rightward, signalling the slider is dragged to the right.
   ::before is free on start handles (the radar-ping uses ::after on the
   active End slider), so they coexist. Tucked toward the handle's right
   edge so it reads alongside the centered play icon. Stops during drag,
   after commit, and when disabled. Respects prefers-reduced-motion. */
.slide-confirm-start .slide-confirm-handle::before {
  content: "\f285"; /* bi-chevron-right */
  font-family: "bootstrap-icons";
  position: absolute;
  right: 0.25rem;
  color: var(--bs-body-bg);
  font-size: 0.7rem;
  line-height: 1;
  animation: slide-confirm-march 1.6s ease-in-out infinite;
}
.slide-confirm-start.is-committing .slide-confirm-handle::before,
.slide-confirm-start.is-committed .slide-confirm-handle::before,
.slide-confirm-start.slide-confirm--disabled .slide-confirm-handle::before {
  animation: none;
  opacity: 0;
}
@keyframes slide-confirm-march {
  0%   { transform: translateX(-2px); opacity: 0; }
  30%  { opacity: 1; }
  70%  { opacity: 1; }
  100% { transform: translateX(6px);  opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .slide-confirm-start .slide-confirm-handle::before {
    animation: none;
    opacity: 0.7;
    transform: none;
  }
}

.slide-confirm-start.is-committing { background: rgba(var(--bs-success-rgb), 0.15); }
.slide-confirm-start[data-accent="warning"].is-committing { background: rgba(var(--bs-warning-rgb), 0.15); }
.slide-confirm-start[data-accent="primary"].is-committing { background: rgba(var(--bs-primary-rgb), 0.15); }
.slide-confirm-start.is-committing .slide-confirm-title { opacity: 0; }
.slide-confirm-start.is-committing .slide-confirm-zone-start { opacity: 1; }
.slide-confirm-start.is-committing .slide-confirm-handle {
  box-shadow: 0 1px 3px rgba(0,0,0,0.2), 0 0 0 3px rgba(var(--bs-success-rgb), 0.25);
}
.slide-confirm-start[data-accent="warning"].is-committing .slide-confirm-handle {
  box-shadow: 0 1px 3px rgba(0,0,0,0.2), 0 0 0 3px rgba(var(--bs-warning-rgb), 0.25);
}
.slide-confirm-start[data-accent="primary"].is-committing .slide-confirm-handle {
  box-shadow: 0 1px 3px rgba(0,0,0,0.2), 0 0 0 3px rgba(var(--bs-primary-rgb), 0.25);
}
.slide-confirm-start.is-committed { pointer-events: none; opacity: 0.6; }
.slide-confirm-start.slide-confirm--disabled { opacity: 0.5; pointer-events: none; }

/* Linked-ending: when the Continue slider is dragged past threshold, the
   End slider track dims to signal its session is being ended. Toggled by
   slide-confirm.js via the data-linked-to attribute. */
.slide-confirm-start.is-linked-ending { opacity: 0.6; transition: opacity 0.2s ease; }

/* Ongoing-session indicator: a green radar-ping ring expands from the
   handle and fades, signalling the session is live. Only on .is-active
   (the End slider on the active reading page). The track needs
   overflow: visible (overriding the .slide-confirm base) so the expanding
   ring isn't clipped. Stops during drag (is-committing) and after commit
   (is-committed). Respects prefers-reduced-motion. */
@keyframes radar-ping {
  0%   { transform: scale(1);   opacity: 0.5; }
  100% { transform: scale(1.8); opacity: 0;   }
}
.slide-confirm-start.is-active { overflow: visible; }
.slide-confirm-start.is-active:not(.is-committing):not(.is-committed)
  .slide-confirm-handle::after {
  content: ''; position: absolute; inset: 0;
  border-radius: 50%; border: 2px solid var(--bs-success);
  animation: radar-ping 2s ease-out infinite; pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  .slide-confirm-start.is-active .slide-confirm-handle::after { animation: none; }
}
