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
+3 -3
View File
@@ -29,12 +29,12 @@ export function Cover({ book, className }: { book: Book; className?: string }) {
const { src, failed, retry } = useAuthedImage(book.cover_url);
if (failed)
return (
<div className={`grid place-items-center bg-zinc-900 ${className ?? ""}`}>
<div className={`grid place-items-center bg-stone-900 ${className ?? ""}`}>
<button className={btn} onClick={retry} aria-label="封面加载失败,重试">
重试
</button>
</div>
);
if (!src) return <div className={`animate-pulse bg-zinc-800 ${className ?? ""}`} />;
return <img src={src} alt="" loading="lazy" className={`bg-zinc-900 object-cover ${className ?? ""}`} />;
if (!src) return <div className={`animate-pulse bg-stone-800 ${className ?? ""}`} />;
return <img src={src} alt="" loading="lazy" className={`bg-stone-900 object-cover ${className ?? ""}`} />;
}
+2 -2
View File
@@ -13,8 +13,8 @@ export class ErrorBoundary extends Component<{ children: ReactNode }, { err: Err
<div className="grid h-full place-items-center p-8 text-center">
<div>
<p className="mb-2 text-lg text-red-400">页面出错了</p>
<p className="mb-4 max-w-md text-sm break-all text-zinc-500">{String(this.state.err)}</p>
<button className="rounded-md bg-zinc-800 px-3 py-1.5 text-sm" onClick={() => this.setState({ err: null })}>
<p className="mb-4 max-w-md text-sm break-all text-stone-500">{String(this.state.err)}</p>
<button className="rounded-md bg-stone-800 px-3 py-1.5 text-sm" onClick={() => this.setState({ err: null })}>
重试
</button>
</div>
+1 -1
View File
@@ -31,7 +31,7 @@ export function Toaster() {
<div
key={t.id}
role="status"
className={`max-w-sm rounded-lg px-4 py-2 text-sm shadow-lg ${
className={`max-w-sm rounded-lg px-4 py-2 text-sm shadow-lg ring-1 ring-white/10 ${
t.kind === "ok" ? "bg-emerald-900 text-emerald-100" : "bg-red-900 text-red-100"
}`}
>
+12 -6
View File
@@ -6,23 +6,29 @@ import { btn } from "./ui";
export function TopBar({ right }: { right?: ReactNode }) {
const { user, isAdmin, logout } = useAuth();
return (
<header className="flex shrink-0 items-center gap-4 border-b border-zinc-800 px-4 py-3">
<Link to="/" className="font-semibold">
BookLib
<header className="flex shrink-0 items-center gap-2 border-b border-stone-800/80 px-4 py-3">
<Link to="/" className="font-serif text-lg font-semibold tracking-wide">
Book<span className="text-amber-500">Lib</span>
</Link>
{isAdmin && (
<Link className="text-sm text-zinc-400 hover:text-zinc-100" to="/admin/users">
<Link
className="rounded-md px-2 py-1 text-sm text-stone-400 transition-colors hover:bg-stone-800 hover:text-stone-100"
to="/admin/users"
>
用户
</Link>
)}
{isAdmin && (
<Link className="text-sm text-zinc-400 hover:text-zinc-100" to="/admin/libraries">
<Link
className="rounded-md px-2 py-1 text-sm text-stone-400 transition-colors hover:bg-stone-800 hover:text-stone-100"
to="/admin/libraries"
>
库管理
</Link>
)}
<div className="ml-auto flex items-center gap-3">
{right}
<span className="text-sm text-zinc-400">
<span className="text-sm text-stone-400">
{user?.username}
{isAdmin ? "(admin)" : ""}
</span>
+4 -4
View File
@@ -1,7 +1,7 @@
export const btn =
"rounded-md bg-zinc-800 px-3 py-1.5 text-sm hover:bg-zinc-700 disabled:cursor-not-allowed disabled:opacity-50";
"rounded-lg bg-stone-800 px-3 py-1.5 text-sm transition-colors hover:bg-stone-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-500 active:bg-stone-600 disabled:cursor-not-allowed disabled:opacity-50";
export const btnPrimary =
"rounded-md bg-emerald-700 px-3 py-1.5 text-sm hover:bg-emerald-600 disabled:cursor-not-allowed disabled:opacity-50";
"rounded-lg bg-amber-600 px-3 py-1.5 text-sm text-stone-950 transition-colors hover:bg-amber-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-500 active:bg-amber-400 disabled:cursor-not-allowed disabled:opacity-50";
export const input =
"rounded-md border border-zinc-700 bg-zinc-900 px-3 py-1.5 text-sm outline-none focus:border-zinc-500";
export const card = "rounded-xl border border-zinc-800 bg-zinc-900";
"rounded-lg border border-stone-700 bg-stone-900 px-3 py-1.5 text-sm outline-none transition-colors placeholder:text-stone-500 focus:border-amber-500/70";
export const card = "rounded-xl border border-stone-800 bg-stone-900";