美工改稿 V4 四屏落地 · 禁止项是待办不是护栏
入档:2026-08-05 来源:PB Arena(pb.tiaozhuxiansheng.com)接收美工「浅色对战 V4」改稿并落地四屏,2 个提交(
9f4f766四屏改稿 /b734374更新报告),已部署上线并线上验证 状态:已上线;insight 1/2/3/5 为首次发现 ⚠️,insight 4 是 美工改稿全站落地_跨稿重复才是规范_v1 insight 5 的二次验证并扩展形态
事实记录(不可修改区)
- 交付物:
PB-Arena-全套UI改稿_浅色对战_V4_20260731.zip,30.9 MB,32 个文件(30 PNG + 2 份规范 md) - 按文件 mtime 分组:8 张是 2026-07-31 新稿(02 匹配大厅 / 03 赛事报名 / 04 赛事详情弹窗 / 05 比赛房间,各 desktop + mobile),其余 22 张 mtime 为 2026-07-24,与上一轮 V2 同名同内容
- 规范文档两份:
对战UI设计说明_浅色协调V4.md(只覆盖上述 4 屏)、UI视觉规范_简洁低疲劳V2.md(全站基线,与上一轮同一份) - V4 说明原文写「内容来源:V3 对战强化画面」;V3 从未交付到本仓库,也没有落地过任何代码——V4 是把那一版走回浅色
- V4 相对 V2 的 token 增量:新增次级舞台色
--stage:#f2f2ef;--line由#e6e5e1调为#e3e2dd;新增计时数字规则(等宽字形 / 数字炭黑 / 只有冒号是珊瑚红),V2 无此条 - V4 禁止项原文:「科技网格、瞄准镜、扫描线、霓虹、发光和玻璃拟态」「为追求视觉效果牺牲中文信息、数字、状态或操作完整性」
- 改动范围:只动
pb-arena-optimized/下 3 个文件(index.html/styles.css/app.js),后端零改动 - 客观事实(线上):
?v=20260805a生效、health 正常;后端测试 11 项全部通过 - 本机环境与上一轮不同:这次有 Node,能起本地服务、能跑测试、能驱动真实页面(上一轮是借来的机器,只能
file://静态快照) - 未验证项(已如实写进交付报告):本地库无官方赛事 → 赛事推送轮播整块隐藏,03 的新卡片只在静态样例页上验过,没跑过真实数据;比赛房间左右分栏只验了 4 人对称,3/5 人奇数未验;移动端只验了 430px 一个宽度
- 附带产物:更新报告长图 2240×7524,走 report-longimage/SKILL.md 产线渲染(母版 + 两趟截图),md + html 源码 + png 同目录归档
一句话总结
改稿落地里每一样「看起来已经对了」的东西都得亲手核一遍——包名不等于范围、规范的禁止项里藏着待办、代码里写了的 CSS 不等于生效过、稿子上画着的组件不等于它真的会动。这一轮四处全中。
五条可复用 insight
1. 成套交付包的范围以文件时间戳为准,不以包名为准 ⚠️首次
包名是「PB-Arena-全套UI改稿_浅色对战_V4」,30 张 PNG,读起来就是”全站重做”。ls -l 按 mtime 一分组才发现:只有 8 张是 2026-07-31 的新稿,另外 22 张 mtime 停在 2026-07-24,是上一轮 V2 原样重发。
如果按包名理解,后果不是”多做一点”,而是把 22 张已经落地过的旧稿再逐张对一遍——而且因为它们确实和当前代码一致,对完还会得出”没什么要改的”这种毫无信息量的结论,白烧一轮。
同一份包里还有个更隐蔽的:两份规范 md,其中 UI视觉规范_简洁低疲劳V2.md 是上一轮那份一字未改的重发。如果不核,很容易把它当成”V4 也重新定义了全站基线”。
判据:拿到成套交付物,第一个动作是按 mtime / 内容哈希分组算出「这一轮真正变了什么」,这一步排在读规范之前。包名、目录名、交付说明都是当事人的描述,不是事实来源。
一般化:外包交付、第三方 SDK 更新包、数据集「全量重发」都是同一形态——声称的范围和实际的 diff 之间没有任何强制约束。
2. 规范的否定条款是待办,肯定条款才是待核实 ⚠️首次
这条是 美工改稿全站落地_跨稿重复才是规范_v1 insight 2 的镜像面,两条必须配着用。
那一篇的教训是:规范写「移动端长列表优先两列图库」,我就当成待办报了”还没做”,实际早就有了——肯定条款不能当待办清单,要先回读实现。
这一轮反过来。V4 禁止项写着「科技网格、瞄准镜、扫描线、霓虹、发光」,我第一反应读成”以后别加这些”。实际去搜,匹配大厅里就有:
.radar::before { ... animation: scan 3s linear infinite; }
@keyframes scan { to { transform: rotate(360deg); } }
一条绕圈的扫描线,.radar 整块加上三个同心圆环,精确命中禁止项。它不是”以后别加”,是”现在就得下掉”。
判据:读规范时把肯定句和否定句拆开处理——
| 条款形态 | 正确动作 | 跳过的后果 |
|---|---|---|
| 肯定句「要有 X」 | 先回读实现,确认到底缺不缺 | 把已有功能误报成缺失,交付进度是虚的 |
| 否定句「不要 X」 | 去实现里搜有没有违例 | 违例原样留在线上,而你以为自己遵守了规范 |
两种都跳过,就等于没读规范——只是抄了一遍色值。
3. 按下标匹配的全局选择器,在组件被复用第二处时静默失效 ⚠️首次
比赛房间底部那条「01 出图 — 02 上传 — 03 投票」时间轴,从来没有高亮过当前阶段。定位到:
$$(".phase-step").forEach((step, index) => {
step.classList.toggle("active", index === currentIndex); // currentIndex ∈ {0,1,2}
});
.phase-step 全页有 6 个:官方直播面板 3 个 + 比赛房间 3 个。而官方直播面板在文档里排前面,于是下标 0/1/2 落到了那一组(此时它是 hidden),比赛房间自己那三个拿到的是 3/4/5,永远匹配不上。
值得记的不是这个 bug 本身,而是它为什么能活这么久:
- 不报错 ——
classList.toggle(x, false)完全合法,没有任何异常; - 症状是”没反应”而不是”错了” —— 界面上不会出现一个错的高亮,只是三个圆点都不亮,像本来就这样;
- 元素本身不显眼 —— V2 版式里这条时间轴只是页头下的一条细线。
是 V4 把它提为核心元素(规范原文:「创作 / 上传 / 投票状态沿一条连续时间轴表达」)才暴露出来。
判据:凡是 querySelectorAll(...) + 数组下标做匹配的代码,都隐含一个前提——「全页只有这一组」。这个前提在组件被复用第二次时失效,且不产生任何可观测信号。写这类代码时作用域必须锚到容器 id($$("#phaseTrack .phase-step")),不能只锚类名。
附带一层,同样值钱:改版是既有 bug 的显影剂。 一个原本不显眼的元素被提为主视觉时,先假设它从来没被真正验证过——它此前不显眼,正意味着没人盯着它看过。
4. 同权重后置胜出,让「写了」和「生效过」之间没有任何信号(二次验证 + 新形态)
.event-detail-dialog 写的是 width: min(620px, calc(100vw - 40px)),而基础样式 .dialog { width: min(460px, 100%) } 在文件后面,权重同为 (0,1,0) → 后置胜出,弹窗实际一直是 460px。
上一篇 insight 5 已经记过这个机制(padding: 0 吃掉后续的 padding-left),但那次是我刚写的代码当场没生效,改完就发现了。这次是新形态:
- 620px 是很久以前写的,从来没有生效过,线上一直是 460px;
- 没有人发现,因为 460px 的弹窗看起来也很正常 —— 它不残缺、不溢出、不报错,只是比设计稿窄;
- 是这次拿 V4 稿去排三栏时间摘要,发现中间那栏被挤成”每周 / 六、 / 周日”竖着一列,才回头量出来的。
判据(扩展):组件专用规则必须比基础规则更具体(.dialog.event-detail-dialog),不能靠「写在前面」。同权重靠源码顺序决胜意味着——代码里写了一个值 ≠ 这个值生效过,而且这种失效不产生任何可观测信号,只能靠对着设计稿量。
推论:基础组件类(.dialog / .card / .btn)上任何带具体数值的声明,都是在给所有变体设默认值兼天花板。 要么它写得足够弱(让变体自然覆盖),要么变体必须提权;指望源码顺序是最脆的一种。
5. Windows 上 headless Chrome 的窄屏截图会被最小窗宽夹住,截出假溢出 ⚠️首次
移动端校对时用 chrome --headless=new --window-size=430,2700 --screenshot,截出来的图到处横向溢出:卡片右边被切、1,289 ON 少半截、阶段轴第三格不见了。我据此判断”移动端布局崩了”,改了一轮 CSS。
真相是:Windows 会把浏览器窗口宽度夹到约 500px,页面按 ~500px 布局,截图再裁到 430px 输出。页面本身 scrollWidth === 430,从来没有溢出过。
解法是把被测页面套进 iframe,让 iframe 而不是窗口来定宽:
<iframe id="f" src="/target.html" style="width:430px;height:2700px;border:0"></iframe>
<script>
// 在 iframe 内直接量,而不是看截图
var d = f.contentDocument, vw = d.documentElement.clientWidth;
d.querySelectorAll("*").forEach(el => {
var r = el.getBoundingClientRect();
if (r.right > vw + 1 && r.width > 0) bad.push(el.className);
});
out.textContent = "scrollW=" + d.documentElement.scrollWidth + "\n" + (bad.join("\n") || "NO OVERFLOW");
</script>
宿主窗口开 520+(避开夹取),iframe 给 430 —— 媒体查询在 iframe 里按 430 求值,截出来的就是真的。
判据:响应式验证不能信「我给浏览器传了多宽」,要信「页面自己报了多宽」。 与 全站文字截断体检_检测工具本身要先被证伪_v1 同族:那篇是判据常数拍错把正常元素判成缺陷,这篇是承载判据的容器尺寸就是错的——工具链的每一环都要先被证伪。
这一条已作为形态五补进 Windows下编码与DPI的所见非真相。
顺手教训
- 把真实站点套进 iframe 还能顺便驱动它 —— 加个
?view=参数在 iframe 里.click()导航按钮,就能到达需要交互才看得到的页面(匹配大厅、赛事报名),同时挂window.onerror收报错。比只截首页有用得多,一次调用同时拿到:目标页截图、溢出清单、控制台报错三样。 - 真实库里没数据的模块,视觉改动无法在真实页面验证 —— 本地没有官方赛事,赛事推送轮播整块
hidden,03 的新卡片在真实页面上根本不出现。只能另建一个贴真实 DOM 结构的静态样例页去验 CSS。这属于降级验证,必须在交付里如实标注”这一屏是样例页验的,不是真实数据验的”,同族见上一篇的「登录态 UI 够不着时退到量计算值并标注验证等级」。 1fr的最小尺寸是 min-content,不是 0 —— 移动端把三栏指标栏写成repeat(3, 1fr),里面塞了个大字时钟,时钟的 min-content 直接把整行顶到比屏幕还宽。要写repeat(3, minmax(0, 1fr))。这条极常见,而且症状(整页横向溢出)看起来完全不像出在这里。- 可换行的 flex 做多栏,换行后分隔线会落在行首 —— 三栏时间摘要用
flex-wrap,第三栏一换行,它的border-left就变成孤零零竖在行首的一条线。改用grid-template-columns: auto minmax(0,1fr) auto:grid 不换行,宁可让中间栏 ellipsis,布局也是确定的。要”排不下时优雅降级”,grid + ellipsis 比 flex-wrap 可控。 - 版本号覆盖”会一起变的那一组” —— 这轮 CSS 与 JS 同时改,三个文件挂同一个
?v=20260805a。上一篇已记过,本次照做,属二次验证。 - 稿子只画了对称情况时要主动交回不确定性 —— V4 比赛房间稿画的是 4 人左右各 2 个。实现用多列自动均分支持 3–16 人,但 3 人 / 5 人的奇数分栏效果稿子上没有,这属于稿子没覆盖的区间,应该点名交回给美工验收,而不是自己拍一个然后当已完成报上去。
下次改进
- 拿到成套交付包,先
ls -l按 mtime 分组算出本轮真实范围,再读规范、再动代码。 - 读规范时把否定条款单独摘成一张清单,逐条回代码搜有没有违例;肯定条款则逐条回代码确认是不是已经有了。
- 见到
querySelectorAll+ 下标匹配,先数这个类名全页有几处;超过一处就把作用域锚到容器 id。 - 窄屏验证一律走 iframe + 页内
getBoundingClientRect实测,不信--window-size。 - 组件专用样式写完,回头确认它比基础类更具体;拿不准就直接量一次实际计算值。
关联文档
- ⚠️ 美工改稿全站落地_跨稿重复才是规范_v1 —— 直接前篇(同项目同类任务,V2 全站 30 张 → 本篇 V4 四屏 8 张)。本篇 insight 2 是那篇 insight 2 的镜像面(肯定条款待核实 / 否定条款是待办,两条配着用);本篇 insight 4 是那篇 insight 5 的二次验证并扩展(那次是当场没生效,这次是从未生效过且无人发现);顺手教训里的版本号、降级验证要标注两条同样是对它的二次验证
- ⭐ 全站文字截断体检_检测工具本身要先被证伪_v1 —— 同项目、同一验证纪律。那篇是判据里的常数拍错,本篇 insight 5 是承载判据的窗口尺寸被系统夹错,共同点是工具链每一环都要先被证伪,不能只证伪最后一步
- Windows下编码与DPI的所见非真相 —— 本篇 insight 5 已作为「形态五:headless 最小窗宽」补进那篇的陷阱三,同属 Windows 上「截图尺寸会骗人」家族
- ⚠️ 赛事状态机到点不切换_写入方不等于推进方_v1 —— 同项目更早会话。那篇是「字段有人写没人推进」,本篇 insight 3 是「元素画了但匹配逻辑选错了对象」,共同点是链路上每一环单看都在,合起来不通
- ⚠️ 零作品的比赛_界面演完不等于链路存在_v1 —— 同族「看起来在≠真的在」:那篇是界面演完但表里 0 行,本篇 insight 3 是圆点画着但永远不亮
- 导出运行时无系统字体回退律_CJK豆腐块_v1 —— 本轮更新报告长图渲染时,
--mono的 CJK 兜底栈正是它的应用(CJK 必须排在 genericmonospace之前) - 文档密集页两栏排版与对外截图脱敏_v1 —— 同族无头 Chrome 静态视觉自检
- 复盘事实先行原则 —— 本篇顶部事实冻结区按它执行
- 09_平台工程索引 —— 平台工程区入口