Files
lexgo/learner/src/style.css
T
ila 728f7d6c1e feat: 书籍页精简封面、可编辑书名与作者、标题旁显示作者 (#37)
- schema v11:lexgo_books 增加 author VARCHAR(120) NOT NULL DEFAULT '',复用 v10 的条件加列
  助手(改名 addAuthorColumn,表名取自本文件常量),保持迁移可重放;新建库 v3 语句也带该列
- PATCH /api/v1/books/:id 接受可选 author(省略保留、空串清空、trim、≤120),BookSummary 与
  BookRef 返回它;「编辑书名」改为「编辑书籍」,对话框同行编辑书名与作者
- 书籍页封面压成一行紧凑控件(预览+上传/替换/移除),去掉「封面」「书籍封面」规格与
  「音频与插图按章节设置」等文字,把高度让给章节列表;书名右侧显示书级作者(未设置不显示)
- 测试:Go 91 项(新增书级作者往返与 v10→v11 迁移用例)、学习端 157 单测与 26 项 E2E
- 真实链路:封面行高 68px、删掉的四段文字不再出现、作者显示在书名同一行右侧且刷新后保留
- 文档:Architecture / Business-Rules / Local-Development / Requirements / Home 同步
2026-09-15 23:10:36 +08:00

408 lines
27 KiB
CSS

:root {
/* One palette for both themes. Every surface, text and status colour in the rules below
resolves through these variables, so the dark theme only overrides values, never layout.
These are the only literal colours in the file. */
--bg: #f7f5ee;
--surface: #fffdf8;
--surface-raised: #fffdf7;
--surface-input: #fffefa;
--text: #233d31;
--text-strong: #264a35;
--text-muted: #748073;
--text-faint: #687568;
--border: #e0e3d8;
--border-strong: #d9decf;
--border-input: #d6dccf;
--accent: #315c43;
--accent-soft: #eef2eb;
--accent-marker: #bc803d;
--accent-gold: #ba8345;
--brand-panel: #284c38;
--brand-panel-text: #f7f5ee;
--brand-panel-muted: #d4dfd2;
--brand-panel-faint: #c4d1c0;
--brand-panel-line: #d6b980;
--status-pending-bg: #f5f2e4;
--status-pending-border: #e2dcc2;
--status-pending-text: #7a6a35;
--status-processing-bg: #eaf1f7;
--status-processing-border: #c9dcea;
--status-processing-text: #35566e;
--status-ready-bg: #eef2eb;
--status-ready-border: #cbd9c9;
--status-ready-text: #315c43;
--status-failed-bg: #fff0e7;
--status-failed-border: #ebc3a8;
--status-failed-text: #8b4324;
--status-read-bg: #e7f0e9;
--status-read-border: #b9d3bf;
--status-read-text: #2c5b3d;
--notice-bg: #fff0e7;
--notice-border: #ebc3a8;
--notice-text: #8b4324;
--highlight: #eef2eb;
--sense-bg: #fbf7ee;
--sense-border: #d6b980;
--sense-text: #6b5b3e;
--sense-muted: #8b794e;
--saved: #2f6b45;
--word-learning-bg: #f6e7c9;
--word-known-bg: #dee9d6;
--word-ignored-text: #949c94;
--word-selected-bg: #e3e9d9;
--reader-font-scale: 1;
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
color: var(--text);
background: var(--bg);
font-synthesis: none;
--el-color-primary: var(--accent);
--el-color-primary-light-3: #597f66;
--el-color-primary-light-5: #8da493;
--el-color-primary-light-7: #bdccc0;
--el-color-primary-light-9: var(--accent-soft);
--el-color-primary-dark-2: var(--text-strong);
--el-border-radius-base: 8px;
--el-font-size-base: 15px;
}
/* Dark palette. The Element Plus dark class is toggled by the display store, so components
follow the same choice as the page palette. */
html[data-theme='dark'] {
--bg: #171a17;
--surface: #1f231f;
--surface-raised: #1c201c;
--surface-input: #242824;
--text: #e7ece4;
--text-strong: #cfe0d3;
--text-muted: #9fae9c;
--text-faint: #8e9c8c;
--border: #333a33;
--border-strong: #3b433b;
--border-input: #3f483f;
--accent: #7db28f;
--accent-soft: #24312a;
--accent-marker: #d7a45f;
--accent-gold: #d7a45f;
--brand-panel: #10140f;
--brand-panel-text: #e7ece4;
--brand-panel-muted: #b9c7b6;
--brand-panel-faint: #97a695;
--brand-panel-line: #d7a45f;
--status-pending-bg: #33301f;
--status-pending-border: #4a442b;
--status-pending-text: #e0cf94;
--status-processing-bg: #1d2a33;
--status-processing-border: #2f4756;
--status-processing-text: #a8cbe0;
--status-ready-bg: #22301f;
--status-ready-border: #35492f;
--status-ready-text: #a9d0ac;
--status-failed-bg: #33231b;
--status-failed-border: #4d3527;
--status-failed-text: #e9ad86;
--status-read-bg: #1f3024;
--status-read-border: #33493a;
--status-read-text: #a5d3b1;
--notice-bg: #33231b;
--notice-border: #4d3527;
--notice-text: #e9ad86;
--highlight: #26332a;
--sense-bg: #23261d;
--sense-border: #4a4227;
--sense-text: #d9cba6;
--sense-muted: #c0ad7f;
--saved: #8ec69f;
--word-learning-bg: #3b3520;
--word-known-bg: #253120;
--word-ignored-text: #7d867d;
--word-selected-bg: #2b382d;
}
/* Element Plus reads its own dark variables; the shared palette keeps both in step. */
html.dark {
--el-bg-color: var(--surface);
--el-bg-color-overlay: var(--surface-raised);
--el-text-color-primary: var(--text);
--el-text-color-regular: var(--text);
--el-text-color-secondary: var(--text-muted);
--el-border-color: var(--border);
--el-border-color-light: var(--border);
--el-fill-color-blank: var(--surface-input);
--el-fill-color-light: var(--accent-soft);
color-scheme: dark;
}
* { box-sizing: border-box; }
body { margin: 0; min-width: 320px; }
button, input { font: inherit; }
a { color: inherit; }
.brand { font-family: Georgia, serif; font-size: 30px; font-weight: 700; letter-spacing: -1px; text-decoration: none; }
.brand-dot { color: var(--accent-gold); }
.login-page { min-height: 100dvh; display: grid; grid-template-columns: minmax(340px, 1fr) minmax(400px, 1fr); }
.welcome { background: var(--brand-panel); color: var(--brand-panel-text); padding: 46px 10%; display: flex; flex-direction: column; justify-content: space-between; }
.welcome .brand { color: var(--brand-panel-text); }
.welcome-copy { padding: 80px 0; max-width: 450px; }
.welcome-copy h2 { font-family: Georgia, 'Microsoft YaHei', serif; font-weight: 400; font-size: clamp(32px, 3.3vw, 52px); line-height: 1.55; letter-spacing: 2px; margin: 26px 0; }
.welcome-copy p { font-size: 16px; color: var(--brand-panel-muted); line-height: 1.9; }
.book-mark { width: 60px; height: 60px; color: var(--brand-panel-line); }
.welcome-foot { font-size: 13px; letter-spacing: 2px; color: var(--brand-panel-faint); }
.login-main { display: grid; place-items: center; padding: 48px 28px; }
.login-form { width: min(100%, 360px); }
.eyebrow { color: var(--text-faint); font-size: 12px; letter-spacing: 3px; }
h1 { font-size: 30px; font-weight: 600; margin: 14px 0; letter-spacing: 1px; }
.subtle { color: var(--text-muted); font-size: 14px; line-height: 1.8; }
.login-form form { margin-top: 36px; }
.field { display: block; margin-bottom: 22px; }
.field label { display: block; font-size: 14px; margin-bottom: 10px; }
.el-input { --el-input-height: 46px; --el-input-bg-color: var(--surface-input); --el-input-border-color: var(--border-input); }
.el-button { min-height: 42px; }
.login-submit { width: 100%; margin-top: 8px; height: 48px; letter-spacing: 4px; }
.notice { padding: 12px 15px; background: var(--notice-bg); border: 1px solid var(--notice-border); color: var(--notice-text); border-radius: 6px; font-size: 14px; line-height: 1.6; }
.site-header { padding: 24px max(24px, calc((100vw - 1200px) / 2)); border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 32px; background: var(--surface-raised); }
.site-header nav { flex: 1; }
.active-nav { font-size: 15px; text-decoration: none; padding: 13px 0; border-bottom: 2px solid var(--accent); }
.account { display: flex; align-items: center; gap: 18px; font-size: 14px; }
.account-name { max-width: 200px; overflow: hidden; text-overflow: ellipsis; }
.library { max-width: 1120px; margin: 60px auto; padding: 0 28px; }
.library-title { display: flex; align-items: center; justify-content: space-between; gap: 20px; margin-bottom: 38px; }
.language { border: 1px solid var(--border-strong); padding: 9px 17px; border-radius: 20px; font-size: 13px; background: var(--surface-raised); }
.empty-library { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; min-height: 360px; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 44px 20px; text-align: center; }
.empty-library .book-mark { color: var(--accent); width: 56px; height: 56px; }
.empty-library h2 { font-weight: 500; font-size: 21px; margin: 26px 0 4px; }
.loading { padding: 80px 24px; text-align: center; color: var(--text-muted); }
/* Import / book / reader pages share one shell. */
.page { max-width: 1120px; margin: 60px auto; padding: 0 28px; }
.page-title { display: flex; align-items: center; justify-content: space-between; gap: 18px; flex-wrap: wrap; margin-bottom: 28px; }
.page-title h1 { margin: 14px 0 6px; overflow-wrap: anywhere; }
.page-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.library-actions { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.link-button { display: inline-flex; align-items: center; min-height: 42px; padding: 0 8px; color: var(--accent); }
.breadcrumb { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0 0 4px; font-size: 14px; color: var(--text-muted); }
.breadcrumb a { color: var(--accent); }
/* Library book cards. */
.book-grid { list-style: none; margin: 0; padding: 0; display: grid; gap: 18px; grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); }
.book-card { display: flex; flex-direction: column; gap: 10px; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 22px; }
.book-card .subtle { margin: 0; }
.book-title { font-family: Georgia, 'Microsoft YaHei', serif; font-size: 21px; font-weight: 600; color: var(--text); text-decoration: none; overflow-wrap: anywhere; }
.book-title:hover { color: var(--accent); text-decoration: underline; }
.status-summary { align-self: flex-start; margin: 0; padding: 6px 13px; border-radius: 20px; background: var(--accent-soft); color: var(--accent); font-size: 13px; }
/* Chapter and job status, identical vocabulary for both. */
.status-chip { display: inline-flex; align-items: center; white-space: nowrap; padding: 5px 13px; border: 1px solid transparent; border-radius: 20px; font-size: 13px; }
.status-pending { background: var(--status-pending-bg); border-color: var(--status-pending-border); color: var(--status-pending-text); }
.status-processing { background: var(--status-processing-bg); border-color: var(--status-processing-border); color: var(--status-processing-text); }
.status-ready { background: var(--accent-soft); border-color: var(--status-ready-border); color: var(--accent); }
.status-failed { background: var(--notice-bg); border-color: var(--notice-border); color: var(--notice-text); }
.status-read { background: var(--status-read-bg); border-color: var(--status-read-border); color: var(--status-read-text); }
/* Completing a chapter: one explicit action, with the reading rule spelled out. */
.chapter-complete { display: flex; align-items: center; justify-content: space-between; gap: 18px; flex-wrap: wrap; margin-top: 22px; padding: 18px 22px; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; }
.chapter-complete .subtle { margin: 6px 0 0; }
.read-state { margin: 0; font-weight: 600; color: var(--status-read-text); }
/* Progress page. */
.progress-stats { display: grid; gap: 16px; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); margin-top: 6px; }
.stat-card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 20px 22px; }
.stat-card h2 { margin: 0 0 10px; font-size: 15px; font-weight: 600; color: var(--accent); letter-spacing: 0; }
.stat-card .subtle { margin: 6px 0 0; }
.stat-value { margin: 0; font-family: Georgia, 'Microsoft YaHei', serif; font-size: 30px; line-height: 1.2; color: var(--text); }
.progress-books { margin-top: 34px; }
.progress-books h2 { margin: 0 0 14px; font-size: 19px; }
.progress-book-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.progress-book-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 16px 18px; }
/* Import form. */
.import-form { max-width: 720px; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 28px; }
.field-label { display: block; font-size: 14px; margin-bottom: 10px; }
.fixed-value { margin: 0; padding: 12px 15px; border: 1px solid var(--border-input); border-radius: 8px; background: var(--surface-input); font-size: 15px; }
.field-error { margin: 8px 0 0; color: var(--notice-text); font-size: 13px; line-height: 1.6; }
.counter { margin: 8px 0 0; color: var(--text-muted); font-size: 13px; }
.form-actions { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin-top: 14px; }
.import-form .el-textarea__inner { min-height: 220px; line-height: 1.9; }
.book-select { width: 100%; }
/* Book chapters. */
.chapter-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.chapter-row { display: grid; grid-template-columns: 40px minmax(0, 1fr) auto auto; align-items: center; gap: 14px; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 16px 18px; }
.chapter-ordinal { font-family: Georgia, serif; font-size: 17px; color: var(--text-faint); text-align: center; }
.chapter-info { min-width: 0; }
.chapter-name { display: inline-block; font-size: 16px; font-weight: 500; color: var(--text); text-decoration: none; overflow-wrap: anywhere; }
a.chapter-name:hover { color: var(--accent); text-decoration: underline; }
.chapter-meta { margin: 4px 0 0; color: var(--text-muted); font-size: 13px; overflow-wrap: anywhere; }
/* Reader keeps the pasted text exactly as it was, including line breaks and tabs. */
.reader-page { max-width: 820px; }
.chapter-author { margin: -6px 0 0; color: var(--text-muted); font-size: 14px; }
.reader-text { white-space: pre-wrap; overflow-wrap: break-word; margin: 26px 0 0; font-family: Georgia, 'Times New Roman', 'Microsoft YaHei', serif; font-size: calc(17px * var(--reader-font-scale)); line-height: 2; }
.processing-hint { padding: 36px 0; color: var(--text-muted); }
.reader-nav { display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap; margin-top: 36px; padding-top: 22px; border-top: 1px solid var(--border); }
.reader-page.has-lookup { max-width: 1120px; }
.reader-workspace { display: grid; grid-template-columns: minmax(0, 1fr); gap: 32px; align-items: start; }
.has-lookup .reader-workspace { grid-template-columns: minmax(0, 1fr) 320px; }
.reader-body { min-width: 0; }
.reader-word { cursor: pointer; border-radius: 3px; }
/* Saved words keep one style per status everywhere they appear. */
.reader-word.is-new { border-bottom: 2px dotted var(--accent-marker); }
.reader-word.is-learning { background: var(--word-learning-bg); }
.reader-word.is-known { background: var(--word-known-bg); }
.reader-word.is-ignored { color: var(--word-ignored-text); }
.reader-word:hover, .reader-word.is-selected { background: var(--word-selected-bg); color: var(--text-strong); }
.reader-word:focus-visible { outline: 2px solid var(--accent-marker); outline-offset: 2px; background: var(--accent-soft); }
.tokens-notice { color: var(--text-muted); font-size: 14px; margin-top: 24px; }
.lookup-panel { position: sticky; top: 24px; margin-top: 26px; padding: 22px; border: 1px solid var(--border-strong); border-radius: 12px; background: var(--surface); max-height: calc(100dvh - 48px); overflow-y: auto; overflow-wrap: anywhere; }
.lookup-heading { display: flex; align-items: start; justify-content: space-between; gap: 10px; border-bottom: 1px solid var(--border); padding-bottom: 12px; }
.lookup-heading h2 { margin: 6px 0; font-family: Georgia, serif; font-size: 25px; }
.lookup-content { font-size: calc(15px * var(--reader-font-scale)); line-height: 1.7; }
.lookup-message { color: var(--notice-text); }
.lookup-senses { padding-left: 22px; }
.lookup-senses li { padding-left: 3px; margin-bottom: 20px; }
.lookup-senses p { margin: 8px 0; }
.sense-heading span { color: var(--text-muted); font-size: 13px; }
.lookup-senses blockquote { border-left: 2px solid var(--status-ready-border); margin: 10px 0; padding-left: 12px; color: var(--text-faint); font-style: italic; }
.lookup-range { border-bottom: 1px solid var(--border); padding-bottom: 14px; }
.lookup-range p { margin: 0 0 10px; }
.lookup-range-actions { display: flex; gap: 6px; flex-wrap: wrap; }
/* A saved phrase is one unit: the run is underlined and keeps its status colour. */
.reader-word.is-phrase { text-decoration: underline; text-decoration-style: double; text-underline-offset: 2px; border-radius: 3px; }
.reader-word.is-phrase-selected { outline: 2px solid var(--accent-marker); outline-offset: 1px; }
.lookup-term { border-top: 1px solid var(--border); padding-top: 18px; margin-top: 20px; }
.lookup-term label { display: flex; justify-content: space-between; gap: 10px; font-size: 14px; margin-top: 14px; }
.lookup-term-title { display: flex; font-size: 14px; margin: 0; }
.lookup-term label span { color: var(--sense-muted); font-size: 12px; }
.lookup-term .el-radio-group { margin-top: 8px; flex-wrap: wrap; gap: 4px 12px; }
.lookup-term .el-textarea { margin-top: 8px; }
.lookup-term .el-textarea textarea { font: inherit; line-height: 1.6; }
.lookup-saved { color: var(--saved); font-size: 14px; margin: 12px 0 0; }
.lookup-actions { display: flex; gap: 8px; margin-top: 14px; flex-wrap: wrap; }
.review-page { max-width: 680px; }
.vocab-page { max-width: 760px; }
.vocab-search { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 22px; }
.vocab-search label { font-size: 14px; }
.vocab-search .el-input { max-width: 260px; }
.vocab-filter { width: 140px; }
.vocab-list { list-style: none; padding: 0; margin: 18px 0 0; }
.vocab-row { display: flex; align-items: center; gap: 14px; padding: 14px 16px; margin-bottom: 10px; border: 1px solid var(--border); border-radius: 10px; background: var(--surface); }
.vocab-info { min-width: 0; flex: 1; }
.vocab-term { margin: 0; font-family: Georgia, serif; font-size: 18px; overflow-wrap: anywhere; }
.vocab-paging { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 18px; }
.term-fields label { display: flex; justify-content: space-between; gap: 10px; font-size: 14px; margin-top: 14px; }
.term-fields label span { color: var(--sense-muted); font-size: 12px; }
.term-fields .el-textarea { margin-top: 8px; }
.term-fields .el-radio-group { margin-top: 8px; flex-wrap: wrap; gap: 4px 12px; }
.term-level { display: flex; align-items: center; gap: 10px; margin: 12px 0 0; font-size: 14px; }
.term-level-select { width: 120px; }
.chapter-notice { margin: 12px 0 0; padding: 10px 14px; border: 1px solid var(--border-strong); border-radius: 8px; background: var(--sense-bg); color: var(--sense-text); }
.chapter-list .chapter-row { flex-wrap: wrap; }
.review-notice { margin: 10px 0 0; padding: 10px 14px; border: 1px solid var(--border-strong); border-radius: 8px; background: var(--sense-bg); color: var(--sense-text); }
.review-card, .review-summary { margin-top: 26px; padding: 28px; border: 1px solid var(--border-strong); border-radius: 14px; background: var(--surface); }
.review-summary h2 { margin-top: 0; font-family: Georgia, serif; font-size: 24px; }
.review-word { margin: 12px 0; font-family: Georgia, serif; font-size: calc(34px * var(--reader-font-scale)); }
.review-example { margin: 12px 0; font-family: Georgia, serif; font-size: 19px; line-height: 1.8; }
.review-answer { margin: 18px 0; padding-top: 18px; border-top: 1px solid var(--border); }
.review-definition { margin: 0 0 12px; font-size: calc(18px * var(--reader-font-scale)); }
.review-answer blockquote { border-left: 2px solid var(--status-ready-border); margin: 10px 0; padding-left: 12px; color: var(--text-faint); font-style: italic; }
.review-grades { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 8px; }
.review-actions { margin: 16px 0 0; }
@media (max-width: 760px) {
.login-page { grid-template-columns: 1fr; }
.welcome { padding: 28px; }
.welcome-copy, .welcome-foot { display: none; }
.login-main { padding: 50px 28px 70px; align-items: start; }
.site-header { padding: 20px; gap: 22px; flex-wrap: wrap; }
.account { margin-left: auto; gap: 10px; }
.account-name { max-width: 120px; }
.site-header nav { order: 3; flex-basis: 100%; padding-top: 8px; }
.library { margin-top: 32px; padding: 0 20px; }
h1 { font-size: 26px; }
/* Single column, tap-friendly controls and no horizontal overflow. */
.page { margin-top: 32px; padding: 0 20px; }
.page-title { align-items: flex-start; }
.library-title { align-items: flex-start; }
.library-actions { width: 100%; justify-content: space-between; }
.book-grid { grid-template-columns: 1fr; }
.import-form { padding: 20px; }
.chapter-row { grid-template-columns: 30px minmax(0, 1fr); align-items: start; row-gap: 10px; padding: 15px 16px; }
.chapter-row .status-chip, .chapter-row .el-button { grid-column: 2; justify-self: start; }
.chapter-complete { flex-direction: column; align-items: stretch; }
.chapter-complete .el-button { width: 100%; }
.reader-text { font-size: calc(16px * var(--reader-font-scale)); line-height: 1.95; }
.has-lookup .reader-workspace { grid-template-columns: minmax(0, 1fr); gap: 20px; }
.reader-page.has-lookup { padding-bottom: calc(45dvh + 28px); }
.lookup-panel { position: fixed; inset: auto 0 0; z-index: 20; max-height: 45dvh; margin-top: 0; padding: 16px 20px max(20px, env(safe-area-inset-bottom)); border-radius: 16px 16px 0 0; box-shadow: 0 -5px 24px var(--text)14; }
.lookup-heading { position: sticky; top: -16px; z-index: 1; background: var(--surface); }
.reader-nav .el-button { flex: 1; }
}
/* One visible focus ring for every interactive element, including Element Plus controls. */
button:focus-visible, a:focus-visible, [role='button']:focus-visible,
.el-button:focus-visible, .el-input__inner:focus-visible, .el-select__wrapper:focus-visible {
outline: 3px solid var(--accent-marker);
outline-offset: 3px;
}
/* Header display control: theme and reader font size. */
.display-trigger { display: inline-flex; align-items: center; gap: 8px; min-height: 38px; padding: 0 12px; border: 1px solid var(--border-strong); border-radius: 20px; background: var(--surface-raised); color: var(--text); font-size: 13px; cursor: pointer; }
.display-trigger:hover { border-color: var(--accent); }
/* Element Plus styles arrive after this file for lazy-loaded views, so the ring is stated
with enough weight to win the tie while a control from its own library is focused. */
.display-trigger:focus-visible, .display-trigger.el-tooltip__trigger:focus-visible { outline: 3px solid var(--accent-marker); outline-offset: 3px; }
.display-current { color: var(--text-muted); }
.el-dropdown-menu__item.is-current { color: var(--accent); font-weight: 600; }
/* Discoverable keyboard operations on the review page. */
.shortcut-hint { margin: 22px 0 0; color: var(--text-muted); font-size: 13px; }
/* Chapter illustration: a small thumbnail that opens the original in a dialog. */
.illustration-thumb { display: inline-flex; flex-direction: column; align-items: flex-start; gap: 6px; margin: 0 0 18px; padding: 8px; border: 1px solid var(--border); border-radius: 12px; background: var(--surface); cursor: zoom-in; }
.illustration-thumb img { display: block; height: 120px; width: auto; max-width: 100%; border-radius: 8px; object-fit: cover; }
.illustration-thumb:hover { border-color: var(--accent); }
.illustration-thumb:focus-visible { outline: 3px solid var(--accent-marker); outline-offset: 3px; }
.illustration-hint { font-size: 12px; color: var(--text-muted); }
.illustration-full { display: block; max-width: min(88vw, 1200px); max-height: 78vh; width: auto; height: auto; object-fit: contain; }
/* The book page keeps the cover control to one compact row so the chapter list gets the space. */
.cover-row { display: flex; align-items: center; gap: 14px; margin: 0 0 18px; }
.cover-preview { flex: 0 0 120px; aspect-ratio: 16 / 9; border-radius: 10px; overflow: hidden; background: var(--accent-soft); }
.cover-preview img { width: 100%; height: 100%; object-fit: cover; display: block; }
.cover-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.cover-actions input[type='file'] { position: absolute; width: 1px; height: 1px; opacity: 0; overflow: hidden; }
.cover-actions label { position: relative; cursor: pointer; border: 1px solid var(--border-strong); border-radius: 20px; padding: 0 14px; background: var(--surface-raised); }
.cover-actions label.is-busy { opacity: 0.6; cursor: progress; }
/* The book title shows its author next to it. */
.title-line { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.title-line h1 { margin: 14px 0 6px; }
.book-author { color: var(--text-muted); font-size: 15px; }
/* Book covers: the card shows the stored image or a default block built from the title. */
.book-cover { display: block; aspect-ratio: 16 / 9; border-radius: 10px; overflow: hidden; background: var(--accent-soft); text-decoration: none; }
.book-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.book-cover-default { display: grid; place-items: center; width: 100%; height: 100%; font-family: Georgia, serif; font-size: 40px; color: var(--accent); }
/* Book attachments: metadata, preview and the upload controls. */
.attachments { margin-top: 26px; padding: 22px; border: 1px solid var(--border); border-radius: 12px; background: var(--surface); }
.attachments h2 { margin: 0 0 16px; font-size: 19px; }
.attachment-row { display: flex; gap: 18px; align-items: flex-start; padding: 14px 0; border-top: 1px solid var(--border); }
.attachment-row:first-of-type { border-top: 0; padding-top: 0; }
.attachment-preview { flex: 0 0 160px; aspect-ratio: 16 / 9; border-radius: 8px; overflow: hidden; background: var(--accent-soft); }
.attachment-preview img { width: 100%; height: 100%; object-fit: cover; display: block; }
.attachment-info { min-width: 0; flex: 1; }
.attachment-label { margin: 0 0 4px; font-weight: 600; }
.attachment-info .subtle { margin: 4px 0 0; }
/* A label and its input share one line, so the dialog spends its height on the text being edited. */
.field-row { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
.field-row label { flex: 0 0 76px; margin: 0; font-size: 14px; }
.field-row .el-input { flex: 1; }
/* One compact line per attachment: label, state, actions, then the size rule underneath. */
.attachment-block { display: flex; gap: 14px; align-items: flex-start; margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border); }
.attachment-block .attachment-preview { flex: 0 0 96px; aspect-ratio: 16 / 9; border-radius: 8px; overflow: hidden; background: var(--accent-soft); }
.attachment-block .attachment-info { min-width: 0; flex: 1; }
.attachment-block .attachment-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 0; }
.attachment-block .attachment-label { font-weight: 600; }
.attachment-block .subtle { margin: 2px 0 0; }
.attachment-heading { margin: 22px 0 4px; font-size: 15px; font-weight: 600; color: var(--text-strong); }
.attachment-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 10px 0 0; }
.attachment-actions input[type='file'] { position: absolute; width: 1px; height: 1px; opacity: 0; overflow: hidden; }
.attachment-actions label { position: relative; cursor: pointer; border: 1px solid var(--border-strong); border-radius: 20px; padding: 0 14px; background: var(--surface-raised); }
.attachment-actions label.is-busy { opacity: 0.6; cursor: progress; }
/* Audio player: sticky so the controls stay reachable while the chapter is read. */
.audio-player { position: sticky; top: 0; z-index: 5; display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-bottom: 18px; padding: 12px 16px; border: 1px solid var(--border); border-radius: 12px; background: var(--surface); box-shadow: 0 2px 10px #233d3114; }
.audio-clock { font-size: 13px; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.audio-range { flex: 1 1 160px; min-width: 120px; accent-color: var(--accent); }
.audio-rates { display: flex; gap: 4px; }
.audio-rate { min-width: 44px; min-height: 32px; border: 1px solid var(--border-strong); border-radius: 16px; background: var(--surface-raised); color: var(--text); font-size: 12px; cursor: pointer; }
.audio-rate.is-active { border-color: var(--accent); color: var(--accent); font-weight: 600; }
.audio-error { display: inline-flex; align-items: center; gap: 6px; color: var(--notice-text); font-size: 13px; }
.audio-player audio { display: none; }