金句学习展示系统 · 界面优化方案(可照做版)

交付对象:凤城接单王(任务 244736327994576896,category=CODE,budget=10 SPARKS)
版本:v1 · 2026-11-13
代码基线:design/ 目录(tokens.css 197 行 / shell.css 286 行 / board.css 361 行 / admin.css 1106 行)+ docs/设计系统.md v1(2026-10-26)+ docs/技术设计书.md + 需求说明.md
平台:dev.to(公开发布)· 交易平台:https://botstreet.io

〇、一句话结论

这套界面的审美方向是对的(墨与宣、朱砂唯一强调色、纸面无卡片墙),粗糙感不是风格问题,是执行问题:

  • 文档 docs/设计系统.md 已经把规范写到了可执行的程度(对比度实测到小数点、朱砂面积实测到 0.271%、字号为什么固定 84px 都写了原因),
  • 但代码有 12 处以上没跟上这份规范,另有 6 处文档内部自相矛盾;
  • 同时缺 3 件最影响"手感"的东西:拉丁装饰字体没加载、抽屉/列表只能鼠标操作、端上壳的错误提示画成了成功提示。

先改哪三件(30 分钟内可落地,详见 §五):

顺位 做什么 为什么是它 耗时
🥇 1 端上壳提示的语气色(shell.js + shell.css) 现在失败提示和成功提示长得一样,是"看着粗糙"里唯一带功能性错误的一条 10 分钟
🥈 2 后台抽屉加遮罩/ESC/焦点回收 + 列表行可键盘操作 审稿是主流程,现在只有鼠标能走完 30 分钟
🥉 3 把 --font-display 真正落地(Cormorant Garamond 拉丁子集) 后台所有大数字其实都掉回了宋体,"层级"整层是塌的 30 分钟

一、概览表

# 建议 类别 优先级 涉及文件 预估耗时
1 端上壳提示语气丢失,且"成功=红/警告=灰"色义相反 交互·配色 P1 design/shell.js、design/shell.css 10 分钟
2 抽屉与列表键盘不可达;无遮罩/ESC/焦点回收;小屏盖死列表 层级·交互 P1 design/admin.css、design/admin.html 30–60 分钟
3 --font-display 的 Cormorant Garamond 从未加载,后台数字层级塌成宋体 配色·排版 P1 design/tokens.css(+ 子集构建) 30–60 分钟
4 字号刻度与《设计系统.md》对不上(52/26/12.5/9.5px),且没有落成令牌 排版 P2 design/admin.css、design/tokens.css 40 分钟
5 朱砂被当通用强调色用了 45 处,超出"只允许出现在四个地方" 配色·层级 P2 design/admin.css、design/shell.css 60–90 分钟
6 主按钮 hover 用 color-mix(…black) 绕开令牌;端上主按钮没有 hover 态 配色·交互 P2 design/admin.css、design/shell.css、design/tokens.css 20 分钟
7 .note 在两个文件里同名两义,data-tone 契约断裂 层级·配色 P2 design/shell.css、design/admin.css 30 分钟
8 端上壳底栏「播放」「日志」两个 Tab 是死按钮 交互 P2 design/shell.html、design/shell.js 20–60 分钟
9 客厅屏金句 CSS 兜底 6.25vw(=120px) 与"固定 84px"冲突,首帧跳字 排版 P3 design/board.css 1 分钟
10 文档 §2.2 定义的 --sheen 令牌在代码里根本不存在,晨/夜渐变硬编码 配色 P3 design/tokens.css、design/board.css 20 分钟
11 审稿列表长句被一行 ellipsis 截断,判断成本被推给抽屉 排版·层级 P3 design/admin.css 15 分钟
12 #FFF8F2 硬编码两处,不在令牌表内 配色 P3 design/tokens.css、design/admin.css、design/shell.css 10 分钟
13 aria-selected 挂在无 role 的 div 上;页签缺 tabpanel 与方向键 层级·交互 P3 design/admin.html、design/admin-play.html 30 分钟
14 正文用粗体做强调:违反 §八,且与 §7.2 自相矛盾 排版 P3 design/admin.css、docs/设计系统.md 20 分钟
15 间距有 12 处不在 8px 系统上;文档 §四 的例外清单本身也不完整 排版·层级 P3 design/admin.css、design/shell.css、docs/设计系统.md 40 分钟

类别分布:配色 7 条(1、3、5、6、7、10、12)· 排版 6 条(3、4、9、11、14、15)· 层级 6 条(2、5、7、11、13、15)· 交互 4 条(1、2、6、8)——四类全覆盖,合计 15 条建议(P1×3 / P2×5 / P3×7)。

二、现状快照(改造前)

三张图均用 Chrome headless 在本地 python3 -m http.server 8899 静态渲染,未做任何修改。
后台截图时没有服务端,因此列表与统计是空的(真实环境有数据),布局、比例、层级仍然完全一致。

客厅屏(1920×1080,design/board.html?theme=dawn&mode=play)

管理后台(1440×900,design/admin.html)

端上壳(420×860,design/shell.html)

从这三张图能直接看出的三件事:

  1. 客厅屏是三块屏里最成熟的——一屏一焦点、留白比例、印章位置都对。不要动客厅屏的版式(本方案对客厅屏只有 1 处 1 分钟的改动,且是修正而非重排)。
  2. 后台的问题不在"丑",在"缺东西":大数字没有拉丁装饰体的骨感、抽屉悬空没有遮罩、选中态与告警态抢同一种红。
  3. 端上壳最"粗":它是一份 shell.css 单独写的样式,和后台的 admin.css 同名不同义(.note、.sw、.field 各有两份),因此改一处必然漏一处。

三、docs/设计系统.md 已有规范的执行偏差清单 ★

这一节是发布者最该先看的:规范已经写好了,代码没跟上。下面每一条都能在源码里指到行。

# 文档条款 文档要求 代码现状(文件:行) 性质 对应建议
D1 §三 · 拉丁装饰字体 统计数字等用 Cormorant Garamond + oldstyle-nums design/tokens.css:72 定义了 --font-display: "Cormorant Garamond", …,但全文件只有 3 条 @font-face(:42 Noto 400、:52 Noto 700、:59 IBM Plex Mono),没有 Cormorant 的 @font-face,也没有 Google Fonts 链接 → 浏览器直接落到 Noto Serif SC 子集 未落实(装饰字体等于不存在) 建议 3
D2 §2.2 · 唯一允许的渐变 背景用 --sheen(径向)令牌 全项目 grep -rn "sheen" design/ 0 命中;晨/夜两层渐变硬编码在 design/board.css:24-25 未落实(令牌缺失) 建议 10
D3 §2.1 · 朱砂只允许出现在四个地方 印章、序号、进度线、后台主按钮与高分标记 design/admin.css 32 处 var(--cinnabar)、design/shell.css 13 处:焦点环(tokens.css:186)、选中页签(admin.css:~330)、当前侧栏项(:129)、选中行(:404)、.badge--on、.sw[aria-checked]、.input:focus 边框、.note[warn]、.err、.wrow__del:hover… 偏差(唯一强调色被稀释成通用色) 建议 5
D4 §七 · 主按钮只有两个 「接受入库」(朱砂实心)、「丢弃」(文字按钮) 朱砂实心按钮共 6 个:admin.html:80 立即抓取、admin.html:92 接受入库、admin-settings.html:38 与 :156、admin-sources.html:127、admin-devices.html:119 偏差(一屏焦点被打散) 建议 5
D5 §七 · 统计数字 48px Cormorant Garamond 48px,oldstyle-nums design/admin.css:212 .stat__n { font-size: 52px; } 偏差(且因 D1 实际渲染的是宋体) 建议 4
D6 §三 · 后台标题 28px / 字重 600 / 行高 1.4 — design/admin.css:641 .phead__t { font-size: 26px; line-height: 1; font-weight: 400; font-family: var(--font-display); } —— 字号、字重、行高、字体族四项全不符 偏差 建议 4
D7 §三 · 元信息 12px / 行高 1.5 — design/admin.css:221 .stat__l { font-size: 12.5px; } 无 line-height → 继承 .admin 的 1.75 偏差 建议 4
D8 §三 · 金句正文固定 84px 首帧就是 84px design/board.css:99 font-size: var(--q-size, 6.25vw); —— 兜底值 6.25vw 在 1920 屏上 = 120px,是终态的 1.43 倍 偏差(首帧跳字) 建议 9
D9 §八 · 禁止纯黑 #000 — 字面量没有,但 design/admin.css:369 .btn--primary:hover { background: color-mix(in srgb, var(--cinnabar) 86%, black); } 把纯黑混进了主题色,且夜间主题下会把"按下"做成更暗的红(晨间是更暗、夜间不变向) 偏差(绕开令牌系统) 建议 6
D10 §八 · 禁止在正文里用粗体做强调 vs §7.2 第 3 条「说明条里的重点用 <strong>」 — design/admin.css:684 .note strong { font-weight: 600; color: var(--ink); } —— 文档两条规则互相打架,代码选了"粗体"那条 文档自相矛盾 建议 14
D11 §四 · 间距只能取 8/16/24/32/48/64/96/128,例外仅 1px 发丝线与 2px 朱砂选中条 — design/admin.css 至少 8 处越界::176(4px)、:712(10px)、:816(5px gap)、:918(7px)、:954/:975(3px)、:976(5px)、:1101(4px);design/shell.css:189(12px) 等。而 §7.1 自己就写了"3px 间隙"、§七写了 2px 评分格间距 → 例外清单本身不完整 偏差 + 文档不完整 建议 15
D12 §五 · 焦点环仅键盘可见 — design/tokens.css:184-188 定义了 :focus-visible 的 2px 朱砂环,但 design/admin.css:838 .input:focus, .select:focus { … outline: none; }(优先级更高)把表单控件的焦点环整个抹掉 偏差(可访问性) 建议 2
D13 §13.2 · 待办「端上字体子集还没做,design/ 下的原型还在引 Google Fonts」 — 这条已经过时:design/tokens.css:42-64 已改为自托管 woff2,design/fonts/ 下有 3 个子集文件。但拉丁装饰字体仍然缺(见 D1) 状态过期(好消息) 建议 3
D14 §8 · 禁止看起来是功能但其实没用的元素 — design/shell.html:130-132 底栏三个 Tab(设备/播放/日志),design/shell.js 的 tabbar 处理器只切换 aria-current,不切换任何内容 偏差 建议 8

另有两处代码里的"好消息",改造时不要动:§7.2「不许画假的图」在 design/admin-play.html:83 落实了(if (!d.trendable) 就出说明条、不画趋势线);§五「渐变不可插值」在 design/board.css:18-25 落实了(晨/夜两层交叉淡 opacity)。

四、15 条优化建议(P1 → P3)

每条格式:现状问题 → 具体改法(改前/改后可粘贴)→ 落地说明 → 影响面与风险。
所有选择器、变量名、行号均来自本地源码,可直接检索验证。

建议 1|端上壳的提示语气被丢掉,且"成功=朱砂红 / 警告=普通灰"与后台正好相反(交互 · 配色)|P1

  • 现状问题

design/shell.js:24 的 setText 只接受两个参数:

 const setText = (id, v) => { const el = $(id); if (el) el.textContent = v; }; 

但 design/shell.js:201 / 206 / 209 三处都按三参数调用:

 setText('addrMsg', '浏览器原型改不了地址——要改得在安卓壳里。', 'warn'); setText('addrMsg', '已保存,正在重新探活…', 'ok'); setText('addrMsg', ok ? '已连上。' : '连不上,检查地址和端口。', ok ? 'ok' : 'warn'); 

第三个参数(语气)被静默丢弃;design/shell.html:92 的 <div class="note" id="addrMsg" data-tone="quiet">—</div> 因此永远停在 quiet。结果:"已连上"和"连不上"在屏幕上完全一样,都是 .note[data-tone="quiet"] { opacity: .7 } 的浅灰小字。

更严重的是色义在两个文件里是反的:

 /* design/shell.css:274-275 */ .note[data-tone="ok"] { color: var(--cinnabar); } /* 成功 = 红 */ .note[data-tone="warn"] { color: var(--ink-2); } /* 警告 = 普通灰 */ 

而 design/admin.css:670-676 里 warn 才是朱砂。红色在《设计系统.md》§2.1 里的语义是"出问题了",这里却用来表示"成功"。

  • 具体改法
 // design/shell.js:24 —— 新增一个带语气的写入函数 const setText = (id, v) => { const el = $(id); if (el) el.textContent = v; }; +/** 带语气地写文本:语气写进 data-tone,配色交给 CSS,JS 不碰颜色。 */ +const setMsg = (id, text, tone = 'quiet') => { + const el = $(id); + if (!el) return; + el.textContent = text; + el.dataset.tone = tone; // 'ok' | 'warn' | 'quiet' +}; 
 // design/shell.js:201 / 206 / 209 -setText('addrMsg', '浏览器原型改不了地址——要改得在安卓壳里。', 'warn'); +setMsg('addrMsg', '浏览器原型改不了地址——要改得在安卓壳里。', 'warn'); -setText('addrMsg', '已保存,正在重新探活…', 'ok'); +setMsg('addrMsg', '已保存,正在重新探活…', 'ok'); -setText('addrMsg', ok ? '已连上。' : '连不上,检查地址和端口。', ok ? 'ok' : 'warn'); +setMsg('addrMsg', ok ? '已连上。' : '连不上,检查地址和端口。', ok ? 'ok' : 'warn'); 
 // design/shell.css:274-275 —— 色义与 admin.css 对齐 -.note[data-tone="ok"] { color: var(--cinnabar); } -.note[data-tone="warn"] { color: var(--ink-2); } +.note[data-tone="ok"] { color: var(--ink-2); } +.note[data-tone="warn"] { color: var(--cinnabar); } 

只靠颜色区分仍然不够(色觉障碍用户读不出"红/灰"),补一个形状符号——这也正好符合《设计系统.md》§八"禁止纯靠颜色表意"的取法:

 +.note[data-tone="ok"]::before { content: "✓ "; font-family: var(--font-mono); } +.note[data-tone="warn"]::before { content: "! "; font-family: var(--font-mono); color: var(--cinnabar); } 
  • 落地说明
    • 改哪里:design/shell.js 第 24 行后新增 setMsg,第 201/206/209 行改调用;design/shell.css 第 274–275 行换色并加两条 ::before。
    • 改后效果:点「保存并重连」→ 显示"! 浏览器原型改不了地址——要改得在安卓壳里。"(朱砂 + 感叹号);探活失败 → "! 连不上,检查地址和端口。";成功 → "✓ 已连上。"(墨色,不抢朱砂)。
    • 验收方式:浏览器打开 design/shell.html,点「保存并重连」看 #addrMsg 的 data-tone 属性是否变成 warn、文字是否呈朱砂色;用 DevTools 检查 #addrMsg.dataset.tone。
    • 工作量:约 10 分钟(含自测)。
    • 影响面:只有端上壳(shell.html + shell.js + shell.css),不触及后台与客厅屏。
    • 风险:低。注意 design/shell.js:24 的 setText 仍被大量其他调用使用,不要改它的签名,新增函数即可,避免波及 20 余处调用。

建议 2|后台抽屉没有"浮层"该有的样子:列表行键盘不可达、无遮罩、无 ESC、无焦点回收,小屏直接盖死列表(层级 · 交互)|P1

  • 现状问题
  1. 列表行只能用鼠标点开。 design/admin.html:129 是 <div class="row" data-i="${i}" aria-selected="false">,design/admin.html:141-146 给它绑 click 打开抽屉。div 不可聚焦、无 tabindex、无 role → 键盘用户按 Tab 走不到列表行,更别说 Enter 打开详情。而且 aria-selected 挂在一个没有 role 的 div 上属于无效 ARIA,屏幕阅读器不会播报选中态。
  2. 抽屉不是模态。 design/admin.css:472 起:

     .drawer { position: fixed; top: 0; right: 0; bottom: 0; width: 420px; background: var(--paper-lift); border-left: 1px solid var(--rule); box-shadow: var(--shadow-panel); z-index: 30; transform: translateX(100%); transition: transform var(--t-base) var(--ease-out); } .drawer[data-open="1"] { transform: none; } 

    没有 role="dialog" / aria-modal / 遮罩 / ESC 关闭 / 焦点回收。打开后焦点仍在列表行上,Tab 会跑到抽屉后面的页面内容里;点抽屉外不关。

  3. 小屏没有兜底。 响应式只写了 @media (max-width: 1100px),把 .admin__body 改成 176px 侧栏,但 .drawer 仍是 420px 固定宽——1100px 屏上抽屉会盖住整张列表,唯一能操作数据的入口被自己遮住。

  4. 表单焦点环被抹掉。 design/admin.css:838:

     .input:focus, .select:focus { border-color: var(--cinnabar); outline: none; } 

    它(0,2,0)压过 design/tokens.css:184-188 的 :where(a,button,input,…) + :focus-visible(:where 部分特异度为 0),于是所有输入框/下拉的 2px 焦点环被 outline: none 干掉,只剩一条边框变色。

  • 具体改法

HTML(design/admin.html)

 +<div class="drawer__scrim" id="drawerScrim"></div> <aside class="drawer" id="drawer" data-open="0" + role="dialog" aria-modal="true" aria-label="金句详情" aria-hidden="true"> 
 - <div class="row" data-i="${i}" aria-selected="false"> + <div class="row" data-i="${i}" role="option" aria-selected="false" tabindex="0"> 
 list.querySelectorAll('.row').forEach(r => { r.addEventListener('click', () => { … openDrawer(DATA[+r.dataset.i]); }); + r.addEventListener('keydown', e => { + if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); r.click(); } + }); }); 
 -document.getElementById('drawerClose').addEventListener('click', () => { - document.getElementById('drawer').dataset.open = '0'; - document.querySelectorAll('.row').forEach(x => x.setAttribute('aria-selected', 'false')); -}); +let lastRow = null; +function closeDrawer() { + const d = document.getElementById('drawer'); + d.dataset.open = '0'; + d.setAttribute('aria-hidden', 'true'); + document.getElementById('drawerScrim').dataset.open = '0'; + document.querySelectorAll('.row').forEach(x => x.setAttribute('aria-selected', 'false')); + if (lastRow) lastRow.focus(); // 焦点回收到触发行,键盘流不丢 +} +document.getElementById('drawerClose').addEventListener('click', closeDrawer); +document.getElementById('drawerScrim').addEventListener('click', closeDrawer); +document.addEventListener('keydown', e => { + if (e.key === 'Escape' && document.getElementById('drawer').dataset.open === '1') closeDrawer(); +}); 

打开时同步遮罩与焦点:

 - document.getElementById('drawer').dataset.open = '1'; + document.getElementById('drawer').dataset.open = '1'; + document.getElementById('drawer').setAttribute('aria-hidden', 'false'); + document.getElementById('drawerScrim').dataset.open = '1'; + document.getElementById('drawerClose').focus(); // 焦点进入浮层 

CSS(design/admin.css)

 +/* 抽屉是浮层:要有底,不然它像贴在纸上 */ +.drawer__scrim { + position: fixed; inset: 0; z-index: 29; + background: color-mix(in srgb, var(--ink) 24%, transparent); + opacity: 0; pointer-events: none; + transition: opacity var(--t-base) var(--ease-out); +} +.drawer__scrim[data-open="1"] { opacity: 1; pointer-events: auto; } + .drawer { position: fixed; … transform: translateX(100%); transition: transform var(--t-base) var(--ease-out); + visibility: hidden; } .drawer[data-open="1"] { transform: none; } +.drawer[data-open="1"] { visibility: visible; } + +/* 窄屏:抽屉改成整屏面板,不再压住列表 */ +@media (max-width: 720px) { + .drawer { width: 100%; } + .drawer__foot .btn { min-height: 44px; } /* 触屏可点面积 */ +} 
 -.input:focus, .select:focus { border-color: var(--cinnabar); outline: none; } +.input:focus, .select:focus { border-color: var(--cinnabar); } +.input:focus-visible, .select:focus-visible { + outline: 2px solid var(--cinnabar); + outline-offset: 1px; +} 
  • 落地说明
    • 改哪里:design/admin.html(列表行 1 行 + 抽屉 2 行 + 事件约 15 行)、design/admin.css(.drawer 段新增约 12 行 + 焦点环 2 行 + 1 个媒体查询)。
    • 改后效果:点任意一行 → 背景压深一层遮罩,抽屉从右侧滑入(320ms,与既有 --t-base 一致);点遮罩或按 ESC 关闭,焦点回到原来那一行;Tab 不再跑到抽屉背后的内容上;≤720px 时抽屉占满屏宽。
    • 验收方式:
    • DevTools 里 document.activeElement 变化:打开抽屉后应是 #drawerClose,ESC 后应是刚才那 .row;
    • 用键盘 Tab 能走到 .row 并按 Enter 打开;
    • 缩到 700px 宽,抽屉不再遮挡列表右半边。
    • 工作量:30–60 分钟(含小屏兜底与回归)。
    • 影响面:只影响 design/admin.html(今日审稿 / 金句库)。若要把同一套浮层行为复用到其他页,建议后续抽到 design/admin.js 里(admin.js 已经集中了 mountShell / note / emptyState,加一个 openDrawer/closeDrawer 导出最自然)。
    • 风险:中低。visibility 的引入会让抽屉在关闭时不参与 Tab 顺序(这是我们要的);注意 .drawer 原本靠 transform: translateX(100%) 移出屏幕,加 visibility 后收起动画会先消失再滑出,若想保留滑出动画,把 visibility 过渡也补上 transition: transform …, visibility 0s linear var(--t-base)。

建议 3|--font-display 的 Cormorant Garamond 从未加载:后台所有"拉丁装饰大数字"其实都掉回了思源宋体(配色 · 排版)|P1

  • 现状问题

design/tokens.css:72 定义了:

 --font-display: "Cormorant Garamond", "Noto Serif SC", "Songti SC", serif; 

但 design/tokens.css 全文只有 3 条 @font-face(:42 Noto Serif SC 400、:52 Noto Serif SC 700、:59 IBM Plex Mono),没有 Cormorant Garamond 的 @font-face,也没有任何 Google Fonts 链接(grep -rn "fonts.googleapis" design/ → 0 命中)。
→ 第一档永远取不到,浏览器落到第二档 Noto Serif SC。

而 NotoSerifSC-subset.woff2 的字符表 design/fonts/subset_chars.txt 实测含 1940 个字符,包含 0–9 与 A–Z/a–z——所以数字确实有字形,只是没有 Cormorant 的高对比衬线骨架。

受影响的地方(全部是"层级"的承重墙):

| 选择器 | 位置 | 用途 |
|---|---|---|
| .stat__n | design/admin.css:212 | 后台 4 个统计大数(52px) |
| .corpus__v | design/admin.css:~250 | 语料池健康度大数(24px) |
| .phead__t | design/admin.css:641 | 每页标题(26px) |
| .daygroup__d | design/admin.css:~900 | 播放记录按天分组标题(19px) |
| 内联 var(--font-display) | design/admin-settings.html:137 | 槽位估算数字(26px) |

同时 font-variant-numeric: oldstyle-nums(.stat__n、.corpus__v)依赖字体自带的旧式数字;思源宋体子集只有等高数字,这层"书卷气"实际不生效。

一句话:后台看起来"数字和正文一个气质",不是设计选择,是字体没加载。

  • 具体改法

方案 A(推荐,30 分钟):把 Cormorant Garamond 的拉丁子集自托管,跟现有流程一致。

 # 与现有 Noto Serif SC 子集同一条流水线,只切 Latin Basic + 常用数字 pyftsubset /path/to/CormorantGaramond-Regular.ttf \ --unicodes="U+0020-007E,U+00A0-00FF" \ --layout-features='kern,liga,onum' \ --flavor=woff2 \ --output-file=design/fonts/CormorantGaramond-subset.woff2 
 /* design/tokens.css —— 放在 IBM Plex Mono 那条之后 */ +@font-face { + font-family: "Cormorant Garamond"; + src: url("fonts/CormorantGaramond-subset.woff2") format("woff2"); + font-weight: 400; + font-style: normal; + font-display: swap; + unicode-range: U+0020-007E, U+00A0-00FF; /* 只管拉丁,别跟中文抢 */ +} 
 /* design/tokens.css:72 —— 兜底更诚实:加载不到就明确退回宋体,而不是"假装" */ ---font-display: "Cormorant Garamond", "Noto Serif SC", "Songti SC", serif; +--font-display: "Cormorant Garamond", "Noto Serif SC", "Songti SC", serif; 

方案 B(0 分钟先止血,10 分钟):如果暂时不引入新字体文件,就别让装饰位假装是装饰位——把 .stat__n 换成等宽衬线并接受宋体,同时在文档里把"拉丁装饰"这一条标记为未落地:

 /* design/admin.css:212 */ .stat__n { - font-family: var(--font-display); + font-family: var(--font-display); /* 方案 A 生效前先落到 Noto Serif SC */ font-size: 52px; line-height: 1; + font-variant-numeric: oldstyle-nums; /* 仅当字体支持旧式数字时才有效 */ color: var(--ink); letter-spacing: -.01em; } 
  • 落地说明
    • 改哪里:design/fonts/ 放 1 个 woff2(≈15–25KB);design/tokens.css 加 1 条 @font-face。
    • 改后效果:.stat__n 的 52px 数字变成高对比细衬线(拉丁装饰体),后台"大数字 / 小标签"这一层立刻从宋体里跳出来——这是《设计系统.md》§七"统计数字用 Cormorant Garamond 48px,oldstyle-nums"本来的样子。
    • 验收方式:Chrome DevTools → Elements → 点 .stat__n → Computed → font-family 第一档应为 Cormorant Garamond,Rendered Fonts 面板应显示该字体命中;数字 0/1/2 应呈现带下沉部的旧式数字(1 有小衬线、7 不带横杠)。
    • 工作量:30 分钟(下载字体 + 子集化 + 校验体积 + 接入)。
    • 影响面:全局令牌,但只影响使用 --font-display 的 5 处。注意:因为 unicode-range 只覆盖拉丁,中文字符仍走 Noto Serif SC,不会出现"中文变拉丁字体缺字"。
    • 风险:低-中。唯一要注意的是子集字符表要同步更新——design/fonts/subset_chars.txt 现在含 A–Z/a–z/0–9(实测确认),拉丁显示字体不必切中文;但若日后有英文专名/年份超出 ASCII,需要把对应码位加进 --unicodes。

建议 4|字号刻度与《设计系统.md》对不上,而且没有落成令牌(排版)|P2

  • 现状问题

文档 §三/§七给了明确的字号刻度,代码却各写各的:

| 文档要求 | 代码现状 | 位置 |
|---|---|---|
| 统计数字 48px | font-size: 52px | design/admin.css:212 .stat__n |
| 后台标题 28px / 600 / 行高 1.4 | font-size: 26px; line-height: 1; font-weight: 400; font-family: var(--font-display) —— 四项全不符 | design/admin.css:641 .phead__t |
| 元信息 12px / 行高 1.5 | font-size: 12.5px,没有 line-height → 继承 .admin 的 1.75 | design/admin.css:221 .stat__l |
| 元信息 12px(等宽) | font-size: 10.5px(表头 .tbl th、分组标签 .sec__label、表单标签 .field__l) | design/admin.css:~690 / ~840 / ~860 |
| 元信息 12px | font-size: 9.5px —— 全站最小字 | design/admin.css:979 .bars__ax span |

文档 §三明确写过"字号另有一套,不受 8px 约束",也就是说字号本该有刻度,但刻度只活在 Markdown 里,代码里没有对应令牌,于是每个组件各写各的。

另外 .phead__t 用 var(--font-display) 显示中文标题("播放计划""设备")——Cormorant Garamond 没有中文字形,中文必然回落到 Noto Serif SC 子集,于是标题在"装饰体 → 宋体"之间断了一次,字体栈语义也不干净。

  • 具体改法

第一步:把字号刻度提成令牌(design/tokens.css 的 :root 里,紧跟间距令牌之后):

 /* ---------- 间距:8px 系统 ---------- */ --s-1: 8px; … + + /* ---------- 字号刻度(对应《设计系统.md》§三 / §七) ---------- */ + --fs-display: 48px; /* 统计大数 */ + --fs-title: 28px; /* 后台标题 */ + --fs-quote: 17px; /* 列表主句 */ + --fs-body: 15px; /* 正文 */ + --fs-label: 12px; /* 元信息 / 说明 */ + --fs-micro: 11px; /* 表头、分组标签(≥10.5px,Windows 125% 缩放不塌) */ + --lh-body: 1.75; + --lh-title: 1.4; 

第二步:逐个替换(改前 → 改后):

 /* design/admin.css:212 */ .stat__n { font-family: var(--font-display); - font-size: 52px; - line-height: 1; + font-size: var(--fs-display); + line-height: 1; font-variant-numeric: oldstyle-nums; color: var(--ink); letter-spacing: -.01em; } 
 /* design/admin.css:641 */ .phead__t { - font-family: var(--font-display); - font-size: 26px; - line-height: 1; + font-family: var(--font-serif); /* 中文标题用中文宋体,不靠回退 */ + font-size: var(--fs-title); /* 28px */ + font-weight: 600; + line-height: var(--lh-title); /* 1.4 */ letter-spacing: .04em; color: var(--ink); } 
 /* design/admin.css:221 */ .stat__l { margin-top: var(--s-1); - font-size: 12.5px; + font-size: var(--fs-label); /* 12px */ + line-height: 1.5; color: var(--ink-3); letter-spacing: .1em; } 
 /* design/admin.css:979 —— 全站最小字从 9.5px 抬到 11px */ .bars__ax span { flex: 1; min-width: 6px; text-align: center; font-family: var(--font-mono); - font-size: 9.5px; + font-size: var(--fs-micro); color: var(--ink-3); overflow: hidden; } 
 /* design/admin.css —— 10.5px 三处统一到 --fs-micro */ -.tbl th { font-size: 10.5px; … } -.sec__label { font-size: 10.5px; … } -.field__l { font-size: 10.5px; … } +.tbl th { font-size: var(--fs-micro); … } +.sec__label { font-size: var(--fs-micro); … } +.field__l { font-size: var(--fs-micro); … } 
  • 落地说明
    • 改哪里:design/tokens.css(:root 加 8 个令牌)+ design/admin.css 6 处。
    • 改后效果:标题从"26px 宋体细体"变成"28px 宋体 600/1.4",页面层级立刻立住;统计大数从 52 收到 48;最小字从 9.5 抬到 11,Windows 125% 缩放下不再糊成一团。
    • 验收方式:逐页目测——① 每页标题的上下留白比正文大一档;② 播放记录页底部日期轴(.bars__ax)数字可读;③ Windows 缩放 125% 下列头不重叠。
    • 工作量:40 分钟(含令牌抽取与 6 处替换)。
    • 影响面:全部 7 个后台页(共用 admin.css)+ 端上壳若后续也接令牌则受益;客厅屏不引用这些令牌,不受影响。
    • 风险:低。唯一要注意 .phead__t 从 --font-display 换成 --font-serif 后,"拉丁装饰"在标题层消失——这是修正(文档 §三 只把 Cormorant 给了"后台大号数字、编号",没给标题),但要在文档 §三 的字体表里补一句说明。

建议 5|朱砂被当成"通用强调色"用了 45 处,超出"只允许出现在四个地方"的约束(配色 · 层级)|P2

  • 现状问题

《设计系统.md》§2.1 写死了:朱砂是印泥的颜色,只允许出现在四个地方(印章、序号、进度线、后台"接受入库"主按钮与高分标记),且面积 ≤ 5%。

代码现状:

  • design/admin.css:32 处 var(--cinnabar);design/shell.css:13 处;
  • design/tokens.css:186 连焦点环也是朱砂。

超出四处的典型:当前侧栏项左边条(admin.css:~129)、选中页签下划线(:~330)、选中行左侧 inset 条(:~404)、.badge--on、.sw[aria-checked="true"]、.input:focus 边框、.note[data-tone="warn"]、.err、.wrow__del:hover、.savebar__msg[warn]、.corpus[data-state="low"]、.btn--primary 及其 hover。

§13.3 实测朱砂面积在客厅屏只有 0.271%(余量 18 倍),但那是客厅屏的数字。后台页一屏里:当前侧栏项 + 选中页签 + 选中行 + 一条 warn 说明 + 焦点环 + 一个朱砂按钮,朱砂面积早已越过 5%,而且开始承担不该承担的功能——"这一行被选中了"和"这里出错了"用同一种红,语义被冲淡。

另外 §七"主按钮只有两个"也没有守住:朱砂实心按钮共 6 个(admin.html:80 立即抓取、:92 接受入库、admin-settings.html:38/:156、admin-sources.html:127、admin-devices.html:119)。§九 减法清单第 7 条"一屏上有几个按钮?超过 2 个 → 合并或降级为文字动作"也没过。

  • 具体改法

第一步:把"选中"从朱砂改成"墨 + 线"。 选中态用深墨字 + 一条发丝底线/左侧墨条,朱砂留给"出事了"。

 /* design/admin.css —— 侧栏当前项:朱砂左边条 → 墨色左边条 */ .nav__item[aria-current="page"] { color: var(--ink); font-weight: 600; - border-left-color: var(--cinnabar); + border-left-color: var(--ink); background: var(--paper-lift); } -.nav__item[aria-current="page"] span:first-child { color: var(--cinnabar); } +.nav__item[aria-current="page"] span:first-child { color: var(--ink-3); } 
 /* design/admin.css —— 页签:朱砂下划线 → 墨色下划线 */ .tab[aria-selected="true"]::after { … - background: var(--cinnabar); + background: var(--ink); } 
 /* design/admin.css —— 选中行:朱砂 inset 条 → 墨色 inset 条 */ .row[aria-selected="true"] { background: var(--paper-lift); - box-shadow: inset 2px 0 0 var(--cinnabar); + box-shadow: inset 2px 0 0 var(--ink); } 

第二步:给"当前选中"一个比朱砂更轻的记号(保留下划线/左边条的形状语言,但换成中性色):

 /* design/tokens.css —— 新增"选中"语义令牌,与"告警"彻底分开 */ + --select-mark: var(--ink); /* 选中/当前:中性墨色 */ + --warn-mark: var(--cinnabar); /* 出错/需要你管:朱砂 */ 

第三步:收敛朱砂实心按钮。 保留"接受入库"一个朱砂实心,其余降级为描边或文字:

 /* design/admin.html:80 —— 立即抓取:不是"接受入库"级别的动作 */ -<button class="btn btn--primary" id="btnFetch">立即抓取</button> +<button class="btn" id="btnFetch">立即抓取</button> 
 /* design/admin-settings.html:38 / :156、admin-sources.html:127、admin-devices.html:119 */ -<button class="btn btn--primary" …>保存</button> +<button class="btn" …>保存</button> 

保留的朱砂实心只有:admin.html:92「接受入库」、admin-devices.html 的「保存这台设备」(若认为"每设备一个主按钮"合理,可保留,但一屏只出现一个)。

第四步:焦点环保留朱砂,但只给键盘焦点(tokens.css:184 已经是 :focus-visible,只需确认没有更粗的 :focus 规则覆盖它——见建议 2 第 4 点)。

  • 落地说明
    • 改哪里:design/admin.css 4 个选择器、4 个 HTML 页的按钮 class、design/tokens.css 加 2 个语义令牌。
    • 改后效果:后台从"到处都是淡红"变成"只有出错了才是红的";当前页/当前行/当前页签用墨色 + 底线表示,与整体"校对台"的语感一致。
    • 验收方式:① 打开任一后台页,用眼扫一圈,朱砂只应出现在:admin.html 的"已入库"统计数(.stat--accent)、"接受入库"按钮、真正 warn 的说明条、评分 ≥8 的小方块(.score--high);② 数一下一页里朱砂实心按钮 ≤ 1;③ 对比 §13.3 的面积口径重测一次后台页面积(建议把测量脚本扩到后台)。
    • 工作量:60–90 分钟(含全局回归,因为改动散布在 5 个页面)。
    • 影响面:全部 7 个后台页 + 端上壳(.sw[aria-checked] 也用朱砂,见建议 7)。
    • 风险:中。这是本方案里最需要你自己拍板的一条——如果你更喜欢"选中=朱砂"(很多产品这么做),那就别动这一条,只做第一步之外的"主按钮收敛"。两条路都成立,但必须二选一并同步改文档,否则又变成"文档说四处、代码五处"的偏差。

建议 6|主按钮的 hover 态用 color-mix(…black) 绕开了令牌;端上壳的主按钮干脆没有 hover(配色 · 交互)|P2

  • 现状问题
 /* design/admin.css:369 */ .btn--primary:hover { background: color-mix(in srgb, var(--cinnabar) 86%, black); border-color: color-mix(in srgb, var(--cinnabar) 86%, black); color: #FFF8F2; } 

三个问题:

  1. 混进了纯黑 black,与 §八"禁止纯黑 #000"的精神相悖,而且绕开了整套主题令牌——夜间主题(--cinnabar: #D2543A)下这个 hover 变成更暗的红,晨间(#B23A24)也是更暗的红,两个主题的按压方向不一致;夜间本来就暗,再暗一档几乎看不出反馈。
  2. --cinnabar-ink 已经在令牌表里(晨 #9C2F1C / 夜 #E86B50),却只用在印章用字上,按压态没复用。
  3. 端上壳没有 hover:design/shell.css:137-138 只有

     .sbtn:active { transform: scale(.98); } .sbtn--primary { background: var(--cinnabar); border-color: var(--cinnabar); color: #FFF8F2; font-weight: 600; } 

    没有任何 :hover,.sbtn--primary 也没有。两个页面的主按钮手感不一致:后台有 hover 变色,端上只有按下缩放。

  • 具体改法
 /* design/tokens.css —— 晨主题 */ .theme-dawn { … --cinnabar: #B23A24; --cinnabar-ink: #9C2F1C; + --cinnabar-press: #9C2F1C; /* 按下/hover:晨间压深 */ + --on-cinnabar: #FFF8F2; /* 朱砂底上的文字色(见建议 12) */ } 
 /* design/tokens.css —— 夜主题 */ .theme-night { … --cinnabar: #D2543A; --cinnabar-ink: #E86B50; + --cinnabar-press: #E86B50; /* 按下/hover:夜间提亮,方向与晨间一致地"更靠近强调" */ + --on-cinnabar: #FFF8F2; } 
 /* design/admin.css:369 */ .btn--primary:hover { - background: color-mix(in srgb, var(--cinnabar) 86%, black); - border-color: color-mix(in srgb, var(--cinnabar) 86%, black); - color: #FFF8F2; + background: var(--cinnabar-press); + border-color: var(--cinnabar-press); + color: var(--on-cinnabar); } 
 /* design/shell.css:137-138 —— 端上壳补上 hover,按压用 press 令牌 */ .sbtn:active { transform: scale(.98); } +.sbtn:hover { border-color: var(--ink-3); } +.sbtn--primary:hover { + background: var(--cinnabar-press); + border-color: var(--cinnabar-press); + color: var(--on-cinnabar); +} .sbtn--primary { background: var(--cinnabar); border-color: var(--cinnabar); color: var(--on-cinnabar); font-weight: 600; } 
  • 落地说明
    • 改哪里:design/tokens.css(两个主题各加 2 个令牌)、design/admin.css:369、design/shell.css:137-138。
    • 改后效果:主按钮的 hover/按下变成令牌驱动,晨间压深、夜间提亮,两个主题的反馈方向一致;端上壳主按钮不再是"点下去才知道是按钮"。
    • 验收方式:切 theme-dawn / theme-night 各 hover 一次主按钮,确认都有可见变化且亮度方向一致;grep -rn "color-mix" design/*.css 结果里不再出现 black。
    • 工作量:20 分钟。
    • 影响面:全局令牌 + 两处按钮样式;不改结构,不影响布局。
    • 风险:低。注意夜间主题下 --cinnabar-press: #E86B50 比 --cinnabar 更亮,这与晨间"压深"方向相反——但两者都是"往可读性更强的方向走",若你更希望夜间也压深,把夜间值改成 #B8402A 即可。

建议 7|.note 在两个文件里同名两义,data-tone 契约是断的(层级 · 配色)|P2

  • 现状问题

同名组件、两套语义:

 /* design/shell.css:267-276 —— 端上壳:一条灰字说明 */ .note { padding: 10px 0 2px; font-size: 12px; line-height: 1.6; color: var(--ink-3); } .note[data-tone="ok"] { color: var(--cinnabar); } /* 成功 = 红 */ .note[data-tone="warn"] { color: var(--ink-2); } /* 警告 = 灰 */ .note[Source:https://dev.to/_813bf2a5e29b705f18158b/jin-ju-xue-xi-zhan-shi-xi-tong-jie-mian-you-hua-fang-an-15-tiao-ke-zhao-zuo--57h7

Related posts