first commit

This commit is contained in:
douboer
2026-03-21 18:57:10 +08:00
commit c49aa1a5e9
570 changed files with 107167 additions and 0 deletions

View File

@@ -0,0 +1,105 @@
/**
* 主题引擎:提供 WCAG 对比度计算与背景自动优化。
*/
export interface RgbColor {
r: number;
g: number;
b: number;
}
/**
* 终端强调色插值系数:
* - 0.5 代表正中间;
* - >0.5 代表向前景色偏移;
* - 当前取 0.64,满足“中间色且略偏前景”的视觉要求。
*/
export const SHELL_ACCENT_BLEND_T = 0.64;
export function normalizeHex(input: string, fallback: string): string {
return /^#[0-9a-fA-F]{6}$/.test(input) ? input.toLowerCase() : fallback;
}
export function hexToRgb(hex: string): RgbColor {
const value = normalizeHex(hex, "#000000");
return {
r: Number.parseInt(value.slice(1, 3), 16),
g: Number.parseInt(value.slice(3, 5), 16),
b: Number.parseInt(value.slice(5, 7), 16)
};
}
function srgbToLinear(value: number): number {
const normalized = value / 255;
if (normalized <= 0.03928) {
return normalized / 12.92;
}
return ((normalized + 0.055) / 1.055) ** 2.4;
}
export function luminance(hex: string): number {
const rgb = hexToRgb(hex);
return 0.2126 * srgbToLinear(rgb.r) + 0.7152 * srgbToLinear(rgb.g) + 0.0722 * srgbToLinear(rgb.b);
}
export function contrastRatio(a: string, b: string): number {
const la = luminance(a);
const lb = luminance(b);
const lighter = Math.max(la, lb);
const darker = Math.min(la, lb);
return (lighter + 0.05) / (darker + 0.05);
}
export function pickBestBackground(textColor: string, accentColor: string): string {
const candidates = [
"#0a1325",
"#132747",
"#102b34",
"#2e223b",
normalizeHex(accentColor, "#5bd2ff")
];
let best = candidates[0] ?? "#0a1325";
let bestScore = 0;
for (const candidate of candidates) {
const score = contrastRatio(normalizeHex(textColor, "#e6f0ff"), candidate);
if (score > bestScore) {
bestScore = score;
best = candidate;
}
}
return best;
}
/**
* 颜色线性插值:
* - t=0 表示返回背景色;
* - t=1 表示返回前景色;
* - 用于按钮色与终端强调色等“在 bg/text 之间取色”的场景。
*/
function mixColor(bgColor: string, textColor: string, t: number, fallbackBg: string, fallbackText: string): string {
const bg = hexToRgb(normalizeHex(bgColor, fallbackBg));
const text = hexToRgb(normalizeHex(textColor, fallbackText));
const factor = Number.isFinite(t) ? Math.min(1, Math.max(0, t)) : 0.5;
const r = Math.round(bg.r + (text.r - bg.r) * factor);
const g = Math.round(bg.g + (text.g - bg.g) * factor);
const b = Math.round(bg.b + (text.b - bg.b) * factor);
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
}
/**
* 按钮色自动推导在背景色和文本色之间取色偏向文本色一侧t=0.72)。
* 确保按钮与背景有足够对比度,同时色调协调。
*/
export function pickBtnColor(bgColor: string, textColor: string): string {
return mixColor(bgColor, textColor, 0.72, "#192b4d", "#e6f0ff");
}
/**
* 终端强调色自动推导:
* - 在终端背景色与前景色之间取“中间偏前景”的颜色;
* - 目标是避免强调色贴近背景导致识别度不足,同时避免过亮抢占正文层级。
*/
export function pickShellAccentColor(bgColor: string, textColor: string): string {
return mixColor(bgColor, textColor, SHELL_ACCENT_BLEND_T, "#192b4d", "#e6f0ff");
}