Skip to content

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 色值不一致:

A1A2B1B2C1C2
RB(改造前,暖色难度坡)#22c55e#4ade80#f59e0b#f97316#ef4444#dc2626
RVH(冷色三段)#93C5FD#2563EB#5EEAD4#0F766E#C4B5FD#6D28D9

RVH 选冷色的理由(theme_config.dart:595 写明)= 避开品牌主色松绿 #1F4E3D。 该理由在 RB 同样成立——RB 的 A1/A2 绿正与品牌绿撞色。

裁定:色系一致(都走冷色),色值各自适配场景,不互相照搬。 理由三条:

  1. 用法不同:RVH 画列表里的填充色块,RB 画网页正文里的 1.5px 下划线。 浅蓝 #93C5FD 作填充底(配深色文字)没问题,作白底细线对比度只有 1.80:1,基本看不见。
  2. 蓝下划线 = 超链接。RB 在真实网页上画线,A1 用浅蓝会被读成链接;RVH 无此冲突。
  3. 「段内浅/深」是弱序信号。浅蓝 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 也用这套)

Tailwindhex白底 CR
A1teal-600#0d94883.74
A2cyan-700#0e74905.36
B1sky-700#0369a15.93
B2blue-700#1d4ed86.70
C1indigo-700#4338ca7.90
C2violet-800#5b21b68.98

3-b 暗底色板(深色页面 / 暗色主题下的 reader·EPUB 纸面)

Tailwindhex#121212 底 CR#1f1f1f(reader 暗纸面)CR
A1teal-700#0f766e3.423.01
A2cyan-600#0891b25.094.48
B1sky-500#0ea5e96.765.95
B2blue-400#60a5fa7.376.48
C1indigo-300#a5b4fc9.408.27
C2violet-200#ddd6fe13.4911.87

两张表对比度均严格单调递增,且各档在两套背景下都 ≥3:1(实测复算,非引用)。

3-c unknown("没有难度数据",不是坡上的一档)

刻意留在暖色 amber——站在冷色序列之外正好表达"不属于这个刻度"。 hex 由 amber-400 #fbbf24amber-700 #b45309(原值白底仅 1.63:1, 而 learning 高亮模式下所有词都吃这一档 = 最常见的色却最看不见);暗底板仍用 #fbbf24


4. RB 侧落点(供 RVH 会话理解改了什么,不必镜像)

文件改动
src/lib/design-tokens.ts::CEFR_PALETTEhex 换亮底板;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. 跨端约定(本文的正事)

  1. RVH 不需要改代码——它的色板在自己的场景(填充色块)里是自洽的,RB 也没有照搬它。
  2. 但两端色板从此互为参照物:任一端要动 CEFR 配色,先读本文, 在本文追加一节说明"改成什么 / 为什么 / 与对端的关系",再动手。 参照品牌主色的同款纪律(memory project_brand_primary_cross_end)。
  3. 若 RVH 将来也想要"对比度即序信号"(比如 RVH 加了深色主题后填充块看不清), §2 的原则和 §3 的算法可直接复用:按目标背景算 CR,保证单调递增即可,色值不必与 RB 相同
  4. RB 内部两份副本design-tokens.ts TS 侧 + styles.js CSS 字符串侧)必须同步改 ——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 会话)