feat(ui): warm library palette (zinc+emerald -> stone+amber) with hover/focus feedback; text reader gains paper/sepia/night themes + persisted font-size (lib/readerPrefs.ts), serif justified measure; PDF drops emoji icons for 首页/末页, CBZ pill page counter, EPUB centered spread, blur reader header with format badge, aria labels on login/search/chapter select

This commit is contained in:
2026-09-07 22:37:47 +08:00
parent 2478da059b
commit 7fb01ce335
20 changed files with 281 additions and 126 deletions
+17 -12
View File
@@ -23,9 +23,14 @@ function BookCard({
return (
<div className="group relative w-32 shrink-0">
<button onClick={onOpen} className="block w-full text-left">
<Cover book={book} className="aspect-[3/4] w-32 rounded-lg" />
<p className="mt-1 truncate text-xs text-zinc-300">{book.title}</p>
<p className="truncate text-[10px] text-zinc-500">
<Cover
book={book}
className="aspect-[3/4] w-32 rounded-lg shadow-md ring-1 ring-stone-800 transition duration-200 group-hover:-translate-y-1 group-hover:shadow-xl group-hover:ring-stone-600"
/>
<p className="mt-1.5 truncate text-xs text-stone-200 transition-colors group-hover:text-amber-300">
{book.title}
</p>
<p className="truncate text-[10px] text-stone-500">
{book.format} · {book.path}
</p>
</button>
@@ -38,13 +43,13 @@ function BookCard({
</span>
)}
{book.percent > 0 && (
<div className="mt-1 h-1 w-32 overflow-hidden rounded bg-zinc-800">
<div className="h-full bg-emerald-600" style={{ width: `${Math.round(book.percent * 100)}%` }} />
<div className="mt-1 h-1 w-32 overflow-hidden rounded-full bg-stone-800">
<div className="h-full rounded-full bg-amber-500" style={{ width: `${Math.round(book.percent * 100)}%` }} />
</div>
)}
{isAdmin && (
<button
className="absolute right-1 top-1 hidden rounded bg-zinc-900/80 px-1.5 text-xs text-red-300 group-hover:block"
className="absolute right-1 top-1 hidden rounded bg-stone-900/80 px-1.5 text-xs text-red-300 group-hover:block"
title="删除(连磁盘文件)"
onClick={() => {
if (confirm(`删除《${book.title}》?磁盘文件一并删除,阅读进度保留`)) onDelete();
@@ -121,7 +126,7 @@ export default function Shelf() {
)}
<input
className={input + " w-44"}
placeholder="搜索标题…"
aria-label="搜索标题" placeholder="搜索标题…"
value={qRaw}
onChange={(e) => setQRaw(e.target.value)}
/>
@@ -130,7 +135,7 @@ export default function Shelf() {
/>
<div className="flex-1 overflow-y-auto p-4">
{libs.length === 0 && (
<p className="text-sm text-zinc-500">还没有书库。admin 请到「库管理」建库并扫描。</p>
<p className="text-sm text-stone-500">还没有书库。admin 请到「库管理」建库并扫描。</p>
)}
<div className="mb-4 flex flex-wrap gap-2">
<button className={lib === 0 ? btnPrimary : btn} onClick={() => setLib(0)}>
@@ -145,13 +150,13 @@ export default function Shelf() {
{continueRows.length > 0 && (
<section className="mb-8">
<h2 className="mb-2 text-sm font-semibold text-zinc-400">继续阅读</h2>
<h2 className="mb-2 text-sm font-semibold text-stone-400">继续阅读</h2>
<div className="flex gap-4 overflow-x-auto pb-2">
{continueRows.map((r) => {
const b = booksByKey.get(`${r.library_id}/${r.path}`);
if (!b)
return (
<div key={`${r.library_id}/${r.path}`} className={`w-28 shrink-0 p-2 text-xs text-zinc-600 ${card}`}>
<div key={`${r.library_id}/${r.path}`} className={`w-28 shrink-0 p-2 text-xs text-stone-600 ${card}`}>
<p className="truncate">{r.title || "已删除的书"}</p>
<p>{Math.round(r.percent * 100)}%</p>
</div>
@@ -178,11 +183,11 @@ export default function Shelf() {
</p>
)}
{!booksQ.isPending && groups.length === 0 && !booksQ.isError && (
<p className="text-sm text-zinc-500">此库暂无书目(放入文件后等待扫描或点「扫描此库」)。</p>
<p className="text-sm text-stone-500">此库暂无书目(放入文件后等待扫描或点「扫描此库」)。</p>
)}
{groups.map((g) => (
<section key={g.dir || "//"} className="mb-8">
<h2 className="mb-2 text-sm font-semibold text-zinc-400">{g.dir || "根目录"}</h2>
<h2 className="mb-2 text-sm font-semibold text-stone-400">{g.dir || "根目录"}</h2>
<div className="flex flex-wrap gap-4">
{g.books.map((b) => (
<BookCard key={b.id} book={b} onOpen={() => nav(`/book/${b.id}`)} onDelete={() => del.mutate(b.id)} />