Files
rond-with-you/frontend-plan-2026-10-09.md
tamakyi ab7ccd22ce 前端改造:设计令牌 + 卡片层级 + 图表可读性 + 类型色收口 + 窄屏导航
先出方案(frontend-plan-2026-10-09.md)再动手,四批按顺序落地。方法上先存基线、再用同一份
探针在改动前后逐值 diff —— 这批里有 6 处问题只有「量」才看得见,看截图全是正常的。

A 设计令牌(app.css 为主)
- 字阶收敛到 6 档 --fs-xs/sm/md/lg/xl/2xl = 11/13/15/21/26/34px。原来 app.css 有 19 种字号、
  模板里另有 54 处内联字号,光 12~15px 之间就挤了 4 档 —— 层级挤不出来,只能靠加粗硬顶。
- 两个「字外档」不是漏的:--fs-control: 16px(窄屏表单控件必须 ≥16px,否则 iOS 聚焦放大整页,
  属功能约束);--glyph-*(✕/箭头/空态图标按控件尺寸定,硬塞进字阶只会把按钮撑坏)。
- 字重收敛到 400/500/600(删掉 650 与 700)。
- 圆角 4 档 --r-*、阴影 3 档 --sh-1/2/3(暗色整套覆盖)、间距 --sp-1..5、
  卡片内边距单列 --card-pad(与折叠卡片标题行的负 margin 绑在一起,改一个即改两处)。
- 门禁 TestNoBareFontSizePx:静态扫 app.css 与全部模板,出现 font-size: <数字>px 即红。

B 卡片分主次
- .card--hero / .card(默认)/ .card--quiet 三档。quiet 走「页面底色 + 细边 + 无阴影」——
  第一版拿 --bg-2 当底色,实测它与页面底 #f6f7f8 只差 4/255,看不出边界、像破了个洞。
- 首屏 KPI 从「4×2 等重白盒」改成 .kpi-lead(主数 + 一行上下文)+ 5 个常规指标,8 个数字一个没少。

C 图表说人话
- 月度分布:柱底加基线;峰值柱不透明拉满、柱顶标次数(.pv),副标题里不再重复写「最高 N 次」。
- 一天中的分布:把「0/6/12/18 四个数字」换成 6 小时一段的标尺(深夜/上午/下午/晚上),
  用同一套 24 列网格,段边界与柱子严格对齐。
- 首页月度图同样加峰值锚点(给 HomeData 补 MaxMonthly,与统计页同口径 maxOf)。
- 空态没动:查下来已经统一(8 页共用 emptystate、局部走 .empty)。

D1 类型色语义化(结论与计划不同,按实测改了)
- 先算对比度:白底上 yellow 1.87 / mint 2.12 / orange 2.20 / green 2.22 / cyan 2.54 / teal 2.57,
  6 个色连图形对象 3:1 都到不了;暗底只有 indigo 2.90 不够。
- 所以**没有**把类型色用到 chips / 表格类型列 / KPI 强调 —— 那几处要的是文字色,只会更难读。
  这也解释了全站为什么一直只用 8px 小圆点做身份标记。浅色那 6 个色一个没动:
  它们是手机端与地图上的同一套身份色,改暗会破坏对应关系。
- 实际做的:色值收口成 --cat-* / --cat-*-soft(26 个变量);模板 colorof → csscolor / csssoft(19 处);
  暗色只提亮 indigo(2.90 → 3.55:1);colorOf 成了死代码,删掉。
- ⚠️ 返回值必须是 template.CSS:html/template 的 CSS 过滤器不认识 var(),普通 string 会被静默
  换成 ZgotmplZ —— 页面上所有圆点当场变透明,而且不报错、模板测试照过。挂了它等于关掉那道过滤,
  所以数据侧色值先过严格校验(只有 # + 6 位 hex 放行),否则 #"><img… 能逃出 style 属性。
  守卫 TestCSSColorSurvivesTemplateSanitizer 把两个方向都钉住(做过变异验证)。

D2 窄屏导航
- 改前 ≤620px 靠横向滚动兜底:390 溢 63px、320 溢 133px,「统计」被切成半个字紧贴后面的按钮,
  渐隐提示实际看不出来。现在链接本体抽成 {{define "navitems"}} 渲染两遍:桌面 .nav-links 平铺、
  窄屏 .nav-menu(原生 <details>,不靠 JS 也能开合);JS 只补点外面 / Esc / 点链接后收起。
- ⚠️ 顶栏 --z-nav 抬到 1200:菜单是 .nav 的子元素、自己抬不动层叠上下文,不抬就会被地图页的
  --z-map-overlay(1100) 盖住。已在 /map 上用 elementFromPoint 实测。
- 守卫 TestNarrowNavCollapsesToMenu 钉「CSS 两半 + 模板两个类 + 渲染两遍」的成对关系。

途中发现并一并修掉的(都只有量 DOM 才看得见)
- Leaflet 的 .leaflet-container 字号是 0.75rem、跟着根字号漂:根 14 → 13px 后地图署名/比例尺
  掉到 9.8px(低于本站 11px 下限)、弹窗正文也跟着缩。已按自己的字阶钉死。
- h1..h4 原来只设了字重与 margin、没设字号,靠浏览器默认的 2em/1.5em/1.17em —— 同样跟着根字号漂:
  /stats 那两个 h2 从 21px 变成 19.5px,比同页卡片标题大一圈。已显式给出字号。
- <b>/<strong> 浏览器默认 700,比全站最高层级(标题 600)还重,已钉成 600。

回归
- ./scripts/verify.sh:PASS=348 FAIL=0 SKIP=0
- internal/fog 加 -race:PASS=22 FAIL=0 SKIP=0
- JS 单测 34/34
- 移动端 320 / 390 溢出全 0(改前 /admin 在 320 还溢 34px);桌面 1440 与改前一致(−15 是滚动条宽)
- 全站 7 个页面都没有低于 11px 的文字

说明:A/B 与 C/D 本想拆两笔提交,但 app.css / layout.html / view.go 里两批改动交织,按文件拆不开、
按 hunk 拆又容易做出「中间那笔编译不过」的历史,所以合成一笔、在消息里分段。
2026-10-09 17:01:08 +08:00

16 KiB
Raw Permalink Blame History

前端现代化方案

  • 日期:2026-10-09
  • 范围:视觉层升级 —— app.css(1114 行)+ 24 个模板的样式类使用;不动信息架构、路由、模板数据结构
  • 方法:先用无头 Chrome 在真实视口量现状(390 / 430 / 1440),再定方案。下面每个数字都是量的,不是估的
  • 前提约束(这些都是现状,方案不得破坏):无前端构建链(服务端渲染 + 单 CSS + 手写 JS)/ 1C1G 服务器 / 已有暗色模式(prefers-color-scheme 3 段)/ 已按 WCAG AA 调过 --text-3

⚠️ shots/ 里的截图(mobile-home.png 等)是旧状态:那张顶栏折成 3 行、右按钮竖排的图早就不成立了, 现在 390 下品牌名只留 🐾、按钮是「后台」。做视觉判断前一律现场重新截图,别拿 shots/ 当现状。

一、现状实测

观测 实测值 说明
卡片样式数 17 个卡片盒子(.card/.kpi/.filters-card)→ 只有 2 种样式指纹 全站一个「白盒 + 1px 边 + 浅阴影 + 10px 圆角」用到底,KPI / 地图 / 图表 / 表格 / 筛选器全等重 ⇒ 眼睛没有落点
字阶 字号 8 档:10/12/13/14/15/18/21/23px,其中 10–15px 挤了 5 档 中段过密、大段太少;层级挤不出来,只能靠加粗
字重 400 / 500 / 600 / 650 650 是半档,等于没规范
正文色 4 种(--text/--text-2/--text-3 + 链接)+ 1 个主色 整页几乎单色
图表 手写 .bars(无图表库),月度/一天分布只有色块 无基线、无峰值标注、无静止态数值
类型色 10 种(家/工作/餐厅/…)只用在 8px 圆点和柱子上 没进入界面语义层(chips、表格类型列、KPI 都用不到)
窄屏导航 站长导航(6 项)scrollWidth=277,可用宽:430→254(溢 23)、390→214(溢 63)、320→144(溢 133) 靠 .nav-links 横向滚动兜底;390 下「统计」被切成半个字紧贴「后台」按钮,渐隐提示实际看不见 ⇒ 像坏了
暗色模式 已存在(586/590/742 三段 media) 任何配色改动要同步两套变量
已有优点(别改坏) KPI 数字已是 tabular-nums;--text-3 按 AA 调过;地图/时间线的 z-index 与安全区已有处理 这些是已经踩过坑换来的

二、为什么不重写(明确排除项)

  • 不引入前端框架 / 构建链(Vue/React/Tailwind/Vite):现状是 Go 模板直出 + 单 CSS + 无 npm。引构建链会让「改一行字要跑 node」、部署变复杂,而站点是个人足迹站、并发极低 —— 收益不抵成本。
  • 不上图表库:为几个柱图多背 100KB+ 与一个依赖,不值得。.bars 手写继续用,只升级表达力。
  • 不重做信息架构:页面、路由、模板数据结构都不动;只改「长什么样、怎么看」。

三、四批方案(由低风险到高收益)

批次 A:设计令牌收敛(纯 CSS,约半天)

  1. 字阶砍到 6 档:11 / 13 / 15 / 21 / 26 / 34
    • 11 = 脚注/单位,13 = 正文(默认),15 = 卡片标题,21 = 页面标题,26 = KPI,34 = 首屏主数
    • 现在 10/12/14/18/23 全部并档;--text-3 的灰阶不动(它承载 a11y 口径)
  2. 字重砍到 3 档:400 / 500 / 600(删掉 650)。层级主要靠字号 + 颜色,不靠加粗 —— 这更接近现代做法
  3. 间距令牌:--sp-1..5 = 4/8/12/16/24,替换散落的 padding/margin 字面量
  4. 圆角令牌:--r-sm/md/lg = 6/10/14(现在只有 --radius:10px 和一个 8px)
  5. 阴影分 3 档:--sh-1(卡片)/--sh-2(浮层)/--sh-3(hero);现在只有 1 档

判据:cardVariants 2 → ≥3;(字号/字重) 组合 10 → ≤6;CSS 里裸 border-radius: 数字 0 处。

批次 B:给卡片分主次(约 1 天)

三档面,替代现在唯一的一种:

类 用途 样子
.card--hero 首屏 KPI 区、地图 更大内边距 + --sh-3、无边框、左侧 3px 主色条
.card(默认) 图表、列表、表单 保持现状(已够干净)
.card--quiet 说明块、次级列表 --bg-2 浅底、无边框无阴影

配套把首屏 KPI 从「4×2 等重白盒」改成 1 个 hero 大数 + 3 个指标:首屏必须有一个一眼能落下去的点。手机端同理(hero 占整行,后 3 个两列)。

判据:把首屏截图给人看 3 秒,对方能指出「最重要的那个数」。

批次 C:让图表说人话(约 1–2 天,仍不用库)

  • 月度分布:加基线、峰值柱顶部标数值 + 一处「最高 197 次」文案、每根柱 <title> 与 hover 值
  • 一天分布:加 0/6/12/18 轴标 + 早/午/晚三段极浅底色,让「作息」这个概念不用读标题也懂
  • 类型分布:柱色改用类型色(见批次 D),数值列 tabular-nums(数字列宽不再跳)
  • 空态统一:现在有的写「暂无数据」、有的空着;统一一个 emptystate(模板里已有这个定义,直接用)

判据:不看图例、不看标题,也能说出这张图在讲什么。

批次 D:色彩语义化 + 窄屏导航(约 1 天)

  1. 类型色升级为语义令牌:--cat-home / --cat-work / --cat-food … 10 个,并用 color-mix() 派生各自浅底(沿用现有 --accent-soft 的做法,不手写 10 套浅色)
    • 用在:筛选 chips、地点表「主要类型」列、KPI 强调、图表
    • 硬约束:正文用色对比 ≥4.5:1、大字/图形 ≥3:1(延续现有口径),暗色模式各派生一套
  2. 窄屏导航:现状溢出 23–133px。
    • 第一步(低风险):主链接收到 4 个(主页/地图/地点/统计),「时间线/报告」进「更多」;把 .overflow-right 的渐隐加重到看得见
    • 第二步(可选,需评估):底部 tab bar。⚠️ 地图页的浮层 z-index 与 --nav-h 已经踩过坑(审计 F11/F12),上底部栏要先确认与 Leaflet 控件、安全区的冲突

判据:320/390 下没有任何链接被切半个字;导航高度不增(现 51px);暗色模式下新色阶对比度同样达标。

四、建议顺序

先 A + B(约 1.5 天):纯 CSS + 少量模板 class,风险最低、观感提升最直接,改完能立刻判断方向对不对。 C 独立(图表可以慢慢磨),D 涉及配色与导航,放最后一次性做,避免中途改两次色。

五、验收方式(沿用项目既有纪律)

  • 视觉:rond-web-verify 技能在 320/390/430/1440 四个宽度现场截图 + 量数(别用 shots/ 里的旧图)
  • 不改坏:./scripts/verify.sh 必须仍然 0 skip 全绿(模板改动会影响 tb_stack_test、TestVisitorPagesUseThirdPersonCopy 等)
  • 暗色:--color-scheme dark 跑同一组断言
  • 无障碍:audit/a11y.js 的对比度与字号判据不得比改动前更差(改动前基线要先跑一遍存档)
  • 本次只出方案,未改任何代码。

落地记录:A + B 批次(2026-10-09)

方案通过后先做了 A(设计令牌收敛)与 B(卡片分主次)。下表每个数字都是同一份探针在改动前后各跑一遍得到的。

A:设计令牌收敛

字阶:文本 6 档 --fs-xs/sm/md/lg/xl/2xl = 11 / 13 / 15 / 21 / 26 / 34px。

  • app.css 原来 19 种字号、96 处声明;模板里另有 54 处内联字号 —— 全部并档。
  • 两个字外档,都不是漏掉的:--fs-control: 16px(窄屏表单控件必须 ≥16px,否则 iOS 聚焦放大整页,属功能约束,不能进字阶);--glyph-* 6 档(✕ / 箭头 / 空态图标,按控件尺寸定 —— 把 22px 的 ✕ 塞进字阶只会把按钮撑坏)。
  • 字重收敛到 400 / 500 / 600(删掉 650 与 700 两个半档)。

圆角 4 档 --r-xs/sm/md/lg = 3 / 6 / 8 / 12px(原来 2.5/4/5/6/7/8/9/10/12/20px 混用;卡片 10 → 12)。 阴影 3 档 --sh-1/2/3(原来只有 1 档变量 + 6 处写死的阴影);暗色模式整套覆盖。 间距 5 档 --sp-1..5。⚠️ 本批只用在卡片内边距这类主节奏上,没有做全量迁移 —— 一次性铺开的改动面太大、收益最低,留到需要时再逐批替换。 --card-pad 单列:折叠卡片的标题行要用负 margin 顶到边,两处必须永远相等。

B:卡片分主次

  • .card--hero(首屏地图:无边框 + --sh-3 + 更大内边距)、.card(默认)、.card--quiet(设置页「地图底图 / 图片存储」两块只读说明:页面底色 + 细边 + 无阴影)。 ⚠️ quiet 第一版用 --bg-2(#fafbfb),实测与页面底 #f6f7f8 只差 4/255 —— 根本看不出边界,卡片像「破了个洞」。改成「页面底色 + 保留细边」才对。
  • 首屏 KPI 从「4×2 等重白盒」改成 .kpi-lead(主数 + 城市/省份上下文)+ 5 个常规指标。8 个数字一个没少,只是分了主次。

改动前后(同一探针)

页面 / 视口 (字号,字重) 组合 卡片样式档 最小字号
/ 390 13 → 10 2 → 3 10 → 11
/ 1440 15 → 10 2 → 3 10 → 11
/places 390 10 → 5 2 → 2 12 → 12
/timeline 390 10 → 6 2 → 2 11.5 → 11
/stats 1440 15 → 9 2 → 2 10 → 11
/admin/settings 1440 10 → 7 1 → 2 12 → 13

首页 390 的字阶从 10/400 11.5/400 12.5/400 13/400 13/500 13.5/400 14/400 14/500 15/600 17/400 18/600 21/600 21.8/650 收敛到 11/400 13/400 13/500 15/500 15/600 18/400 18/600 21/600 21.8/600 28.1/600 (字重只剩 400/500/600;多出来的 28.1px 是新加的首屏主数)

回归

  • ./scripts/verify.sh:PASS=347 FAIL=0 SKIP=0(含本轮新增的两条守卫)
  • 移动端 390 审计(溢出 / 小触控 / 小字 / iOS 自放大):逐值一致
  • 桌面 1440 溢出审计:唯一差异是 /map 露外元素 9 → 8
  • 暗色模式:三档阴影与主色强调条都按暗色变量生效(实测 computed 值)

途中发现并修掉的四处

  1. Leaflet 的字号跟着根字号漂。vendor 里 .leaflet-container { font-size: 0.75rem },根字号 14 → 13px 之后,地图右下角的署名与比例尺缩到 9.8px(掉到本站 11px 下限之下),弹窗正文也跟着缩。已按自己的字阶钉死:容器 = --fs-sm,署名/比例尺 = --fs-xs;/map 上小于 11px 的元素从 2 个降到 0 个。
  2. 静态守卫抓到并档。TestNavLinksNarrowRuleHasNoDeadPadding 里写死了 font-size: 13px,改成令牌后它红了 —— 守卫本身是对的(它就该在写法漂移时叫),已把判据从「钉具体值」改成「钉窄屏那条规则还在、且设了字号」。另补 TestNoBareFontSizePx:静态扫 app.css + 全部模板,禁止再出现裸 px 字号(做过变异验证:注入一条 font-size: 14px 会红)。
  3. h1..h4 从来就没设过字号,一直靠浏览器默认的 2em / 1.5em / 1.17em —— 也就是跟着根字号漂。根从 14 → 13px 后,/stats 里没被 .card > h2 命中的两个 h2(「我的结论」「访客点赞」)从 21px 悄悄变成 19.5px,比同页其它卡片标题(15px)大一圈。已显式给出字号:h1 = --fs-lg、h2 = --fs-md、h3/h4 = --fs-sm;实测 /stats 的 12 个 h2 现在统一 15px/600。
  4. <b> / <strong> 走浏览器默认 700,比全站最高层级(标题 600)还重。已钉成 600。

3 与 4 都是「量 DOM 的 (字号,字重) 组合」才浮出来的:肉眼看页面完全正常,只是觉得「有点不齐」。 这也是为什么这一批要先存基线、再用同一份探针逐值 diff,而不是改完看一眼截图。

附:一处历史遗留(本批未动)

7 个模板(admin/index.html、admin/login.html、admin/twofa-login.html、admin/twofa.html、map.html、places.html、timeline.html)在工作区里是 CRLF,而 .gitattributes 要求 eol=lf。 本批按字节替换、原样保住了各自行尾,所以 diff 里没有行尾噪声。要不要统一成 LF,另开一次清理。


落地记录:C + D 批次(2026-10-09)

C:让图表说人话

  • 月度分布:柱底压一道基线(原来柱子直接悬在空白里,高低只能靠互相比);峰值柱不透明拉满、并在柱顶标出次数(.pv),副标题里就不再重复写一遍「最高 N 次」。
  • 一天中的分布:原来只能靠 0/6/12/18 四个数字猜。现在换成一行 6 小时一段的标尺(深夜 0-6 / 上午 6-12 / 下午 12-18 / 晚上 18-24),用的是同一套 24 列网格(不是 flex: 6),所以段边界与柱子严格对齐;峰值小时也标了出来。
  • 首页那份「月度活跃度」同样加了峰值锚点 —— 为此给 HomeData 补了 MaxMonthly,与统计页同一口径(maxOf)。
  • 空态这条划掉了:查下来已经统一 —— 8 个页面共用 emptystate,局部空态一律 <div class="empty">,没有需要收拾的。

D1:类型色语义化(结论和原计划差得比较多)

先把 13 个色在浅/暗两个底上算了对比度:

底 结果
白底(浅色面板) 6 个色低于图形对象 3:1:yellow 1.87、mint 2.12、orange 2.20、green 2.22、cyan 2.54、teal 2.57
暗底 #1c2024 只有 indigo 2.90 不够,其余都在 3.9 以上

所以没有按原计划把类型色用到 chips / 表格类型列 / KPI 强调 —— 那几处要的是文字,这套色连图形 3:1 都到不了, 做成彩色标签只会更难读。这也正好解释了全站为什么从头到尾只用 8px 小圆点做身份标记。 浅色那 6 个色一个没动:它们是手机端与地图上的同一套身份色,改暗会破坏对应关系;而且它们只当标记、不承载唯一信息。

实际做了三件事:

  1. 色值收口到 CSS:--cat-* 13 个(原样 Apple 色名值)+ --cat-*-soft 13 个浅底(半透明,两个主题自适应)。 Go 侧 appleColors 退成「哪些色名合法」的名单;colorOf 已成死代码,删掉。
  2. 模板函数换成 csscolor / csssoft(17 + 2 处)。⚠️ 返回值必须是 template.CSS: html/template 的 CSS 上下文过滤器不认识 var(),普通 string 会被替换成 ZgotmplZ —— 页面上所有圆点当场变透明,而且不报错、模板测试照过(本批实测踩到)。 挂了 template.CSS 等于关掉这道过滤,所以数据侧色值必须先过严格校验(只有 # + 6 位十六进制放行)。 守卫 TestCSSColorSurvivesTemplateSanitizer 把两个方向都钉住:不出现 ZgotmplZ、恶意色值不得逃出 style 属性(做过变异验证)。
  3. 暗色只提亮一个:--cat-indigo 掺 10% 白(2.90 → 3.55:1),其余不动。 地图那份调色板(rond-map.js 的 APPLE)保持独立:地图瓦片在暗色下仍是浅色,标记就该用浅色底那套值 —— 硬统一反而会错。

D2:窄屏导航

改前 ≤620px 靠横向滚动兜底:390 溢 63px、320 溢 133px,最深处的「统计」被切成半个字紧贴后面的按钮, 而 .overflow-right 的渐隐提示实际看不出来。现在:

  • 链接本体抽成 {{define "navitems"}} 渲染两遍:桌面在 .nav-links 平铺,窄屏收进 .nav-menu(原生 <details>,不靠 JS 也能开合)。
  • JS 只补原生不给的三件事:点外面 / Esc / 点链接后收起。
  • ⚠️ 顶栏 z-index 抬到 --z-nav: 1200:菜单属于 .nav 这个层叠上下文、自己抬不动,不抬就会被 地图页的 --z-map-overlay(1100) 盖住。已在 /map 用 elementFromPoint 实测(menuOnTop=true)。
  • 顺手删掉窄屏那几条已经够不着的滚动/遮罩规则;宽屏保留横向兜底(以后又加入口时宁可能滑,也别切字)。

C + D 的回归

  • ./scripts/verify.sh:PASS=348 FAIL=0 SKIP=0
  • 移动端 390 / 320:溢出全 0(改前 /admin 在 320 还溢 34px,现在也归零)
  • 桌面 1440:溢出与改前一致(−15 是滚动条宽,按既有口径属正常)
  • 7 个页面都没有低于 11px 的文字(审计的「小字」判据是 <12px,会把我们 11px 的脚注档一并算进去,属设计内)
  • 暗色:菜单、类型色、三档阴影都按暗色变量生效(实测 computed 值)