主题
22 · CEFR 配色改冷色系(RB 侧已落地 · 交接 RVH)
物理位置:本文件在 RB 仓
~/reading-browser/docs/cross-end/22-rb-cefr-cool-palette-alignment.md状态:RB 侧 2026-08-12 完成。RVH 侧无需改代码,但改色前须先读本文(见 §5 约定)。 零 migration、零 schema 变更、零 sync 影响——纯呈现层。
1. 起因
用户发现两端 CEFR 色值不一致:
| A1 | A2 | B1 | B2 | C1 | C2 | |
|---|---|---|---|---|---|---|
| RB(改造前,暖色难度坡) | #22c55e | #4ade80 | #f59e0b | #f97316 | #ef4444 | #dc2626 |
| RVH(冷色三段) | #93C5FD | #2563EB | #5EEAD4 | #0F766E | #C4B5FD | #6D28D9 |
RVH 选冷色的理由(theme_config.dart:595 写明)= 避开品牌主色松绿 #1F4E3D。 该理由在 RB 同样成立——RB 的 A1/A2 绿正与品牌绿撞色。
裁定:色系一致(都走冷色),色值各自适配场景,不互相照搬。 理由三条:
- 用法不同:RVH 画列表里的填充色块,RB 画网页正文里的 1.5px 下划线。 浅蓝
#93C5FD作填充底(配深色文字)没问题,作白底细线对比度只有 1.80:1,基本看不见。 - 蓝下划线 = 超链接。RB 在真实网页上画线,A1 用浅蓝会被读成链接;RVH 无此冲突。
- 「段内浅/深」是弱序信号。浅蓝 vs 深蓝读作"两个类别"而非"后者更难"; RB 每天在正文里连续显示 6 档,单调难度坡比分段更重要。
2. 核心设计原则(RB 侧,也是本文最值得跨端沿用的一条)
序信号 = 与页面背景的对比度,不是明度。色相只负责标识档位。
这样白底 / 深底上语义一致(A1 安静 → C2 突出,即"越难越显眼"),换背景时用户不必重新学配色。
为什么必须做背景自适应:RB 的下划线会落在任意网页背景上(original 档页面不是我们的)。 算过账——同时要求「白底 ≥3:1」且「深底 ≥3:1」,相对亮度必须挤进 L ∈ [0.117, 0.283] 的窄窗口, 明度扛不了序信号。「单调明度坡」与「双底都可见」在数学上互斥,只能靠双色板破。
⚠️ 因此暗底色板的明度方向是反的(A1 暗 → C2 亮)——那是"对比度而非明度"原则的体现,不是笔误。
3. RB 定稿色值
色相族 1:1 对齐 Tailwind 六个冷色族:A1 teal · A2 cyan · B1 sky · B2 blue · C1 indigo · C2 violet (hex 与 badge class 同源,省掉一层手工映射)。
3-a 亮底色板(白/浅色页面 · 应用内图表与 chip 也用这套)
| 档 | Tailwind | hex | 白底 CR |
|---|---|---|---|
| A1 | teal-600 | #0d9488 | 3.74 |
| A2 | cyan-700 | #0e7490 | 5.36 |
| B1 | sky-700 | #0369a1 | 5.93 |
| B2 | blue-700 | #1d4ed8 | 6.70 |
| C1 | indigo-700 | #4338ca | 7.90 |
| C2 | violet-800 | #5b21b6 | 8.98 |
3-b 暗底色板(深色页面 / 暗色主题下的 reader·EPUB 纸面)
| 档 | Tailwind | hex | #121212 底 CR | #1f1f1f(reader 暗纸面)CR |
|---|---|---|---|---|
| A1 | teal-700 | #0f766e | 3.42 | 3.01 |
| A2 | cyan-600 | #0891b2 | 5.09 | 4.48 |
| B1 | sky-500 | #0ea5e9 | 6.76 | 5.95 |
| B2 | blue-400 | #60a5fa | 7.37 | 6.48 |
| C1 | indigo-300 | #a5b4fc | 9.40 | 8.27 |
| C2 | violet-200 | #ddd6fe | 13.49 | 11.87 |
两张表对比度均严格单调递增,且各档在两套背景下都 ≥3:1(实测复算,非引用)。
3-c unknown("没有难度数据",不是坡上的一档)
刻意留在暖色 amber——站在冷色序列之外正好表达"不属于这个刻度"。 hex 由 amber-400 #fbbf24 改 amber-700 #b45309(原值白底仅 1.63:1, 而 learning 高亮模式下所有词都吃这一档 = 最常见的色却最看不见);暗底板仍用 #fbbf24。
4. RB 侧落点(供 RVH 会话理解改了什么,不必镜像)
| 文件 | 改动 |
|---|---|
src/lib/design-tokens.ts::CEFR_PALETTE | hex 换亮底板;badge class 换冷色族;新增 badgeDark 字段 |
同上 cefrBadgeClassDark() | 由"按 level 前缀硬写的 if-else"改为查 CEFR_PALETTE.badgeDark(原来它不共享 palette 数据,是改色时最容易漏的一处) |
同上 CEFR_CHART_COLORS | 改为从 CEFR_PALETTE 派生(原来是第三份手抄 hex 副本) |
content-script/features/styles.js | 新增 CEFR_HEX(双色板)+ CSS 自定义属性生成器 + applyPageBackgroundPalette()(实测页面背景亮度 → <html> 打 .rb-dark-page);.rb-highlight-* / .rb-discover-* 全部改吃 var(--rb-cefr-*) |
色板择一机制(靠 CSS 变量的子树继承,不涉及特异性比较):
:root → 亮底板(默认)
html.rb-dark-page → 暗底板(宿主页背景实测偏暗)
.rb-reader-content, .rb-reading-paper → 亮底板(我们自己的白纸面,收回宿主页的暗色板)
html.rb-dark 下的上面两个 → 暗底板(暗色主题的暗纸面)亮度判据用 0.179(WCAG 里"该配白字还是黑字"的相对亮度交叉点)。 已知盲区两个,都只是继续用亮底板、不报错:① 纯背景图铺底读不到;② 站点自带暗色开关在管线跑完后才切,要等下次重跑。
顺带修掉的既有漂移:styles.js 里 A1/A2 长期共用一条规则、共用同一个绿,而 design-tokens.ts 一直是两个色。本次拆开。
附带收益:原暖坡对红绿色盲极不友好(A 段绿 vs C 段红是最经典的混淆对),冷色改造顺带改善。
5. 跨端约定(本文的正事)
- RVH 不需要改代码——它的色板在自己的场景(填充色块)里是自洽的,RB 也没有照搬它。
- 但两端色板从此互为参照物:任一端要动 CEFR 配色,先读本文, 在本文追加一节说明"改成什么 / 为什么 / 与对端的关系",再动手。 参照品牌主色的同款纪律(memory
project_brand_primary_cross_end)。 - 若 RVH 将来也想要"对比度即序信号"(比如 RVH 加了深色主题后填充块看不清), §2 的原则和 §3 的算法可直接复用:按目标背景算 CR,保证单调递增即可,色值不必与 RB 相同。
- RB 内部两份副本(
design-tokens.tsTS 侧 +styles.jsCSS 字符串侧)必须同步改 ——content-script 不能 import TS,这是结构性的,不是疏忽。
6. 验收(RB 侧)
- [x]
pnpm exec tsc --noEmit/pnpm build/cargo check全绿 - [x]
pnpm run build:cs重新打包 content-script - [x] 对比度数值实测复算(非引用):两套色板均严格单调递增、双底均 ≥3:1
- [x] CSS 变量引用完备性:被
var(--rb-cefr-*)引用的名字全部有定义 - [ ] 真机跨亮/暗背景各看一遍(原始网页亮底 / 原始网页暗底 / reader 亮 / reader 暗 / EPUB)——待用户验收
创建:2026-08-12(RB 会话)