fix(opensource): 修视觉终审的四个 P1 与灰阶/对齐细节

视觉终审对开源页改版判「需修」,逐条修完并量化复验。

P1-1 数据板空档与三段右缘不齐:streak 只占半栏、snake 通栏 → 右侧空 306px。
- 网格改 `repeat(auto-fit, minmax(320px, 1fr))`,仅一张非通栏卡时加 `.pf-card-solo`
  (`grid-column: 1/-1`),语言条去掉 max-width
- 实测:统计行/语言条/数据板右缘统一 959px,streak 卡宽 298→638

P1-2 卡内 SVG 文字不可读:streak 固有 495px 被压到 298px(0.6×),14px 标签变 8.4px。
- 卡改全宽后 1:1 渲染(实测 rendered=495=natural);抓取参数把次级灰换成站点 ink:
  亮色 #3f424a(10.05:1)、暗色 #b9bec8(9.07:1),旧值 #9aa0aa 仅 2.63:1

P1-3 移动端打字机折行推挤:390 下第 4 句 260px > 可用 230px 必折两行,
min-height:1.6em 挡不住 → 每轮下推 21px 再弹回。
- `@media(max-width:560px){.typing{min-height:calc(1.6em*2)}}`(实测 42px 恒定零推挤)
- 光标收成 2px 竖条 + baseline 对齐(原来是 0.5em 实心块,两行时被拉到行间)
- typing 第 4 句裁短(中文 ≤16 字、英文 ≤36 字符)

P1-4 streak 卡入场动画空壳帧:SVG 内 fadein 序列(环 0.4s/数字 0.6s/…/1.2s)+ lazy
加载 → 用户先看到 0.5–1.5s 半成品,截图也会随机拍到空卡。
- 抓取脚本新增 freezeAnimations():剥掉全部 animation 声明、opacity:0 改 1、
  删除 @keyframes;重抓后 grep animation:/opacity: 0 均为 0(streak 各 6KB、
  snake 35KB→20KB)

P2 一并:
- streak 亮色容器 grayscale(1) 回到灰阶基调;暗色蛇卡改 grayscale+invert+contrast+
  brightness 组合,空网格不再与底色同色
- 项目墙描述 text-wrap: pretty(长描述末行孤词)
- 统计口径注释改为「三格」,不硬塞第 4 格(贡献数已由热力图承载)
- 社交图标行负 margin 做光学对齐

附:content-lint 增加 SKIP_ALL 白名单(一次性预览路由 /books-preview/ 无 en 对应页,
语言切换链接会误报断链+配对缺)

验证: 构建 51 页 exit 0;content-lint 54 页 0/0/0;E2E 46/46;
      Playwright 实测三段右缘 959 一致、streak 1:1、1280/390 零横向溢出、
      移动打字机高度 42px 恒定、光标 2px、项目墙 text-wrap=pretty

Spec: 2026-11-02_blog-5i-fonts-polish (T8 修复)

Co-authored-by: GLM-5.3
This commit is contained in:
wait4xandGLM-5.3 committed 2026-11-03 11:48:25 +08:00
1 parent 907f64472a
commit cfe508ff14
10 files changed
+96 -62

No files matched your search

+24 -3
View File
@@ -18,8 +18,9 @@ const UA = 'wait4x-blog-assets/1.0 (+https://wait4x.online)';
const ACCENT = '3454d1';
const INK_LIGHT = '33343a';
const INK_DARK = 'c9ccd4';
const MUTED_LIGHT = '8b8f98';
const MUTED_DARK = '9aa0aa';
// 次级文字(Total Contributions / dates)也用 ink 档:旧的 #8b8f98/#9aa0aa 缩小后对比不足 4.5:1
const MUTED_LIGHT = '3f424a';
const MUTED_DARK = 'b9bec8';
/** 同一张卡按明暗两套参数各存一份(组件内用 CSS 切换,零运行时请求) */
const ASSETS = [
@@ -62,6 +63,26 @@ const ASSETS = [
},
];
/**
* 冻结进场动画:streak-stats 的 SVG 里元素带 `style='opacity: 0; animation: fadein … forwards'`,
* loading=lazy 时用户会先看到 0.5–1.5s 的半成品。这里把动画声明剥掉、opacity 归 1,
* 静态呈现最终态(同时删掉 @keyframes 块,减小体积)。
*/
function freezeAnimations(svg) {
let out = svg.replace(/style='([^']*)'/g, (_, css) => {
const cleaned = css
.split(';')
.map((decl) => decl.trim())
.filter((decl) => decl && !decl.startsWith('animation'))
.map((decl) => (/^opacity\s*:\s*0\b/.test(decl) ? 'opacity: 1' : decl))
.join('; ');
return cleaned ? `style='${cleaned}'` : '';
});
out = out.replace(/animation\s*:\s*[^;'"]*;?/g, '');
out = out.replace(/@keyframes[^{]*\{(?:[^{}]*\{[^{}]*\})*[^{}]*\}/g, '');
return out;
}
/** 10s 超时抓取;返回 SVG 文本或 null */
async function grab(url) {
try {
@@ -78,7 +99,7 @@ async function grab(url) {
console.warn(`[assets] 非 SVG 响应,已跳过: ${url.slice(0, 90)}…`);
return null;
}
return text;
return freezeAnimations(text);
} catch (err) {
console.warn(`[assets] 抓取失败(${err.name === 'TimeoutError' ? '超时' : err.message}): ${url.slice(0, 90)}…`);
return null;