/** * Admin 主题 token 单源(任务 4 契约,module 13 task 241 翻转为蓝白浅色)。 * main.css 使用同名 --admin-* CSS 变量承载渲染值;tests 用本模块校验 * “侧边栏与内容区均为浅色、主色为蓝灰、正文对比度达标”的不变量,防止回退到靛蓝深色侧栏。 */ export const ADMIN_THEME = { sidebar: '#f8fbfe', sidebarDeep: '#edf3f9', primary: '#4f78a5', primarySoft: '#e7f0f8', bg: '#f4f7fb', border: '#d8e3ee', text: '#24384d', muted: '#5b6f83', } as const export type AdminThemeKey = keyof typeof ADMIN_THEME export const ADMIN_THEME_KEYS = Object.keys(ADMIN_THEME) as AdminThemeKey[] /** CSS 变量名:camelCase -> kebab(sidebarDeep -> --admin-sidebar-deep)。 */ export function cssVarName(key: AdminThemeKey): string { const kebab = key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`) return `--admin-${kebab}` } function parseHex(hex: string): { r: number; g: number; b: number } { if (!/^#[0-9a-fA-F]{6}$/.test(hex)) { throw new Error(`主题色必须是 #rrggbb 六位十六进制: ${hex}`) } const raw = hex.slice(1) return { r: parseInt(raw.slice(0, 2), 16), g: parseInt(raw.slice(2, 4), 16), b: parseInt(raw.slice(4, 6), 16), } } /** sRGB 通道 -> 线性亮度。 */ function channelLuminance(channel: number): number { const s = channel / 255 return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4 } /** 相对亮度(0 纯黑 ~ 1 纯白)。 */ export function relativeLuminance(hex: string): number { const { r, g, b } = parseHex(hex) return 0.2126 * channelLuminance(r) + 0.7152 * channelLuminance(g) + 0.0722 * channelLuminance(b) } export function isDarkSurface(hex: string, threshold = 0.2): boolean { return relativeLuminance(hex) < threshold } /** WCAG 对比度(1 ~ 21),fg/bg 可为 8 位 hex。 */ export function contrastRatio(fgHex: string, bgHex: string): number { const lighter = Math.max(relativeLuminance(fgHex), relativeLuminance(bgHex)) const darker = Math.min(relativeLuminance(fgHex), relativeLuminance(bgHex)) return (lighter + 0.05) / (darker + 0.05) } /** 取某个语义 token 的 hex;未知 key 给出可操作错误。 */ export function tokenHex(key: AdminThemeKey): string { const value = ADMIN_THEME[key] if (!value) { throw new Error(`未知 Admin 主题 token: ${String(key)},允许值: ${ADMIN_THEME_KEYS.join(', ')}`) } return value }