/* ============================================================
   BookedIt — design system
   A warm, literary theme. Custom typography + components that
   intentionally look nothing like the stock Blazor/Bootstrap template.
   This sheet loads after bootstrap.min.css and overrides its
   component classes in place, so page markup is left untouched.
   ============================================================ */

:root {
    /* Surfaces */
    --paper:        #f6f3ec;
    --surface:      #fffdf9;
    --surface-2:    #f1ece1;
    --line:         #e4ddcd;
    --line-strong:  #d3c9b3;

    /* Text */
    --ink:          #211c15;
    --ink-soft:     #6c6354;
    --ink-faint:    #978c79;

    /* Accent — warm terracotta / bookmark */
    --accent:        #bd542d;
    --accent-strong: #9d4220;
    --accent-soft:   #f7ebe2;
    --accent-ring:   rgba(189, 84, 45, 0.28);

    /* Semantic */
    --green:  #2f6f4f;
    --plum:   #6f3f86;
    --red:    #b23a2e;
    --amber:  #b8860b;

    /* Sidebar (espresso) */
    --sidebar:        #221c15;
    --sidebar-2:      #2c241b;
    --sidebar-fg:     #ece3d3;
    --sidebar-muted:  #a99c87;
    --sidebar-line:   rgba(255, 255, 255, 0.08);

    /* Shape & depth */
    --r-sm: 8px;
    --r:    12px;
    --r-lg: 18px;
    --shadow-sm: 0 1px 2px rgba(33, 28, 21, 0.06), 0 1px 3px rgba(33, 28, 21, 0.08);
    --shadow:    0 4px 14px rgba(33, 28, 21, 0.10);
    --shadow-lg: 0 12px 34px rgba(33, 28, 21, 0.18);

    --ease: 180ms cubic-bezier(0.4, 0, 0.2, 1);

    --sans: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
    --serif: 'Fraunces', Georgia, 'Times New Roman', serif;
}

* { box-sizing: border-box; }

html, body {
    font-family: var(--sans);
    color: var(--ink);
    background: var(--paper);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

body {
    /* Warm accent wash + a faint paper grain (SVG fractal noise) for a printed,
       tactile feel rather than a flat fill. */
    background-image:
        radial-gradient(1200px 480px at 78% -8%, rgba(189, 84, 45, 0.05), transparent 60%),
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.035'/%3E%3C/svg%3E");
    background-size: auto, 160px 160px;
    background-attachment: fixed, fixed;
}

h1, h2, h3, h4 {
    font-family: var(--serif);
    font-weight: 500;
    font-optical-sizing: auto;
    letter-spacing: -0.018em;
    color: var(--ink);
    line-height: 1.1;
    text-wrap: balance;
}
h1 { font-size: clamp(2.1rem, 1.6rem + 1.8vw, 2.75rem); font-weight: 480; }
h2 { font-size: 1.55rem; }
h3 { font-size: 1.2rem; }
em, .italic { font-style: italic; }

a, .btn-link {
    color: var(--accent-strong);
    text-decoration: none;
    transition: color var(--ease);
}
a:hover, .btn-link:hover { color: var(--accent); text-decoration: underline; }

::selection { background: var(--accent-soft); color: var(--accent-strong); }

/* True overlay scrollbars — thumb paints ON TOP of content, doesn't reserve a gutter, and
   is ~4px thin. Two techniques layered for cross-browser coverage:

   1. `overflow: overlay` (legacy WebKit alias) — when supported, the scrollbar literally
      floats over the content edge with no layout impact. Chrome/Edge/Safari honour this; we
      promote `overflow-y: auto` to `overlay` globally for any scrollable element.
   2. `scrollbar-width: thin` + transparent track + semi-opaque thumb everywhere — Firefox and
      Windows/Linux Chromium variants that don't honour overlay fall back to a still-narrow
      6px gutter that visually disappears thanks to the transparent track. Content may shift
      slightly when scrollbars appear there; we accept that as the trade-off.

   The thumb is the only visible part. 4px wide (2px border collapses the styled 8px gutter),
   subtle ink-tone at rest, ramps up on hover so it's discoverable when needed. */

@supports (overflow: overlay) {
    html, body { overflow-y: overlay; }
    .cat-filter-side, .pick-list, .req-modal, .bk-dialog-card { overflow: overlay; }
}

* {
    scrollbar-width: thin;
    scrollbar-color: rgba(33, 28, 21, 0.22) transparent;
}
*::-webkit-scrollbar { width: 8px; height: 8px; background: transparent; }
*::-webkit-scrollbar-track { background: transparent; border: 0; }
*::-webkit-scrollbar-thumb {
    background: rgba(33, 28, 21, 0.20);
    border-radius: 999px;
    border: 2px solid transparent;   /* renders thumb at ~4px visible width inside the gutter */
    background-clip: padding-box;
    transition: background-color 160ms ease;
}
*::-webkit-scrollbar-thumb:hover { background: rgba(33, 28, 21, 0.45); }
*::-webkit-scrollbar-corner { background: transparent; }

.content {
    padding: 2.5rem clamp(1.25rem, 3.5vw, 3rem) 4rem;
    max-width: 1320px;
    margin: 0 auto;
    width: 100%;
}

h1:focus { outline: none; }

.muted { color: var(--ink-soft); }
code, .file-path { font-family: ui-monospace, 'SF Mono', 'Cascadia Code', Menlo, monospace; }
code {
    background: var(--surface-2);
    border: 1px solid var(--line);
    padding: 0.1rem 0.4rem;
    border-radius: 6px;
    font-size: 0.85em;
    color: var(--accent-strong);
}

/* ---------------- Buttons ---------------- */

.btn {
    --bs-btn-box-shadow: none;
    font-family: var(--sans);
    font-weight: 600;
    font-size: 0.9rem;
    line-height: 1.2;
    padding: 0.6rem 1.05rem;
    border-radius: var(--r-sm);
    border: 1px solid transparent;
    transition: background var(--ease), border-color var(--ease), color var(--ease), box-shadow var(--ease), transform var(--ease);
    cursor: pointer;
}
.btn:active { transform: translateY(1px); }
.btn-sm { padding: 0.32rem 0.6rem; font-size: 0.8rem; }

.btn-primary {
    background: linear-gradient(180deg, #c75f37 0%, var(--accent) 55%, var(--accent-strong) 100%);
    border-color: var(--accent-strong);
    color: #fff;
    box-shadow: 0 1px 2px rgba(157, 66, 32, 0.30), inset 0 1px 0 rgba(255, 255, 255, 0.18);
}
.btn-primary:hover,
.btn-primary:focus {
    background: linear-gradient(180deg, var(--accent) 0%, var(--accent-strong) 100%);
    border-color: var(--accent-strong);
    color: #fff;
    box-shadow: 0 3px 10px rgba(157, 66, 32, 0.32), inset 0 1px 0 rgba(255, 255, 255, 0.18);
}

.btn-outline-secondary {
    background: var(--surface);
    border-color: var(--line-strong);
    color: var(--ink);
}
.btn-outline-secondary:hover,
.btn-outline-secondary:focus {
    background: var(--surface-2);
    border-color: var(--ink-faint);
    color: var(--ink);
}

.btn-outline-primary {
    background: transparent;
    border-color: var(--accent);
    color: var(--accent-strong);
}
.btn-outline-primary:hover,
.btn-outline-primary:focus {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
}

.btn-outline-danger {
    background: transparent;
    border-color: rgba(178, 58, 46, 0.5);
    color: var(--red);
}
.btn-outline-danger:hover,
.btn-outline-danger:focus {
    background: var(--red);
    border-color: var(--red);
    color: #fff;
}

.btn-link {
    padding: 0.3rem 0.2rem;
    font-weight: 600;
    border: none;
    background: none;
}
.btn-link.text-danger { color: var(--red) !important; }

.btn:focus-visible,
.form-control:focus,
.form-select:focus,
.form-check-input:focus {
    outline: none;
    box-shadow: 0 0 0 3px var(--accent-ring);
    border-color: var(--accent);
}

/* ---------------- Forms ---------------- */

.form-control,
.form-select {
    font-family: var(--sans);
    font-size: 0.92rem;
    color: var(--ink);
    background: var(--surface);
    border: 1px solid var(--line-strong);
    border-radius: var(--r-sm);
    padding: 0.6rem 0.7rem;
    transition: border-color var(--ease), box-shadow var(--ease);
}

/* Restore the dropdown caret on .form-select — our override above replaces Bootstrap's full
   rule (which inlines the chevron SVG), so without this the select renders as a plain text
   input. Inline SVG colored to match --ink-soft. */
.form-select {
    appearance: none;
    -webkit-appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236c6354'%3E%3Cpath d='M3.2 5.6a.9.9 0 0 1 1.3 0L8 9.1l3.5-3.5a.9.9 0 1 1 1.3 1.3l-4.1 4.1a.9.9 0 0 1-1.3 0L3.2 6.9a.9.9 0 0 1 0-1.3Z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.65rem center;
    background-size: 1rem 1rem;
    padding-right: 2.2rem;
}
.form-control::placeholder { color: var(--ink-faint); }
.form-control[type="color"] { padding: 0.25rem; height: 2.6rem; }

.form-group { margin-bottom: 1.1rem; display: flex; flex-direction: column; }
.form-group > label,
.detail-form > label {
    font-weight: 600;
    font-size: 0.82rem;
    letter-spacing: 0.01em;
    color: var(--ink-soft);
    margin-bottom: 0.4rem;
}
.form-row { display: flex; gap: 1rem; flex-wrap: wrap; }
.form-row .form-group { flex: 1 1 0; min-width: 9rem; }
.form-actions { display: flex; gap: 0.6rem; align-items: center; margin-top: 0.5rem; }

.checkbox-list { display: flex; flex-direction: column; gap: 0.5rem; }
.check {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    font-weight: 400 !important;
    font-size: 0.92rem;
    color: var(--ink);
    padding: 0.4rem 0.55rem;
    border-radius: var(--r-sm);
    cursor: pointer;
    transition: background var(--ease);
}
.check:hover { background: var(--surface-2); }
.check input { width: auto; }

.form-check-input {
    border: 1px solid var(--line-strong);
    cursor: pointer;
}
.form-check-input:checked {
    background-color: var(--accent);
    border-color: var(--accent);
}
input[type="checkbox"]:not(.form-check-input) { accent-color: var(--accent); width: 1.05rem; height: 1.05rem; }

.valid.modified:not([type=checkbox]) { outline: 1px solid var(--green); }
.invalid { outline: 1px solid var(--red); }
.validation-message { color: var(--red); font-size: 0.85rem; margin-top: 0.25rem; }

/* ---------------- Alerts ---------------- */

.alert {
    border-radius: var(--r);
    border: 1px solid transparent;
    padding: 0.85rem 1rem;
    font-size: 0.92rem;
    margin-bottom: 1.25rem;
}
.alert-danger { background: #fbeae7; border-color: #f0c8c1; color: #8f2c22; }
.alert-info   { background: var(--accent-soft); border-color: #ecd6c7; color: var(--accent-strong); }

/* ---------------- Tables ---------------- */

.table {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    font-size: 0.9rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r);
    overflow: hidden;
    --bs-table-bg: transparent;
}
.table thead th {
    background: var(--surface-2);
    font-family: var(--sans);
    font-weight: 600;
    font-size: 0.74rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--ink-soft);
    border-bottom: 1px solid var(--line);
    padding: 0.7rem 0.9rem;
    text-align: left;
}
.table tbody td {
    padding: 0.65rem 0.9rem;
    border-bottom: 1px solid var(--line);
    vertical-align: middle;
    color: var(--ink);
}
.table tbody tr:last-child td { border-bottom: none; }
.table tbody tr:hover td { background: rgba(189, 84, 45, 0.035); }

/* ---------------- Badges ---------------- */

.badge {
    display: inline-flex;
    align-items: center;
    background: var(--surface-2);
    color: var(--ink-soft);
    border: 1px solid var(--line);
    padding: 0.2rem 0.5rem;
    border-radius: 999px;
    font-family: var(--sans);
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}
.badge-ebook { background: #e4f0e9; color: var(--green); border-color: #c8e2d4; }
.badge-audio { background: #efe6f4; color: var(--plum); border-color: #ddccea; }

/* Format icons (masked SVG → inherits the chip's text colour) */
.badge-ebook::before,
.badge-audio::before {
    content: "";
    width: 0.92em;
    height: 0.92em;
    margin-right: 0.36em;
    flex-shrink: 0;
    background-color: currentColor;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-position: center;
    mask-position: center;
    -webkit-mask-size: contain;
    mask-size: contain;
}
.badge-ebook::before {
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M1 2.828c.885-.37 2.154-.769 3.388-.893 1.33-.134 2.458.063 3.112.752v9.746c-.935-.53-2.12-.603-3.213-.493-1.18.12-2.37.461-3.287.811zm7.5-.141c.654-.689 1.782-.886 3.112-.752 1.234.124 2.503.523 3.388.893v9.923c-.918-.35-2.107-.692-3.287-.81-1.094-.111-2.278-.039-3.213.492zM8 1.783C7.015.936 5.587.81 4.287.94c-1.514.153-3.042.672-3.994 1.105A.5.5 0 0 0 0 2.5v11a.5.5 0 0 0 .707.455c.882-.4 2.303-.881 3.68-1.02 1.409-.142 2.59.087 3.223.877a.5.5 0 0 0 .78 0c.633-.79 1.814-1.019 3.222-.877 1.378.139 2.8.62 3.681 1.02A.5.5 0 0 0 16 13.5v-11a.5.5 0 0 0-.293-.455c-.952-.433-2.48-.952-3.994-1.105C10.413.809 8.985.936 8 1.783'/%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M1 2.828c.885-.37 2.154-.769 3.388-.893 1.33-.134 2.458.063 3.112.752v9.746c-.935-.53-2.12-.603-3.213-.493-1.18.12-2.37.461-3.287.811zm7.5-.141c.654-.689 1.782-.886 3.112-.752 1.234.124 2.503.523 3.388.893v9.923c-.918-.35-2.107-.692-3.287-.81-1.094-.111-2.278-.039-3.213.492zM8 1.783C7.015.936 5.587.81 4.287.94c-1.514.153-3.042.672-3.994 1.105A.5.5 0 0 0 0 2.5v11a.5.5 0 0 0 .707.455c.882-.4 2.303-.881 3.68-1.02 1.409-.142 2.59.087 3.223.877a.5.5 0 0 0 .78 0c.633-.79 1.814-1.019 3.222-.877 1.378.139 2.8.62 3.681 1.02A.5.5 0 0 0 16 13.5v-11a.5.5 0 0 0-.293-.455c-.952-.433-2.48-.952-3.994-1.105C10.413.809 8.985.936 8 1.783'/%3E%3C/svg%3E");
}
.badge-audio::before {
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 3a5 5 0 0 0-5 5v1h1a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V8a6 6 0 1 1 12 0v5a1 1 0 0 1-1 1h-1a1 1 0 0 1-1-1v-3a1 1 0 0 1 1-1h1V8a5 5 0 0 0-5-5'/%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 3a5 5 0 0 0-5 5v1h1a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V8a6 6 0 1 1 12 0v5a1 1 0 0 1-1 1h-1a1 1 0 0 1-1-1v-3a1 1 0 0 1 1-1h1V8a5 5 0 0 0-5-5'/%3E%3C/svg%3E");
}
.badge.status-imported { background: #e4f0e9; color: var(--green); border-color: #c8e2d4; }
.badge.status-skipped  { background: var(--surface-2); color: var(--ink-soft); }
.badge.status-failed   { background: #fbeae7; color: var(--red); border-color: #f0c8c1; }
.badge.status-pending  { background: #f7efd9; color: var(--amber); border-color: #ecddb3; }

/* Conversion job statuses */
.badge.job-queued    { background: #f7efd9; color: var(--amber); border-color: #ecddb3; }
.badge.job-running   { background: #e7eef7; color: #2f5d8f; border-color: #ccdcf0; }
.badge.job-succeeded { background: #e4f0e9; color: var(--green); border-color: #c8e2d4; }
.badge.job-failed    { background: #fbeae7; color: var(--red); border-color: #f0c8c1; }

/* Jobs page — progress bar + expandable detail row. Bar sits inline in the table; expand
   shows the verbose per-phase breakdown beneath. */
.jobs-table .job-row.is-open td { border-bottom-color: transparent; }
.jobs-table .job-detail td { background: var(--surface-2); padding-top: 0.4rem; padding-bottom: 0.9rem; }
.job-progress {
    position: relative;
    height: 1.25rem;
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: 4px;
    overflow: hidden;
}
.job-progress--mini { height: 0.9rem; max-width: 12rem; }
.job-progress-bar {
    height: 100%;
    background: linear-gradient(90deg, var(--accent, #4a78c6), #6a96da);
    transition: width 0.4s ease-out;
}
.job-progress-label {
    position: absolute; inset: 0;
    display: flex; align-items: center; justify-content: center;
    font-size: 0.78rem; font-weight: 600;
    color: var(--ink);
    mix-blend-mode: luminosity;
}
.job-progress-meta { font-size: 0.75rem; margin-top: 0.2rem; }
.job-detail-grid { padding: 0.4rem 0.2rem; }
.job-detail-grid .kv { display: grid; grid-template-columns: 11rem 1fr; gap: 0.3rem 1rem; margin: 0; }
.job-detail-grid dt { color: var(--ink-soft); font-size: 0.82rem; }
.job-detail-grid dd { margin: 0; font-size: 0.88rem; }

.job-pill {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.7rem;
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    background: var(--surface);
    font-size: 0.85rem;
    color: var(--ink-soft);
}
.job-spinner {
    width: 0.85rem;
    height: 0.85rem;
    flex-shrink: 0;
    border-radius: 50%;
    border: 2px solid var(--line-strong);
    border-top-color: var(--accent);
    animation: bk-spin 0.8s linear infinite;
}
@keyframes bk-spin { to { transform: rotate(360deg); } }

/* ---------------- Page header & toolbar ---------------- */

.page-header {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 1.75rem;
    padding-bottom: 1.1rem;
    border-bottom: 1px solid var(--line);
    flex-wrap: wrap;
}
.page-header h1 { margin: 0; }
.page-header h1 .muted { font-family: var(--sans); font-size: 1rem; font-weight: 400; }
.page-header .muted { font-size: 0.95rem; }
/* Header count → a quiet pill that pairs with the serif title */
.page-header > span.muted {
    align-self: center;
    font-family: var(--sans);
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--ink-soft);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 999px;
    padding: 0.32rem 0.75rem;
    box-shadow: var(--shadow-sm);
    white-space: nowrap;
}

.toolbar {
    display: flex;
    gap: 0.6rem;
    margin-bottom: 1.75rem;
    flex-wrap: wrap;
}
.toolbar .form-control,
.toolbar .form-select { max-width: 22rem; }
.toolbar input[type="search"] {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23978c79' stroke-width='1.4'%3E%3Ccircle cx='7' cy='7' r='4.5'/%3E%3Cpath d='M10.5 10.5 14 14' stroke-linecap='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: 0.7rem center;
    background-size: 1rem;
    padding-left: 2.2rem;
}

/* Settings section tab strip */
.settings-tabs {
    display: flex;
    gap: 0.25rem;
    flex-wrap: wrap;
    border-bottom: 1px solid var(--line);
    margin-bottom: 1.9rem;
}
.settings-tabs .settings-tab {
    padding: 0.6rem 0.95rem;
    margin-bottom: -1px;
    color: var(--ink-soft);
    font-weight: 600;
    font-size: 0.92rem;
    text-decoration: none;
    border-bottom: 2px solid transparent;
    transition: color var(--ease), border-color var(--ease);
    /* Reset the user-agent <button> chrome so the rule renders identically whether the tab
       is a NavLink (<a>) or an in-page toggle (<button>). Without these, the browser's grey
       background + outset border bleed through for the button variant. */
    background: none;
    border-top: 0;
    border-left: 0;
    border-right: 0;
    cursor: pointer;
    font-family: inherit;
}
.settings-tabs .settings-tab:hover { color: var(--ink); text-decoration: none; }
.settings-tabs .settings-tab.active { color: var(--accent-strong); border-bottom-color: var(--accent); }

/* Library filter chips on the catalog */
.library-filter {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
    margin-bottom: 1.75rem;
}
.lib-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.4rem 0.85rem;
    border-radius: 999px;
    border: 1px solid var(--line-strong);
    background: var(--surface);
    color: var(--ink-soft);
    font-size: 0.86rem;
    font-weight: 500;
    text-decoration: none;
    transition: border-color var(--ease), background var(--ease), color var(--ease);
}
.lib-pill:hover { text-decoration: none; border-color: var(--ink-faint); color: var(--ink); }
.lib-pill.active {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
    box-shadow: 0 1px 2px rgba(157, 66, 32, 0.25);
}
.lib-pill-dot {
    width: 0.6rem;
    height: 0.6rem;
    border-radius: 50%;
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.18);
}
.lib-pill.active .lib-pill-dot { box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.5); }

/* ---------------- Catalog grid ---------------- */

.book-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(165px, 1fr));
    gap: 2.1rem 1.6rem;
}
.book-card {
    text-decoration: none;
    color: inherit;
    display: block;
}
.book-card .cover {
    position: relative;
    aspect-ratio: 2 / 3;
    background: linear-gradient(135deg, var(--surface-2), #e7dfce);
    border-radius: 4px 9px 9px 4px;
    overflow: hidden;
    box-shadow: var(--shadow);
    transition: transform 260ms cubic-bezier(0.22, 1, 0.36, 1), box-shadow 260ms cubic-bezier(0.22, 1, 0.36, 1);
}
/* Book-like depth: a printed-edge ring, a spine shadow + page highlight down the
   binding edge, and a soft scrim so light cover art never washes out the badge. */
.book-card .cover::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
    border-radius: inherit;
    box-shadow: inset 0 0 0 1px rgba(33, 28, 21, 0.10),
                inset 9px 0 11px -9px rgba(0, 0, 0, 0.45),
                inset 3px 0 0 -1px rgba(255, 255, 255, 0.22);
    background: linear-gradient(to bottom, rgba(0, 0, 0, 0.12), transparent 18%);
}
.book-card:hover .cover {
    transform: translateY(-6px) scale(1.018);
    box-shadow: var(--shadow-lg);
}
.book-card .cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.book-card:hover .title { color: var(--accent-strong); }

.cover-placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%; height: 100%;
    font-family: var(--serif);
    font-size: 2.6rem;
    font-weight: 500;
    color: var(--ink-faint);
}
.cover-placeholder.lg { font-size: 4.5rem; border-radius: var(--r); }

/* Format badges sit in the top-right of the cover. Constrained to the cover width with
   right-aligned flex-wrap so both chips appear on separate lines when the cover is narrow
   (small grid) or when the chip text gets long (e.g. "AUDIO 34%" + "EPUB 47%"). Without
   `max-width` the chips spill past the cover edge — visible on the small-cover layout. */
.book-card .badges {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    left: 0.5rem;
    z-index: 3;
    display: flex;
    justify-content: flex-end;
    flex-wrap: wrap;
    gap: 0.25rem;
}
.book-card .badges .badge {
    backdrop-filter: blur(6px);
    background: rgba(255, 253, 249, 0.86);
    box-shadow: var(--shadow-sm);
    white-space: nowrap;
}
.book-card .meta { margin-top: 0.65rem; }
.book-card .title {
    font-weight: 600;
    font-size: 0.92rem;
    line-height: 1.25;
    transition: color var(--ease);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.book-card .author { color: var(--ink-soft); font-size: 0.82rem; margin-top: 0.15rem; }

.pager {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 1.25rem;
    margin-top: 2.5rem;
}

.empty {
    padding: 4rem 2rem;
    text-align: center;
    color: var(--ink-soft);
    background: var(--surface);
    border: 1px dashed var(--line-strong);
    border-radius: var(--r-lg);
}

/* ---------------- Detail / two-column layouts ---------------- */

.detail-layout {
    display: grid;
    grid-template-columns: 260px 1fr;
    gap: 2.5rem;
    align-items: start;
}
.detail-cover img {
    width: 100%;
    border-radius: var(--r);
    box-shadow: var(--shadow);
    display: block;
}
.detail-form {
    max-width: 760px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    padding: 1.75rem;
    box-shadow: var(--shadow-sm);
}
.detail-form h2 { margin-top: 0; }
.detail-form h3 {
    margin: 0 0 1.1rem;
    padding-bottom: 0.6rem;
    border-bottom: 1px solid var(--line);
}
.detail-form + .detail-form,
.detail-form .mt-3,
.detail-form .mt-4 { margin-top: 1.75rem; }
.detail-form .subtitle { color: var(--ink-soft); margin-top: -0.4rem; }

.files { margin-top: 1.75rem; }
.files h3 { font-size: 1rem; margin-bottom: 0.6rem; }

/* Audiobook tracks/chapters list on BookDetail. Three-column row: numeric track number on the
   left, nice title in the middle, duration on the right — same shape Plappa renders so the
   user can sanity-check what the API will serve before they switch to the player. */
.track-list {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    overflow: hidden;
}
.track-row {
    display: grid;
    grid-template-columns: 2.4rem 1fr auto;
    align-items: baseline;
    gap: 0.6rem;
    padding: 0.35rem 0.6rem;
    font-size: 0.85rem;
    border-bottom: 1px solid var(--line);
}
.track-row:last-child { border-bottom: 0; }
.track-num {
    text-align: right;
    color: var(--ink-soft);
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}
.track-title {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--ink);
}
.track-dur { font-variant-numeric: tabular-nums; font-size: 0.78rem; }
.file-row {
    display: flex;
    gap: 0.6rem;
    align-items: center;
    font-size: 0.84rem;
    padding: 0.5rem 0.6rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    margin-bottom: 0.4rem;
}
.file-path { word-break: break-all; color: var(--ink-soft); }
.sync-url {
    display: block;
    word-break: break-all;
    padding: 0.6rem 0.7rem;
    margin: 0.5rem 0;
    line-height: 1.4;
}

/* Editions (files grouped by format) on the book detail page */
.edition-summary { display: flex; gap: 0.4rem; flex-wrap: wrap; margin-bottom: 0.9rem; }
.edition-group { margin-bottom: 0.85rem; }
.edition-kind {
    display: block;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-faint);
    margin-bottom: 0.35rem;
}

/* Convert-to control on the book detail sidebar */
.convert-form { display: flex; gap: 0.5rem; }
.convert-form .form-select { flex: 1; min-width: 0; }

/* ---------------- Combine editions (merge) ---------------- */

.merge-panel {
    margin-top: 2.75rem;
    padding: 1.5rem 1.75rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-sm);
    max-width: 760px;
}
.merge-panel h3 { margin: 0 0 0.4rem; }
.merge-panel > .muted { font-size: 0.9rem; margin-bottom: 1rem; }
.merge-search { display: flex; gap: 0.6rem; margin-bottom: 1.1rem; }
.merge-search .form-control { max-width: 22rem; }

.merge-list { display: flex; flex-direction: column; gap: 0.5rem; }
.merge-candidate {
    display: grid;
    grid-template-columns: 40px 1fr auto;
    gap: 0.85rem;
    align-items: center;
    padding: 0.55rem 0.7rem;
    border: 1px solid var(--line);
    border-radius: var(--r);
    background: var(--surface);
}
.merge-candidate:hover { border-color: var(--line-strong); }
.merge-cand-cover {
    width: 40px; height: 58px;
    border-radius: 3px;
    overflow: hidden;
    background: var(--surface-2);
    box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08);
}
.merge-cand-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.merge-cand-meta { display: flex; flex-direction: column; min-width: 0; line-height: 1.3; }
.merge-cand-title { font-weight: 600; font-size: 0.94rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.merge-cand-sub { color: var(--ink-soft); font-size: 0.82rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ---------------- Libraries / Import lists ---------------- */

.library-list { display: flex; flex-direction: column; gap: 0.55rem; }
.library-list h3 { margin: 0 0 0.4rem; font-size: 1rem; }
.library-row {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 0.7rem 0.85rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r);
    box-shadow: var(--shadow-sm);
    transition: border-color var(--ease), box-shadow var(--ease);
}
.library-row:hover { border-color: var(--line-strong); }
.library-row.active {
    border-color: var(--accent);
    box-shadow: 0 0 0 1px var(--accent), var(--shadow-sm);
    background: var(--accent-soft);
}
.library-row .library-name { flex: 1; font-weight: 600; min-width: 0; }
.library-row .swatch {
    width: 1.1rem; height: 1.1rem;
    border-radius: 5px;
    display: inline-block;
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12);
}

.import-log { margin-top: 2.75rem; }
.import-log h3 { margin-bottom: 0.9rem; }

/* ---------------- Import review queue ---------------- */

/* Callout link from the Import page */
.review-callout {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin: 0 0 2rem;
    padding: 1rem 1.25rem;
    background: var(--accent-soft);
    border: 1px solid #ecd6c7;
    border-radius: var(--r);
    text-decoration: none;
    color: var(--ink);
    transition: border-color var(--ease), box-shadow var(--ease);
}
.review-callout:hover { text-decoration: none; border-color: var(--accent); box-shadow: var(--shadow-sm); }
.review-callout-count {
    flex-shrink: 0;
    width: 2.5rem; height: 2.5rem;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 50%;
    background: var(--accent);
    color: #fff;
    font-weight: 700;
}
.review-callout-text { display: flex; flex-direction: column; line-height: 1.35; }
.review-callout-text .muted { font-size: 0.88rem; }

.review-queue { display: flex; flex-direction: column; gap: 1.5rem; }

.review-item {
    display: grid;
    grid-template-columns: 200px 1fr;
    grid-template-areas: "parsed actions" "search search" "skip skip";
    gap: 0.5rem 2rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    padding: 1.5rem 1.75rem;
    box-shadow: var(--shadow-sm);
}

.review-parsed { grid-area: parsed; }
.review-actions { grid-area: actions; min-width: 0; }
.review-search { grid-area: search; }
.review-skip { grid-area: skip; }

.review-cover {
    position: relative;
    width: 100%;
    aspect-ratio: 2 / 3;
    border-radius: 4px 9px 9px 4px;
    overflow: hidden;
    background: linear-gradient(135deg, var(--surface-2), #e7dfce);
    box-shadow: var(--shadow), inset 0 0 0 1px rgba(33, 28, 21, 0.10), inset 9px 0 11px -9px rgba(0, 0, 0, 0.4);
    margin-bottom: 0.85rem;
}
.review-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }

.review-eyebrow {
    display: block;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--ink-faint);
    margin-bottom: 0.3rem;
}
.review-title { margin: 0 0 0.15rem; font-size: 1.15rem; }
.review-author { margin: 0 0 0.5rem; color: var(--ink-soft); font-size: 0.9rem; }
.review-formats { display: flex; gap: 0.3rem; flex-wrap: wrap; }
.review-error { font-size: 0.82rem; }

.candidate-list { display: flex; flex-direction: column; gap: 0.5rem; }

.candidate {
    display: grid;
    grid-template-columns: auto 38px 1fr auto;
    gap: 0.8rem;
    align-items: center;
    padding: 0.6rem 0.8rem;
    border: 1px solid var(--line);
    border-radius: var(--r);
    cursor: pointer;
    background: var(--surface);
    transition: border-color var(--ease), background var(--ease), box-shadow var(--ease);
}
.candidate:hover { border-color: var(--line-strong); }
.candidate:has(input:checked) {
    border-color: var(--accent);
    background: var(--accent-soft);
    box-shadow: inset 0 0 0 1px var(--accent);
}
.candidate input[type="radio"] { accent-color: var(--accent); width: 1.05rem; height: 1.05rem; }
.candidate-cover {
    width: 38px; height: 54px;
    border-radius: 3px;
    overflow: hidden;
    background: var(--surface-2);
    box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08);
}
.candidate-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.candidate-meta { display: flex; flex-direction: column; min-width: 0; line-height: 1.3; }
.candidate-title { font-weight: 600; font-size: 0.94rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.candidate-sub { color: var(--ink-soft); font-size: 0.82rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.candidate-source { color: var(--ink-faint); font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.04em; margin-top: 0.1rem; }
.candidate-parsed .candidate-title { font-style: italic; font-weight: 500; }

.confidence {
    font-family: var(--sans);
    font-weight: 700;
    font-size: 0.74rem;
    padding: 0.22rem 0.5rem;
    border-radius: 999px;
    white-space: nowrap;
}
.confidence.high { background: #e4f0e9; color: var(--green); }
.confidence.mid  { background: #f7efd9; color: var(--amber); }
.confidence.low  { background: var(--surface-2); color: var(--ink-faint); }

.review-libraries { margin-top: 1.1rem; }
.lib-chips { display: flex; gap: 0.5rem; flex-wrap: wrap; }

/* "Files (N)" link under the parsed meta block — sits flush left, no padding, so it lines up
   with the format badges above. */
.review-files-btn { padding: 0; font-size: 0.82rem; }

/* v2 review card layout: cover + parsed details on the left (200px), libraries + metadata
   matching wrap together in the right column so they share the wide side instead of stacking
   below the cover. Overrides the original .review-item grid (which had a single right area
   for the form, now obsolete). */
.review-item--v2 {
    display: flex;
    gap: 2rem;
    align-items: flex-start;
    /* Defeat the named-areas grid the base .review-item still defines — flex gives us a
       clean two-column layout without juggling grid-template-areas for every new section. */
    grid-template-columns: none;
    grid-template-areas: none;
}
.review-item--v2 .review-parsed { width: 200px; flex-shrink: 0; }
.review-item--v2 .review-body {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 1.2rem;
}
/* Reset the top-margin that the legacy layout used for vertical spacing — the body's flex
   gap handles spacing now. */
.review-item--v2 .review-libraries,
.review-item--v2 .review-meta { margin-top: 0; }
@media (max-width: 760px) {
    .review-item--v2 { flex-direction: column; }
    .review-item--v2 .review-parsed { width: 100%; max-width: 220px; }
}
.lib-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.35rem 0.7rem;
    border: 1px solid var(--line-strong);
    border-radius: 999px;
    font-size: 0.88rem;
    cursor: pointer;
    transition: border-color var(--ease), background var(--ease);
}
.lib-chip:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-strong); }
.lib-chip input { accent-color: var(--accent); }

/* Review v2: per-provider metadata tabs sit below the libraries chips. The tab strip uses the
   same border-bottom-as-rail visual as the settings tabs so the queue card stays calm. */
.review-meta { margin-top: 1.2rem; }
.meta-tabs {
    display: flex;
    gap: 0.25rem;
    flex-wrap: wrap;
    border-bottom: 1px solid var(--line);
    margin: 0.5rem 0 0.9rem;
}
.meta-tab {
    background: none;
    border: 0;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    padding: 0.45rem 0.85rem;
    color: var(--ink-soft);
    font-weight: 600;
    font-size: 0.9rem;
    cursor: pointer;
    transition: color var(--ease), border-color var(--ease);
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-family: inherit;
}
.meta-tab:hover { color: var(--ink); }
.meta-tab.is-active { color: var(--accent-strong); border-bottom-color: var(--accent); }
.meta-tab-count {
    display: inline-block;
    min-width: 1.5rem;
    padding: 0 0.35rem;
    border-radius: 999px;
    background: var(--surface-2);
    color: var(--ink-faint);
    font-size: 0.72rem;
    text-align: center;
    line-height: 1.35rem;
}
.meta-tab.is-active .meta-tab-count { background: var(--accent-soft); color: var(--accent-strong); }
.meta-actions { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; margin-bottom: 0.6rem; }
.meta-foot { display: flex; align-items: center; gap: 0.6rem; justify-content: space-between; margin-top: 0.8rem; }

/* The card grid is shared with ReviewEdit; we just need the anchor variant to look the same as
   the button variant the edit page uses (no underline, inherit text colour). */
a.candidate-card { text-decoration: none; color: inherit; }
a.candidate-card:hover { text-decoration: none; }

.review-search { margin-top: 0.5rem; border-top: 1px dashed var(--line); padding-top: 0.85rem; }
.review-search summary {
    cursor: pointer;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--accent-strong);
    width: max-content;
}
.review-search .search-form { margin-top: 0.85rem; }
.review-skip { margin-top: 0.25rem; }
.review-skip .btn-link { padding-left: 0; }

/* ---------------- Login / Setup ---------------- */

.login-shell {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
    background:
        radial-gradient(900px 600px at 50% -10%, rgba(189, 84, 45, 0.10), transparent 60%),
        var(--paper);
}
.login-card {
    background: var(--surface);
    padding: 2.5rem;
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-lg);
    border: 1px solid var(--line);
    width: 100%;
    max-width: 400px;
}
.login-card h1 { margin: 0 0 0.35rem; }
.login-card .subtitle { color: var(--ink-soft); margin-bottom: 1.75rem; }
.login-card .btn-primary { margin-top: 0.5rem; }

/* ---------------- Sidebar footer (rendered in NavMenu) ---------------- */

.nav-footer {
    margin-top: auto;
    padding: 1.1rem 1.25rem;
    border-top: 1px solid var(--sidebar-line);
}
.nav-footer .nav-user {
    display: block;
    color: var(--sidebar-fg);
    font-size: 0.9rem;
    font-weight: 600;
    margin-bottom: 0.15rem;
    text-decoration: none;
}
.nav-footer .nav-user:hover { color: var(--accent); text-decoration: none; }
.nav-footer .nav-link { padding: 0; color: var(--sidebar-muted); font-size: 0.85rem; }
.nav-footer .nav-link:hover { color: var(--accent); }

/* ---------------- Profile page ---------------- */

.profile-section { margin-bottom: 2.75rem; max-width: 980px; }
.profile-section > h2 {
    font-size: 1.35rem;
    margin: 0 0 1rem;
    padding-bottom: 0.5rem;
    border-bottom: 1px solid var(--line);
}

.stat-tiles { display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1.5rem; }
.stat {
    display: flex;
    flex-direction: column;
    align-items: center;
    min-width: 6.5rem;
    padding: 1rem 1.4rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r);
    box-shadow: var(--shadow-sm);
}
.stat-num { font-family: var(--serif); font-size: 2rem; font-weight: 500; color: var(--ink); line-height: 1; }
.stat-label { font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-faint); margin-top: 0.4rem; }

.reading-list { display: flex; flex-direction: column; gap: 0.6rem; }
.reading-row {
    display: grid;
    grid-template-columns: 44px 1fr 9rem;
    gap: 1rem;
    align-items: center;
    padding: 0.7rem 0.85rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r);
}
.reading-cover { width: 44px; height: 64px; border-radius: 3px; overflow: hidden; background: var(--surface-2); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08); display: block; }
.reading-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.reading-meta { min-width: 0; display: flex; flex-direction: column; gap: 0.15rem; }
.reading-title { font-weight: 600; color: var(--ink); text-decoration: none; }
.reading-title:hover { color: var(--accent-strong); }
.reading-author { color: var(--ink-soft); font-size: 0.85rem; }
.src-chips { display: flex; gap: 0.35rem; flex-wrap: wrap; margin-top: 0.2rem; }
.src-chip {
    font-size: 0.72rem;
    padding: 0.15rem 0.45rem;
    border-radius: 999px;
    background: var(--surface-2);
    border: 1px solid var(--line);
    color: var(--ink-soft);
}
.src-chip-name { font-weight: 700; color: var(--ink); }
.reading-progress { display: flex; flex-direction: column; gap: 0.3rem; align-items: stretch; }
.progress-bar { height: 6px; border-radius: 999px; background: var(--surface-2); overflow: hidden; }
.progress-bar > span { display: block; height: 100%; background: var(--accent); border-radius: 999px; }
.progress-pct { font-size: 0.78rem; color: var(--ink-soft); text-align: right; }

.profile-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.25rem; align-items: start; }

/* Compact device tiles that open a dialog */
.device-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 0.85rem; }
.device-tile {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    padding: 1rem 1.15rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r);
    box-shadow: var(--shadow-sm);
    text-decoration: none;
    color: var(--ink);
    transition: border-color var(--ease), box-shadow var(--ease), transform var(--ease);
}
.device-tile:hover {
    text-decoration: none;
    border-color: var(--accent);
    box-shadow: var(--shadow);
    transform: translateY(-2px);
}
.device-tile-name { font-weight: 600; font-size: 1rem; }
.device-tile-tag { color: var(--ink-soft); font-size: 0.82rem; }

.field-label { display: block; font-size: 0.78rem; font-weight: 600; color: var(--ink-soft); margin-bottom: 0.35rem; }

/* URL + Copy button */
.copy-row { display: flex; gap: 0.5rem; align-items: stretch; }
.copy-row .sync-url { flex: 1; margin: 0; min-width: 0; }
.btn-copy {
    flex-shrink: 0;
    align-self: stretch;
    padding: 0 0.85rem;
    border: 1px solid var(--line-strong);
    border-radius: var(--r-sm);
    background: var(--surface);
    color: var(--ink);
    font-family: var(--sans);
    font-size: 0.82rem;
    font-weight: 600;
    cursor: pointer;
    transition: background var(--ease), border-color var(--ease), color var(--ease);
}
.btn-copy:hover { background: var(--surface-2); border-color: var(--ink-faint); }
.btn-copy.is-copied { background: #e4f0e9; border-color: #c8e2d4; color: var(--green); }
.device-steps {
    margin: 0.9rem 0 0;
    padding-left: 1.2rem;
    color: var(--ink-soft);
    font-size: 0.9rem;
    line-height: 1.6;
}
.device-steps li { margin-bottom: 0.3rem; }

/* ---------------- Dialog (pure-CSS :target modal) ---------------- */
/* Prefixed class names so they don't collide with Bootstrap's own .modal / .modal-backdrop. */

.bk-dialog {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 1100;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
}
.bk-dialog:target,
.bk-dialog.is-open { display: flex; }
/* Backdrop renders either as <div> (pure-CSS :target dialogs) or as <button> (interactive
   <DialogPanel>); reset the button defaults so it looks identical either way. */
.bk-dialog-backdrop {
    position: absolute;
    inset: 0;
    z-index: 0;
    background: rgba(33, 28, 21, 0.55);
    border: 0;
    padding: 0;
    cursor: pointer;
    appearance: none;
}
/* Wider variant for forms with many fields (Libraries, Import). */
.bk-dialog-card.is-wide { max-width: 620px; }
.bk-dialog-card {
    position: relative;
    z-index: 1;
    width: 100%;
    max-width: 460px;
    max-height: calc(100vh - 3rem);
    overflow-y: auto;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-lg);
    /* Asymmetric: tighter on top (close × sits at top:0.65rem, title comes right after),
       generous left/right + bottom for content breathing room. */
    padding: 1.1rem 1.85rem 1.65rem;
    animation: bk-dialog-pop 160ms cubic-bezier(0.22, 1, 0.36, 1);
}
.bk-dialog-card h3 { margin: 0 0 0.4rem; padding-right: 2.2rem; /* room for close × */ }
.bk-dialog-card > .muted { font-size: 0.9rem; margin-bottom: 1rem; }
.bk-dialog-close {
    position: absolute;
    top: 0.6rem;
    right: 0.7rem;
    width: 2rem;
    height: 2rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    background: transparent;
    border-radius: var(--r-sm);
    font-size: 1.35rem;
    line-height: 1;
    color: var(--ink-soft);
    text-decoration: none;
    cursor: pointer;
    transition: background var(--ease), color var(--ease);
}
.bk-dialog-close:hover { background: var(--surface-2); color: var(--ink); text-decoration: none; }
@keyframes bk-dialog-pop {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: none; }
}

/* ---------------- Blazor error UI (kept functional, restyled) ---------------- */

.blazor-error-boundary {
    background: var(--red);
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
    border-radius: var(--r);
    background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=);
    background-repeat: no-repeat;
    background-position: 1rem center;
    background-size: 1.8rem;
}
.blazor-error-boundary::after { content: "An error has occurred."; }

#blazor-error-ui {
    color-scheme: light only;
    background: var(--surface);
    color: var(--ink);
    border-top: 3px solid var(--accent);
    bottom: 0;
    box-shadow: 0 -2px 14px rgba(33, 28, 21, 0.18);
    box-sizing: border-box;
    display: none;
    left: 0;
    padding: 0.75rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
    font-size: 0.9rem;
}
#blazor-error-ui .dismiss { cursor: pointer; position: absolute; right: 0.9rem; top: 0.6rem; }

/* ---------------- Responsive ---------------- */

@media (max-width: 760px) {
    .detail-layout { grid-template-columns: 1fr; gap: 1.75rem; }
    .detail-cover { max-width: 240px; }
    .content { padding: 1.75rem 1.25rem 3rem; }
    h1 { font-size: 1.7rem; }

    .review-item {
        grid-template-columns: 1fr;
        grid-template-areas: "parsed" "actions" "search" "skip";
    }
    .review-parsed { display: flex; gap: 1rem; align-items: flex-start; }
    .review-cover { width: 110px; flex-shrink: 0; }
}

/* File-organization pattern editor (Settings → File organization) */
.path-pattern header { display: flex; align-items: baseline; gap: 0.6rem; margin-bottom: 0.4rem; }
.path-pattern header h4 { margin: 0; }
.path-pattern textarea { font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 0.95rem; }
.token-chips { display: flex; flex-wrap: wrap; gap: 0.35rem; margin: 0.55rem 0 0.5rem; }
.token-chip {
    font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);
    font-size: 0.78rem;
    padding: 0.18rem 0.55rem;
    border: 1px solid var(--ink-faint, #d4d4d4);
    background: var(--bg-soft, #f6f6f4);
    color: var(--ink, #222);
    border-radius: 12px;
    cursor: pointer;
}
.token-chip:hover { background: var(--accent-soft, #e7eef9); border-color: var(--accent, #4a78c6); }
.token-chip--reset { font-family: var(--sans); }
.path-pattern .preview {
    display: flex; gap: 0.5rem; align-items: baseline;
    padding: 0.4rem 0.6rem;
    background: var(--bg-soft, #f6f6f4);
    border-left: 3px solid var(--accent, #4a78c6);
    border-radius: 4px;
}
.path-pattern .preview code {
    font-size: 0.9rem;
    word-break: break-all;
}

/* Review-queue manual-search panel + result cards */
.merge-section { margin: 1.5rem 0; padding: 1rem 1.1rem; border: 1px solid var(--line, #ddd); border-radius: 6px; background: var(--surface, #fff); }
.merge-section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; margin-bottom: 0.6rem; }
.merge-section-head h3 { margin: 0; font-size: 1rem; }
.search-grid { display: grid; grid-template-columns: 1fr 1fr 1fr auto; gap: 0.6rem; align-items: end; margin-bottom: 0.55rem; }
.search-actions { display: flex; align-items: flex-end; }
.provider-chips { display: flex; flex-wrap: wrap; gap: 0.75rem; }
.candidate-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 0.6rem; }
.candidate-card {
    text-align: left;
    display: flex; gap: 0.6rem; align-items: flex-start;
    padding: 0.55rem 0.65rem;
    border: 1px solid var(--line-strong, #d0d0d0);
    border-radius: 6px;
    background: var(--surface, #fff);
    cursor: pointer;
    transition: border-color 80ms ease, background 80ms ease;
}
.candidate-card:hover { border-color: var(--accent, #4a78c6); }
.candidate-card.is-selected { border-color: var(--accent, #4a78c6); background: var(--accent-soft, #eef3fb); box-shadow: 0 0 0 2px var(--accent, #4a78c6) inset; }
.candidate-card-cover { width: 44px; height: 64px; flex-shrink: 0; background: var(--bg-soft, #f6f6f4); border-radius: 3px; overflow: hidden; }
.candidate-card-cover img { width: 100%; height: 100%; object-fit: cover; }
.candidate-card-meta { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.candidate-card-title { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.candidate-card-sub { font-size: 0.82rem; color: var(--ink-soft, #888); }
.candidate-card-src { display: flex; align-items: center; gap: 0.4rem; margin-top: 0.2rem; font-size: 0.78rem; }
@media (max-width: 760px) {
    .search-grid { grid-template-columns: 1fr; }
}

/* Review-queue field-merge editor (/import/review/{id}/edit) */
.merge-form { display: flex; flex-direction: column; gap: 1rem; }
.merge-row { display: grid; grid-template-columns: 140px 1fr; gap: 1rem; align-items: start; padding-bottom: 0.75rem; border-bottom: 1px solid var(--line-faint, #eee); }
.merge-row-label { font-weight: 600; color: var(--ink, #333); padding-top: 0.55rem; }
.merge-row-body { display: flex; flex-direction: column; gap: 0.45rem; min-width: 0; }
.candidate-chip-row { display: flex; flex-wrap: wrap; gap: 0.35rem; }
.candidate-chip {
    display: inline-flex; align-items: center; gap: 0.4rem;
    padding: 0.18rem 0.5rem;
    background: var(--bg-soft, #f6f6f4);
    border: 1px solid var(--line-strong, #d0d0d0);
    border-radius: 999px;
    cursor: pointer;
    font-size: 0.78rem;
    max-width: 100%;
}
.candidate-chip:hover { background: var(--accent-soft, #e7eef9); border-color: var(--accent, #4a78c6); }
.candidate-chip-val { font-family: inherit; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 30rem; }
.candidate-chip-src { color: var(--ink-soft, #888); font-size: 0.7rem; }
@media (max-width: 760px) {
    .merge-row { grid-template-columns: 1fr; gap: 0.3rem; }
    .merge-row-label { padding-top: 0; }
}

/* ─── Integrated foliate-js reader ─────────────────────────────────────────── */
.reader-host { position: fixed; inset: 0; display: flex; flex-direction: column; background: var(--reader-bg, #f6f1e6); z-index: 50; }
.reader-bar { display: flex; align-items: center; gap: 0.75rem; padding: 0.55rem 1rem; background: var(--surface); border-bottom: 1px solid var(--line); font-size: 0.9rem; }
.reader-back { text-decoration: none; color: var(--ink); font-size: 1.2rem; line-height: 1; padding: 0.2rem 0.5rem; border-radius: 4px; }
.reader-back:hover { background: var(--surface-2); }
.reader-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.reader-controls { display: flex; gap: 0.3rem; }
.reader-btn { background: none; border: 1px solid var(--line); border-radius: 4px; padding: 0.3rem 0.7rem; font-size: 1rem; cursor: pointer; color: var(--ink); }
.reader-btn:hover:not(:disabled) { background: var(--surface-2); }
.reader-btn:disabled { opacity: 0.5; cursor: default; }
.reader-view { flex: 1; min-height: 0; display: flex; }
/* The <foliate-view> custom element extends plain HTMLElement, so without an explicit host
   style it defaults to `display: inline` and collapses to zero size — nothing renders even
   though parsing succeeded. Force it to fill its container so foliate's internal paginator
   gets real dimensions to lay text into. */
foliate-view { flex: 1; display: block; width: 100%; height: 100%; min-height: 0; }
.reader-loading, .reader-error { display: flex; flex: 1; align-items: center; justify-content: center; color: var(--ink-soft); }
.reader-error { color: var(--red); }
.reader-toc { position: absolute; top: 0; right: 0; bottom: 0; width: min(360px, 90vw); background: var(--surface); border-left: 1px solid var(--line); box-shadow: -6px 0 16px rgba(0, 0, 0, 0.08); overflow-y: auto; padding: 0.85rem 1.1rem; z-index: 1; }
.reader-toc header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.5rem; }
.reader-toc h4 { margin: 0; font-size: 1rem; }
.reader-toc ul { list-style: none; padding: 0; margin: 0; }
.reader-toc li { padding: 0.1rem 0; }
.reader-toc button { background: none; border: 0; padding: 0.35rem 0.4rem; text-align: left; width: 100%; cursor: pointer; color: var(--ink); font-size: 0.92rem; }
.reader-toc button:hover { background: var(--surface-2); }

/* ─── Audiobook edition collapse (BookDetail) ─────────────────────────────── */
.edition-collapse { border: 0; }
.edition-collapse summary { list-style: none; cursor: pointer; padding: 0.25rem 0; display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: center; }
.edition-collapse summary::-webkit-details-marker { display: none; }
.edition-collapse-toggle { font-size: 0.78rem; }
.edition-collapse[open] .edition-collapse-toggle::before { content: "Hide files ↑"; }
.edition-collapse:not([open]) .edition-collapse-toggle::before { content: "View files ↓"; }
.edition-collapse-toggle { font-size: 0.78rem; margin-left: auto; }
.edition-collapse-toggle::before { color: var(--accent-strong); }
.edition-collapse-body { padding: 0.4rem 0 0.2rem 0.25rem; display: flex; flex-direction: column; gap: 0.15rem; border-left: 2px solid var(--line); margin-left: 0.2rem; padding-left: 0.6rem; }

/* ─── Catalog: series view + series-aware card additions ─────────────────── */
/* Series cards are <button>s (for InteractiveServer @onclick) but should look identical to
   the regular <a class="book-card"> cards. Strip the user-agent button chrome so only the
   cover image carries the visual weight — the meta text below sits on the page background,
   exactly like a regular book card. */
.series-card {
    background: none;
    border: 0;
    padding: 0;
    width: 100%;
    text-align: left;
    cursor: pointer;
    font: inherit;
    color: inherit;
}
.series-card:hover .cover { transform: translateY(-2px); }
.series-count-badge {
    position: absolute;
    right: 0.35rem;
    bottom: 0.35rem;
    background: var(--accent);
    color: #fff;
    font-weight: 700;
    border-radius: 999px;
    min-width: 1.6rem;
    height: 1.6rem;
    padding: 0 0.45rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.78rem;
    box-shadow: 0 2px 6px rgba(33, 28, 21, 0.18);
}
.series-line { font-size: 0.78rem; margin-top: 0.1rem; }

/* Series detail dialog list */
.series-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 0.4rem; }
.series-list-item { display: flex; gap: 0.7rem; padding: 0.5rem; border: 1px solid var(--line); border-radius: var(--r-sm); text-decoration: none; color: inherit; background: var(--surface); }
.series-list-item:hover { border-color: var(--accent); }
.series-list-cover { width: 42px; height: 60px; flex-shrink: 0; border-radius: 4px; overflow: hidden; background: var(--surface-2); display: flex; align-items: center; justify-content: center; }
.series-list-cover img { width: 100%; height: 100%; object-fit: cover; }
.series-list-placeholder { font-weight: 600; color: var(--ink-soft); }
.series-list-meta { display: flex; flex-direction: column; gap: 0.05rem; min-width: 0; }
.series-list-num { font-weight: 700; color: var(--accent-strong); font-size: 0.78rem; }
.series-list-title { font-weight: 600; }
.series-list-author { font-size: 0.82rem; }

/* lib-pill renders as button in InteractiveServer mode; reset chrome */
.lib-pill { background: transparent; border: 1px solid var(--line); cursor: pointer; }

/* ─── Per-format edition cards (BookDetail left column) ──────────────────── */
.edition-cards { display: flex; flex-direction: column; gap: 0.55rem; margin-top: 0.5rem; }
.edition-card {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    padding: 0.7rem 0.85rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    text-decoration: none;
    color: inherit;
    transition: border-color var(--ease), box-shadow var(--ease), transform var(--ease);
    cursor: pointer;
}
.edition-card:hover {
    border-color: var(--accent);
    box-shadow: var(--shadow-sm);
    text-decoration: none;
    color: inherit;
}
.edition-card:active { transform: translateY(1px); }
.edition-card-chips { display: flex; gap: 0.35rem; flex-wrap: wrap; }
.edition-card-meta { font-size: 0.85rem; }

/* The per-format file dialog is a list of plain rows — looser than .file-row used elsewhere
   so titles + the delete button breathe. */
.file-list { display: flex; flex-direction: column; gap: 0.25rem; max-height: 60vh; overflow-y: auto; padding-right: 0.4rem; }
.file-list .file-row { padding: 0.3rem 0; border-bottom: 1px solid var(--line); display: flex; gap: 0.7rem; align-items: center; }
.file-list .file-row:last-child { border-bottom: 0; }
.file-list .file-path { flex: 1; min-width: 0; word-break: break-all; }

/* ─── Book detail — top action bar + read-only view ───────────────────────── */
/* Back link stacks above the title (left-aligned); title + action buttons share the next
   row, with the title taking the slack and actions hugging the right edge. */
.book-detail-header {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-areas:
        "back back"
        "title actions";
    column-gap: 1rem;
    row-gap: 0.25rem;
    align-items: baseline;
}
.book-back-link { grid-area: back; padding: 0; font-weight: 500; justify-self: start; }
.book-detail-header h1 { grid-area: title; margin: 0; min-width: 0; }
.book-detail-actions { grid-area: actions; display: flex; gap: 0.4rem; align-items: center; flex-shrink: 0; }

/* Inline IDs like ISBN/ASIN read better as plain text than as <code>-styled chips —
   the chip variant looked like a different field type compared to its neighbours. */
.book-meta-id { font-family: ui-monospace, 'SF Mono', 'Cascadia Code', Menlo, monospace; font-size: 0.88rem; letter-spacing: 0.01em; }

/* Read-only metadata view: a tidy two-column grid for short facts, full-width for the
   description. Keeps the page from looking like a half-empty form when there's no edit. */
.book-detail-view { padding: 1.3rem 1.5rem; }
.book-detail-view h2 { margin: 0 0 0.2rem; font-size: 1.65rem; }
.book-detail-view .subtitle { color: var(--ink-soft); font-style: italic; margin: 0 0 1rem; }
.book-meta-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 0.4rem 1.2rem;
    margin: 0.5rem 0 0.9rem;
}
.book-meta-grid > div { display: flex; flex-direction: column; gap: 0.05rem; font-size: 0.92rem; }
.book-meta-grid .muted { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; font-weight: 600; }
.book-tag-row { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-bottom: 1rem; }
.book-tag-row .badge-tag { background: var(--accent-soft); color: var(--accent-strong); border-color: var(--accent-soft); }
.book-description { line-height: 1.6; white-space: pre-line; }

/* Edition dialog header: title chips on the left, delete-edition action on the right. */
.edition-dialog-head { display: flex; align-items: center; justify-content: space-between; gap: 0.6rem; margin-bottom: 0.4rem; flex-wrap: wrap; }

/* ─── Review queue: compact one-row cards ─────────────────────────────────── */
.review-queue { display: flex; flex-direction: column; gap: 0.75rem; }
.review-item-row {
    display: grid;
    grid-template-columns: 70px 1fr auto;
    gap: 1rem;
    align-items: center;
    padding: 0.85rem 1.1rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r);
    box-shadow: var(--shadow-sm);
}
.review-cover-sm {
    width: 70px;
    aspect-ratio: 2 / 3;
    border-radius: 3px 6px 6px 3px;
    overflow: hidden;
    background: linear-gradient(135deg, var(--surface-2), #e7dfce);
    box-shadow: var(--shadow-sm);
}
.review-cover-sm img { width: 100%; height: 100%; object-fit: cover; display: block; }
.review-cover-sm .cover-placeholder {
    display: flex; align-items: center; justify-content: center;
    width: 100%; height: 100%;
    font-family: var(--serif); font-size: 1.6rem; color: var(--ink-soft);
}
.review-item-meta { min-width: 0; }
.review-item-meta .review-title { margin: 0; font-family: var(--serif); font-size: 1.15rem; line-height: 1.2; }
.review-item-meta .review-author { margin: 0.15rem 0 0.4rem; font-size: 0.88rem; }
.review-item-formats { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; }
.review-item-formats .review-files-btn { padding: 0; font-size: 0.78rem; }
.review-item-actions { display: flex; gap: 0.4rem; align-items: center; flex-shrink: 0; }

@media (max-width: 760px) {
    .review-item-row { grid-template-columns: 56px 1fr; }
    .review-item-actions { grid-column: 1 / -1; }
    .review-cover-sm { width: 56px; }
}

/* ─── ReviewEdit step headings + page actions ────────────────────────────── */
/* Each numbered step has its own h3 above the surface so the surface itself stays clean
   (no in-card title). Reads as 4 banded sections instead of 4 card-with-title repeats. */
.merge-step-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    margin: 1.6rem 0 0.5rem;
}
.merge-step-head h3 { margin: 0; font-family: var(--serif); font-size: 1.15rem; }
.merge-step-head .muted { font-size: 0.85rem; }
.merge-page-actions { margin-top: 1.3rem; display: flex; align-items: center; gap: 0.7rem; }

/* Full-width candidate rows with a description preview. The grid puts the cover on the left
   and gives the meta column as much room as the page can spare; on narrow screens it falls
   back to two-line. */
.candidate-cards--wide { grid-template-columns: 1fr !important; gap: 0.55rem; }
.candidate-card--wide {
    display: grid !important;
    grid-template-columns: 96px 1fr;
    gap: 0.85rem;
    padding: 0.7rem 0.9rem;
    text-align: left;
}
.candidate-card--wide .candidate-card-cover { width: 96px; height: 144px; border-radius: 4px; }
.candidate-card-subtitle { font-style: italic; font-size: 0.88rem; }
.candidate-card-desc {
    margin-top: 0.3rem;
    font-size: 0.85rem;
    line-height: 1.45;
    color: var(--ink-soft);
    /* Cap description height visually so a 1000-char synopsis doesn't dominate the page;
       Truncate(280) keeps it short, this just constrains the rare longer pre-truncate cases. */
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.candidate-card-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 0.45rem; margin-top: 0.4rem; font-size: 0.8rem; }

/* "Already in your catalog" merge-target card. Distinct from the regular candidate cards so
   the reviewer reads it as "fold INTO this book", not "this is a metadata source". Accented
   border + action column on the right with the merge CTA pinned. */
.candidate-card--existing {
    grid-template-columns: 96px 1fr auto !important;
    background: var(--accent-soft, #f4efe7);
    border: 1px solid var(--accent, #8a6a3b);
    cursor: default;
}
.candidate-card--existing:hover { transform: none; box-shadow: var(--shadow-sm); }
.candidate-card--existing .candidate-card-action {
    display: flex;
    align-items: center;
    padding-left: 0.5rem;
}
.candidate-card--existing .candidate-card-title { display: inline-flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.badge.badge-existing {
    background: var(--accent, #8a6a3b);
    color: #fff;
    border-color: var(--accent, #8a6a3b);
    text-transform: uppercase;
    font-size: 0.62rem;
    letter-spacing: 0.04em;
}

/* ─── Reader rich controls (footer, tap zones, theme buttons) ────────────── */
.reader-controls .reader-theme-group { display: inline-flex; gap: 0.2rem; margin: 0 0.3rem; }
.reader-btn.is-active { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-strong); }

/* Three-column stage: tap zones flank the foliate-view so a left/right tap turns the page
   on touch devices (the foliate iframe absorbs taps inside the text area itself). */
.reader-stage { flex: 1; min-height: 0; display: flex; align-items: stretch; position: relative; }
/* Side buttons live IN the stage but render as flex slots so they don't overlap the text.
   Chevron is centered vertically; the whole zone responds to clicks/taps. */
.reader-tap {
    flex: 0 0 56px;
    background: transparent;
    border: 0;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--ink-soft);
    transition: background var(--ease), color var(--ease);
    padding: 0;
}
.reader-tap-chevron {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: 999px;
    font-size: 1.7rem;
    line-height: 1;
    /* Always-on pill with enough contrast to read against any theme. Uses the page's surface-2
       (warmer than the reader background) so the button is visibly distinct even when the user
       hasn't hovered yet — without it the chevron read as "just a stray character on the page". */
    background: var(--surface-2);
    border: 1px solid var(--line-strong);
    color: var(--ink-soft);
    box-shadow: var(--shadow-sm);
    transition: transform var(--ease), border-color var(--ease), color var(--ease), background var(--ease);
}
.reader-tap:hover:not(:disabled) .reader-tap-chevron {
    background: var(--surface);
    border-color: var(--accent);
    color: var(--accent-strong);
    transform: scale(1.08);
}
.reader-tap:disabled { cursor: default; opacity: 0.3; }
.reader-stage .reader-view { flex: 1; }
@media (max-width: 640px) {
    .reader-tap { flex-basis: 44px; }
    .reader-tap-chevron { width: 34px; height: 34px; font-size: 1.4rem; }
}

/* Bottom bar: chapter on the left, progress slider + percent on the right. */
.reader-foot {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.45rem 1rem;
    background: var(--surface);
    border-top: 1px solid var(--line);
    font-size: 0.82rem;
}
.reader-chapter { max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.reader-foot-spacer { flex: 1; }
.reader-progress {
    flex: 0 1 32rem;
    accent-color: var(--accent);
    margin: 0;
    cursor: pointer;
}
.reader-percent { min-width: 2.5rem; text-align: right; }
.reader-page { font-size: 0.78rem; }

/* ─── Integrated audiobook player ───────────────────────────────────────── */
.listen-host {
    position: fixed;
    inset: 0;
    display: flex;
    flex-direction: column;
    background: var(--paper);
    z-index: 50;
}
.listen-bar {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.55rem 1rem;
    background: var(--surface);
    border-bottom: 1px solid var(--line);
    font-size: 0.9rem;
}
.listen-back { text-decoration: none; color: var(--ink); font-size: 1.2rem; padding: 0.2rem 0.5rem; border-radius: 4px; }
.listen-back:hover { background: var(--surface-2); }
.listen-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.listen-controls { display: flex; gap: 0.3rem; }

.listen-stage {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1.25rem;
    padding: 1.5rem 1.5rem 2rem;
}
.listen-cover {
    width: clamp(180px, 32vmin, 320px);
    aspect-ratio: 2 / 3;
    border-radius: 4px 9px 9px 4px;
    overflow: hidden;
    background: linear-gradient(135deg, var(--surface-2), #e7dfce);
    box-shadow: var(--shadow);
}
.listen-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.listen-cover .cover-placeholder { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; font-family: var(--serif); font-size: 4rem; color: var(--ink-soft); }

.listen-now { width: 100%; max-width: 640px; display: flex; flex-direction: column; gap: 0.7rem; align-items: stretch; }
.listen-now-chapter { text-align: center; font-family: var(--serif); font-size: 1.1rem; color: var(--ink); }
.listen-now-pos { display: flex; align-items: center; gap: 0.6rem; }
.listen-now-time, .listen-now-total { font-variant-numeric: tabular-nums; min-width: 4ch; font-size: 0.85rem; color: var(--ink-soft); }
.listen-progress { flex: 1; accent-color: var(--accent); margin: 0; cursor: pointer; }
.listen-now-controls { display: flex; align-items: center; justify-content: center; gap: 0.45rem; flex-wrap: wrap; }
.listen-btn {
    background: var(--surface);
    border: 1px solid var(--line-strong);
    border-radius: 999px;
    padding: 0.45rem 0.9rem;
    font-size: 0.9rem;
    color: var(--ink-soft);
    cursor: pointer;
    transition: border-color var(--ease), color var(--ease), background var(--ease), transform var(--ease);
    line-height: 1;
}
.listen-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent-strong); }
.listen-btn:active:not(:disabled) { transform: translateY(1px); }
.listen-btn:disabled { opacity: 0.4; cursor: default; }
.listen-btn--primary {
    background: var(--accent);
    border-color: var(--accent-strong);
    color: #fff;
    width: 56px;
    height: 56px;
    padding: 0;
    font-size: 1.2rem;
}
.listen-btn--primary:hover:not(:disabled) { background: var(--accent-strong); color: #fff; }
.listen-rate { padding: 0.45rem 0.6rem; }

.listen-loading, .listen-error { padding: 1.5rem; text-align: center; color: var(--ink-soft); }
.listen-error { color: var(--red); }
.listen-audio-host { display: none; } /* the <audio> element is created here but hidden — UI is the host's */

/* Tracks drawer (chapter / file list). Same UX as the reader's TOC drawer. */
.listen-tracks {
    position: absolute;
    top: 0; right: 0; bottom: 0;
    width: min(360px, 90vw);
    background: var(--surface);
    border-left: 1px solid var(--line);
    box-shadow: -6px 0 16px rgba(0,0,0,0.08);
    overflow-y: auto;
    padding: 0.85rem 1.1rem;
    z-index: 1;
}
.listen-tracks header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.5rem; }
.listen-tracks h4 { margin: 0; font-size: 1rem; }
.listen-tracks ol { list-style: none; padding: 0; margin: 0; }
.listen-track {
    display: grid;
    grid-template-columns: 2rem 1fr auto;
    gap: 0.6rem;
    width: 100%;
    background: none;
    border: 0;
    text-align: left;
    cursor: pointer;
    padding: 0.45rem 0.45rem;
    border-radius: 6px;
    color: var(--ink);
    font: inherit;
}
.listen-track:hover { background: var(--surface-2); }
.listen-track.is-current { background: var(--accent-soft); color: var(--accent-strong); font-weight: 600; }
.listen-track-num { color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.listen-track-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.listen-track-dur { font-variant-numeric: tabular-nums; font-size: 0.78rem; }

/* Audiobook edition strip — always visible above the now-playing line so the user knows
   which audio set is loaded. Becomes a real dropdown when the book has multiple formats. */
.listen-edition { display: flex; align-items: center; justify-content: center; gap: 0.5rem; font-size: 0.85rem; }
.listen-edition-select {
    font: inherit;
    padding: 0.3rem 0.6rem;
    border: 1px solid var(--line-strong);
    border-radius: var(--r-sm);
    background: var(--surface);
    color: var(--ink);
    cursor: pointer;
}

/* ─── Catalog view-mode toggle + layouts ─────────────────────────────────── */
.view-mode-toggle {
    display: inline-flex;
    border: 1px solid var(--line-strong);
    border-radius: var(--r-sm);
    overflow: hidden;
}
.view-mode-btn {
    width: 38px;
    height: 38px;
    background: var(--surface);
    border: 0;
    border-left: 1px solid var(--line);
    color: var(--ink-soft);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: background var(--ease), color var(--ease);
}
.view-mode-btn:first-child { border-left: 0; }
.view-mode-btn:hover { background: var(--surface-2); color: var(--ink); }
.view-mode-btn.is-active { background: var(--accent-soft); color: var(--accent-strong); }
/* Inline SVG icons via background-image so the buttons stay text-free + scalable. */
.vm-ic { width: 18px; height: 18px; background-repeat: no-repeat; background-position: center; background-size: contain; }
.vm-ic-large { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236c6354'%3E%3Crect x='1' y='1' width='6' height='6'/%3E%3Crect x='9' y='1' width='6' height='6'/%3E%3Crect x='1' y='9' width='6' height='6'/%3E%3Crect x='9' y='9' width='6' height='6'/%3E%3C/svg%3E"); }
.vm-ic-small { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236c6354'%3E%3Crect x='1' y='1' width='3' height='3'/%3E%3Crect x='6' y='1' width='3' height='3'/%3E%3Crect x='11' y='1' width='3' height='3'/%3E%3Crect x='1' y='6' width='3' height='3'/%3E%3Crect x='6' y='6' width='3' height='3'/%3E%3Crect x='11' y='6' width='3' height='3'/%3E%3Crect x='1' y='11' width='3' height='3'/%3E%3Crect x='6' y='11' width='3' height='3'/%3E%3Crect x='11' y='11' width='3' height='3'/%3E%3C/svg%3E"); }
.vm-ic-list  { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236c6354'%3E%3Crect x='1' y='2' width='14' height='2'/%3E%3Crect x='1' y='7' width='14' height='2'/%3E%3Crect x='1' y='12' width='14' height='2'/%3E%3C/svg%3E"); }
.view-mode-btn.is-active .vm-ic-large { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%239d4220'%3E%3Crect x='1' y='1' width='6' height='6'/%3E%3Crect x='9' y='1' width='6' height='6'/%3E%3Crect x='1' y='9' width='6' height='6'/%3E%3Crect x='9' y='9' width='6' height='6'/%3E%3C/svg%3E"); }
.view-mode-btn.is-active .vm-ic-small { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%239d4220'%3E%3Crect x='1' y='1' width='3' height='3'/%3E%3Crect x='6' y='1' width='3' height='3'/%3E%3Crect x='11' y='1' width='3' height='3'/%3E%3Crect x='1' y='6' width='3' height='3'/%3E%3Crect x='6' y='6' width='3' height='3'/%3E%3Crect x='11' y='6' width='3' height='3'/%3E%3Crect x='1' y='11' width='3' height='3'/%3E%3Crect x='6' y='11' width='3' height='3'/%3E%3Crect x='11' y='11' width='3' height='3'/%3E%3C/svg%3E"); }
.view-mode-btn.is-active .vm-ic-list  { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%239d4220'%3E%3Crect x='1' y='2' width='14' height='2'/%3E%3Crect x='1' y='7' width='14' height='2'/%3E%3Crect x='1' y='12' width='14' height='2'/%3E%3C/svg%3E"); }

/* Small-cover grid: tighter columns + reduced row gap + small title text. */
.book-grid.book-grid--small {
    grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
    gap: 1.4rem 1rem;
}
.book-grid--small .title { font-size: 0.85rem; }
.book-grid--small .author { font-size: 0.74rem; }
.book-grid--small .badges .badge { font-size: 0.62rem; padding: 0.05rem 0.3rem; }

/* List view: one row per book. Cover left, meta center (flex-grows), chips right. */
.book-list { display: flex; flex-direction: column; gap: 0.55rem; }
.book-row {
    display: grid;
    grid-template-columns: 64px 1fr auto;
    gap: 1rem;
    align-items: center;
    padding: 0.7rem 0.95rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    text-decoration: none;
    color: inherit;
    transition: border-color var(--ease), box-shadow var(--ease);
}
.book-row:hover { border-color: var(--accent); box-shadow: var(--shadow-sm); text-decoration: none; color: inherit; }
.book-row-cover { width: 64px; aspect-ratio: 2 / 3; border-radius: 3px 6px 6px 3px; overflow: hidden; background: linear-gradient(135deg, var(--surface-2), #e7dfce); box-shadow: var(--shadow-sm); }
.book-row-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.book-row-cover .cover-placeholder { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; font-family: var(--serif); font-size: 1.2rem; color: var(--ink-soft); }
.book-row-meta { min-width: 0; }
.book-row-title { font-family: var(--serif); font-size: 1.05rem; line-height: 1.2; }
.book-row-sub { font-size: 0.85rem; margin-top: 0.15rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.book-row-chips { display: flex; gap: 0.35rem; flex-wrap: wrap; }

/* ─── Catalog cover progress decorations ─────────────────────────────────── */
/* Small marker pinned to the cover's top-LEFT (the format-badge stack lives on top-right).
   Two variants: terracotta dot for "not started yet", green pill with a check for "done." */
.cover { position: relative; }
.progress-dot {
    position: absolute;
    top: 0.5rem;
    left: 0.5rem;
    z-index: 3;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    box-shadow: 0 2px 6px rgba(0,0,0,0.18);
    font-size: 0.78rem;
    font-weight: 700;
    line-height: 1;
}
.progress-dot--unread {
    width: 14px;
    height: 14px;
    background: var(--accent);
    border: 2px solid rgba(255,255,255,0.7);
}
.progress-dot--done {
    width: 22px;
    height: 22px;
    background: var(--green);
    color: #fff;
    border: 2px solid rgba(255,255,255,0.7);
}
/* Listed-row variant: smaller marker stuck in the cover's corner. */
.book-row-cover { position: relative; }
.book-row .progress-dot { top: 2px; left: 2px; }
.book-row .progress-dot--unread { width: 10px; height: 10px; border-width: 1.5px; }
.book-row .progress-dot--done { width: 16px; height: 16px; font-size: 0.62rem; border-width: 1.5px; }

/* Series-number chip in the bottom-left corner of the cover. Just the number,
   no "Series" text — the badge is the affordance. Lives in the same .cover
   coordinate space as .badges and .progress-dot, opposite corner from each. */
.series-num-chip {
    position: absolute;
    bottom: 0.4rem;
    left: 0.4rem;
    z-index: 3;
    font-size: 0.7rem;
    font-weight: 600;
    line-height: 1;
    padding: 0.22rem 0.45rem;
    border-radius: 999px;
    background: rgba(20, 20, 22, 0.72);
    color: #fff;
    backdrop-filter: blur(4px);
    box-shadow: var(--shadow-sm);
    letter-spacing: 0.02em;
}
.series-num-chip--row {
    bottom: 2px;
    left: 2px;
    font-size: 0.6rem;
    padding: 0.12rem 0.32rem;
}
.book-grid--small .series-num-chip { font-size: 0.62rem; padding: 0.15rem 0.35rem; }

/* ─── Unified page sub-header toolbar ─────────────────────────────────────
   One sticky strip per page directly under the global topbar (3.75rem). Folds
   page title, tabs, search, sort, view-mode toggle and item counts into a
   single row instead of three stacked sections. Pages opt in by replacing
   their .page-header + .toolbar + .settings-tabs blocks with .page-toolbar. */
.page-toolbar {
    position: sticky;
    top: 3.75rem;
    z-index: 100;
    display: flex;
    align-items: center;
    gap: 0.55rem;
    flex-wrap: wrap;
    padding: 0.55rem 0.9rem;
    margin: -1.2rem -0.5rem 1.3rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r);
    box-shadow: 0 2px 8px rgba(33, 28, 21, 0.05);
}
.page-toolbar-title {
    margin: 0;
    font-family: var(--sans);
    font-size: 1.05rem;
    font-weight: 600;
    letter-spacing: -0.01em;
    color: var(--ink);
    padding: 0 0.2rem 0 0.1rem;
}
.page-toolbar-divider {
    width: 1px;
    height: 1.6rem;
    background: var(--line);
    margin: 0 0.15rem;
}
.page-toolbar-meta { font-size: 0.82rem; color: var(--ink-soft); }
.page-toolbar-spacer { flex: 1; }
/* Shrink form chrome inside the toolbar so it stays compact. */
.page-toolbar .form-control,
.page-toolbar .form-select { padding: 0.4rem 0.65rem; font-size: 0.88rem; }
.page-toolbar .form-select { padding-right: 2rem; }
.page-toolbar input[type="search"] { max-width: 22rem; min-width: 12rem; flex: 1 1 16rem; }
.page-toolbar .btn { padding: 0.4rem 0.8rem; font-size: 0.85rem; }
.page-toolbar .btn-link { padding: 0.4rem 0.4rem; }
/* Tabs render inline (no border-bottom rail), since the toolbar surface is the rail. */
.page-toolbar .settings-tabs { margin: 0; border-bottom: 0; gap: 0; }
.page-toolbar .settings-tab { padding: 0.4rem 0.75rem; font-size: 0.85rem; margin-bottom: 0; border-bottom-width: 2px; }
/* View-mode toggle compact variant. */
.page-toolbar .view-mode-toggle { border-radius: var(--r-sm); }
.page-toolbar .view-mode-btn { width: 32px; height: 32px; }
.page-toolbar .vm-ic { width: 16px; height: 16px; }

/* Sub-row: filters / library chips. Hangs below the toolbar in its own line so the toolbar
   stays scannable left-to-right. Uses the same compact chip styling we already have. */
.page-toolbar-aux { display: flex; gap: 0.5rem; flex-wrap: wrap; margin: -0.7rem 0 1.1rem 0.2rem; }

/* Forced flex-wrap break inside .page-toolbar — used by SettingsTabs to push per-page child
   controls onto their own line when present, while keeping everything in one sticky strip. */
.page-toolbar-line-break { flex-basis: 100%; height: 0; }

/* ─── AutoRefresh component ──────────────────────────────────────────────
   Self-contained refresh control: a one-shot ↻ manual-refresh button on the
   left, a segmented "Off / 3s / 10s / 60s" interval picker on the right. The
   whole thing is one bordered pill so the controls read as a single unit. */
.auto-refresh {
    display: inline-flex;
    align-items: stretch;
    border: 1px solid var(--line-strong);
    border-radius: var(--r-sm);
    overflow: hidden;
    background: var(--surface);
    height: 32px;
    line-height: 1;
}
.auto-refresh-now {
    background: var(--surface);
    border: 0;
    border-right: 1px solid var(--line);
    padding: 0 0.65rem;
    cursor: pointer;
    color: var(--ink-soft);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background var(--ease), color var(--ease), transform var(--ease);
    font-size: 0.95rem;
}
.auto-refresh-now:hover:not(:disabled) { background: var(--surface-2); color: var(--ink); }
.auto-refresh-now:disabled { opacity: 0.5; cursor: default; }
.auto-refresh-now.is-spinning { animation: auto-refresh-spin 0.9s linear infinite; }
@keyframes auto-refresh-spin { to { transform: rotate(360deg); } }
.auto-refresh-opts { display: inline-flex; }
.auto-refresh-opt {
    background: var(--surface);
    border: 0;
    border-left: 1px solid var(--line);
    padding: 0 0.7rem;
    font-family: var(--sans);
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--ink-soft);
    cursor: pointer;
    min-width: 2.2rem;
    transition: background var(--ease), color var(--ease);
}
.auto-refresh-opt:first-child { border-left: 0; }
.auto-refresh-opt:hover:not(.is-active) { background: var(--surface-2); color: var(--ink); }
.auto-refresh-opt.is-active { background: var(--accent-soft); color: var(--accent-strong); }

/* ---------------- Logs viewer (ported from BookingIt) ---------------- */
.log-table {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r);
    overflow: hidden;
    font-family: ui-monospace, 'SF Mono', 'Cascadia Code', Menlo, monospace;
    font-size: 0.82rem;
}
.log-row {
    display: grid;
    grid-template-columns: 9rem 4rem 14rem 1fr;
    column-gap: 0.75rem;
    gap: 0.5rem;
    padding: 0.35rem 0.7rem;
    border-bottom: 1px solid var(--line);
    align-items: baseline;
}
.log-row:last-child { border-bottom: none; }
.log-time  { color: var(--ink-faint); white-space: nowrap; }
.log-level { font-weight: 700; letter-spacing: 0.04em; }
.log-cat   { color: var(--ink-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.log-msg   { color: var(--ink); white-space: pre-wrap; word-break: break-word; }
.log-row.log-trace .log-level    { color: var(--ink-faint); }
.log-row.log-debug .log-level    { color: var(--ink-soft); }
.log-row.log-info .log-level     { color: var(--green); }
.log-row.log-warn .log-level     { color: var(--amber); }
.log-row.log-warn                { background: rgba(184, 134, 11, 0.04); }
.log-row.log-error .log-level    { color: var(--red); }
.log-row.log-error               { background: rgba(178, 58, 46, 0.06); }
.log-row.log-critical .log-level { color: #fff; background: var(--red); padding: 0 0.3rem; border-radius: 3px; }
.log-row.log-critical            { background: rgba(178, 58, 46, 0.14); }
.log-exception { margin-top: 0.4rem; }
.log-exception summary { cursor: pointer; color: var(--accent-strong); font-family: var(--sans); }
.log-exception pre { background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--r-sm); padding: 0.55rem 0.7rem; margin-top: 0.4rem; overflow-x: auto; font-size: 0.78rem; line-height: 1.4; }

/* ---------------- Catalog dashboard cards ----------------
   Compact "active jobs" status banner shown above the catalog grid when conversion jobs
   are running. Quiet visual: small accent stripe on the left, no shadow — it should fade
   into the page when nothing's happening and pop just enough when something is. */
.jobs-banner {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-left: 3px solid var(--accent);
    border-radius: var(--r);
    padding: 0.55rem 0.9rem;
    margin: -0.4rem 0 1.1rem;
    font-size: 0.88rem;
}
.jobs-banner-icon {
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: var(--accent-soft);
    color: var(--accent-strong);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.95rem;
    flex-shrink: 0;
    animation: jobs-banner-pulse 1.8s ease-in-out infinite;
}
@keyframes jobs-banner-pulse {
    0%, 100% { transform: scale(1); }
    50% { transform: scale(1.08); }
}
.jobs-banner-title { font-weight: 600; color: var(--ink); }
.jobs-banner-detail { color: var(--ink-soft); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.jobs-banner-progress {
    background: var(--surface-2);
    border-radius: 999px;
    height: 6px;
    width: 80px;
    overflow: hidden;
    flex-shrink: 0;
}
.jobs-banner-progress-bar {
    height: 100%;
    background: var(--accent);
    transition: width 0.3s ease-out;
}
.jobs-banner-link {
    color: var(--accent-strong);
    text-decoration: none;
    font-weight: 600;
    font-size: 0.82rem;
    flex-shrink: 0;
    white-space: nowrap;
}
.jobs-banner-link:hover { text-decoration: underline; }

/* ───────────────── Cover editor + picker dialog ─────────────────
   Lives inside .detail-cover on the book detail page. The cover itself fills the parent the
   same way <img> + .cover-placeholder did before; the hover overlay sits on top, fading in
   when the cursor enters the cover area. */
.cover-editor { position: relative; display: block; }
.cover-editor img,
.cover-editor .cover-placeholder { display: block; width: 100%; }

.cover-editor-overlay {
    position: absolute;
    inset: 0;
    border: 0;
    border-radius: inherit;
    background: linear-gradient(180deg, rgba(0,0,0,0) 35%, rgba(0,0,0,0.55) 100%);
    color: #fff;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    gap: 0.4rem;
    padding: 0.8rem 0.6rem;
    cursor: pointer;
    opacity: 0;
    transition: opacity 180ms ease;
    font-weight: 600;
    font-size: 0.88rem;
}
.cover-editor:hover .cover-editor-overlay,
.cover-editor-overlay:focus-visible { opacity: 1; }
.cover-editor-overlay-icon {
    width: 1.6rem;
    height: 1.6rem;
    border-radius: 50%;
    background: rgba(255,255,255,0.95);
    color: var(--accent-strong);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.9rem;
}

/* Modal */
.cover-modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(20, 16, 12, 0.55);
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
}
.cover-modal {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r);
    box-shadow: 0 20px 50px rgba(0,0,0,0.35);
    width: min(960px, 100%);
    max-height: 88vh;
    display: flex;
    flex-direction: column;
}
.cover-modal-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.85rem 1.1rem;
    border-bottom: 1px solid var(--line);
}
.cover-modal-head h3 { margin: 0; font-size: 1.05rem; }

/* Current-covers strip — shows the three format slots (only those with art) as small
   thumbnails with a per-slot remove button. Lives directly under the modal head so the user
   sees what's in place before deciding what to change. */
.cover-modal-current {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.7rem 1.1rem;
    border-bottom: 1px solid var(--line);
    flex-wrap: wrap;
}
.cover-modal-current-label {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--ink-soft);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.cover-current-tile {
    position: relative;
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: 0.2rem;
    width: 70px;
}
.cover-current-tile img {
    width: 70px;
    height: 105px;
    object-fit: cover;
    border-radius: var(--r-sm);
    border: 1px solid var(--line);
    background: var(--surface-2);
}
.cover-current-label {
    font-size: 0.7rem;
    font-weight: 600;
    color: var(--ink-soft);
    text-align: center;
}
.cover-current-remove {
    position: absolute;
    top: -4px;
    right: -4px;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    border: 1px solid var(--line-strong);
    background: var(--surface);
    color: var(--danger, #c0392b);
    font-size: 0.7rem;
    line-height: 1;
    cursor: pointer;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.cover-current-remove:hover:not(:disabled) {
    background: var(--danger, #c0392b);
    color: #fff;
}

.cover-modal-search {
    display: flex;
    gap: 0.6rem;
    align-items: center;
    padding: 0.7rem 1.1rem;
    border-bottom: 1px solid var(--line);
    flex-wrap: wrap;
}
.cover-modal-search .form-control { flex: 1 1 200px; min-width: 0; }

/* Search-format radio — Any / Ebook / Audio. Only reorders provider results so square or
   rectangular art surfaces first; doesn't restrict the result set or affect what gets saved. */
.cover-search-format {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.82rem;
}
.cover-search-format-label {
    font-weight: 600;
    color: var(--ink-soft);
}
.cover-search-format-opt {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    cursor: pointer;
    color: var(--ink);
}
.cover-search-format-opt input[type=radio] {
    margin: 0;
    accent-color: var(--accent);
}

.cover-modal-body {
    padding: 0.85rem 1.1rem 1.1rem;
    overflow-y: auto;
}
.cover-results {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: 0.7rem;
}
.cover-results-empty {
    grid-column: 1 / -1;
    text-align: center;
    color: var(--ink-soft);
    padding: 2rem;
    font-size: 0.9rem;
}

.cover-tile {
    position: relative;
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    background: var(--surface);
    overflow: hidden;
    cursor: pointer;
    padding: 0;
    aspect-ratio: 2 / 3;
    transition: transform var(--ease), border-color var(--ease), box-shadow var(--ease);
}
.cover-tile:hover:not(:disabled) {
    transform: translateY(-2px);
    border-color: var(--accent);
    box-shadow: 0 4px 12px rgba(0,0,0,0.12);
}
.cover-tile img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.cover-tile-source {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    background: rgba(0,0,0,0.65);
    color: #fff;
    font-size: 0.72rem;
    font-weight: 600;
    padding: 0.25rem 0.4rem;
    text-align: center;
}
/* Selected-state tile — outline + checkmark badge so the user can tell at a glance which hit
   the Apply button will commit. Replaces the old per-tile committing spinner; commit now
   happens via the modal-footer Apply button instead of inline. */
.cover-tile.is-selected {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
    transform: translateY(-2px);
}
.cover-tile-selected-badge {
    position: absolute;
    top: 6px;
    right: 6px;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: var(--accent);
    color: #fff;
    font-size: 0.85rem;
    font-weight: 700;
    line-height: 22px;
    text-align: center;
    box-shadow: 0 1px 4px rgba(0,0,0,0.3);
}

.cover-tile--upload {
    background: var(--surface-2);
    border-style: dashed;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    color: var(--ink-soft);
    text-align: center;
    padding: 1rem;
    gap: 0.5rem;
}
.cover-tile--upload:hover { border-color: var(--accent); color: var(--accent-strong); }
.cover-tile--upload input[type=file] {
    position: absolute;
    inset: 0;
    opacity: 0;
    cursor: pointer;
}
.cover-tile-icon { font-size: 2rem; }
.cover-tile-label { font-weight: 600; font-size: 0.85rem; }

/* Modal footer — apply-to checkboxes (which format slots receive the selection) + Cancel /
   Apply buttons. Apply stays disabled until both a hit is pending AND at least one target box
   is checked, so the "what does this do?" question is unambiguous before commit. */
.cover-modal-apply {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.8rem 1.1rem;
    border-top: 1px solid var(--line);
    background: var(--surface-2);
    flex-wrap: wrap;
}
.cover-apply-targets {
    display: inline-flex;
    align-items: center;
    gap: 0.9rem;
    font-size: 0.88rem;
    flex: 1 1 auto;
    flex-wrap: wrap;
}
.cover-apply-label {
    font-weight: 600;
    color: var(--ink-soft);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-size: 0.78rem;
}
.cover-apply-opt {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    cursor: pointer;
    color: var(--ink);
    font-weight: 500;
}
.cover-apply-opt input[type=checkbox] {
    margin: 0;
    accent-color: var(--accent);
    width: 16px;
    height: 16px;
}
.cover-modal-actions {
    display: inline-flex;
    gap: 0.5rem;
}

/* ───────────────── Catalog filter sidebar + bar ─────────────────
   Top-of-catalog horizontal bar (cat-filter-bar) holds the sidebar toggle + a chip preview of
   active filters. Sidebar (cat-filter-side) is a left-column drawer toggled by the bar's
   button; persisted open state lives in ProtectedLocalStorage. Sections collapse via native
   <details>/<summary> so users only see the formats they actually want to filter on.

   On narrow viewports the sidebar overlays the grid instead of stealing column width. */

/* --- Compact bar with toggle + active-filter chips --- */
.cat-filter-bar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin: -0.4rem 0 1rem 0.05rem;
}
.cat-filter-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    background: var(--surface);
    border: 1px solid var(--line-strong);
    border-radius: 999px;
    padding: 0.3rem 0.85rem 0.3rem 0.7rem;
    cursor: pointer;
    font-family: var(--sans);
    font-size: 0.86rem;
    font-weight: 600;
    color: var(--ink);
    transition: background var(--ease), border-color var(--ease), box-shadow var(--ease);
    box-shadow: 0 1px 2px rgba(33,28,21,0.04);
}
.cat-filter-toggle:hover { background: var(--surface-2); border-color: var(--accent); }
.cat-filter-toggle.is-open {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--accent-strong);
}
.cat-filter-toggle-icon {
    width: 0.95rem;
    height: 0.95rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: currentColor;
    opacity: 0.85;
}
.cat-filter-toggle-icon svg { width: 100%; height: 100%; display: block; }
.cat-filter-toggle-badge {
    background: var(--accent);
    color: #fff;
    border-radius: 999px;
    padding: 0 0.45rem;
    font-size: 0.7rem;
    font-weight: 700;
    line-height: 1.2rem;
    min-width: 1.25rem;
    text-align: center;
}

/* Active filter chips inline beside the toggle — each chip can clear its own filter. */
.cat-active-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 999px;
    padding: 0.18rem 0.5rem 0.18rem 0.6rem;
    font-size: 0.78rem;
    color: var(--ink);
    line-height: 1.4;
}
.cat-active-chip--lib { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-strong); font-weight: 600; }
.cat-active-chip--all { color: var(--ink-soft); font-weight: 500; }
.cat-active-chip-dot {
    width: 0.55rem;
    height: 0.55rem;
    border-radius: 50%;
    flex-shrink: 0;
}
.cat-active-chip-dot--all { background: linear-gradient(135deg, var(--accent), #b06043); }
.cat-active-chip-x {
    background: none;
    border: 0;
    color: var(--ink-soft);
    font-size: 0.95rem;
    line-height: 1;
    padding: 0 0.05rem 0 0.15rem;
    cursor: pointer;
    border-radius: 50%;
    transition: background var(--ease), color var(--ease);
}
.cat-active-chip-x:hover { background: rgba(0,0,0,0.06); color: var(--ink); }
.cat-filter-clear-all {
    background: none;
    border: 0;
    color: var(--accent-strong);
    font-weight: 600;
    font-size: 0.78rem;
    cursor: pointer;
    padding: 0.18rem 0.4rem;
    border-radius: var(--r-sm);
    margin-left: 0.2rem;
}
.cat-filter-clear-all:hover { background: var(--surface-2); }

.cat-layout {
    display: grid;
    grid-template-columns: 0 minmax(0, 1fr);
    gap: 0;
    transition: grid-template-columns 200ms ease, gap 200ms ease;
}
.cat-layout--open {
    grid-template-columns: 13.5rem minmax(0, 1fr);
    gap: 1.1rem;
}
.cat-main { min-width: 0; }

.cat-filter-side {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r);
    box-shadow: 0 2px 8px rgba(33,28,21,0.04);
    /* Stick below the sticky toolbar (~3.75rem topbar + ~3.4rem page-toolbar + a small gap) so
       the filter list is always visible while the user scrolls the catalog. max-height keeps
       it scrollable when the filter list grows. */
    position: sticky;
    top: 8rem;
    align-self: flex-start;
    max-height: calc(100vh - 9rem);
    overflow-y: auto;
    overflow-x: hidden;
    /* Hide visually when the layout column is 0 — keeps the DOM mounted (state preserved) but
       prevents a flash when toggling. */
    opacity: 1;
    transition: opacity 160ms ease;
}
.cat-layout:not(.cat-layout--open) .cat-filter-side {
    opacity: 0;
    pointer-events: none;
}
.cat-filter-side-head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.7rem 0.85rem;
    border-bottom: 1px solid var(--line);
    position: sticky;
    top: 0;
    background: var(--surface);
    z-index: 1;
}
.cat-filter-side-title {
    font-weight: 700;
    font-size: 0.95rem;
    flex: 1;
    letter-spacing: 0.01em;
}
.cat-filter-side-close {
    background: none;
    border: 0;
    color: var(--ink-soft);
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
    padding: 0 0.25rem;
    border-radius: var(--r-sm);
}
.cat-filter-side-close:hover { background: var(--surface-2); color: var(--ink); }
.cat-filter-side-body {
    /* Sections fill edge-to-edge. Use block layout instead of flex so any quirk in flex item
       sizing (cross-axis auto-stretch sometimes not applying to <details>) can't surface. */
    padding: 0;
    margin: 0;
    display: block;
}

/* --- Collapsible section band ---
   Each section is a full-width horizontal band. Defensively explicit on width/margin/padding
   /box-sizing so no browser default or flex quirk leaves a sliver of sidebar background
   visible around the edges. */
.cat-filter-side-body details.cat-filter-section,
.cat-filter-side-body .cat-filter-section {
    display: block;
    width: 100%;
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: transparent;
    transition: opacity var(--ease);
}
.cat-filter-section + .cat-filter-section { border-top: 1px solid var(--line); }
.cat-filter-section.is-off { opacity: 0.55; }
.cat-filter-section.is-off .cat-filter-section-body { pointer-events: none; }

.cat-filter-summary {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.65rem 0.85rem;
    cursor: pointer;
    user-select: none;
    list-style: none;
    color: var(--ink);
    margin: 0;
    box-sizing: border-box;
    width: 100%;
    /* Default (Library / Authors) tint. Stronger than the body's tint by design so the header
       is always visually a touch darker than the open body below it — gives the card a clear
       "lid" without needing a divider line. */
    background: rgba(0, 0, 0, 0.05);
    /* Hover indicator is a left accent bar (see below), NOT a background-color shift —
       that way the tint hierarchy header-vs-body stays stable while hovering. The 3px
       transparent border reserved on every summary keeps the layout from jumping when the
       accent fills it in on hover. */
    border-left: 3px solid transparent;
    padding-left: calc(0.85rem - 3px);
    transition: border-color 140ms ease, color 140ms ease;
}
.cat-filter-section--audiobook > .cat-filter-summary { background: rgba(157, 122, 178, 0.20); }
.cat-filter-section--ebook     > .cat-filter-summary { background: rgba(110, 156, 116, 0.22); }
/* Hide the default disclosure triangle on every browser */
.cat-filter-summary::-webkit-details-marker { display: none; }
.cat-filter-summary::marker { content: ""; }
.cat-filter-summary:hover {
    border-left-color: var(--accent);
    color: var(--accent-strong);
}
/* Caret takes on the accent on hover too — subtle "this is clickable" cue that doesn't
   compete with the section's tint hierarchy. */
.cat-filter-summary:hover .cat-filter-summary-caret {
    border-color: var(--accent-strong);
}

/* Body fills the section width with the LIGHTER half of the section's tint so the open card
   reads as one continuous block while the summary stays the dominant "lid". */
.cat-filter-section-body { margin: 0; box-sizing: border-box; width: 100%; }
.cat-filter-section[open]                                      > .cat-filter-section-body { background: rgba(0, 0, 0, 0.02); }
.cat-filter-section--audiobook[open]                           > .cat-filter-section-body { background: rgba(157, 122, 178, 0.10); }
.cat-filter-section--ebook[open]                               > .cat-filter-section-body { background: rgba(110, 156, 116, 0.11); }

.cat-filter-summary-icon {
    width: 1.2rem;
    text-align: center;
    font-size: 0.95rem;
    opacity: 0.85;
    flex-shrink: 0;
}
.cat-filter-summary-label {
    font-weight: 600;
    font-size: 0.86rem;
    color: var(--ink);
    flex: 1;
    min-width: 0;
}
.cat-filter-summary-meta {
    font-size: 0.78rem;
    color: var(--ink-soft);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    margin-right: 0.2rem;
}
.cat-filter-summary-count {
    background: var(--accent);
    color: #fff;
    border-radius: 999px;
    padding: 0 0.42rem;
    font-size: 0.68rem;
    font-weight: 700;
    line-height: 1.15rem;
    min-width: 1.15rem;
    text-align: center;
    margin-right: 0.05rem;
}
.cat-filter-summary-caret {
    width: 0.55rem;
    height: 0.55rem;
    border-right: 1.8px solid var(--ink-soft);
    border-bottom: 1.8px solid var(--ink-soft);
    transform: rotate(-45deg);
    transition: transform 160ms ease;
    flex-shrink: 0;
}
.cat-filter-section[open] > .cat-filter-summary .cat-filter-summary-caret { transform: rotate(45deg); }

.cat-filter-section-body {
    padding: 0.4rem 0.85rem 0.8rem;
    animation: cat-filter-slide 160ms ease;
}
@keyframes cat-filter-slide {
    from { opacity: 0; transform: translateY(-2px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* --- Kind master switch in the summary ---
   iOS-ish toggle. Sits at the right end of the summary header next to the caret so users have
   a one-click way to show/hide an entire kind without opening the section to find a checkbox. */
.cat-kind-toggle {
    background: var(--line);
    border: 0;
    width: 2rem;
    height: 1.1rem;
    border-radius: 999px;
    position: relative;
    cursor: pointer;
    padding: 0;
    margin-left: 0.35rem;
    transition: background var(--ease);
    flex-shrink: 0;
}
.cat-kind-toggle.is-on { background: var(--accent); }
.cat-kind-toggle-thumb {
    position: absolute;
    top: 0.13rem;
    left: 0.15rem;
    width: 0.85rem;
    height: 0.85rem;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 2px rgba(0,0,0,0.25);
    transition: transform 160ms ease;
}
.cat-kind-toggle.is-on .cat-kind-toggle-thumb { transform: translateX(0.85rem); }

/* --- Format pills (replaces indented checklist) --- */
.cat-format-hint {
    margin: 0 0 0.55rem;
    font-size: 0.74rem;
    color: var(--ink-soft);
    line-height: 1.35;
}
.cat-format-pills { display: flex; flex-wrap: wrap; gap: 0.3rem; }
.cat-format-pill {
    background: var(--surface);
    border: 1px solid var(--line-strong);
    border-radius: 999px;
    padding: 0.18rem 0.65rem;
    font-size: 0.74rem;
    font-weight: 600;
    color: var(--ink-soft);
    cursor: pointer;
    transition: background var(--ease), color var(--ease), border-color var(--ease);
    letter-spacing: 0.02em;
}
.cat-format-pill:hover { background: var(--surface-2); color: var(--ink); border-color: var(--ink-soft); }
.cat-format-pill.is-on {
    background: var(--accent);
    color: #fff;
    border-color: var(--accent-strong);
}
.cat-format-pill.is-on:hover { background: var(--accent-strong); }

/* --- Library picker rows inside the sidebar --- */
.cat-lib-row {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    width: 100%;
    background: none;
    border: 0;
    border-radius: var(--r-sm);
    padding: 0.4rem 0.55rem;
    cursor: pointer;
    font-size: 0.88rem;
    color: var(--ink);
    text-align: left;
    transition: background var(--ease);
}
.cat-lib-row:hover { background: rgba(0,0,0,0.045); }
.cat-lib-row.is-active {
    background: var(--accent-soft);
    color: var(--accent-strong);
    font-weight: 600;
}
.cat-lib-row-dot {
    width: 0.7rem;
    height: 0.7rem;
    border-radius: 50%;
    flex-shrink: 0;
}
.cat-lib-row-dot--all {
    background: linear-gradient(135deg, var(--accent), #b06043);
    box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08);
}
/* "Unassigned" library row — hollow dotted-outline circle reads as "no library" without
   stealing color from a real one. Distinct from the solid-color dot a real library uses. */
.cat-lib-row-dot--unassigned {
    background: transparent;
    border: 1.5px dashed var(--ink-soft, #888);
    box-sizing: border-box;
}
.cat-lib-row-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* --- Active chip: composite (kind + formats) --- */
.cat-active-chip--kind { padding-left: 0.4rem; }
.cat-active-chip-icon { font-size: 0.85rem; line-height: 1; }
.cat-active-chip-label { font-weight: 600; }
.cat-active-chip-sep { color: var(--ink-faint); margin: 0 0.05rem; }
.cat-active-chip-formats {
    font-weight: 500;
    color: var(--ink-soft);
    font-size: 0.74rem;
    letter-spacing: 0.02em;
}

/* On narrow viewports the inline layout would crush the grid — drop to an overlay drawer that
   floats over the grid instead of taking column width away from it. */
@media (max-width: 880px) {
    .cat-layout--open {
        grid-template-columns: 0 minmax(0, 1fr);  /* column stays 0 */
        gap: 0;
    }
    .cat-layout--open .cat-filter-side {
        position: fixed;
        top: 3.75rem;
        left: 0;
        bottom: 0;
        width: min(15rem, 92vw);
        max-height: none;
        z-index: 110;
        box-shadow: 0 10px 28px rgba(0,0,0,0.2);
        border-radius: 0 var(--r) var(--r) 0;
    }
}
.cat-filter-section { padding: 0.3rem 0.55rem 0.55rem; }
.cat-filter-section + .cat-filter-section { border-top: 1px solid var(--line); margin-top: 0.35rem; padding-top: 0.55rem; }
.cat-filter-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-weight: 700;
    font-size: 0.78rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--ink-soft);
    margin-bottom: 0.4rem;
}
.cat-filter-clear {
    background: none;
    border: 0;
    color: var(--accent-strong);
    font-weight: 600;
    font-size: 0.72rem;
    cursor: pointer;
    padding: 0;
    text-transform: none;
    letter-spacing: 0;
}
.cat-filter-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.25rem 0.2rem;
    border-radius: var(--r-sm);
    cursor: pointer;
    font-size: 0.88rem;
    user-select: none;
}
.cat-filter-row:hover { background: var(--surface-2); }
.cat-filter-row--child { padding-left: 1.4rem; font-size: 0.84rem; color: var(--ink); }
.cat-filter-row input[type=checkbox] { margin: 0; }
.cat-filter-footer {
    padding: 0.3rem 0.55rem 0;
    border-top: 1px solid var(--line);
    margin-top: 0.3rem;
    display: flex;
    justify-content: flex-end;
}

/* ─── Chip-input control ────────────────────────────────────────────────
   Same component as BookingIt's ChipInput.razor. Used wherever the user is
   entering a list of named things (authors, narrators, tags, formats) — one
   chip per value, type then Enter/comma to commit, X to remove. */
.chip-input {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    align-items: center;
    padding: 0.3rem 0.45rem;
    border: 1px solid var(--line-strong);
    background: var(--surface);
    border-radius: var(--r-sm);
    min-height: 2.4rem;
    transition: border-color 80ms ease, box-shadow 80ms ease;
}
.chip-input:focus-within {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(189, 84, 45, 0.18);
}
.chip {
    display: inline-flex; align-items: center; gap: 0.25rem;
    padding: 0.15rem 0.35rem 0.15rem 0.55rem;
    border-radius: 999px;
    background: var(--accent-soft);
    color: var(--accent-strong);
    font-size: 0.82rem;
    font-weight: 500;
    user-select: none;
    transition: opacity 80ms ease, transform 80ms ease;
}
.chip-input--sortable .chip { cursor: grab; }
.chip-input--sortable .chip:active { cursor: grabbing; }
.chip.is-dragging { opacity: 0.4; }
.chip.is-drop-target { box-shadow: -3px 0 0 0 var(--accent); transform: translateX(2px); }
.chip-grip { color: var(--accent); opacity: 0.45; font-size: 0.7rem; line-height: 1; letter-spacing: -0.15em; cursor: grab; }
.chip-text { font-size: 0.85rem; }
.chip-x {
    border: none;
    background: transparent;
    color: var(--accent-strong);
    font-size: 1rem;
    line-height: 1;
    padding: 0 0.15rem;
    cursor: pointer;
    border-radius: 50%;
    opacity: 0.6;
}
.chip-x:hover { background: rgba(189, 84, 45, 0.18); opacity: 1; }
.chip-input-field {
    flex: 1;
    min-width: 6rem;
    border: none;
    background: transparent;
    outline: none;
    font-size: 0.92rem;
    padding: 0.25rem 0.35rem;
    color: var(--ink);
    font-family: inherit;
}
.chip-input-field::placeholder { color: var(--ink-faint); font-size: 0.85rem; }

/* ============================================================================
   Mobile responsive overhaul
   ----------------------------------------------------------------------------
   Two-tier breakpoints: 760px (small tablet / phone landscape) tightens layouts
   and stacks the book-detail two-column grid; 480px (typical phone portrait)
   trims paddings further, halves the catalog tile width so two cards fit per
   row, and switches the page-toolbar to wrap mode with smaller controls.
   Targets the high-traffic pages (Catalog, BookDetail, Authors, ImportQueue)
   first — niche admin pages keep their desktop layout but at least don't break.
   ============================================================================ */

@media (max-width: 760px) {
    /* Page chrome: smaller side padding so we stop wasting ~3rem of width to gutters. */
    main, .main-content { padding-left: 0.75rem; padding-right: 0.75rem; }

    /* Top nav: let the horizontal nav scroll instead of wrapping into a stack. */
    .topnav { overflow-x: auto; flex-wrap: nowrap; -webkit-overflow-scrolling: touch; scrollbar-width: thin; }
    .topnav::-webkit-scrollbar { height: 3px; }

    /* Page toolbar: still sticky but allow controls to stack onto two rows. */
    .page-toolbar { padding: 0.55rem 0.7rem; gap: 0.45rem; }
    .page-toolbar-title { font-size: 1.05rem; min-width: 0; }
    .page-toolbar input[type="search"] { flex: 1 1 100%; min-width: 0; max-width: none; }
    .page-toolbar .form-select { font-size: 0.85rem; }
    .page-toolbar-meta { width: 100%; text-align: right; }

    /* Catalog grid: smaller min column so phones get 2 cards per row instead of 1. */
    .book-grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 1.3rem 0.9rem; }
    .book-card .title { font-size: 0.86rem; }
    .book-card .author { font-size: 0.76rem; }
    .book-card .series-line { font-size: 0.74rem; }

    /* Book detail: stack into a single column so the cover doesn't crush the metadata. */
    .detail-layout { grid-template-columns: 1fr; gap: 1.4rem; }
    .detail-cover { max-width: 240px; margin: 0 auto; }
    .detail-form { padding: 0.9rem; }

    /* Book detail header: stack the back link / title / action stack. */
    .book-detail-header { flex-wrap: wrap; gap: 0.4rem; }
    .book-detail-header h1 { font-size: 1.25rem; flex: 1 1 100%; order: 2; }
    .book-back-link { order: 1; }
    .book-detail-actions { order: 3; width: 100%; display: flex; gap: 0.4rem; }
    .book-detail-actions .btn { flex: 1; }

    /* Edition cards: one column instead of two so each card stays legible. */
    .edition-cards { grid-template-columns: 1fr; }

    /* Catalog filter chip row: wrap and reduce per-chip horizontal padding. */
    .cat-filter-bar { padding: 0.4rem 0; flex-wrap: wrap; gap: 0.3rem; }
    .cat-active-chip { font-size: 0.78rem; padding: 0.2rem 0.55rem; }

    /* Tables → switch to card-style rows where we have them (review queue, jobs). */
    .table-responsive .table th { font-size: 0.75rem; padding: 0.45rem 0.5rem; }
    .table-responsive .table td { font-size: 0.85rem; padding: 0.45rem 0.5rem; }
}

@media (max-width: 480px) {
    /* Tighter still on phone portrait. */
    main, .main-content { padding-left: 0.5rem; padding-right: 0.5rem; }
    .page-toolbar { padding: 0.45rem 0.5rem; }

    /* Catalog: 2 cards per row sized to the viewport, smaller text. */
    .book-grid { grid-template-columns: repeat(2, 1fr); gap: 1rem 0.7rem; }
    .book-card .meta { margin-top: 0.45rem; }
    .book-card .title {
        font-size: 0.8rem;
        -webkit-line-clamp: 2;
    }

    /* Small-cover layout: 3 per row on phones (was meant for desktop's "small" view). */
    .book-grid.book-grid--small { grid-template-columns: repeat(3, 1fr); gap: 0.7rem 0.5rem; }

    /* List layout: drop the cover thumb to a smaller size and let title wrap two lines. */
    .book-row { gap: 0.55rem; padding: 0.5rem 0; }
    .book-row-cover { width: 44px; height: 64px; }
    .book-row-title { font-size: 0.9rem; -webkit-line-clamp: 2; }
    .book-row-sub { font-size: 0.78rem; }

    /* Hide the "view mode" toggle on phone portrait — the grid is already mobile-optimal. */
    .view-mode-toggle { display: none; }

    /* Book detail cover narrower so the cover doesn't dominate. */
    .detail-cover { max-width: 200px; }

    /* Tabs row wraps and gets smaller text so multi-tab pages aren't horizontally clipped. */
    .settings-tabs { gap: 0; flex-wrap: wrap; }
    .settings-tab { padding: 0.4rem 0.55rem; font-size: 0.82rem; }

    /* Forms: full-width inputs/buttons; the desktop "side by side" arrangement is too cramped. */
    .merge-form .merge-form-row { grid-template-columns: 1fr; gap: 0.5rem; }
    .merge-page-actions { flex-direction: column; }
    .merge-page-actions .btn { width: 100%; }

    /* Filter sidebar gets the phone-screen overlay treatment automatically via the existing
       <880px overlay rule; nothing to change here, just noting the chain. */
}

/* ── Catalog multi-select (admin bulk actions) ─────────────────────────────── */
.bulk-bar {
    position: sticky;
    top: 0;
    z-index: 20;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    padding: 0.5rem 0.75rem;
    margin-bottom: 0.85rem;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--accent-soft, rgba(120, 90, 220, 0.08));
    backdrop-filter: blur(6px);
}
.bulk-bar-count { font-weight: 600; }

/* Selection affordance on tiles/rows. The card stays an <a>; in select mode the click is
   intercepted (preventDefault) so it ticks instead of navigating. */
.book-card.is-selectable,
.book-row.is-selectable { cursor: pointer; }
.book-card.is-selected,
.book-row.is-selected {
    outline: 2px solid var(--accent, #7a5adc);
    outline-offset: 2px;
    border-radius: 8px;
}
.book-card.is-selected { outline-offset: -2px; }

.select-check {
    position: absolute;
    top: 6px;
    left: 6px;
    z-index: 3;
    width: 22px;
    height: 22px;
    border-radius: 6px;
    border: 2px solid #fff;
    background: rgba(0, 0, 0, 0.35);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.8rem;
    font-weight: 700;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}
.select-check.is-on { background: var(--accent, #7a5adc); border-color: #fff; }
.select-check--row { position: relative; top: auto; left: auto; flex: 0 0 auto; margin-right: 0.4rem; }

.bulk-mode { display: flex; gap: 1rem; flex-wrap: wrap; }

/* Merge target picker (bulk merge dialog) */
.merge-target-list { display: flex; flex-direction: column; gap: 0.4rem; max-height: 50vh; overflow-y: auto; }
.merge-target-row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.45rem 0.6rem;
    border: 1px solid var(--line);
    border-radius: 8px;
    cursor: pointer;
}
.merge-target-row.is-target { border-color: var(--accent, #7a5adc); background: var(--accent-soft, rgba(120, 90, 220, 0.08)); }
.merge-target-cover { flex: 0 0 auto; width: 32px; height: 48px; border-radius: 3px; overflow: hidden; background: #eee; }
.merge-target-cover img { width: 100%; height: 100%; object-fit: cover; }
.merge-target-meta { display: flex; flex-direction: column; min-width: 0; }
.merge-target-title { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
