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
+36
View File
@@ -0,0 +1,36 @@
import { useEffect, useState } from "react";
export const RD_THEMES = ["paper", "sepia", "night"] as const;
export type RdTheme = (typeof RD_THEMES)[number];
export const RD_THEME_LABEL: Record<RdTheme, string> = { paper: "纸", sepia: "米", night: "夜" };
export const FONT_SIZES = [16, 18, 20, 22, 25, 28];
const KEY = "reader-prefs";
export interface ReaderPrefs {
theme: RdTheme;
sizeIdx: number;
}
export function useReaderPrefs() {
const [prefs, setPrefs] = useState<ReaderPrefs>(() => {
try {
const p = JSON.parse(localStorage.getItem(KEY) ?? "") as ReaderPrefs;
if (RD_THEMES.includes(p.theme) && Number.isInteger(p.sizeIdx) && p.sizeIdx >= 0 && p.sizeIdx < FONT_SIZES.length)
return p;
} catch {
/* 首次使用或存了坏值 */
}
return { theme: "sepia", sizeIdx: 2 };
});
useEffect(() => localStorage.setItem(KEY, JSON.stringify(prefs)), [prefs]);
return {
prefs,
fontSize: FONT_SIZES[prefs.sizeIdx],
cycleTheme: () =>
setPrefs((p) => ({ ...p, theme: RD_THEMES[(RD_THEMES.indexOf(p.theme) + 1) % RD_THEMES.length] })),
bump: (d: number) =>
setPrefs((p) => ({ ...p, sizeIdx: Math.min(FONT_SIZES.length - 1, Math.max(0, p.sizeIdx + d)) })),
};
}