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

+19 -6
View File
@@ -82,6 +82,8 @@ for (const [key, label] of [
dark: hasAsset(`${key}-dark.svg`) ? assetPath(`${key}-dark.svg`) : undefined,
});
}
// 只有一张非通栏卡时让它占满整行(否则右半空档;stats/top-langs 上游 503 时就是这种情形)
const soloCard = boardCards.length === 1;
const snakeCard = hasAsset('snake.svg')
? { label: locale === 'zh' ? '贡献蛇' : 'Contribution snake', src: assetPath('snake.svg') }
: null;
@@ -130,6 +132,7 @@ const L = {
</div>
</div>
{/* 统计三格:关注者 / 公开仓库 / 总星标(贡献数走热力图,不在此重复) */}
{(stats || totalStars !== null) && (
<dl class="pf-stats mono">
{stats && (
@@ -165,7 +168,12 @@ const L = {
<p class="pf-board-title mono">{locale === 'zh' ? '数据板' : 'Data board'}</p>
<div class="pf-board-grid">
{boardCards.map((card) => (
<figure class="pf-card">
<figure
class:list={[
'pf-card',
{ 'pf-card-streak': card.light.includes('streak'), 'pf-card-solo': soloCard },
]}
>
<img class="pf-card-img only-light" src={card.light} alt={card.label} loading="lazy" decoding="async" />
{card.dark && (
<img class="pf-card-img only-dark" src={card.dark} alt={card.label} loading="lazy" decoding="async" />
@@ -240,7 +248,8 @@ const L = {
display: flex;
gap: var(--space-2);
list-style: none;
margin: var(--space-2) 0 0;
/* 光学对齐:.tap 的 44px 热区带左右 padding,整体左移使图标视觉左缘对齐正文列 */
margin: var(--space-2) 0 0 -0.55rem;
padding: 0;
}
.pf-socials a { color: var(--ink-3); display: inline-flex; }
@@ -281,7 +290,6 @@ const L = {
padding: 0;
display: grid;
gap: 0.35rem;
max-width: 32rem;
}
.pf-lang {
display: grid;
@@ -323,9 +331,11 @@ const L = {
letter-spacing: 0.04em;
margin-bottom: var(--space-2);
}
/* 自适应列:只有 1 张卡时自动占满整行(避免右半空档),≥2 张时并排;
320px 下限保证车卡内 SVG 不被压到字号过小 */
.pf-board-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: var(--space-2);
}
.pf-card {
@@ -339,7 +349,7 @@ const L = {
align-items: center;
gap: 4px;
}
.pf-card-wide { grid-column: 1 / -1; }
.pf-card-wide, .pf-card-solo { grid-column: 1 / -1; }
.pf-card-img { max-width: 100%; height: auto; display: block; }
.pf-card figcaption {
font-size: 11px;
@@ -349,9 +359,12 @@ const L = {
/* 明暗两版自托管 SVG:默认只显示亮色,暗色主题切到暗色版 */
/* 贡献蛇:SVG 内部用自带 CSS 变量(绿阶 + #ebedf0 空格),<img> 无法改写内部配色 →
滤镜归一到站点灰阶;暗色下反相,避免浅灰格子贴在深底上刺眼(想去掉绿→灰只改这一行) */
/* streak 卡的环/火苗是写死的品牌蓝:亮色下去色以贴合灰阶数据卡;暗色保留原卡 */
.pf-card-streak .pf-card-img { filter: grayscale(1); }
.pf-card-snake .pf-card-img { filter: grayscale(1) contrast(1.03); }
/* 暗色蛇:只 invert 会把空格色压得与页面底色接近、矩阵骨架消失 → 叠加 contrast/brightness 找回网格 */
:global([data-theme='dark']) .pf-card-snake .pf-card-img {
filter: grayscale(1) invert(1) contrast(0.92);
filter: grayscale(1) invert(1) contrast(1.18) brightness(1.06);
}
.only-dark { display: none; }
+2 -1
View File
@@ -88,8 +88,9 @@ try {
.repo-desc {
font-size: 13px;
color: var(--ink-3);
/* 居中后长描述限行宽,避免整行摊平散架 */
/* 居中后长描述限行宽,避免整行摊平散架;pretty 减少末行孤词 */
max-width: 34em;
text-wrap: pretty;
}
.repo-meta { display: flex; gap: var(--space-3); font-size: 12px; color: var(--ink-3); margin-top: 4px; }
.lang { display: inline-flex; align-items: center; gap: 4px; }
+10 -3
View File
@@ -85,14 +85,17 @@ const first = safe[0] ?? '';
margin: 0 0 var(--space-2);
min-height: 1.6em; /* 固定行高,打字期间不推挤下方内容 */
display: flex;
align-items: center;
gap: 1px;
/* baseline:光标与末行基线对齐(center 会在两行时把光标拉到中间) */
align-items: baseline;
gap: 2px;
}
.typing-text { white-space: pre-wrap; }
.typing-caret {
display: inline-block;
width: 0.5em;
width: 2px; /* 细竖条:不参与折行计算 */
height: 1em;
align-self: baseline;
transform: translateY(0.12em);
background: var(--accent);
animation: caret-blink 1.05s steps(1) infinite;
}
@@ -100,6 +103,10 @@ const first = safe[0] ?? '';
0%, 49% { opacity: 1; }
50%, 100% { opacity: 0; }
}
/* 窄屏预留两行高度:长句折行时不再每轮下推再弹回(实测 390 视口曾推挤 ~21px) */
@media (max-width: 560px) {
.typing { min-height: calc(1.6em * 2); }
}
@media (prefers-reduced-motion: reduce) {
.typing-caret { animation: none; opacity: 0.6; }
}