/*
 * 样式
 *
 * 组织方式：先设计令牌，再基础排版，然后按「布局 → 组件 → 视图」的顺序往下写。
 * 所有颜色都走变量，主题切换只改 <html> 上的两个属性（data-theme / data-accent），
 * 因此不存在「深色模式下某处忘了改」这种问题。
 *
 * 不做 CSS 变量之外的主题体系（比如给每个组件写一遍 dark 覆盖）：
 * 那类写法每加一个组件就要多写一份，漏掉的那一处只会表现为「某个角落颜色不对」。
 */

/* ---------------------------------------------------------------- 设计令牌 */

:root {
    --font-ui:
        system-ui, -apple-system, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei',
        sans-serif;
    --font-read:
        'Noto Serif SC', 'Songti SC', 'Source Han Serif SC', Georgia, 'Times New Roman', serif;
    --font-mono: ui-monospace, SFMono-Regular, Menlo, 'Cascadia Mono', Consolas, monospace;

    --radius-sm: 8px;
    --radius-md: 12px;
    --radius-lg: 18px;
    --radius-xl: 26px;

    --sp-1: 4px;
    --sp-2: 8px;
    --sp-3: 12px;
    --sp-4: 16px;
    --sp-5: 24px;
    --sp-6: 32px;

    --topbar-h: 58px;
    --bnav-h: 0px;

    --reader-font-size: 19px;
    --reader-line-height: 1.85;

    --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* 纸色（阅读底色）跟着强调色走：这是「这本电子书的纸」 */
:root,
[data-accent='amber'] {
    --accent: #b4712a;
    --accent-ink: #ffffff;
    --accent-soft: rgba(180, 113, 42, 0.12);
    --paper: #fbf6ec;
    --paper-ink: #3a3126;
}
[data-accent='green'] {
    --accent: #3f7a5e;
    --accent-ink: #ffffff;
    --accent-soft: rgba(63, 122, 94, 0.12);
    --paper: #f1f7f0;
    --paper-ink: #2c3a31;
}
[data-accent='blue'] {
    --accent: #3b6ea5;
    --accent-ink: #ffffff;
    --accent-soft: rgba(59, 110, 165, 0.12);
    --paper: #eef4fa;
    --paper-ink: #28323d;
}
[data-accent='rose'] {
    --accent: #a4496b;
    --accent-ink: #ffffff;
    --accent-soft: rgba(164, 73, 107, 0.12);
    --paper: #fbf0f3;
    --paper-ink: #3a2a31;
}

[data-theme='light'] {
    --bg: #f6f5f2;
    --bg-grad: radial-gradient(1200px 600px at 12% -12%, #ffffff 0%, #f6f5f2 60%);
    --surface: #ffffff;
    --surface-2: #f1efea;
    --line: rgba(28, 25, 20, 0.1);
    --line-strong: rgba(28, 25, 20, 0.18);
    --ink: #211d18;
    --ink-soft: #5c554b;
    --ink-faint: #8d8579;
    --shadow-1: 0 1px 2px rgba(28, 25, 20, 0.06), 0 1px 3px rgba(28, 25, 20, 0.04);
    --shadow-2: 0 6px 18px rgba(28, 25, 20, 0.08), 0 2px 6px rgba(28, 25, 20, 0.05);
    --shadow-3: 0 18px 44px rgba(28, 25, 20, 0.16);
    --ok: #2f7a4f;
    --warn: #97701d;
    --err: #b43a34;
}

[data-theme='dark'] {
    --bg: #16151a;
    --bg-grad: radial-gradient(1200px 600px at 12% -12%, #201f26 0%, #16151a 60%);
    --surface: #201f26;
    --surface-2: #29282f;
    --line: rgba(255, 255, 255, 0.09);
    --line-strong: rgba(255, 255, 255, 0.16);
    --ink: #ece9e4;
    --ink-soft: #a9a49c;
    --ink-faint: #7d7871;
    --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow-2: 0 8px 22px rgba(0, 0, 0, 0.45);
    --shadow-3: 0 20px 50px rgba(0, 0, 0, 0.6);
    --ok: #5fbf85;
    --warn: #d8b25c;
    --err: #e0796f;
    /* 深色下的「纸」也要压暗，否则夜里阅读会像开了一盏灯 */
    --paper: #1d1c22;
    --paper-ink: #ddd9d2;
}

[data-theme='dark'][data-accent='amber'],
[data-theme='dark'][data-accent='rose'] {
    --accent: #d59a53;
    --accent-ink: #241a0c;
}
[data-theme='dark'][data-accent='green'] {
    --accent: #6fbf95;
    --accent-ink: #0b2016;
}
[data-theme='dark'][data-accent='blue'] {
    --accent: #7aa9dd;
    --accent-ink: #0c1a28;
}

/* ---------------------------------------------------------------- 阅读背景（纸色）

   只在阅读界面生效：`--paper` / `--paper-ink` 除了 `body[data-reader='1']`
   与 `.reader-shell` 之外没有别处消费。

   放在主题色之后是因为优先级相同、靠源码顺序决定 —— 用户显式选了背景色
   就该盖过「跟随主题色」的那一套。没选（`auto`）时 `<html>` 上没有
   `data-paper` 属性，这几条选择器都不命中，行为与以前完全一致。          */

[data-paper='white'] {
    --paper: #ffffff;
    --paper-ink: #26231f;
}
[data-paper='sepia'] {
    --paper: #fbf3e2;
    --paper-ink: #3d3427;
}
[data-paper='green'] {
    --paper: #e7f2e4;
    --paper-ink: #2b3a2f;
}
[data-paper='blue'] {
    --paper: #e9f1fb;
    --paper-ink: #28313d;
}
[data-paper='pink'] {
    --paper: #fbedf0;
    --paper-ink: #3d2a30;
}
[data-paper='gray'] {
    --paper: #eceae6;
    --paper-ink: #34322e;
}
/* 夜间与纯黑：纯黑给 OLED 屏省电，夜间带一点暖调更不刺眼 */
[data-paper='night'] {
    --paper: #1c1b21;
    --paper-ink: #cfcbc4;
}
[data-paper='black'] {
    --paper: #000000;
    --paper-ink: #b6b2ab;
}

/* ---------------------------------------------------------------- 基础 */

*,
*::before,
*::after {
    box-sizing: border-box;
}

html,
body {
    margin: 0;
    padding: 0;
}

body {
    min-height: 100vh;
    background: var(--bg);
    background-image: var(--bg-grad);
    color: var(--ink);
    font-family: var(--font-ui);
    font-size: 15px;
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
    display: flex;
    flex-direction: column;
    padding-bottom: env(safe-area-inset-bottom);
}

h1,
h2,
h3 {
    margin: 0;
    line-height: 1.3;
    font-weight: 650;
    letter-spacing: -0.01em;
}

a {
    color: var(--accent);
    text-decoration: none;
}

img {
    max-width: 100%;
    display: block;
}

button {
    font: inherit;
    color: inherit;
    cursor: pointer;
}

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

.spacer {
    flex: 1;
}
.row {
    display: flex;
    gap: var(--sp-3);
    align-items: center;
    flex-wrap: wrap;
}
.row.tight {
    gap: var(--sp-2);
}
.row.center {
    justify-content: center;
}
.center {
    text-align: center;
}
.muted {
    color: var(--ink-soft);
}
.tiny {
    font-size: 12px;
}
.mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* 滚动条：默认的又宽又亮，在深色下尤其跳 */
::-webkit-scrollbar {
    width: 10px;
    height: 10px;
}
::-webkit-scrollbar-thumb {
    background: var(--line-strong);
    border-radius: 99px;
    border: 3px solid transparent;
    background-clip: content-box;
}

/* ---------------------------------------------------------------- 布局 */

.topbar {
    position: sticky;
    top: 0;
    z-index: 30;
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    min-height: var(--topbar-h);
    padding: 0 var(--sp-4);
    background: color-mix(in srgb, var(--surface) 86%, transparent);
    backdrop-filter: saturate(1.4) blur(14px);
    border-bottom: 1px solid var(--line);
}

.brand {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    color: var(--ink);
    font-weight: 700;
    flex-shrink: 0;
}
.brand-mark {
    width: 30px;
    height: 30px;
    border-radius: 9px;
    background: linear-gradient(145deg, var(--accent), color-mix(in srgb, var(--accent) 62%, #000));
    color: var(--accent-ink);
    display: grid;
    place-items: center;
    font-size: 15px;
    font-weight: 700;
    box-shadow: var(--shadow-1);
}
.brand-text {
    letter-spacing: 0.01em;
}

.nav {
    display: flex;
    gap: 2px;
    overflow-x: auto;
    scrollbar-width: none;
}
.nav::-webkit-scrollbar {
    display: none;
}
.tab {
    border: 0;
    background: transparent;
    color: var(--ink-soft);
    padding: 8px 14px;
    border-radius: 99px;
    font-weight: 550;
    white-space: nowrap;
    transition:
        background 0.18s var(--ease),
        color 0.18s var(--ease);
}
.tab:hover {
    background: var(--surface-2);
    color: var(--ink);
}
.tab.active {
    background: var(--accent-soft);
    color: var(--accent);
}

.topbar-right {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: var(--sp-2);
}

main#view {
    flex: 1;
    width: 100%;
    max-width: 1080px;
    margin: 0 auto;
    padding: var(--sp-5) var(--sp-4) var(--sp-6);
    display: flex;
    flex-direction: column;
    gap: var(--sp-5);
}

.bottom-nav {
    display: none;
}

.footer {
    display: flex;
    gap: var(--sp-2);
    justify-content: center;
    align-items: center;
    padding: var(--sp-4);
    color: var(--ink-faint);
    font-size: 12px;
}
.footer .dot {
    opacity: 0.5;
}

.page-title {
    font-size: 24px;
}
.page-head {
    display: flex;
    align-items: flex-end;
    gap: var(--sp-4);
}
.page-head .page-title {
    margin-bottom: 2px;
}
.section-head {
    display: flex;
    align-items: baseline;
    gap: var(--sp-3);
    margin-bottom: var(--sp-3);
}
.section-head h2 {
    font-size: 17px;
}
.link {
    border: 0;
    background: none;
    color: var(--accent);
    padding: 0;
    font-size: 13px;
}

/* ---------------------------------------------------------------- 组件 */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-height: 38px;
    padding: 8px 16px;
    border-radius: var(--radius-md);
    border: 1px solid var(--line-strong);
    background: var(--surface);
    color: var(--ink);
    font-weight: 550;
    white-space: nowrap;
    transition:
        transform 0.12s var(--ease),
        box-shadow 0.18s var(--ease),
        background 0.18s var(--ease);
    box-shadow: var(--shadow-1);
}
.btn:hover:not(:disabled) {
    transform: translateY(-1px);
    box-shadow: var(--shadow-2);
}
.btn:active:not(:disabled) {
    transform: translateY(0);
}
.btn:disabled {
    opacity: 0.55;
    cursor: not-allowed;
    box-shadow: none;
}
.btn.primary {
    background: linear-gradient(150deg, var(--accent), color-mix(in srgb, var(--accent) 80%, #000));
    border-color: transparent;
    color: var(--accent-ink);
}
.btn.ghost {
    background: transparent;
    border-color: var(--line);
    box-shadow: none;
    color: var(--ink-soft);
}
.btn.ghost:hover:not(:disabled) {
    color: var(--ink);
    background: var(--surface-2);
}
.btn.danger {
    color: var(--err);
    border-color: color-mix(in srgb, var(--err) 40%, transparent);
}
.btn.danger.ghost {
    border-color: var(--line);
}
.btn.sm {
    min-height: 32px;
    padding: 5px 12px;
    font-size: 13px;
}
.btn.block {
    width: 100%;
}
.btn.dl {
    margin-bottom: var(--sp-2);
}

.icon-btn {
    width: 34px;
    height: 34px;
    display: grid;
    place-items: center;
    border-radius: 50%;
    border: 0;
    background: transparent;
    color: var(--ink-soft);
    font-size: 16px;
    transition:
        background 0.18s var(--ease),
        color 0.18s var(--ease);
}
.icon-btn:hover:not(:disabled) {
    background: var(--surface-2);
    color: var(--ink);
}
.icon-btn:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

.chips {
    display: flex;
    gap: var(--sp-2);
    overflow-x: auto;
    padding-bottom: var(--sp-1);
    scrollbar-width: none;
}
.chips::-webkit-scrollbar {
    display: none;
}
/* 分类多到几十上百个时，横排必然滚不动（滚动条被隐藏、滚轮不横移），改为换行铺开 */
.chips.wrap {
    flex-wrap: wrap;
    overflow-x: visible;
}
.chip {
    border: 1px solid var(--line);
    background: var(--surface);
    color: var(--ink-soft);
    border-radius: 99px;
    padding: 7px 14px;
    font-size: 13px;
    white-space: nowrap;
    transition: all 0.18s var(--ease);
}
.chip:hover {
    border-color: var(--line-strong);
    color: var(--ink);
}
.chip.active {
    background: var(--accent);
    border-color: transparent;
    color: var(--accent-ink);
}

/* ---------------------------------------------------------------- 书源选择器 */

.explore-bar {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    margin-bottom: var(--sp-3);
}

.picker {
    position: relative;
    min-width: 0;
    flex: 1;
    max-width: 420px;
}
.picker-button {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    width: 100%;
    padding: 9px 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--ink);
    font-size: 14px;
    text-align: left;
}
.picker-button:hover,
.picker-button.open {
    border-color: var(--line-strong);
}
.picker-value {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.picker-caret {
    color: var(--ink-faint);
    font-size: 12px;
}
.picker-panel {
    position: absolute;
    z-index: 40;
    top: calc(100% + 6px);
    left: 0;
    right: 0;
    padding: var(--sp-2);
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface);
    box-shadow: var(--shadow-3);
}
.picker-search {
    width: 100%;
    margin-bottom: var(--sp-2);
}
/* 固定高度 + 竖向滚动：几百条书源也能滚得动，这正是原来的 chips 做不到的 */
.picker-list {
    max-height: 46vh;
    overflow-y: auto;
    overscroll-behavior: contain;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.picker-item {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: 8px 10px;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--ink-soft);
    font-size: 13px;
    text-align: left;
}
.picker-item:hover {
    background: var(--surface-2);
    color: var(--ink);
}
.picker-item.active {
    background: var(--accent-soft);
    color: var(--accent);
    font-weight: 600;
}
.picker-item-name {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* 分组下拉在选择面板里独占一行：面板本来就窄，跟搜索框挤一起两个都用不成 */
.picker-panel .group-select {
    width: 100%;
    max-width: none;
    margin-bottom: var(--sp-2);
}
/*
 * 面板里的分组头
 *
 * 粘住不动：面板是 46vh 的滚动区，几百条滚到一半时得知道自己在哪一组。
 * 背景必须不透明，否则从它底下滚过去的条目会透出来。
 */
.picker-group-head {
    position: sticky;
    top: 0;
    z-index: 1;
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: 6px 10px 4px;
    background: var(--surface);
    font-size: 11px;
    font-weight: 700;
    color: var(--ink-faint);
}
.picker-group-name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*
 * 搜索范围：那一行字 + 选择面板
 *
 * 面板是**内联**在卡片里的，不是浮层（与上面「换源」那个不同）—— 它可以有几十条，
 * 做成浮层会盖住结果，而用户挑书源时要能同时看结果。
 */
.search-scope {
    align-items: center;
    gap: var(--sp-2);
    margin-top: var(--sp-2);
}
.search-picker {
    margin-top: var(--sp-3);
    padding-top: var(--sp-3);
    border-top: 1px solid var(--line);
}
.search-picker[hidden] {
    display: none;
}
.picker-foot {
    align-items: center;
    gap: var(--sp-2);
    margin-top: var(--sp-2);
}

.badge {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 99px;
    background: var(--accent-soft);
    color: var(--accent);
    font-size: 11px;
    font-weight: 600;
}
.badge.ghost {
    background: var(--surface-2);
    color: var(--ink-faint);
}
.badge.ok {
    background: color-mix(in srgb, var(--ok) 14%, transparent);
    color: var(--ok);
}

.field {
    display: block;
    margin-bottom: var(--sp-4);
}
.field-label {
    display: block;
    font-size: 12px;
    color: var(--ink-soft);
    margin-bottom: 6px;
    font-weight: 600;
}

input[type='text'],
input[type='password'],
input[type='search'],
/* 文件输入只用在「导入备份」那一处，跟着同一套外观走 */
input[type='file'],
textarea {
    width: 100%;
    padding: 11px 14px;
    border-radius: var(--radius-md);
    border: 1px solid var(--line-strong);
    background: var(--surface);
    color: var(--ink);
    font: inherit;
    transition:
        border-color 0.18s var(--ease),
        box-shadow 0.18s var(--ease);
}
input:focus,
textarea:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}
textarea {
    resize: vertical;
}

.switch {
    display: inline-flex;
    align-items: center;
}
.switch input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}
.switch-track {
    width: 42px;
    height: 24px;
    border-radius: 99px;
    background: var(--surface-2);
    border: 1px solid var(--line);
    position: relative;
    transition: background 0.2s var(--ease);
}
.switch-track::after {
    content: '';
    position: absolute;
    top: 2px;
    left: 2px;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: var(--surface);
    box-shadow: var(--shadow-1);
    transition: transform 0.2s var(--ease);
}
.switch input:checked + .switch-track {
    background: var(--accent);
    border-color: transparent;
}
.switch input:checked + .switch-track::after {
    transform: translateX(18px);
}

.segmented {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px;
    border-radius: 99px;
    background: var(--surface-2);
    border: 1px solid var(--line);
}
.segment {
    border: 0;
    background: transparent;
    color: var(--ink-soft);
    padding: 5px 12px;
    border-radius: 99px;
    font-size: 13px;
}
.segment.active {
    background: var(--surface);
    color: var(--ink);
    box-shadow: var(--shadow-1);
}

.swatches {
    display: flex;
    gap: var(--sp-2);
}
.swatch {
    width: 26px;
    height: 26px;
    border-radius: 50%;
    border: 2px solid transparent;
    box-shadow: var(--shadow-1);
}
.swatch.amber {
    background: #d9a05b;
}
.swatch.green {
    background: #6faa88;
}
.swatch.blue {
    background: #6f9fd0;
}
.swatch.rose {
    background: #c97a98;
}
.swatch.active {
    border-color: var(--ink);
}

/* ---------------------------------------------------------------- 卡片 */

.card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: var(--sp-4);
    box-shadow: var(--shadow-1);
}

.grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: var(--sp-3);
}

.book-card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    overflow: hidden;
    box-shadow: var(--shadow-1);
    display: flex;
    flex-direction: column;
    transition:
        transform 0.16s var(--ease),
        box-shadow 0.2s var(--ease);
}
.book-card:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-2);
}
.book-card-main {
    display: flex;
    gap: var(--sp-3);
    padding: var(--sp-3);
    color: inherit;
    flex: 1;
}
.book-card-text {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.book-title {
    font-size: 15px;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
}
.book-title.large {
    font-size: 22px;
}
.book-meta {
    color: var(--ink-soft);
    font-size: 12.5px;
    margin: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.book-intro {
    color: var(--ink-faint);
    font-size: 12.5px;
    margin: 0;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
}
.book-card-actions {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: 0 var(--sp-3) var(--sp-3);
}

.cover {
    width: 62px;
    height: 86px;
    border-radius: var(--radius-sm);
    object-fit: cover;
    background: var(--surface-2);
    flex-shrink: 0;
    box-shadow: var(--shadow-1);
}
.cover.small {
    width: 46px;
    height: 64px;
}
.cover.large {
    width: 108px;
    height: 150px;
    border-radius: var(--radius-md);
}
.cover.tile {
    width: 100%;
    height: auto;
    aspect-ratio: 2 / 3;
    border-radius: var(--radius-md);
}
/* 没封面时的兜底：按书名定色的渐变 + 首字，比一片灰块好看得多 */
.cover-fallback {
    display: grid;
    place-items: center;
    color: #fff;
    font-size: 22px;
    font-weight: 700;
    letter-spacing: 0.02em;
    background: linear-gradient(150deg, #8a7a63, #5d5346);
}
.cover-fallback[data-seed='0'] {
    background: linear-gradient(150deg, #c2764a, #8a4a2c);
}
.cover-fallback[data-seed='1'] {
    background: linear-gradient(150deg, #5f8f7a, #38604f);
}
.cover-fallback[data-seed='2'] {
    background: linear-gradient(150deg, #6d86b8, #40567f);
}
.cover-fallback[data-seed='3'] {
    background: linear-gradient(150deg, #a86a8a, #6f3f5b);
}
.cover-fallback[data-seed='4'] {
    background: linear-gradient(150deg, #b09455, #7a6231);
}
.cover-fallback[data-seed='5'] {
    background: linear-gradient(150deg, #7f7fa8, #4d4d75);
}
.cover-fallback[data-seed='6'] {
    background: linear-gradient(150deg, #59a0a6, #2f6a70);
}
.cover-fallback[data-seed='7'] {
    background: linear-gradient(150deg, #b57a57, #7b4a2f);
}

/* 横向滑动栏：推荐位与继续阅读 */
.rail {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: minmax(220px, 260px);
    gap: var(--sp-3);
    overflow-x: auto;
    padding: var(--sp-1) var(--sp-1) var(--sp-3);
    scroll-snap-type: x proximity;
    scrollbar-width: thin;
}
.rail > * {
    scroll-snap-align: start;
}
.rail-card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: var(--sp-3);
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    box-shadow: var(--shadow-1);
}
.rail-head {
    display: flex;
    gap: var(--sp-3);
}
.rail-text {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 3px;
}
.rail-actions {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
}

.shelf-tile {
    display: flex;
    flex-direction: column;
    gap: 6px;
    color: inherit;
}
.tile-title {
    font-size: 13px;
    font-weight: 600;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
}
.tile-meta {
    font-size: 11.5px;
    color: var(--ink-faint);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.source-list {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
}
.source-row {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-3);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
}
.source-main {
    min-width: 0;
    flex: 1;
}
.source-name {
    font-size: 14px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.source-meta {
    display: flex;
    gap: 6px;
    margin: 6px 0 0;
    flex-wrap: wrap;
}
.source-actions {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
}
/* 一行书源 + 它下面的登录面板：包一层，面板才会跟着这一行走 */
.source-item {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
}

/*
 * 「书源」页的筛选条
 *
 * 816 条书源平铺就是一堵墙，所以这一条才是页面的**主入口**：先按名字 / 分组 /
 * 状态 / 能力把范围收到看得见的量，再挑具体哪一条。
 *
 * 粘在顶栏下面 —— 翻到某一组的深处时还能改筛选，不用一路滚回去。
 * 背景要带一点透明度并做毛玻璃，否则从它底下滚过去的行会透出来。
 */
.source-toolbar {
    position: sticky;
    top: calc(var(--topbar-h) + var(--sp-2));
    z-index: 20;
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    background: color-mix(in srgb, var(--surface) 92%, transparent);
    backdrop-filter: saturate(1.4) blur(12px);
}
.source-toolbar[hidden] {
    display: none;
}
.source-controls {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
}
/* 关键词框吃掉剩下的宽度；分组下拉与按钮都不许被压变形 */
.source-filter-main {
    flex-wrap: nowrap;
    gap: var(--sp-2);
}
.source-search {
    flex: 1;
    min-width: 0;
}
.group-select {
    flex: none;
    max-width: 46vw;
}
.source-filter-sub {
    gap: var(--sp-2);
}
.source-tally {
    margin: 0;
}
.source-bulk {
    gap: var(--sp-2);
}
.source-bulk:empty {
    display: none;
}

/*
 * 分组：一个 `<details>` 一张卡
 *
 * 收起时**一行都不建**（见 `viewSources` 的 `sourceGroupNode`）—— 所以「收起」
 * 不只是视觉上的，是真的不占 DOM。816 条全铺开是八千多个节点。
 */
.source-groups {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
}
.source-group {
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface);
    overflow: hidden;
}
.source-group-head {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-3) var(--sp-4);
    cursor: pointer;
    font-size: 14px;
    font-weight: 600;
    color: var(--ink);
    list-style: none;
}
.source-group-head::-webkit-details-marker {
    display: none;
}
.source-group-head:hover {
    background: var(--surface-2);
}
.source-group[open] > .source-group-head {
    border-bottom: 1px solid var(--line);
}
/* 自己画那个三角：原生 marker 在 Chrome / Safari 里位置与大小都不一致 */
.source-group-head::before {
    content: '▸';
    flex: none;
    font-size: 11px;
    color: var(--ink-faint);
    transition: transform 0.15s var(--ease);
}
.source-group[open] > .source-group-head::before {
    transform: rotate(90deg);
}
.source-group-name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* 整组已经是一个框了，组内的行不再各圈一道边 */
.source-group .source-list {
    padding: var(--sp-2);
    gap: var(--sp-1);
}
.source-group .source-row {
    border: 0;
    background: transparent;
}
.source-group .source-row:hover {
    background: var(--surface-2);
}

/*
 * 拖放区
 *
 * 原生那个「选择文件」按钮各家浏览器长得完全不一样，所以把 `input` 藏了、
 * 整块变成 `<label>` —— 点哪儿都能触发选择。拖进来的文件走同一条导入路径。
 */
.drop-row {
    gap: 0;
}
.drop-zone {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    padding: var(--sp-4);
    border: 1px dashed var(--line-strong);
    border-radius: var(--radius-md);
    background: var(--surface-2);
    color: var(--ink-soft);
    font-size: 13px;
    text-align: center;
    cursor: pointer;
    transition: all 0.18s var(--ease);
}
.drop-zone:hover,
.drop-zone.over {
    border-color: var(--accent);
    background: var(--accent-soft);
    color: var(--accent);
}
/* 视觉上藏起来但仍可聚焦：键盘用户也能用它选文件 */
.drop-zone input[type='file'] {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    opacity: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
}
.login-slot[hidden] {
    display: none;
}
.login-body {
    padding: var(--sp-3);
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
}
/* 输入框按宽度自动铺：语料里的登录界面从 2 个控件到 20 多个都有 */
.login-fields {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: 0 var(--sp-3);
}
.login-actions {
    flex-wrap: wrap;
    gap: var(--sp-2);
    align-items: center;
}
.login-status:not(:empty) {
    margin-top: var(--sp-2);
}

.chapter-list {
    list-style: none;
    margin: 0;
    padding: 0;
}
.chapter-link {
    /* 一行里放三样：章节名（占满）、VIP 标记、更新时间 —— 见第五十六轮 */
    display: flex;
    align-items: baseline;
    gap: 8px;
    width: 100%;
    text-align: left;
    border: 0;
    background: transparent;
    padding: 10px 8px;
    border-bottom: 1px solid var(--line);
    color: var(--ink-soft);
    border-radius: var(--radius-sm);
    font-size: 13.5px;
}
.chapter-link:hover {
    background: var(--surface-2);
    color: var(--ink);
}
.chapter-list li[data-current='true'] .chapter-link {
    color: var(--accent);
    font-weight: 650;
    background: var(--accent-soft);
}

/* 目录里书源给的标注：卷标题 / VIP 标记 / 更新时间（第五十六轮） */
.chapter-name {
    flex: 1;
    /* 没有 min-width，长章节名会把右边两样挤出屏幕 */
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.chapter-mark {
    flex: none;
    font-size: 11px;
    line-height: 1.6;
    padding: 0 6px;
    border: 1px solid var(--line);
    border-radius: 999px;
    opacity: 0.85;
}
.chapter-time {
    flex: none;
    max-width: 40%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 11px;
    opacity: 0.7;
}
.chapter-volume {
    padding: 12px 8px 6px;
    background: var(--surface-2);
    border-bottom: 1px solid var(--line);
    font-size: 12.5px;
    font-weight: 650;
    color: var(--ink);
}

/* ---------------------------------------------------------------- 状态 */

.alert {
    border-radius: var(--radius-md);
    padding: 12px 14px;
    border: 1px solid var(--line);
    background: var(--surface);
    font-size: 13.5px;
}
.alert-title {
    font-weight: 600;
}
.alert-extra {
    margin-top: 4px;
    color: var(--ink-soft);
    font-size: 12.5px;
}
.alert.ok {
    border-color: color-mix(in srgb, var(--ok) 34%, transparent);
    background: color-mix(in srgb, var(--ok) 10%, var(--surface));
    color: var(--ok);
}
.alert.warn {
    border-color: color-mix(in srgb, var(--warn) 34%, transparent);
    background: color-mix(in srgb, var(--warn) 10%, var(--surface));
    color: var(--warn);
}
.alert.error {
    border-color: color-mix(in srgb, var(--err) 34%, transparent);
    background: color-mix(in srgb, var(--err) 10%, var(--surface));
    color: var(--err);
}
.alert .alert-extra {
    color: inherit;
    opacity: 0.85;
}

#toast {
    position: fixed;
    z-index: 60;
    left: 12px;
    right: 12px;
    bottom: calc(12px + env(safe-area-inset-bottom));
    max-width: 560px;
    margin: 0 auto;
}
.toast-item {
    background: var(--surface);
    box-shadow: var(--shadow-3);
    animation: toast-in 0.22s var(--ease);
}
@keyframes toast-in {
    from {
        opacity: 0;
        transform: translateY(10px);
    }
}

.skeleton-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: var(--sp-3);
}
.skeleton-card {
    display: flex;
    gap: var(--sp-3);
    padding: var(--sp-3);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
}
.skeleton-lines {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 8px;
    justify-content: center;
}
.skeleton {
    border-radius: 6px;
    background: linear-gradient(90deg, var(--surface-2) 25%, var(--line) 37%, var(--surface-2) 63%);
    background-size: 400% 100%;
    animation: shimmer 1.3s ease-in-out infinite;
}
.skeleton-cover {
    width: 62px;
    height: 86px;
    border-radius: var(--radius-sm);
    flex-shrink: 0;
}
.skeleton-line {
    height: 12px;
}
.skeleton-block {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: var(--sp-4);
}
.skeleton-block .skeleton-line {
    height: 14px;
    width: 100%;
}
.skeleton-block .w50 {
    width: 50%;
}
.w70 {
    width: 70%;
}
.w40 {
    width: 40%;
}
@keyframes shimmer {
    from {
        background-position: 100% 0;
    }
    to {
        background-position: 0 0;
    }
}

.empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-3);
    text-align: center;
    padding: var(--sp-6) var(--sp-4);
    border: 1px dashed var(--line-strong);
    border-radius: var(--radius-lg);
    color: var(--ink-soft);
}
.empty-mark {
    font-size: 34px;
    filter: saturate(0.9);
}
.empty h2 {
    font-size: 18px;
    color: var(--ink);
}

.failures,
.import-card {
    font-size: 13px;
}
.failures summary,
.import-card summary {
    cursor: pointer;
    color: var(--ink-soft);
}
.failures ul {
    margin: var(--sp-2) 0 0;
    padding-left: 18px;
    color: var(--ink-faint);
}
.import-body {
    margin-top: var(--sp-3);
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
}

.claim-banner {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    border-radius: var(--radius-lg);
    border: 1px solid color-mix(in srgb, var(--accent) 34%, transparent);
    background: var(--accent-soft);
}

/* ---------------------------------------------------------------- 登录 */

.auth {
    max-width: 420px;
    margin: 6vh auto;
    display: flex;
    flex-direction: column;
    gap: var(--sp-4);
    text-align: center;
}
.auth-title {
    font-size: 26px;
}
.auth-card {
    text-align: left;
}
.auth-lead {
    margin: 0 0 var(--sp-4);
    color: var(--ink-soft);
    font-size: 13.5px;
}
.auth-form .btn {
    margin-top: var(--sp-2);
}
.auth-switch {
    margin-top: var(--sp-3);
    font-size: 13px;
    color: var(--ink-soft);
    text-align: center;
}
.auth-notice {
    color: var(--ink-soft);
    font-size: 13px;
}

/* ---------------------------------------------------------------- 书籍详情 */

.book-detail-head {
    display: flex;
    gap: var(--sp-4);
}
.book-detail-head .btn {
    margin-top: var(--sp-3);
}
.intro {
    margin-top: var(--sp-4);
}
.intro p {
    margin: var(--sp-2) 0 0;
    color: var(--ink-soft);
    font-size: 13.5px;
    white-space: pre-wrap;
}

/* ---------------------------------------------------------------- 阅读界面 */

body[data-reader='1'] {
    overflow: hidden;
    background: var(--paper);
    background-image: none;
}
body[data-reader='1'] .topbar,
body[data-reader='1'] .footer,
body[data-reader='1'] .bottom-nav {
    display: none;
}
body[data-reader='1'] main#view {
    max-width: none;
    padding: 0;
    margin: 0;
}

.reader-shell {
    position: fixed;
    inset: 0;
    /*
     * 移动端：**不要跟着地址栏涨落**
     *
     * `inset: 0`（以及 `100vh`）在 iOS 上按**大视口**算 —— 地址栏一露出来，底部那条
     * 工具条就被顶到屏幕外；更要紧的是地址栏一收一展会改变这个盒子，而翻页的列是
     * 跟着盒子排的：盒子一动，列就重排，读者正在看的那一段会**当场换页**（第六十二轮）。
     *
     * `100svh` 是「地址栏露出来时」的视口高，取的是最保守的那个值：盒子从此不再变。
     * 代价是地址栏收起时底部留一条纸色空白（几十 px）—— 对阅读器来说，**排版稳定**
     * 比多占那几十 px 重要得多。不支持 `svh` 的老浏览器沿用 `inset: 0`，行为与之前一致。
     */
    height: 100svh;
    /* 高于 .topbar（30）：阅读界面是全屏的，任何时候都不该被导航栏压住 ——
       被压住的后果不只是难看，顶栏那几个按钮会**点不动**。 */
    z-index: 40;
    display: flex;
    flex-direction: column;
    background: var(--paper);
    color: var(--paper-ink);
}

.reader-top,
.reader-bottom {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: 6px 10px;
    background: color-mix(in srgb, var(--paper) 88%, transparent);
    backdrop-filter: blur(10px);
    transition:
        opacity 0.24s var(--ease),
        transform 0.24s var(--ease);
}
.reader-top {
    border-bottom: 1px solid var(--line);
    padding-top: calc(6px + env(safe-area-inset-top));
}
.reader-bottom {
    border-top: 1px solid var(--line);
    padding-bottom: calc(6px + env(safe-area-inset-bottom));
}
.reader-title {
    font-size: 13.5px;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 46vw;
}

/* 点中间收起上下栏：全屏阅读时视线不被工具条切碎 */
.reader-shell.chrome-hidden .reader-top {
    opacity: 0;
    transform: translateY(-100%);
    pointer-events: none;
}
.reader-shell.chrome-hidden .reader-bottom {
    opacity: 0;
    transform: translateY(100%);
    pointer-events: none;
}

.reader-body {
    position: relative;
    flex: 1;
    min-height: 0;
    overflow: auto;
    padding: var(--sp-3) var(--sp-3) var(--sp-4);
    -webkit-overflow-scrolling: touch;
    /*
     * 只留竖向滚动，把浏览器自己的双指缩放与双击缩放让出来 —— 阅读界面的字号由
     * 双指捏合接管（见 public/js/zoom.js）。两层同时生效的话字会被放大两次，
     * 而且页面本身也被缩放糊掉。
     * 横向滑动不受影响：翻页是我们自己的 touch 监听在做，与原生手势无关。
     */
    touch-action: pan-y;
}

/*
 * 字号提示
 *
 * 捏合与 Ctrl/⌘+滚轮都不经过设置面板，得有个自己的反馈：用户要知道「现在几号字」，
 * 也要知道「是不是已经到上限了」。颜色跟着主题反转（浅色主题是深底浅字），
 * 所以四种纸色 + 深色主题下都读得清。
 */
.reader-zoom-hud {
    position: fixed;
    left: 50%;
    top: calc(14px + env(safe-area-inset-top));
    transform: translateX(-50%);
    z-index: 45;
    padding: 5px 14px;
    border-radius: 999px;
    background: color-mix(in srgb, var(--ink) 80%, transparent);
    color: var(--paper);
    font-size: 13px;
    font-variant-numeric: tabular-nums;
    backdrop-filter: blur(6px);
    pointer-events: none;
}

/* 翻页模式：正文用多列排版，一列就是一页 */
.reader-body.paged {
    overflow: hidden;
    padding: var(--sp-4) 0 var(--sp-3);
}
.reader-body.paged .reader-flow {
    height: 100%;
    column-fill: auto;
    transition: transform 0.3s var(--ease);
    will-change: transform;
}

.reader-flow {
    font-family: var(--font-read);
    font-size: var(--reader-font-size);
    line-height: var(--reader-line-height);
    color: var(--paper-ink);
}
.reader-flow p {
    margin: 0 0 0.9em;
    text-indent: 2em;
}
.reader-flow.media-flow {
    font-family: var(--font-ui);
    font-size: 15px;
    line-height: 1.6;
}

/* 翻页时的边缘投影：让「一页推过去」有方向感，而不是整块瞬移 */
.reader-body.paged::before,
.reader-body.paged::after {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    width: 46px;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.32s var(--ease);
    z-index: 2;
}
.reader-body.paged::before {
    left: 0;
    background: linear-gradient(to right, rgba(0, 0, 0, 0.14), transparent);
}
.reader-body.paged::after {
    right: 0;
    background: linear-gradient(to left, rgba(0, 0, 0, 0.14), transparent);
}
.reader-body.turn-next::after {
    opacity: 1;
}
.reader-body.turn-prev::before {
    opacity: 1;
}

/* 翻页动画由显示设置决定（`<html data-turn>`）：
   覆盖＝位移 + 边缘投影；滑动＝更快的位移、不要投影；
   无＝直接跳过去，连过渡都不要 —— 「无动画」要的就是不拖泥带水。 */
[data-turn='slide'] .reader-body.paged .reader-flow {
    transition-duration: 0.18s;
}
[data-turn='slide'] .reader-body.paged::before,
[data-turn='slide'] .reader-body.paged::after,
[data-turn='none'] .reader-body.paged::before,
[data-turn='none'] .reader-body.paged::after {
    display: none;
}
[data-turn='none'] .reader-body.paged .reader-flow {
    transition: none;
}

.reader-progress {
    flex: 1;
    height: 4px;
    border-radius: 99px;
    background: color-mix(in srgb, var(--paper-ink) 14%, transparent);
    overflow: hidden;
}
.reader-progress-fill {
    height: 100%;
    width: 4%;
    border-radius: 99px;
    background: var(--accent);
    transition: width 0.28s var(--ease);
}
.reader-page-label {
    font-size: 12px;
    color: var(--ink-faint);
    min-width: 58px;
    text-align: center;
    font-variant-numeric: tabular-nums;
}

.comic {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    align-items: center;
}
.comic-page {
    width: 100%;
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-2);
}
.comic-failed {
    width: 100%;
    padding: 40px 0;
    text-align: center;
    color: var(--ink-faint);
    border: 1px dashed var(--line-strong);
    border-radius: var(--radius-md);
}
.comic-meta {
    color: var(--ink-faint);
    font-size: 12px;
}
.audio-box {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
}
.audio-player {
    width: 100%;
}
.downloads {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    align-items: flex-start;
}

.sheet-host {
    position: fixed;
    inset: 0;
    z-index: 50;
}
.sheet-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.4);
    animation: fade-in 0.2s var(--ease);
}
@keyframes fade-in {
    from {
        opacity: 0;
    }
}
.sheet {
    position: absolute;
    right: 0;
    top: 0;
    bottom: 0;
    width: min(420px, 88vw);
    background: var(--surface);
    color: var(--ink);
    display: flex;
    flex-direction: column;
    box-shadow: var(--shadow-3);
    animation: sheet-in 0.24s var(--ease);
}
@keyframes sheet-in {
    from {
        transform: translateX(16px);
        opacity: 0;
    }
}
.sheet header {
    display: flex;
    align-items: center;
    padding: var(--sp-3) var(--sp-4);
    border-bottom: 1px solid var(--line);
}
.sheet-body {
    flex: 1;
    overflow: auto;
    padding: var(--sp-3) var(--sp-4) var(--sp-6);
}

/* 换源面板（详情页那颗「找找别的来源」）—— 居中一张卡片，与 `.sheet` 是同一族味道 */
.switch-host {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--sp-4);
}
.switch-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.4);
    animation: fade-in 0.2s var(--ease);
}
.switch-panel {
    position: relative;
    width: min(560px, 100%);
    max-height: min(660px, 100%);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background: var(--surface);
    color: var(--ink);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-3);
    animation: switch-in 0.2s var(--ease);
}
@keyframes switch-in {
    from {
        transform: translateY(10px);
        opacity: 0;
    }
}
.switch-panel header {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-3) var(--sp-4);
    border-bottom: 1px solid var(--line);
}
.switch-panel header h2 {
    margin: 0;
    font-size: 15px;
}
.switch-body {
    flex: 1;
    min-height: 0;
    overflow: auto;
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    padding: var(--sp-3) var(--sp-4) var(--sp-5);
}
.switch-state {
    margin: 0;
}
.switch-list {
    display: flex;
    flex-direction: column;
    gap: 2px;
    overscroll-behavior: contain;
}
.switch-item {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: 8px 10px;
    border-radius: var(--radius-sm);
}
.switch-item:hover {
    background: var(--surface-2);
}
/* 长书名 / 长章名不许把这行撑破 —— 换行之后按钮会被挤到看不见的地方 */
.switch-item-main {
    flex: 1;
    min-width: 0;
}
.switch-item-title {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    font-size: 13.5px;
    color: var(--ink);
}
.switch-item-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.switch-item-meta {
    margin-top: 2px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.settings {
    display: flex;
    flex-direction: column;
    gap: var(--sp-4);
}
.setting-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    font-size: 13.5px;
}

/* 需要占满整行的设置项（背景色、规则列表这类） */
.setting-block {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    font-size: 13.5px;
}
.setting-label {
    color: var(--ink-soft);
}
.setting-row .segment[disabled] {
    opacity: 0.6;
    cursor: default;
}

/* 背景色选择：每个色块自带一个小圆点，直接显示那个背景是什么颜色 */
.papers {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
}
.paper-chip {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 10px;
    border: 1px solid var(--line);
    border-radius: 99px;
    background: var(--surface);
    color: var(--ink-soft);
    font-size: 12.5px;
}
.paper-chip.active {
    border-color: var(--accent);
    color: var(--accent);
    background: var(--accent-soft);
}
.paper-dot {
    width: 14px;
    height: 14px;
    border-radius: 50%;
    border: 1px solid var(--line-strong);
    background: #fff;
}
.paper-chip[data-paper='sepia'] .paper-dot {
    background: #fbf3e2;
}
.paper-chip[data-paper='white'] .paper-dot {
    background: #ffffff;
}
.paper-chip[data-paper='green'] .paper-dot {
    background: #e7f2e4;
}
.paper-chip[data-paper='blue'] .paper-dot {
    background: #e9f1fb;
}
.paper-chip[data-paper='pink'] .paper-dot {
    background: #fbedf0;
}
.paper-chip[data-paper='gray'] .paper-dot {
    background: #eceae6;
}
.paper-chip[data-paper='night'] .paper-dot {
    background: #1c1b21;
}
.paper-chip[data-paper='black'] .paper-dot {
    background: #000000;
}
/* 「跟随主题」用一个双色小圆点表示它是个动态值 */
.paper-chip[data-paper='auto'] .paper-dot {
    background: linear-gradient(135deg, #fbf6ec 50%, #1d1c22 50%);
}

/* 强调色的色点：直接画那个颜色，选起来才有依据 */
.paper-dot.accent-amber {
    background: #b4712a;
}
.paper-dot.accent-green {
    background: #3f7a5e;
}
.paper-dot.accent-blue {
    background: #3b6ea5;
}
.paper-dot.accent-rose {
    background: #a4496b;
}

/* ---------------------------------------------------------------- 替换净化 */

.rule-list {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
}
.rule-row {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-2);
    padding: var(--sp-2) 0;
    border-bottom: 1px solid var(--line);
}
.rule-text {
    flex: 1;
    min-width: 0;
}
.rule-name {
    font-size: 13px;
    font-weight: 600;
    color: var(--ink);
}
.rule-pattern,
.rule-replacement {
    display: block;
    margin-top: 2px;
    font-family: var(--font-mono);
    font-size: 11.5px;
    color: var(--ink-faint);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.rule-replacement {
    color: var(--accent);
}
.rule-preview {
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
}
.rule-preview p + p {
    margin-top: 4px;
}
.rule-add-title {
    font-size: 13px;
    font-weight: 600;
    color: var(--ink-soft);
    padding-top: var(--sp-2);
    border-top: 1px solid var(--line);
}
.rule-form {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
}
.rule-form input {
    width: 100%;
}
.rule-preset {
    display: flex;
    flex-direction: column;
    gap: 2px;
    width: 100%;
    padding: var(--sp-2) var(--sp-3);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--surface);
    text-align: left;
}
.rule-preset:hover {
    border-color: var(--accent);
}

/* 账号同步：两个按钮 + 一行状态。与上面的规则编辑之间用 .rule-add-title 隔开，
   这里只负责内部间距 —— 插进 .settings 后它自己也是一个有节奏的块 */
.rule-sync {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
}
.rule-sync-status > p {
    margin: 0;
}

/* ---------------------------------------------------------------- 章内搜索 */

/* 命中高亮：用半透明底色而不是纯色块，正文本身的墨色不变，
   换背景色（纸色）之后依然能看清，不必为九种纸色各调一遍 */
.reader-flow mark.search-hit {
    background: color-mix(in srgb, var(--accent) 34%, transparent);
    color: inherit;
    border-radius: 3px;
    /* 左右各留 1px 让底色不贴着字，同时用 -1px 的外边距把这点宽度抵消掉：
       内联盒子的前进宽度 = 外边距 + 内边距 + 内容，两边一加一减正好不变，
       于是**高亮的出现不会让正文重新分页** —— 否则一边输关键词页码一边跳。 */
    padding: 0 1px;
    margin: 0 -1px;
}
/* 当前这一处：底色更重，并且在翻页模式下要有足够的视觉重量，
   否则「跳过去了但看不出跳到哪」 */
.reader-flow mark.search-hit.active {
    background: var(--accent);
    /* 用 --accent-ink 而不是写死白色：深色主题下强调色本身是浅色，
       配白字会糊成一片，而这个变量就是「压在强调色上的那个颜色」 */
    color: var(--accent-ink);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 40%, transparent);
}

.search-input {
    flex: 1;
    min-width: 0;
}
.search-status {
    text-align: right;
    white-space: nowrap;
}
.search-results {
    display: flex;
    flex-direction: column;
    gap: 2px;
    max-height: 52vh;
    overflow: auto;
}
.search-result {
    display: flex;
    align-items: baseline;
    gap: var(--sp-2);
    width: 100%;
    padding: var(--sp-2) var(--sp-3);
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    text-align: left;
    font-size: 13px;
    line-height: 1.5;
    color: var(--ink);
}
.search-result:hover {
    border-color: var(--accent);
}
.search-result.active {
    border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 14%, var(--surface-2));
}
.search-result-text {
    flex: 1;
    min-width: 0;
}
.search-result-count {
    flex: none;
    font-size: 11px;
    color: var(--ink-faint);
    font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------------- 书签 */

.bm-list {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
}
.bm-row {
    display: flex;
    align-items: center;
    gap: var(--sp-1);
    padding: var(--sp-1) var(--sp-2) var(--sp-1) 0;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--surface);
}
.bm-row.editing {
    padding: var(--sp-2);
}
.bm-main {
    /* 整块可点＝跳过去，因此把按钮的默认外观抹掉，只留排版 */
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: var(--sp-2);
    border: 0;
    background: transparent;
    text-align: left;
    color: inherit;
    font: inherit;
    cursor: pointer;
}
.bm-row.editing .bm-main {
    cursor: default;
    flex: 1;
}
.bm-row.editing .bm-main input {
    width: 100%;
}
.bm-head {
    display: flex;
    align-items: baseline;
    gap: var(--sp-2);
}
.bm-chapter {
    flex: 1;
    min-width: 0;
    font-size: 13px;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.bm-time {
    flex: none;
    font-size: 11px;
    color: var(--ink-faint);
}
.bm-excerpt {
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--ink-soft);
    /* 摘录最长 80 字，最多铺三行，再多就把列表挤没了 */
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.bm-note {
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--accent);
    border-left: 2px solid var(--accent);
    padding-left: var(--sp-2);
}

/* ---------------------------------------------------------------- 笔记 */

/* 笔记的正文是主体：比摘录重、比书签备注大，而且**保留换行** ——
   人写笔记会分条，把换行压掉的正文读起来是一段糊在一起的字。
   `overflow-wrap: anywhere` 是给长链接与不换行的长串留的活路，
   没有它那一段会把整个面板撑出横向滚动。 */
.note-text {
    margin-top: 2px;
    font-size: 13px;
    line-height: 1.6;
    color: var(--ink);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* 写 / 改笔记的输入区：跟列表一起滚，不另开一个面板 ——
   另开的话「边看已有笔记边写新的」这件事就做不到了 */
.note-compose {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    padding: var(--sp-2);
    border: 1px solid var(--accent);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
}
.note-input {
    width: 100%;
    min-height: 96px;
    padding: var(--sp-2);
    resize: vertical;
    font: inherit;
    font-size: 13px;
    line-height: 1.6;
    color: var(--ink);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
}
.note-input:focus {
    outline: 2px solid var(--accent);
    outline-offset: -1px;
}
/* 输入区上方那段摘录只是交代「这条要挂在哪」，别抢正文的地方 */
.note-compose .bm-excerpt {
    -webkit-line-clamp: 2;
}

/* ---------------------------------------------------------------- 窄屏 */

@media (max-width: 720px) {
    :root {
        --bnav-h: 58px;
    }

    .brand-text {
        display: none;
    }
    .nav {
        display: none;
    }
    main#view {
        /* 底部那段留白交给页脚去顶（见下面的 `.footer`）：页脚永远排在 main 之后，
           两处都留会白出一大块。 */
        padding: var(--sp-4) var(--sp-3) var(--sp-4);
        gap: var(--sp-4);
    }

    .bottom-nav {
        display: flex;
        position: fixed;
        z-index: 30;
        left: 0;
        right: 0;
        bottom: 0;
        height: calc(var(--bnav-h) + env(safe-area-inset-bottom));
        padding-bottom: env(safe-area-inset-bottom);
        background: color-mix(in srgb, var(--surface) 92%, transparent);
        backdrop-filter: blur(14px);
        border-top: 1px solid var(--line);
    }
    .bnav-item {
        flex: 1;
        border: 0;
        background: transparent;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 3px;
        color: var(--ink-faint);
        font-size: 11px;
    }
    .bnav-item.active {
        color: var(--accent);
    }
    .bnav-dot {
        width: 20px;
        height: 4px;
        border-radius: 99px;
        background: currentColor;
        opacity: 0.28;
    }
    .bnav-item.active .bnav-dot {
        opacity: 1;
    }

    /* 页脚在手机上也要在：它带着版本号与**源代码地址**（AGPL 第 13 条要求通过网络
       使用本程序的人能拿到源码），而底部那条固定导航里没有等价的东西。
       这里一度把它整个藏了 —— 结果手机上既看不到版本号，也没有进「关于」的路，
       只剩桌面端能看到。 */
    .footer {
        display: flex;
        flex-wrap: wrap;
        /* 顶住底部那条固定导航 */
        padding-bottom: calc(var(--bnav-h) + env(safe-area-inset-bottom) + var(--sp-3));
    }
    /* 手指点的东西不该只有一行字那么高 */
    .footer > a,
    .footer > .link {
        padding: 6px 4px;
    }
    body[data-reader='1'] .footer {
        display: none;
    }

    .grid {
        grid-template-columns: 1fr;
    }
    .skeleton-list {
        grid-template-columns: 1fr;
    }
    .rail {
        grid-auto-columns: minmax(190px, 62vw);
    }
    .page-title {
        font-size: 21px;
    }
    .book-detail-head {
        gap: var(--sp-3);
    }
    .claim-banner {
        flex-wrap: wrap;
    }

    /* 「书源」页那条筛选：窄屏下一行放不下「搜索框 + 分组下拉 + 清空」三样，
       挤在一起三样都会变得难点。搜索框独占一行，下面一行留给下拉与清空。 */
    .source-filter-main {
        flex-wrap: wrap;
    }
    .source-search {
        flex: 1 1 100%;
    }
    .group-select {
        flex: 1 1 auto;
        max-width: none;
    }
    .source-toolbar {
        padding: var(--sp-3);
    }

    /* 阅读顶栏现在有六个图标（返回 / 收藏 / 搜索 / 书签 / 目录 / 设置）。
       窄屏下按原尺寸算，书名会被挤到只剩两三个字，所以把图标与间距一起收一收 ——
       图标小一点还能点，书名短到看不出是哪一章就没法读了。 */
    .reader-top {
        gap: 2px;
    }
    .reader-top .icon-btn {
        width: 30px;
        height: 30px;
        font-size: 15px;
    }
    .reader-title {
        max-width: 36vw;
    }
}

/* ---------------------------------------------------------------- 关于 */

.about-version {
    font-size: 20px;
    font-weight: 600;
}

/* 更新记录：一条一版。左边那道竖线是分组用的 —— 四十来条排下来，
   没有它就看不出「版本号 + 日期」与下面那句话是一伙的。 */
.releases {
    display: grid;
    gap: var(--sp-3);
}
.release {
    border-left: 2px solid var(--line);
    padding-left: var(--sp-3);
}
.release-head {
    display: flex;
    align-items: baseline;
    gap: var(--sp-2);
}
.release-note {
    color: var(--ink-soft);
    font-size: 13px;
}

/* 系统设了「减少动态效果」时，翻页与骨架屏都停下 —— 那是可访问性偏好，不是审美 */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}
