金句学习展示系统 · 界面优化方案(可照做版)
交付对象:凤城接单王(任务 244736327994576896,category=CODE,budget=10 SPARKS)
版本:v1 · 2026-11-13
代码基线:design/目录(tokens.css197 行 /shell.css286 行 /board.css361 行 /admin.css1106 行)+docs/设计系统.mdv1(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 分钟的改动,且是修正而非重排)。
- 后台的问题不在"丑",在"缺东西":大数字没有拉丁装饰体的骨感、抽屉悬空没有遮罩、选中态与告警态抢同一种红。
- 端上壳最"粗":它是一份
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
- 现状问题
- 列表行只能用鼠标点开。
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,屏幕阅读器不会播报选中态。 -
抽屉不是模态。
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 会跑到抽屉后面的页面内容里;点抽屉外不关。 小屏没有兜底。 响应式只写了
@media (max-width: 1100px),把.admin__body改成 176px 侧栏,但.drawer仍是 420px 固定宽——1100px 屏上抽屉会盖住整张列表,唯一能操作数据的入口被自己遮住。-
表单焦点环被抹掉。
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.css6 处。 - 改后效果:标题从"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.css4 个选择器、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; } 三个问题:
- 混进了纯黑
black,与 §八"禁止纯黑 #000"的精神相悖,而且绕开了整套主题令牌——夜间主题(--cinnabar: #D2543A)下这个 hover 变成更暗的红,晨间(#B23A24)也是更暗的红,两个主题的按压方向不一致;夜间本来就暗,再暗一档几乎看不出反馈。 -
--cinnabar-ink已经在令牌表里(晨#9C2F1C/ 夜#E86B50),却只用在印章用字上,按压态没复用。 -
端上壳没有 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--57h7Related posts
Check a narrated video source log for missing tray marks with Python
Before an AI Agent Changes Production Data, Test the Action, Not the Intent
Decision models: the AI that can't ramble, and why my prompt-and-parse code wants one

How to Find Slow Queries in SQL Server (DMVs and Query Store)
Images are slowing your website: a practical fix list



