项目文件夹

文件
wehub-resource-sync 2114b14ee0
Sync main into demo / sync (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:26 +08:00

138 行
3.7 KiB
TypeScript

/**
* 阅读器「颜色 / 背景」:页面底色、正文字色、顶栏/底栏/弹窗表面色统一由 pageBg 推导,
* 避免只有正文区变色、Chrome 仍保持纯白。
*/
import type { ReaderThemeBackground, ReaderThemeColor } from '../data/types';
export interface ReaderThemeResolved {
pageBg: string;
/** 工具面板、按钮块等(相对 page 略提亮) */
surface: string;
/** 排版面板等次级表面 */
surfaceMuted: string;
/** 顶栏/底栏毛玻璃底色(与纸张同色相近) */
chromeBg: string;
textPrimary: string;
textSecondary: string;
textMuted: string;
border: string;
sliderTrack: string;
sliderFill: string;
isDark: boolean;
}
const THEME_COLOR_PAGE: Record<ReaderThemeColor, string> = {
/** 颜色第一项:纯白纸张 */
white: '#ffffff',
yellow: '#f6f2e5',
green: '#e8f5e9',
dark: '#1a1a1a',
};
const THEME_BG_PAGE: Record<Exclude<ReaderThemeBackground, 'matchTheme'>, string> = {
bg1: '#faf9f4',
bg2: '#f5f5f0',
bg3: '#efefef',
bg4: '#e0e0e0',
};
function parseHex(hex: string): { r: number; g: number; b: number } {
let h = hex.trim();
if (h.startsWith('#')) h = h.slice(1);
if (h.length === 3) {
h = h
.split('')
.map(c => c + c)
.join('');
}
if (h.length !== 6) return { r: 250, g: 249, b: 244 };
return {
r: parseInt(h.slice(0, 2), 16),
g: parseInt(h.slice(2, 4), 16),
b: parseInt(h.slice(4, 6), 16),
};
}
function toHex({ r, g, b }: { r: number; g: number; b: number }): string {
const c = (n: number) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, '0');
return `#${c(r)}${c(g)}${c(b)}`;
}
/** t=0 → a,t=1 → b */
export function mixHex(a: string, b: string, t: number): string {
const A = parseHex(a);
const B = parseHex(b);
return toHex({
r: A.r + (B.r - A.r) * t,
g: A.g + (B.g - A.g) * t,
b: A.b + (B.b - A.b) * t,
});
}
function hexToRgba(hex: string, alpha: number): string {
const { r, g, b } = parseHex(hex);
return `rgba(${r},${g},${b},${alpha})`;
}
function resolveLightChromeFromPage(pageBg: string): Omit<ReaderThemeResolved, 'pageBg' | 'isDark'> {
// 弹窗/卡片:在纸张色上略混入白,仍保持同一色相
const surface = mixHex(pageBg, '#ffffff', 0.22);
// 排版面板:略偏灰一点,与 surface 区分
const surfaceMuted = mixHex(mixHex(pageBg, '#e5e5e5', 0.18), '#ffffff', 0.12);
// 顶栏底栏:与纸张一致略透明,毛玻璃叠在正文上时仍显「同一页」
const chromeBg = hexToRgba(pageBg, 0.97);
const border = mixHex(pageBg, '#000000', 0.07);
const sliderTrack = mixHex(pageBg, '#a8a8a8', 0.32);
const sliderFill = mixHex(pageBg, '#737373', 0.26);
return {
surface,
surfaceMuted,
chromeBg,
textPrimary: '#1f2937',
textSecondary: '#4b5563',
textMuted: '#9ca3af',
border,
sliderTrack,
sliderFill,
};
}
export function resolveReaderTheme(
themeColor: ReaderThemeColor,
themeBg: ReaderThemeBackground,
): ReaderThemeResolved {
const isDark = themeColor === 'dark';
let pageBg: string;
if (isDark) {
pageBg = THEME_COLOR_PAGE.dark;
} else {
pageBg = themeBg === 'matchTheme' ? THEME_COLOR_PAGE[themeColor] : THEME_BG_PAGE[themeBg];
}
if (isDark) {
return {
pageBg,
surface: '#2c2c2c',
surfaceMuted: '#363636',
chromeBg: 'rgba(38,38,38,0.96)',
textPrimary: '#ececec',
textSecondary: '#b4b4b4',
textMuted: '#8a8a8a',
border: 'rgba(255,255,255,0.12)',
sliderTrack: '#404040',
sliderFill: '#525252',
isDark: true,
};
}
const light = resolveLightChromeFromPage(pageBg);
return {
pageBg,
...light,
isDark: false,
};
}