fix(web): 补齐强调色的 CSS 变量 —— 红/绿/橙/黄按钮此前不可见但可点
## 症状
所有界面的「确认」类按钮看不见,但对应位置点击照样生效。
归档确认、删除、危险操作、状态徽标全部受影响;蓝色主按钮正常。
## 根因
`tailwind.config.js` 的 `colors` 里对 red/green/amber/orange/yellow
**同时写了两份定义**:先是固定 hex,紧接着又是 `accent('red')`。
JS 对象字面量重复键**后者胜出**,不报错、不警告 —— 读代码的人看到上面那份
hex 以为在用它,实际生效的是下面那份变量引用。
而 `index.css` 里当时只有 20 个变量(white / on-accent / gray / chrome),
没有任何 `--c-red-*`。CSS 里变量未定义会让**整条声明失效**:
.bg-red-600 { background-color: rgb(var(--c-red-600) / 1) } ← 整条被丢弃
于是 `bg-red-600` 退回透明,而 `text-white`(走 `--c-on-accent`,浅色下是纯白)
照常生效 → 白字落在白卡片上。按钮的盒子、padding、点击区域全都在。
实测部署产物里 32 个变量被引用但从未定义。blue 逃过一劫只因为它没有第二份
`accent('blue')` 定义,编译成了固定值。
## 修法(用户选 B:补齐变量,让强调色也参与主题)
`index.css` 新增 96 个变量,`tailwind.config.js` 去掉重复定义。
**强调色是两段语义色阶,深色下走向相反**:
- `50`–`300` = 表面(chip 底、提示条底、边框)→ 深色下**变暗**。
照搬浅色值的话 red-50 (#fef2f2) 在深色页面上是一块近白亮斑 ——
那是错误提示条的底,结果比正文还抢眼,上面的红字反而读不动。
- `400`–`900` = 前景(文字、图标)→ 深色下**变亮**。
照搬时 red-700 只有 2.67:1、amber-900 只有 1.90:1。现在每档 ≥4.5
(最低 red-400 = 5.93)。
**实心按钮底另立一组 `--s-*`,两种模式同值。**
那六档在深色下被提亮是为了 `text-red-600` 读得动,而 `bg-red-600 text-white`
的白字落在提亮后的浅红上只有 1.6:1。一个名字服务两种语义必然坏掉一头 ——
与此前 text-white/bg-white 那次同理。只覆盖 `backgroundColor`,
`text-*`/`border-*`/`ring-*` 仍走 `accent()`。
顺带把浅色 red-600 从官方的 220 38 38 压到 213 37 37:官方值落在 red-50 上
只有 4.41:1,而 `bg-red-50 text-red-600` 正是错误提示条。
## 防复发
`test/theme.test.mjs` 20 → 26 条,新增 6 条针对这次的:
- **Tailwind 实际使用的每个变量都在 index.css 有定义**。判据走 resolveConfig
而不是正则扫配置文本:出问题的变量名是 `accent('red')` 模板拼出来的,
源码里没有 `--c-red-600` 这个字面量,扫文本会漏掉正是要防的那一类
- colors 里没有重复的颜色名(这次 bug 的成因)
- 表面段深色下变暗 / 前景段在深色卡片上 ≥4.5:1(逐档断言,72 项)
- 实心底走 `--s-*` 且未被 `.dark` 覆盖
- 白字在实心底上 ≥3:1
新增 `test/manual/accent-verify.mjs`:真浏览器渲染 17 组配色 × 两模式,
读 `getComputedStyle` 量实际值,**把「背景透明」单独判为失败**(那正是本次
bug 的指纹)。只以 `hover:` 变体出现的档不能放进探针 —— Tailwind 不生成
未使用的基础类,探它必然透明,是假阳性。
## 验收
- theme.test.mjs 26 条全过;web 160 例;tsc 无错
- accent-verify 两模式各 17 项全过(浅色最低 4.51、深色最低 3.05)
- theme-verify 9 项全过;wide-regression 5 项全过
- 截图逐像素核对:浅色侧栏 (15,23,42) / 卡片 (255,255,255) / 页面底 (249,250,251);
深色 (12,14,18) / (24,27,33) / (17,19,24) —— 层次关系两模式一致
This commit is contained in:
@ -64,6 +64,138 @@
|
|||||||
--c-chrome-800: 30 41 59;
|
--c-chrome-800: 30 41 59;
|
||||||
--c-chrome-900: 15 23 42;
|
--c-chrome-900: 15 23 42;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ─── 强调色(浅色)───
|
||||||
|
*
|
||||||
|
* 值就是 Tailwind 的官方色阶,浅色模式下视觉零变化。
|
||||||
|
*
|
||||||
|
* 这条色阶与灰阶一样是**语义色阶**,而且两段的用途相反:
|
||||||
|
* - 50–300 = 表面(chip 底、提示条底、徽标底、边框)→ 深色下变暗
|
||||||
|
* - 400–900 = 前景(文字、图标) → 深色下变亮
|
||||||
|
*
|
||||||
|
* 上一版把它们写成 tailwind.config.js 里的固定 hex,同时又留了一份
|
||||||
|
* accent() 定义 —— JS 对象字面量重复键后者胜出,而当时 index.css 里
|
||||||
|
* 没有对应的 --c-red-* 等变量。`rgb(var(--c-red-600) / 1)` 里变量未定义会让
|
||||||
|
* **整条声明失效**,于是 bg-red-600 退回透明、白字落在白卡片上:
|
||||||
|
* 按钮看不见但点得动(生产实测,32 个变量全部缺失)。
|
||||||
|
*/
|
||||||
|
/* blue */
|
||||||
|
--c-blue-50: 239 246 255;
|
||||||
|
--c-blue-100: 219 234 254;
|
||||||
|
--c-blue-200: 191 219 254;
|
||||||
|
--c-blue-300: 147 197 253;
|
||||||
|
--c-blue-400: 96 165 250;
|
||||||
|
--c-blue-500: 59 130 246;
|
||||||
|
--c-blue-600: 37 99 235;
|
||||||
|
--c-blue-700: 29 78 216;
|
||||||
|
--c-blue-800: 30 64 175;
|
||||||
|
--c-blue-900: 30 58 138;
|
||||||
|
/* red */
|
||||||
|
--c-red-50: 254 242 242;
|
||||||
|
--c-red-100: 254 226 226;
|
||||||
|
--c-red-200: 254 202 202;
|
||||||
|
--c-red-300: 252 165 165;
|
||||||
|
--c-red-400: 248 113 113;
|
||||||
|
--c-red-500: 239 68 68;
|
||||||
|
/* 比 Tailwind 官方的 220 38 38 略暗:官方值落在 red-50 上只有 4.41:1,
|
||||||
|
而 `bg-red-50 text-red-600` 正是错误提示条 —— 差 2% 也是差。
|
||||||
|
这一档同时用在白卡片上(4.83 → 5.10),压暗后两处都过线。 */
|
||||||
|
--c-red-600: 213 37 37;
|
||||||
|
--c-red-700: 185 28 28;
|
||||||
|
--c-red-800: 153 27 27;
|
||||||
|
--c-red-900: 127 29 29;
|
||||||
|
/* green */
|
||||||
|
--c-green-50: 240 253 244;
|
||||||
|
--c-green-100: 220 252 231;
|
||||||
|
--c-green-200: 187 247 208;
|
||||||
|
--c-green-300: 134 239 172;
|
||||||
|
--c-green-400: 74 222 128;
|
||||||
|
--c-green-500: 34 197 94;
|
||||||
|
--c-green-600: 22 163 74;
|
||||||
|
--c-green-700: 21 128 61;
|
||||||
|
--c-green-800: 22 101 52;
|
||||||
|
--c-green-900: 20 83 45;
|
||||||
|
/* amber */
|
||||||
|
--c-amber-50: 255 251 235;
|
||||||
|
--c-amber-100: 254 243 199;
|
||||||
|
--c-amber-200: 253 230 138;
|
||||||
|
--c-amber-300: 252 211 77;
|
||||||
|
--c-amber-400: 251 191 36;
|
||||||
|
--c-amber-500: 245 158 11;
|
||||||
|
--c-amber-600: 217 119 6;
|
||||||
|
--c-amber-700: 180 83 9;
|
||||||
|
--c-amber-800: 146 64 14;
|
||||||
|
--c-amber-900: 120 53 15;
|
||||||
|
/* orange */
|
||||||
|
--c-orange-50: 255 247 237;
|
||||||
|
--c-orange-100: 255 237 213;
|
||||||
|
--c-orange-200: 254 215 170;
|
||||||
|
--c-orange-300: 253 186 116;
|
||||||
|
--c-orange-400: 251 146 60;
|
||||||
|
--c-orange-500: 249 115 22;
|
||||||
|
--c-orange-600: 234 88 12;
|
||||||
|
--c-orange-700: 194 65 12;
|
||||||
|
--c-orange-800: 154 52 18;
|
||||||
|
--c-orange-900: 124 45 18;
|
||||||
|
/* yellow */
|
||||||
|
--c-yellow-50: 254 252 232;
|
||||||
|
--c-yellow-100: 254 249 195;
|
||||||
|
--c-yellow-200: 254 240 138;
|
||||||
|
--c-yellow-300: 253 224 71;
|
||||||
|
--c-yellow-400: 250 204 21;
|
||||||
|
--c-yellow-500: 234 179 8;
|
||||||
|
--c-yellow-600: 202 138 4;
|
||||||
|
--c-yellow-700: 161 98 7;
|
||||||
|
--c-yellow-800: 133 77 14;
|
||||||
|
--c-yellow-900: 113 63 18;
|
||||||
|
/*
|
||||||
|
* ─── 实心按钮/徽标的底色 ───
|
||||||
|
*
|
||||||
|
* 与上面 --c-* 的 400–900 **同值,但两种模式下都不变**。
|
||||||
|
*
|
||||||
|
* 为什么要单独一组:那六档在深色模式下被提亮成了浅色(red-600 →
|
||||||
|
* rgb(246,141,141)),因为 `text-red-600` 得在深底上读得动。而
|
||||||
|
* `bg-red-600 text-white` 的白字落在那个浅红上只有 1.6:1。
|
||||||
|
* 一个名字服务两种语义就必然坏掉一头 —— 与 text-white/bg-white 那次同理。
|
||||||
|
*
|
||||||
|
* 只有 backgroundColor 走这组(见 tailwind.config.js)。
|
||||||
|
*/
|
||||||
|
--s-blue-400: 96 165 250;
|
||||||
|
--s-blue-500: 59 130 246;
|
||||||
|
--s-blue-600: 37 99 235;
|
||||||
|
--s-blue-700: 29 78 216;
|
||||||
|
--s-blue-800: 30 64 175;
|
||||||
|
--s-blue-900: 30 58 138;
|
||||||
|
--s-red-400: 248 113 113;
|
||||||
|
--s-red-500: 239 68 68;
|
||||||
|
--s-red-600: 220 38 38;
|
||||||
|
--s-red-700: 185 28 28;
|
||||||
|
--s-red-800: 153 27 27;
|
||||||
|
--s-red-900: 127 29 29;
|
||||||
|
--s-green-400: 74 222 128;
|
||||||
|
--s-green-500: 34 197 94;
|
||||||
|
--s-green-600: 22 163 74;
|
||||||
|
--s-green-700: 21 128 61;
|
||||||
|
--s-green-800: 22 101 52;
|
||||||
|
--s-green-900: 20 83 45;
|
||||||
|
--s-amber-400: 251 191 36;
|
||||||
|
--s-amber-500: 245 158 11;
|
||||||
|
--s-amber-600: 217 119 6;
|
||||||
|
--s-amber-700: 180 83 9;
|
||||||
|
--s-amber-800: 146 64 14;
|
||||||
|
--s-amber-900: 120 53 15;
|
||||||
|
--s-orange-400: 251 146 60;
|
||||||
|
--s-orange-500: 249 115 22;
|
||||||
|
--s-orange-600: 234 88 12;
|
||||||
|
--s-orange-700: 194 65 12;
|
||||||
|
--s-orange-800: 154 52 18;
|
||||||
|
--s-orange-900: 124 45 18;
|
||||||
|
--s-yellow-400: 250 204 21;
|
||||||
|
--s-yellow-500: 234 179 8;
|
||||||
|
--s-yellow-600: 202 138 4;
|
||||||
|
--s-yellow-700: 161 98 7;
|
||||||
|
--s-yellow-800: 133 77 14;
|
||||||
|
--s-yellow-900: 113 63 18;
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,6 +253,88 @@
|
|||||||
--c-chrome-800: 30 35 44;
|
--c-chrome-800: 30 35 44;
|
||||||
--c-chrome-900: 12 14 18;
|
--c-chrome-900: 12 14 18;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ─── 强调色(深色)───
|
||||||
|
*
|
||||||
|
* 两段走向相反,理由都是实测出来的:
|
||||||
|
*
|
||||||
|
* **表面段 50–300 变暗**(朝色相方向偏移卡片色)。照搬浅色值的话
|
||||||
|
* red-50 (#fef2f2) 在深色页面上是一块近白亮斑 —— 那是「错误提示条」的底,
|
||||||
|
* 结果比正文还抢眼,而它上面的红字反而读不动。
|
||||||
|
*
|
||||||
|
* **前景段 400–900 变亮**。照搬浅色值时 red-700 (#b91c1c) 落在深色卡片上
|
||||||
|
* 只有 2.67:1、amber-900 只有 1.90:1 —— 远低于 WCAG AA 的 4.5:1。
|
||||||
|
* 这里每一档都拉到 ≥4.5(实测最低 red-400 = 5.93),
|
||||||
|
* 由 test/theme.test.mjs 逐档断言。
|
||||||
|
*
|
||||||
|
* 实心按钮底不在这里 —— 那组是 --s-*,定义在 :root 且两种模式同值。
|
||||||
|
*/
|
||||||
|
/* blue */
|
||||||
|
--c-blue-50: 28 37 54;
|
||||||
|
--c-blue-100: 30 43 67;
|
||||||
|
--c-blue-200: 32 52 84;
|
||||||
|
--c-blue-300: 36 62 105;
|
||||||
|
--c-blue-400: 92 152 247;
|
||||||
|
--c-blue-500: 108 162 248;
|
||||||
|
--c-blue-600: 128 175 249;
|
||||||
|
--c-blue-700: 154 191 250;
|
||||||
|
--c-blue-800: 183 210 251;
|
||||||
|
--c-blue-900: 213 228 253;
|
||||||
|
/* red */
|
||||||
|
--c-red-50: 46 31 37;
|
||||||
|
--c-red-100: 58 34 39;
|
||||||
|
--c-red-200: 76 37 41;
|
||||||
|
--c-red-300: 97 41 45;
|
||||||
|
--c-red-400: 243 109 109;
|
||||||
|
--c-red-500: 244 124 124;
|
||||||
|
--c-red-600: 246 141 141;
|
||||||
|
--c-red-700: 248 164 164;
|
||||||
|
--c-red-800: 250 191 191;
|
||||||
|
--c-red-900: 252 217 217;
|
||||||
|
/* green */
|
||||||
|
--c-green-50: 25 44 39;
|
||||||
|
--c-green-100: 26 54 43;
|
||||||
|
--c-green-200: 26 68 48;
|
||||||
|
--c-green-300: 27 85 54;
|
||||||
|
--c-green-400: 34 197 94;
|
||||||
|
--c-green-500: 56 203 110;
|
||||||
|
--c-green-600: 83 210 129;
|
||||||
|
--c-green-700: 118 219 155;
|
||||||
|
--c-green-800: 158 229 184;
|
||||||
|
--c-green-900: 198 240 213;
|
||||||
|
/* amber */
|
||||||
|
--c-amber-50: 46 40 31;
|
||||||
|
--c-amber-100: 59 48 29;
|
||||||
|
--c-amber-200: 77 58 28;
|
||||||
|
--c-amber-300: 99 72 26;
|
||||||
|
--c-amber-400: 245 158 11;
|
||||||
|
--c-amber-500: 246 168 35;
|
||||||
|
--c-amber-600: 247 179 65;
|
||||||
|
--c-amber-700: 249 195 104;
|
||||||
|
--c-amber-800: 251 212 148;
|
||||||
|
--c-amber-900: 252 230 192;
|
||||||
|
/* orange */
|
||||||
|
--c-orange-50: 47 36 32;
|
||||||
|
--c-orange-100: 60 41 31;
|
||||||
|
--c-orange-200: 78 48 30;
|
||||||
|
--c-orange-300: 101 57 29;
|
||||||
|
--c-orange-400: 249 115 22;
|
||||||
|
--c-orange-500: 250 129 45;
|
||||||
|
--c-orange-600: 250 146 73;
|
||||||
|
--c-orange-700: 251 168 111;
|
||||||
|
--c-orange-800: 252 193 152;
|
||||||
|
--c-orange-900: 253 219 194;
|
||||||
|
/* yellow */
|
||||||
|
--c-yellow-50: 45 42 31;
|
||||||
|
--c-yellow-100: 58 51 29;
|
||||||
|
--c-yellow-200: 74 63 27;
|
||||||
|
--c-yellow-300: 95 79 25;
|
||||||
|
--c-yellow-400: 234 179 8;
|
||||||
|
--c-yellow-500: 236 187 33;
|
||||||
|
--c-yellow-600: 239 196 62;
|
||||||
|
--c-yellow-700: 242 208 102;
|
||||||
|
--c-yellow-800: 246 222 146;
|
||||||
|
--c-yellow-900: 250 235 191;
|
||||||
/* 让浏览器把滚动条、表单控件、autofill 一并切深色 */
|
/* 让浏览器把滚动条、表单控件、autofill 一并切深色 */
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,7 +42,17 @@ const grayScale = {
|
|||||||
950: withAlpha('--c-gray-950')
|
950: withAlpha('--c-gray-950')
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 强调色只需要三档:浅底(chip/提示条)、主色(按钮)、深色(hover/文字)。 */
|
/**
|
||||||
|
* 强调色(blue / red / green / amber / orange / yellow)。
|
||||||
|
*
|
||||||
|
* 这条色阶在这套代码里也是**语义色阶**,与灰阶同理:
|
||||||
|
* - `50` – `300` = 表面(chip 底、提示条底、徽标底、边框)
|
||||||
|
* - `400` – `900` = 前景(文字、图标、实心按钮底)
|
||||||
|
*
|
||||||
|
* 深色模式下两段的走向**相反**:表面段要变暗(照搬浅色的近白值会在深色页面上
|
||||||
|
* 糊出一块刺眼亮斑),前景段要变亮(照搬浅色的 red-700 落在深色卡片上只有
|
||||||
|
* 2.67:1,读不动)。所以它必须走变量,不能写死 —— 见 index.css 的两组定义。
|
||||||
|
*/
|
||||||
const accent = (name) => ({
|
const accent = (name) => ({
|
||||||
50: withAlpha(`--c-${name}-50`),
|
50: withAlpha(`--c-${name}-50`),
|
||||||
100: withAlpha(`--c-${name}-100`),
|
100: withAlpha(`--c-${name}-100`),
|
||||||
@ -56,6 +66,27 @@ const accent = (name) => ({
|
|||||||
900: withAlpha(`--c-${name}-900`)
|
900: withAlpha(`--c-${name}-900`)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实心按钮/徽标的底色 —— `--s-*`,两种模式下**同值**。
|
||||||
|
*
|
||||||
|
* 为什么不能跟 accent 的前景段走:那一段在深色下被提亮成了浅色
|
||||||
|
* (red-600 → rgb(246,141,141)),而 `bg-red-600 text-white` 的白字落上去
|
||||||
|
* 只有 1.6:1。实心按钮的底色本来就该保持饱和 —— 深色模式下变的是页面,
|
||||||
|
* 不是「危险操作按钮是红的」这件事。
|
||||||
|
*
|
||||||
|
* 只覆盖 backgroundColor,`text-red-600` / `border-red-600` 仍走 accent()。
|
||||||
|
* Tailwind 的 backgroundColor 默认继承 colors,这里逐档覆写 400–900
|
||||||
|
* (50–300 是表面段,仍从 colors 继承 —— 它们在深色下就该变暗)。
|
||||||
|
*/
|
||||||
|
const solid = (name) => ({
|
||||||
|
400: withAlpha(`--s-${name}-400`),
|
||||||
|
500: withAlpha(`--s-${name}-500`),
|
||||||
|
600: withAlpha(`--s-${name}-600`),
|
||||||
|
700: withAlpha(`--s-${name}-700`),
|
||||||
|
800: withAlpha(`--s-${name}-800`),
|
||||||
|
900: withAlpha(`--s-${name}-900`)
|
||||||
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
||||||
// class 而不是 media:主题要能被人显式选择。跟系统走是**默认值**,
|
// class 而不是 media:主题要能被人显式选择。跟系统走是**默认值**,
|
||||||
@ -80,6 +111,27 @@ export default {
|
|||||||
textColor: {
|
textColor: {
|
||||||
white: withAlpha('--c-on-accent')
|
white: withAlpha('--c-on-accent')
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* backgroundColor 单独覆盖强调色的 400–900 档,让实心按钮底保持饱和。
|
||||||
|
*
|
||||||
|
* 每个强调色的 400–900 在这套代码里承担**两个冲突用途**:
|
||||||
|
* - `text-red-600` / `border-red-300` = 深色下必须**提亮**才读得动
|
||||||
|
* - `bg-red-600` + `text-white` = 深色下必须**保持饱和**
|
||||||
|
*
|
||||||
|
* 共用一档时后者必坏:深色下 red-600 被提亮到 rgb(246,141,141),
|
||||||
|
* 白字落上去只有 1.6:1 —— 与 text-white/bg-white 那次是同一类错误
|
||||||
|
* (一个名字服务两种语义)。
|
||||||
|
*
|
||||||
|
* 50–300 不覆盖:那是表面段,深色下就该跟着变暗。
|
||||||
|
*/
|
||||||
|
backgroundColor: {
|
||||||
|
blue: solid('blue'),
|
||||||
|
red: solid('red'),
|
||||||
|
green: solid('green'),
|
||||||
|
amber: solid('amber'),
|
||||||
|
orange: solid('orange'),
|
||||||
|
yellow: solid('yellow')
|
||||||
|
},
|
||||||
colors: {
|
colors: {
|
||||||
white: withAlpha('--c-white'),
|
white: withAlpha('--c-white'),
|
||||||
gray: grayScale,
|
gray: grayScale,
|
||||||
@ -107,26 +159,19 @@ export default {
|
|||||||
800: withAlpha('--c-chrome-800'),
|
800: withAlpha('--c-chrome-800'),
|
||||||
900: withAlpha('--c-chrome-900')
|
900: withAlpha('--c-chrome-900')
|
||||||
},
|
},
|
||||||
/* accent 色走固定值,不随主题反转。深色模式下彩色按钮底色不变,
|
/*
|
||||||
变的是卡片/表面的深浅,所以白色文字的对比度始终稳定。 */
|
* 强调色。表面段(50–300)在深色下变暗、前景段(400–900)变亮,
|
||||||
blue: { 50:'#eff6ff', 100:'#dbeafe', 200:'#bfdbfe', 300:'#93c5fd',
|
* 两段走向相反 —— 详见 accent() 的注释与 index.css 的两组定义。
|
||||||
400:'#60a5fa', 500:'#3b82f6', 600:'#2563eb', 700:'#1d4ed8',
|
*
|
||||||
800:'#1e40af', 900:'#1e3a8a' },
|
* 实心按钮底另走 --s-*(见上面的 backgroundColor)。
|
||||||
red: { 50:'#fef2f2', 100:'#fee2e2', 200:'#fecaca', 300:'#fca5a5',
|
*
|
||||||
400:'#f87171', 500:'#ef4444', 600:'#dc2626', 700:'#b91c1c',
|
* 注意这里**只能有一份定义**:JS 对象字面量的重复键后者胜出,
|
||||||
800:'#991b1b', 900:'#7f1d1d' },
|
* 而那不会报错。上一版同时写了固定 hex 与 accent() 两份,
|
||||||
green: { 50:'#f0fdf4', 100:'#dcfce7', 200:'#bbf7d0', 300:'#86efac',
|
* accent() 覆盖了 hex 那份,但 index.css 里当时没有对应的 --c-red-* 等
|
||||||
400:'#4ade80', 500:'#22c55e', 600:'#16a34a', 700:'#15803d',
|
* 变量 —— `rgb(var(--c-red-600) / 1)` 里的变量未定义使整条声明失效,
|
||||||
800:'#166534', 900:'#14532d' },
|
* 于是 bg-red-600 退回透明,白字落在白卡片上:按钮看不见但点得动。
|
||||||
amber: { 50:'#fffbeb', 100:'#fef3c7', 200:'#fde68a', 300:'#fcd34d',
|
*/
|
||||||
400:'#fbbf24', 500:'#f59e0b', 600:'#d97706', 700:'#b45309',
|
blue: accent('blue'),
|
||||||
800:'#92400e', 900:'#78350f' },
|
|
||||||
orange: { 50:'#fff7ed', 100:'#ffedd5', 200:'#fed7aa', 300:'#fdba74',
|
|
||||||
400:'#fb923c', 500:'#f97316', 600:'#ea580c', 700:'#c2410c',
|
|
||||||
800:'#9a3412', 900:'#7c2d12' },
|
|
||||||
yellow: { 50:'#fefce8', 100:'#fef9c3', 200:'#fef08a', 300:'#fde047',
|
|
||||||
400:'#facc15', 500:'#eab308', 600:'#ca8a04', 700:'#a16207',
|
|
||||||
800:'#854d0e', 900:'#713f12' },
|
|
||||||
red: accent('red'),
|
red: accent('red'),
|
||||||
green: accent('green'),
|
green: accent('green'),
|
||||||
amber: accent('amber'),
|
amber: accent('amber'),
|
||||||
|
|||||||
@ -42,6 +42,9 @@ ADMIN_PW=<密码> npm run test:wide
|
|||||||
| `narrow-probe-helper.mjs` | 连浏览器、登录、量盒子/溢出/命中区/命中测试 |
|
| `narrow-probe-helper.mjs` | 连浏览器、登录、量盒子/溢出/命中区/命中测试 |
|
||||||
| `narrow-verify.mjs` | 窄屏 13 项验收 |
|
| `narrow-verify.mjs` | 窄屏 13 项验收 |
|
||||||
| `wide-regression.mjs` | 宽屏 5 项回归 |
|
| `wide-regression.mjs` | 宽屏 5 项回归 |
|
||||||
|
| `inbox-group-verify.mjs` | 收件箱按会话分组 |
|
||||||
|
| `theme-verify.mjs` | 深浅两色的 WCAG 对比度 |
|
||||||
|
| `accent-verify.mjs` | 强调色(红/绿/橙/黄/蓝)17 组配色,两模式各一遍 |
|
||||||
|
|
||||||
`narrow-probe-helper.mjs` 里两个函数值得单独知道:
|
`narrow-probe-helper.mjs` 里两个函数值得单独知道:
|
||||||
|
|
||||||
@ -49,6 +52,20 @@ ADMIN_PW=<密码> npm run test:wide
|
|||||||
伪元素的尺寸)。`.tap` 刻意不改变视觉尺寸,所以只看 `boundingBox` 会误判成偏小
|
伪元素的尺寸)。`.tap` 刻意不改变视觉尺寸,所以只看 `boundingBox` 会误判成偏小
|
||||||
- `hitTest(page, selector)` —— 每个元素点下去是否命中自己。遮挡类 bug 只能这样查
|
- `hitTest(page, selector)` —— 每个元素点下去是否命中自己。遮挡类 bug 只能这样查
|
||||||
|
|
||||||
|
`accent-verify.mjs` 存在的理由是一次真实事故:`tailwind.config.js` 的 `colors`
|
||||||
|
里同时写了固定 hex 与 `accent()` 两份 red/green/amber/orange/yellow,JS 对象
|
||||||
|
字面量重复键**后者胜出**(不报错),而 `index.css` 当时没有对应的 `--c-red-*`
|
||||||
|
变量。`rgb(var(--c-red-600) / 1)` 里变量未定义 → 整条 `background-color` 声明
|
||||||
|
失效 → `bg-red-600` 退回透明、`text-white` 的白字落在白卡片上:
|
||||||
|
**按钮看不见但点得动**。所有静态检查都过,只有肉眼能发现。
|
||||||
|
|
||||||
|
因此这个脚本量的是**实际计算值**:它把类名注入真页面、读 `getComputedStyle`,
|
||||||
|
把「背景透明」单独判为失败(那正是上述 bug 的指纹),再算 WCAG 对比度。
|
||||||
|
|
||||||
|
只以 `hover:` 变体出现的档(`bg-red-700` / `bg-blue-700`)**不能**放进探针:
|
||||||
|
Tailwind 不生成未被使用的基础类,探它必然得到透明背景 —— 那是假阳性。
|
||||||
|
它们由 `../theme.test.mjs` 的档位断言覆盖。
|
||||||
|
|
||||||
## 已知限制
|
## 已知限制
|
||||||
|
|
||||||
headless Chromium 报告 `hover: none`,因此 `.reveal`(只在支持悬停的设备上隐藏)
|
headless Chromium 报告 `hover: none`,因此 `.reveal`(只在支持悬停的设备上隐藏)
|
||||||
|
|||||||
84
web/test/manual/accent-verify.mjs
Normal file
84
web/test/manual/accent-verify.mjs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/**
|
||||||
|
* 强调色可见性验收:真实渲染 + 取实际计算色 + 算 WCAG 对比度。
|
||||||
|
*
|
||||||
|
* 结构性断言(test/theme.test.mjs)只能保证变量存在、档位达标;
|
||||||
|
* 「按钮到底看得见吗」必须在真浏览器里量 —— 上一次那个 bug 正是
|
||||||
|
* 所有静态检查都过、只有肉眼能发现。
|
||||||
|
*/
|
||||||
|
import { openApp, WIDE } from './narrow-probe-helper.mjs';
|
||||||
|
|
||||||
|
const srgb = c => { c /= 255; return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4; };
|
||||||
|
const L = ([r, g, b]) => 0.2126 * srgb(r) + 0.7152 * srgb(g) + 0.0722 * srgb(b);
|
||||||
|
const ratio = (a, b) => { const l1 = L(a), l2 = L(b); const [h, o] = l1 > l2 ? [l1, l2] : [l2, l1]; return (h + 0.05) / (o + 0.05); };
|
||||||
|
const parse = s => (s.match(/\d+/g) || []).slice(0, 3).map(Number);
|
||||||
|
|
||||||
|
// 把一批强调色类名注入页面,量它们的实际计算值
|
||||||
|
// hover 档(bg-red-700 / bg-blue-700)不在这里:源码里它们只以 `hover:` 变体
|
||||||
|
// 出现,Tailwind 因此不生成基础类 —— 探它会得到透明背景,那是探针的假阳性
|
||||||
|
// 而不是真 bug。它们由 theme.test.mjs 的档位断言覆盖(--s-* 两模式同值)。
|
||||||
|
const PROBES = [
|
||||||
|
// [类名组合, 说明, 期望最低对比度]
|
||||||
|
['bg-red-600 text-white', '危险实心按钮(确认归档 / 删除)', 3.0],
|
||||||
|
['bg-red-500 text-white', '未读徽标', 3.0],
|
||||||
|
['bg-green-600 text-white', '同意按钮', 3.0],
|
||||||
|
['bg-orange-700 text-white', '待决策徽标', 4.5],
|
||||||
|
['bg-blue-600 text-white', '主按钮', 4.5],
|
||||||
|
['bg-red-50 text-red-600', '错误提示条', 4.5],
|
||||||
|
['bg-red-50 text-red-700', '危险区块文字', 4.5],
|
||||||
|
['bg-green-50 text-green-700', '成功提示条', 4.5],
|
||||||
|
['bg-orange-100 text-orange-700', '橙色 chip', 4.5],
|
||||||
|
['bg-amber-100 text-amber-700', '警告 chip', 4.5],
|
||||||
|
['bg-amber-50 text-amber-800', '警告条', 4.5],
|
||||||
|
['bg-yellow-100 text-yellow-700', '黄色 chip', 4.5],
|
||||||
|
['bg-blue-50 text-blue-700', '信息条', 4.5],
|
||||||
|
['bg-red-100 text-red-700', '红色 chip', 4.5],
|
||||||
|
['bg-orange-200 text-orange-800', '深橙 chip', 4.5],
|
||||||
|
['bg-white text-gray-900', '卡片正文', 4.5],
|
||||||
|
['bg-white text-gray-500', '卡片次要文字', 4.5],
|
||||||
|
];
|
||||||
|
|
||||||
|
const { browser, page } = await openApp(WIDE);
|
||||||
|
let fail = 0;
|
||||||
|
|
||||||
|
for (const mode of ['light', 'dark']) {
|
||||||
|
await page.evaluate(m => {
|
||||||
|
document.documentElement.classList.toggle('dark', m === 'dark');
|
||||||
|
let host = document.getElementById('__probe');
|
||||||
|
if (host) host.remove();
|
||||||
|
host = document.createElement('div');
|
||||||
|
host.id = '__probe';
|
||||||
|
host.style.position = 'fixed';
|
||||||
|
host.style.top = '0';
|
||||||
|
host.style.left = '0';
|
||||||
|
host.style.zIndex = '99999';
|
||||||
|
document.body.appendChild(host);
|
||||||
|
}, mode);
|
||||||
|
|
||||||
|
console.log(`\n─── ${mode === 'dark' ? '深色' : '浅色'} ───`);
|
||||||
|
for (const [cls, label, min] of PROBES) {
|
||||||
|
const got = await page.evaluate(c => {
|
||||||
|
const host = document.getElementById('__probe');
|
||||||
|
host.innerHTML = `<span id="__p" class="${c}">测试</span>`;
|
||||||
|
const el = document.getElementById('__p');
|
||||||
|
const s = getComputedStyle(el);
|
||||||
|
return { bg: s.backgroundColor, fg: s.color };
|
||||||
|
}, cls);
|
||||||
|
|
||||||
|
// 透明背景 = 声明失效(正是上次那个 bug 的指纹)
|
||||||
|
const transparent = /rgba\(0,\s*0,\s*0,\s*0\)|transparent/.test(got.bg);
|
||||||
|
if (transparent) {
|
||||||
|
console.log(` 失败 ${label} — 背景透明(${cls} 的 background-color 声明失效)`);
|
||||||
|
fail++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const r = ratio(parse(got.fg), parse(got.bg));
|
||||||
|
const ok = r >= min;
|
||||||
|
if (!ok) fail++;
|
||||||
|
console.log(` ${ok ? '通过' : '失败'} ${label.padEnd(22)} ${r.toFixed(2)}:1 (需 ${min}) ${got.bg} / ${got.fg}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.evaluate(() => document.getElementById('__probe')?.remove());
|
||||||
|
await browser.close();
|
||||||
|
console.log(fail ? `\n!! ${fail} 项不达标` : '\n全部达标');
|
||||||
|
process.exit(fail ? 1 : 0);
|
||||||
@ -48,7 +48,7 @@ const varLines = css.match(/--c-[a-z]+-?\d*:\s*[^;]+;/g) || [];
|
|||||||
const hexVars = varLines.filter(l => l.includes('#'));
|
const hexVars = varLines.filter(l => l.includes('#'));
|
||||||
check(
|
check(
|
||||||
'色板变量存 RGB 三元组而非 #hex',
|
'色板变量存 RGB 三元组而非 #hex',
|
||||||
varLines.length >= 30 && hexVars.length === 0,
|
varLines.length >= 100 && hexVars.length === 0,
|
||||||
hexVars.length ? `${hexVars.length} 个变量是 hex:${hexVars[0]}` : `只找到 ${varLines.length} 个变量`
|
hexVars.length ? `${hexVars.length} 个变量是 hex:${hexVars[0]}` : `只找到 ${varLines.length} 个变量`
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -186,15 +186,153 @@ check(
|
|||||||
`on-accent 亮度 ${onAccentDark}`
|
`on-accent 亮度 ${onAccentDark}`
|
||||||
);
|
);
|
||||||
|
|
||||||
// 17) 强调色(blue/red/...)不走变量。
|
// 17) 每个被 Tailwind 实际使用的 CSS 变量都必须在 index.css 有定义。
|
||||||
// 它们跟着反转会让主按钮在深色页面上失去「这是主操作」的视觉重量,
|
//
|
||||||
// 而且白字落在变暗的 blue-600 上对比度会掉到 3:1 以下。
|
// 这一条守的是本项目最贵的一次视觉 bug:tailwind.config.js 里 `colors`
|
||||||
|
// 同时写了固定 hex 与 accent() 两份 red/green/amber/orange/yellow ——
|
||||||
|
// JS 对象字面量重复键**后者胜出**(不报错、不警告),而 index.css 里当时
|
||||||
|
// 没有对应的变量。`rgb(var(--c-red-600) / 1)` 里变量未定义会让整条
|
||||||
|
// background-color 声明失效,于是 bg-red-600 退回透明、text-white 的白字
|
||||||
|
// 落在白卡片上:**按钮看不见但点得动**。32 个变量全部缺失,
|
||||||
|
// 波及所有 red/green/amber/orange/yellow 的地方。
|
||||||
|
//
|
||||||
|
// 判据必须走 resolveConfig 而不是正则扫配置文本:真正出问题的那批变量名
|
||||||
|
// 是 accent('red') 这类**模板拼出来的**,配置源码里根本没有 `--c-red-600`
|
||||||
|
// 这个字面量 —— 扫文本会漏掉正是要防的那一类。
|
||||||
|
const resolveConfig = (await import('tailwindcss/resolveConfig.js')).default;
|
||||||
|
const resolved = resolveConfig((await import('../tailwind.config.js')).default);
|
||||||
|
|
||||||
|
const declared = new Set([...css.matchAll(/--[cs]-[a-z0-9-]+(?=\s*:)/g)].map(m => m[0]));
|
||||||
|
const usedVars = new Set();
|
||||||
|
const walk = (v) => {
|
||||||
|
if (typeof v === 'string') {
|
||||||
|
for (const m of v.matchAll(/var\((--[a-z0-9-]+)/g)) usedVars.add(m[1]);
|
||||||
|
} else if (v && typeof v === 'object') {
|
||||||
|
for (const k of Object.keys(v)) walk(v[k]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 只看颜色相关的 theme 段:其余(spacing/fontSize/...)不走变量
|
||||||
|
for (const key of ['colors', 'textColor', 'backgroundColor', 'borderColor', 'ringColor', 'divideColor']) {
|
||||||
|
walk(resolved.theme[key]);
|
||||||
|
}
|
||||||
|
const undef = [...usedVars].filter(v => !declared.has(v));
|
||||||
check(
|
check(
|
||||||
'强调色为固定值,不随主题反转',
|
'Tailwind 实际使用的每个变量都在 index.css 有定义',
|
||||||
!/blue:\s*accent\(/.test(cfg) && /blue:\s*\{\s*50:\s*'#/.test(cfg)
|
usedVars.size >= 100 && undef.length === 0,
|
||||||
|
undef.length
|
||||||
|
? `${undef.length} 个未定义:${undef.slice(0, 6).join(', ')}`
|
||||||
|
: `只解析出 ${usedVars.size} 个变量引用`
|
||||||
);
|
);
|
||||||
|
|
||||||
// 18) 应用框架(侧栏 / 底部导航)必须有独立色阶。
|
// 18) colors 里每个颜色名只能定义一次。
|
||||||
|
// 重复键静默生效,是上一条那个 bug 的**成因**:读代码的人看到固定 hex
|
||||||
|
// 以为在用它,实际生效的是下面那份 accent()。
|
||||||
|
const colorsBlock = cfg.slice(cfg.indexOf('colors: {'));
|
||||||
|
const dupNames = [];
|
||||||
|
for (const name of ['blue', 'red', 'green', 'amber', 'orange', 'yellow', 'gray', 'chrome']) {
|
||||||
|
const n = (colorsBlock.match(new RegExp(`^\\s{8}${name}:`, 'gm')) || []).length;
|
||||||
|
if (n > 1) dupNames.push(`${name}×${n}`);
|
||||||
|
}
|
||||||
|
check('colors 里没有重复的颜色名', dupNames.length === 0, dupNames.join(' '));
|
||||||
|
|
||||||
|
// 19) 强调色的**表面段**(50–300)深色下必须变暗。
|
||||||
|
// 照搬浅色值的话 red-50 (#fef2f2) 在深色页面上是一块近白亮斑 ——
|
||||||
|
// 那是错误提示条的底,结果比正文还抢眼,上面的红字反而读不动。
|
||||||
|
const surfaceOffenders = [];
|
||||||
|
for (const name of ['blue', 'red', 'green', 'amber', 'orange', 'yellow']) {
|
||||||
|
for (const shade of [50, 100, 200, 300]) {
|
||||||
|
const l = lum(lightBlock, `${name}-${shade}`);
|
||||||
|
const d = lum(darkBlock, `${name}-${shade}`);
|
||||||
|
if (l === null || d === null) { surfaceOffenders.push(`${name}-${shade}:缺失`); continue; }
|
||||||
|
if (d >= l) surfaceOffenders.push(`${name}-${shade}(${d}≥${l})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check(
|
||||||
|
'强调色表面段在深色下变暗',
|
||||||
|
surfaceOffenders.length === 0,
|
||||||
|
surfaceOffenders.slice(0, 6).join(' ')
|
||||||
|
);
|
||||||
|
|
||||||
|
// 20) 强调色的**前景段**(400–900)在深色卡片上必须达到 WCAG AA 4.5:1。
|
||||||
|
// 照搬浅色值时 red-700 只有 2.67:1、amber-900 只有 1.90:1 ——
|
||||||
|
// 「看得见但读不动」跟看不见是同一类 bug。
|
||||||
|
const rgbOf = (block, name) => {
|
||||||
|
const m = block.match(new RegExp(`--c-${name}:\\s*(\\d+)\\s+(\\d+)\\s+(\\d+)`));
|
||||||
|
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
||||||
|
};
|
||||||
|
const srgb = c => { c /= 255; return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4; };
|
||||||
|
const relLum = ([r, g, b]) => 0.2126 * srgb(r) + 0.7152 * srgb(g) + 0.0722 * srgb(b);
|
||||||
|
const contrast = (a, b) => {
|
||||||
|
const l1 = relLum(a), l2 = relLum(b);
|
||||||
|
const [hi, lo] = l1 > l2 ? [l1, l2] : [l2, l1];
|
||||||
|
return (hi + 0.05) / (lo + 0.05);
|
||||||
|
};
|
||||||
|
const darkCard = rgbOf(darkBlock, 'white');
|
||||||
|
const lowContrast = [];
|
||||||
|
for (const name of ['blue', 'red', 'green', 'amber', 'orange', 'yellow']) {
|
||||||
|
for (const shade of [400, 500, 600, 700, 800, 900]) {
|
||||||
|
const fg = rgbOf(darkBlock, `${name}-${shade}`);
|
||||||
|
if (!fg) { lowContrast.push(`${name}-${shade}:缺失`); continue; }
|
||||||
|
const r = contrast(fg, darkCard);
|
||||||
|
if (r < 4.5) lowContrast.push(`${name}-${shade}:${r.toFixed(2)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check(
|
||||||
|
'强调色前景段在深色卡片上达到 4.5:1',
|
||||||
|
darkCard !== null && lowContrast.length === 0,
|
||||||
|
lowContrast.slice(0, 6).join(' ')
|
||||||
|
);
|
||||||
|
|
||||||
|
// 21) 实心按钮底走独立的 --s-* 且**两种模式同值**。
|
||||||
|
//
|
||||||
|
// accent 的 400–900 在深色下被提亮(为了 text-red-600 读得动),
|
||||||
|
// 而 `bg-red-600 text-white` 的白字落在那个浅红上只有 1.6:1。
|
||||||
|
// 一个名字服务两种语义必然坏掉一头 —— 与 text-white/bg-white 那次同理。
|
||||||
|
check(
|
||||||
|
'实心按钮底走 --s-* 并只覆盖 backgroundColor',
|
||||||
|
/const solid = \(name\) =>/.test(cfg) &&
|
||||||
|
/backgroundColor:\s*\{[^}]*red:\s*solid\('red'\)/s.test(cfg) &&
|
||||||
|
/--s-red-600:/.test(css)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 22) --s-* 不能出现在 .dark 里:它必须两种模式同值。
|
||||||
|
// 在 .dark 覆盖等于把「危险操作是红的」这件事也一起反转了。
|
||||||
|
check(
|
||||||
|
'--s-* 未被 .dark 覆盖(实心底不随主题变)',
|
||||||
|
!/--s-[a-z]+-\d+:/.test(darkBlock)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 23) 白字落在实心按钮底上必须达到 4.5:1(两种模式同一组值,只需算一次)。
|
||||||
|
const solidBlock = css.slice(css.indexOf(':root'), css.indexOf('.dark'));
|
||||||
|
const sRgb = name => {
|
||||||
|
const m = solidBlock.match(new RegExp(`--s-${name}:\\s*(\\d+)\\s+(\\d+)\\s+(\\d+)`));
|
||||||
|
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
||||||
|
};
|
||||||
|
// 代码里真正出现过的「实心底 + 白字」组合
|
||||||
|
const solidPairs = [
|
||||||
|
['blue-600'], ['blue-700'], ['blue-500'],
|
||||||
|
['red-500'], ['red-600'], ['red-700'],
|
||||||
|
['green-600'], ['green-700'], ['orange-700']
|
||||||
|
];
|
||||||
|
const onAccentLight = rgbOf(lightBlock, 'on-accent') ||
|
||||||
|
(lightBlock.match(/--c-on-accent:\s*(\d+)\s+(\d+)\s+(\d+)/) || []).slice(1).map(Number);
|
||||||
|
const weakButtons = [];
|
||||||
|
for (const [name] of solidPairs) {
|
||||||
|
const bg = sRgb(name);
|
||||||
|
if (!bg) { weakButtons.push(`${name}:缺失`); continue; }
|
||||||
|
// 3:1 是 WCAG 对大号/粗体文字的下限。这些按钮文字是 11–14px 的 font-medium,
|
||||||
|
// 严格说该要 4.5 —— 但 Tailwind 官方 600 档普遍在 3–4.5 之间(green-600 = 3.05),
|
||||||
|
// 收紧到 4.5 就得偏离官方色值。取 3.0 作为门槛并把偏低的记在这里。
|
||||||
|
const r = contrast(onAccentLight, bg);
|
||||||
|
if (r < 3.0) weakButtons.push(`${name}:${r.toFixed(2)}`);
|
||||||
|
}
|
||||||
|
check(
|
||||||
|
'白字在实心按钮底上达到 3:1',
|
||||||
|
weakButtons.length === 0,
|
||||||
|
weakButtons.join(' ')
|
||||||
|
);
|
||||||
|
|
||||||
|
// 24) 应用框架(侧栏 / 底部导航)必须有独立色阶。
|
||||||
// 它在浅色模式下本来就是深色的 —— 并入反转的 gray 之后深色模式下会变成
|
// 它在浅色模式下本来就是深色的 —— 并入反转的 gray 之后深色模式下会变成
|
||||||
// 近白色,比内容区还亮,整个层次翻过来(实测 rgb(243,245,248))。
|
// 近白色,比内容区还亮,整个层次翻过来(实测 rgb(243,245,248))。
|
||||||
check(
|
check(
|
||||||
@ -204,7 +342,7 @@ check(
|
|||||||
/--c-chrome-900:/.test(darkBlock)
|
/--c-chrome-900:/.test(darkBlock)
|
||||||
);
|
);
|
||||||
|
|
||||||
// 19) 深色下框架必须比内容区更沉(保持浅色下就有的层次关系)。
|
// 25) 深色下框架必须比内容区更沉(保持浅色下就有的层次关系)。
|
||||||
const chromeDark = lum(darkBlock, 'chrome-900');
|
const chromeDark = lum(darkBlock, 'chrome-900');
|
||||||
check(
|
check(
|
||||||
'深色下框架比内容区更暗',
|
'深色下框架比内容区更暗',
|
||||||
@ -212,7 +350,7 @@ check(
|
|||||||
`chrome-900=${chromeDark} gray-50=${darkG50}`
|
`chrome-900=${chromeDark} gray-50=${darkG50}`
|
||||||
);
|
);
|
||||||
|
|
||||||
// 20) 侧栏与底部导航里不该残留 slate-*(那条色阶指向反转的 gray)。
|
// 26) 侧栏与底部导航里不该残留 slate-*(那条色阶指向反转的 gray)。
|
||||||
const chromeFiles = ['Sidebar', 'NarrowNav'];
|
const chromeFiles = ['Sidebar', 'NarrowNav'];
|
||||||
const slateLeft = [];
|
const slateLeft = [];
|
||||||
for (const f of chromeFiles) {
|
for (const f of chromeFiles) {
|
||||||
|
|||||||
Reference in New Issue
Block a user