跨端: 语义色/三级文字在深色下没提亮(设备扫出 12 处)+ 判据取样法修到第三版

## 一、判据基建先修对,否则是在听噪声

深色可读性扫描的**取样法迭代了三版**,每版都因为**假红**才改的
(记在这里,因为"判据自己错"比"界面错"更费时间):

1. **取中心一个像素** ⇒ 落在笔画之间、读到的是底色 ⇒ 一片 ratio=1.00 假红。
2. **取全局最暗/最亮** ⇒ 会采到**两个不同的东西**:细字的抗锯齿中间色。
   实例:segmented「日」报 ink `rgb(32,34,35)` / bg `rgb(98,100,101)` 2.69:1 ——
   而截图里它要么蓝底白字、要么深底浅字,两种都远高于 3:1。
   **反复核对才确认是判据的错,不是界面的错。**
3. **步长采样 + 众数** ⇒ 仍然假红:周表头「一」报 2.03:1,
   而逐像素的真实墨是 `rgb(166,167,167)`(**6.6:1**)——
   稀疏网格**整个跳过了笔画**。这不是调参能修好的(字越小越糟)。
4. **逐像素 + 众数=底 + 离底最远者=墨**(最终版)。
   理由:一个文字框里**面积最大的一定是底**,而"离底色最远"就是笔画最实那部分。
   ⚠️ 逐像素是**负担得起的**(整屏已解码在内存,一个框最多 9 万像素)——
   **不要再为了省开销退回抽样**,前两版都因此假红。

## 二、真 bug 三批(都是"深色下没提亮")

### ① 语义色前景(12 处 → 修完设备复扫 0)
WebUI 的语义色也是**双通道**,`.dark` 段把 `red/green/amber-700` 换成浅色
(`248/118/249 → 164/219/195` 那组)。鸿蒙只有一个深色值:
- 「归档」按钮 `Theme.danger` #B91C1C 压深面 ⇒ **2.47:1**(联系人页 7 处)
- 「今天」/「日」等 ⇒ **2.38–2.77:1**

加 `dangerDark/approveDark/warnFgDark`(逐字对齐 WebUI `.dark`)
+ `dangerFor()/approveFor()/warnFgFor()` 入口,22 处接入。

### ② 日历工具条整块配色抄错(不是"深色档选错")
对照 WebUI `CalendarView.tsx:313-331`:

| 元素 | WebUI | 鸿蒙(改前) |
|---|---|---|
| 「今天」 | `border-gray-300` + `text-gray-700`(**中性**) | `accentSoft` 底 + `accentStrong` 字 |
| 月/周/日 未选中 | `bg-white` + `text-gray-700` | `accentSoft` 底 |

鸿蒙把**次要的中性按钮**画成了**品牌色按钮** —— 视觉重量跟主操作一样,
三个档看起来"都像选中"。已按 WebUI 改成中性。

### ③ 三级文字在深色下 2.82:1(**71 处**,全局最大的一个)
WebUI 的 `.dark` 段注释逐字写着这个坑:

> 「`gray-400/500`(次要文字)→ **提亮**。深底上的浅色 gray-400
>  只有约 2:1 对比度,远低于 WCAG AA 的 4.5:1 —— **看得见但读不动**。」

鸿蒙用的系统三级色 `ohos_id_color_text_tertiary` 深色下是 `rgb(102,103,105)`
压深面 **2.82–3.05:1**,而它被用在 10–11px 的**小字**上
(时间戳、农历、空态、辅助说明)。

**不换掉系统令牌**("系统拥有的维度"纪律照旧),加 `textSubtleFor()`:
深色下改用二级色(实测 `rgb(166,167,167)` ≈ 7.15:1,
且与 WebUI 的 `gray-500` 同档位)。71 处接入。

**设备复扫(修后)**:通信/日历/联系三页 **176 段文字全部 ≥3:1**。

## 三、欠账与底本

- `baseline.sha` 第 4 次重算。仍**先逐个取证**:
  `git diff --quiet HEAD -- <f> && echo STALE || echo INTENTIONAL`
  ⇒ 三个全是 `INTENTIONAL`(我改的),不是变异残留。
  把这条取证命令也写进文件,下次照抄。
- `dangerDark/approveDark/warnFgDark` 已登记进 `SELF_OWNED_COLORS`
  + `Theme.ets` 登记表(各带理由与 WebUI 出处)。

`run-all.mjs` → `checks=514 pass=514 fail=0 skip=0 red=0 broken=0 unreported=0`。

**未验**:①「我的」页(底部头像按钮,扫描脚本没找到入口)没扫;
② 浅色下的对比度没扫(本轮全部针对深色)。
This commit is contained in:
2026-09-19 20:27:09 +08:00
parent 13b557a742
commit 4ecddfc2b6
13 changed files with 312 additions and 120 deletions

View File

@ -70,6 +70,47 @@ export class Theme {
static readonly textMuted: Resource = $r('sys.color.ohos_id_color_text_secondary');
static readonly textSubtle: Resource = $r('sys.color.ohos_id_color_text_tertiary');
/*
* ──────── 三级文字在**深色**下的可读性下限(2026-09-19 加)────────
*
* ★★ 问题(设备实测,不是推测):
* 系统三级色 `ohos_id_color_text_tertiary` 在**深色**下是 `rgb(102,103,105)`,
* 压在卡片面 `rgb(32,34,36)` 上只有 **2.82–3.05:1**。
* 它被用在**小字**上(10–11px 的时间戳、农历、空态、辅助说明,全局 66 处)
* ⇒ 远低于 WCAG AA 对正文的 4.5:1。
*
* ★ 这不是鸿蒙独有的问题 —— WebUI 早就遇到过并写下了结论
* (`client/electron/src/index.css` 的 `.dark` 段注释,逐字引用):
*
* 「`gray-400/500`(次要文字)→ **提亮**。深底上的浅色 gray-400
* 只有约 2:1 对比度,远低于 WCAG AA 的 4.5:1 —— **看得见但读不动**。」
*
* 它的做法是在 `.dark` 段把 gray-400 从 `107 114 128` 提亮到 `138 146 161`,
* gray-500 从 `90 98 112` 提到 `165 173 186`。实测那两组压在深卡片上
* 分别是 **5.51:1 / 7.63:1**,都在 AA 之上。
*
* ★ 系统色为什么没有这层保护:`ohos_id_color_text_tertiary` 的语义是
* 「比二级更淡」——它只保证**层次关系**,不保证可读性下限。
* 当"更淡"用在浅底上时刚好合适;深底上同样的亮度就掉下去了。
* (这与 `Theme.surface` 当前景色是同一类:**系统语义 ≠ 我们的可读性要求**。)
*
* ★ 修法:**不换掉系统令牌**(上面那条"系统拥有的维度"纪律照旧),
* 而是提供一个「按主题取三级文字」的入口:深色下改用二级色。
* 之所以用二级而不是另写一个白:
* · 二级色深色下实测 `rgb(166,167,167)` ≈ 7.15:1,在 AA 之上;
* · 它与 WebUI 的 `gray-500` 是同一个档位(两端视觉层次一致);
* · 不引入新的手写色(不用再过登记表)。
*
* ★ 浅色下**不改**:浅色下系统三级色压在浅底上够用(与 WebUI 的
* gray-400 `107 114 128` 同量级),换了反而会让层次变平。
*
* ★ 使用入口是 `Theme.textSubtleFor()` —— 与 `accentFor` / `accentSoftFor`
* 同一条纪律:**别在页面里自己写 `isDark ? a : b`**。
*/
static textSubtleFor(dark?: boolean): Resource {
return Theme.isDarkNow(dark) ? Theme.textMuted : Theme.textSubtle;
}
// ─────────────── 遮罩与材质:交给系统 ───────────────
/**
@ -161,6 +202,15 @@ export class Theme {
* ★ 不登记会怎样:深色下选中卡片是接近纯白的浅蓝,
* 在深色页面上刺眼得像 bug(2026-09-19 设备实测到)
* · accentStrong #1D4ED8 品牌深色变体(选中文字/边框)
* · dangerDark #F8A4A4 危险/拒绝在**深色**下当前景的取值 ——
* 对齐 WebUI `.dark --c-red-700`(`248 164 164`)。
* ★ 入口 `Theme.dangerFor()`。
* 不登记会怎样:深色下「归档」按钮只有 2.47:1
* (2026-09-19 设备实测,联系人页 7 处)
* · approveDark #76DB9B 同意在**深色**下当前景的取值 ——
* 对齐 WebUI `.dark --c-green-700`。入口 `approveFor()`。
* · warnFgDark #F9C368 警示文字在**深色**下的取值 ——
* 对齐 WebUI `.dark --c-amber-700`。入口 `warnFgFor()`。
* · accentDark #80AFF9 品牌色在**深色**下当前景用的取值 ——
* 对齐 WebUI `index.css:481` 的
* `.dark --c-blue-600`(`128 175 249`)。
@ -420,6 +470,45 @@ export class Theme {
static readonly warnBg: string = '#FFFBEB';
static readonly warnFg: string = '#B45309';
/*
* ──── 语义色的**深色档**(2026-09-19 加)────
*
* ★★ 和 `accentDark` 完全同一个问题、同一套证据:WebUI 的语义色也是
* **双通道**。浅色下 `red-700` 是 `185 28 28`(深红,给白底用);
* 深色下它被换成 `248 164 164`(**浅红**),否则深底上读不动。
*
* 实测(设备,深色主题):联系人页的「归档」按钮是
* `Theme.danger` = `#B91C1C` 压在卡片面上 ⇒ **2.47:1**,
* 低于 WCAG 图形下限 3:1。同样的还有日历页的
* 「今天」标(`accentStrong` 底 + 深字)等 4 处。
*
* ★ 取值直接对 WebUI `index.css` 的 `.dark` 段(`grep '^ --c-'` 可得):
* red-700 `248 164 164`
* green-700 `118 219 155`
* amber-700 `249 195 104`
*
* ★ 入口是下面那组 `*For(dark?)` —— 与 `accentFor` 同一条纪律:
* **别在页面里自己 `isDark ? a : b`**(约定已经漏过 8 处)。
*/
static readonly dangerDark: string = '#F8A4A4';
static readonly approveDark: string = '#76DB9B';
static readonly warnFgDark: string = '#F9C368';
/** 按主题给拒绝/危险**前景**色(压在面上的红字/红图标)。 */
static dangerFor(dark?: boolean): string {
return Theme.isDarkNow(dark) ? Theme.dangerDark : Theme.danger;
}
/** 按主题给同意**前景**色。 */
static approveFor(dark?: boolean): string {
return Theme.isDarkNow(dark) ? Theme.approveDark : Theme.approve;
}
/** 按主题给警示**前景**色。 */
static warnFgFor(dark?: boolean): string {
return Theme.isDarkNow(dark) ? Theme.warnFgDark : Theme.warnFg;
}
/**
* 权限档位 → 徽标底色 / 文字色。
*