iter(g245): 补高对比偏好令牌,对比度门禁扩到四套主题

This commit is contained in:
HIsheR
2026-10-26 21:58:58 +08:00
parent 26c9867576
commit 6e52cc6206
9 changed files with 46 additions and 16 deletions
+18
View File
@@ -189,3 +189,21 @@
--ua-focus-outline-inset: 1.5px solid var(--ua-color-accent-soft); /* 暗色内嵌焦点描边:accent-soft 随底色重混 */
--ua-focus-outline-soft: 2px solid color-mix(in srgb, var(--ua-color-accent) var(--ua-alpha-mid), transparent); /* 暗色柔和外描边:浓度档与亮色一致 */
}
/* ---- 高对比偏好:系统设置「提高对比度」时加深边界与弱文字 ----
为什么放文件末尾:媒体查询里的同选择器规则要靠后置胜出,写在暗色块之前会被顶掉。
只重设已有变量、不新增名字:verify 的颜色令牌 ↔ tokens.json 双向核对保持一一对应。 */
@media (prefers-contrast: more) {
:root {
--ua-color-border: rgba(0, 0, 0, 0.45); /* 边界加深:控件轮廓在强光 / 低视力下仍可辨(边框细分线随之加深,它是 border 的混色派生) */
--ua-color-text-muted: #4b5158; /* 辅助文字升级为次级文字色,说明文字不再是浅灰 */
--ua-color-text-faint: #6b7280; /* 三级文字升到原辅助档:禁用态也要读得清 */
--ua-color-scrollbar: rgba(0, 0, 0, 0.45); /* 滚动条拇指加深,可拖拽区域更明确 */
}
[data-theme='dark'] {
--ua-color-border: rgba(255, 255, 255, 0.45); /* 暗色同样加深:白边提亮而不是加深黑 */
--ua-color-text-muted: #b4bac2;
--ua-color-text-faint: #888780;
--ua-color-scrollbar: rgba(255, 255, 255, 0.5);
}
}
+2 -1
View File
@@ -1592,7 +1592,8 @@ function cmdAuditContrast(args) {
if (args.includes('--json')) {
const pairs = result.rows.map((r) => ({
label: r.label, fg: r.fg, bg: r.bg, min: r.min,
light: Number(r.light.toFixed(2)), dark: Number(r.dark.toFixed(2)), bad: r.bad,
light: Number(r.light.toFixed(2)), dark: Number(r.dark.toFixed(2)),
contrastLight: Number(r.contrastLight.toFixed(2)), contrastDark: Number(r.contrastDark.toFixed(2)), bad: r.bad,
}));
console.log(JSON.stringify({ ok: result.failures.length === 0, total: pairs.length, failing: result.failures.length, pairs }, null, 2));
} else {
+15 -5
View File
@@ -171,12 +171,16 @@ function ratio(fg, bg) {
return (l1 + 0.05) / (l2 + 0.05);
}
/*** 作用:构建两套主题的变量表:亮色 = :root,暗色 = :root 叠加 [data-theme='dark'] ***/
/*** 作用:构建四套主题的变量表:亮/暗 × 常规/高对比(高对比块在文件末尾的媒体查询里,同名变量覆盖即生效) ***/
export function loadThemes(root) {
const css = readText(path.join(root, TOKENS_REL));
const light = declsOf(blockOf(css, ':root'));
const dark = new Map([...light, ...declsOf(blockOf(css, "[data-theme='dark']"))]);
return { light, dark };
// 系统「提高对比度」偏好:媒体查询里只覆盖部分变量,未覆盖的沿用常规档,因此以对应主题为底再叠加
const media = blockOf(css, '@media (prefers-contrast: more)');
const contrastLight = new Map([...light, ...declsOf(blockOf(media, ':root'))]);
const contrastDark = new Map([...dark, ...declsOf(blockOf(media, "[data-theme='dark']"))]);
return { light, dark, contrastLight, contrastDark };
}
/*** 作用:跑一遍全部配对,返回每个主题的明细(纯数据,供 CLI 与测试共用) ***/
@@ -195,13 +199,19 @@ export function auditContrast(root) {
};
const lightOf = binder(themes.light);
const darkOf = binder(themes.dark);
const contrastLightOf = binder(themes.contrastLight);
const contrastDarkOf = binder(themes.contrastDark);
const rows = PAIRS.map((pair) => {
const lightRatio = ratio(lightOf(pair.fg), lightOf(pair.bg));
const darkRatio = ratio(darkOf(pair.fg), darkOf(pair.bg));
const contrastLightRatio = ratio(contrastLightOf(pair.fg), contrastLightOf(pair.bg));
const contrastDarkRatio = ratio(contrastDarkOf(pair.fg), contrastDarkOf(pair.bg));
const bad = [];
if (lightRatio < pair.min) bad.push('亮色');
if (darkRatio < pair.min) bad.push('暗色');
return { ...pair, light: lightRatio, dark: darkRatio, bad };
if (contrastLightRatio < pair.min) bad.push('高对比亮');
if (contrastDarkRatio < pair.min) bad.push('高对比暗');
return { ...pair, light: lightRatio, dark: darkRatio, contrastLight: contrastLightRatio, contrastDark: contrastDarkRatio, bad };
});
return { rows, failures: rows.filter((r) => r.bad.length > 0) };
}
@@ -209,10 +219,10 @@ export function auditContrast(root) {
/*** 作用:把明细打成对齐的文本表(人读模式,CLI 默认输出) ***/
export function formatReport({ rows, failures }) {
const fixed = (n) => n.toFixed(2).padStart(6);
const lines = ['配对 亮色 暗色 阈值 结论'];
const lines = ['配对 亮色 暗色 高亮 高暗 阈值 结论'];
for (const r of rows) {
const ok = r.bad.length === 0 ? 'OK' : `FAIL(${r.bad.join('+')})`;
lines.push(`${r.label.padEnd(26)}${fixed(r.light)} ${fixed(r.dark)} ${String(r.min).padStart(4)} ${ok}`);
lines.push(`${r.label.padEnd(24)}${fixed(r.light)} ${fixed(r.dark)} ${fixed(r.contrastLight)} ${fixed(r.contrastDark)} ${String(r.min).padStart(4)} ${ok}`);
}
lines.push('');
lines.push(failures.length === 0 ? '全部配对达标' : `不达标 ${failures.length} 条:${failures.map((f) => f.label).join(' / ')}`);
+2 -2
View File
@@ -20,7 +20,7 @@
"ua verify:清单一致性门禁(id 唯一 / exports 对应 / props 必带 n t d / steps≥3 / demo 锚点存在 / 视觉回归抽查清单六组件各 ≥3 条且五维度齐全)",
"ua commit-lint <文件|message>:提交信息门禁(type 枚举 / scope 规则 / subject 中文祈使 ≤50 字符),规范见 COMMITTING.md,违规退出码 1",
"ua audit:visual [--json|--baseline]:视觉漂移门禁,统计皮肤包里各类硬编码视觉字面量(字号/动效时长/阴影/圆角/焦点环/内距/控件高度/字重/行高/层级/边框色)并与 .ua/visual-baseline.json 棘轮比对,超基线退出码 1;--baseline 重写基线,--json 出机器可读计数与每类命中行号(line / prop / value / hint,Agent 可直接定位)",
"ua audit:contrast [--json|--list]:对比度门禁,把令牌里 16 组「文字色 × 底色」配对按 WCAG 公式算成对比度(求值支持 var() 与 color-mix,半透明底先压到页面底色),亮暗分算,低于阈值(正文 7 / 常规 4.5 / 禁用 2.6)退出码 1;--json 出逐对明细,--list 只列配对表",
"ua audit:contrast [--json|--list]:对比度门禁,把令牌里 16 组「文字色 × 底色」配对按 WCAG 公式算成对比度(求值支持 var() 与 color-mix,半透明底先压到页面底色),亮/暗 × 常规/高对比四套分算,低于阈值(正文 7 / 常规 4.5 / 禁用 2.6)退出码 1;--json 出逐对明细,--list 只列配对表",
"ua mcp:零依赖 stdio MCP server,暴露 list_components / get_component / search_components / get_tokens / list_recipes / get_recipe"
],
"keyTypes": [
@@ -43,7 +43,7 @@
"查令牌:ua tokens 打印全部 --ua-* 变量(颜色带暗色值),--json 输出结构化数组;写样式前先查,禁止硬编码。令牌分十三组:颜色 / 混色浓度 / 圆角 / 间距 / 字号 / 字重行高 / 边框 / 阴影 / 动效 / 位移 / 焦点环 / 控件高度 / 层级,color-mix 混入浓度用 --ua-alpha-soft/tint/line/mid/strong/heavy,弹层阴影用 --ua-shadow-1..4,过渡用 --ua-duration-* + --ua-ease,transform 位移用 --ua-translate-*(负号 calc(-1 * var(...))),焦点环用 --ua-focus-ring(_inset)",
"改完清单跑 npm run docs:同时刷新人类文档站与 llms-full.txt(Agent 首选喂料)",
"改完样式跑 npm run audit:visual:各类硬编码计数不得高于 .ua/visual-baseline.json(棘轮只减不增);字面量可用 npm run visual:migrate 幂等改写成令牌",
"改完颜色令牌跑 npm run audit:contrast:16 组文字/底色配对在亮暗两套主题下都必须达标(正文 7 / 常规 4.5 / 禁用 2.6),跌破即退出码 1——调一个色值就能让全站辅助文字掉到 4.4,人眼看不出但门禁看得出",
"改完颜色令牌跑 npm run audit:contrast:16 组文字/底色配对在亮/暗 × 常规/高对比四套主题下都必须达标(正文 7 / 常规 4.5 / 禁用 2.6),跌破即退出码 1——调一个色值就能让全站辅助文字掉到 4.4,人眼看不出但门禁看得出",
"在 ~/.workbuddy/mcp.json 注册 ua mcp 后,Agent 可直接调 list_components / get_component / search_components / get_tokens / list_recipes / get_recipe"
],
"components": []