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

+2 -2
View File
@@ -6,8 +6,8 @@ typing:
en: Self-hosting whatever can be self-hosted en: Self-hosting whatever can be self-hosted
- zh: 拥抱开源,热爱开源 - zh: 拥抱开源,热爱开源
en: Embrace open source, love open source en: Embrace open source, love open source
- zh: 把读过的书、按下的快门都放进同一个工作台 - zh: 书与快门都在一个工作台
en: Books read and shutters pressed, gathered in one workbench en: Books and shutters, one workbench
blurb: blurb:
zh: 本站本身即开源: zh: 本站本身即开源:
+5 -5
View File
@@ -1,20 +1,20 @@
{ {
"assets": { "assets": {
"streak-light.svg": { "streak-light.svg": {
"source": "https://streak-stats.demolab.com/?user=wait4xx&hide_border=true&background=00000000&ring=3454d1&fire=3454d1&currStreakLabel=3454d1&currStreakNum=33343a&sideNums=33343a&sideLabels=8b8f98&dates=8b8f98", "source": "https://streak-stats.demolab.com/?user=wait4xx&hide_border=true&background=00000000&ring=3454d1&fire=3454d1&currStreakLabel=3454d1&currStreakNum=33343a&sideNums=33343a&sideLabels=3f424a&dates=3f424a",
"fetchedAt": "2026-11-03T03:07:28.521Z", "fetchedAt": "2026-11-03T03:39:51.755Z",
"label": "连续贡献", "label": "连续贡献",
"theme": "light" "theme": "light"
}, },
"streak-dark.svg": { "streak-dark.svg": {
"source": "https://streak-stats.demolab.com/?user=wait4xx&hide_border=true&background=00000000&ring=3454d1&fire=3454d1&currStreakLabel=3454d1&currStreakNum=c9ccd4&sideNums=c9ccd4&sideLabels=9aa0aa&dates=9aa0aa", "source": "https://streak-stats.demolab.com/?user=wait4xx&hide_border=true&background=00000000&ring=3454d1&fire=3454d1&currStreakLabel=3454d1&currStreakNum=c9ccd4&sideNums=c9ccd4&sideLabels=b9bec8&dates=b9bec8",
"fetchedAt": "2026-11-03T03:07:28.521Z", "fetchedAt": "2026-11-03T03:39:51.755Z",
"label": "连续贡献", "label": "连续贡献",
"theme": "dark" "theme": "dark"
}, },
"snake.svg": { "snake.svg": {
"source": "https://raw.githubusercontent.com/wait4xx/wait4xx/output/github-contribution-grid-snake.svg", "source": "https://raw.githubusercontent.com/wait4xx/wait4xx/output/github-contribution-grid-snake.svg",
"fetchedAt": "2026-11-03T03:07:28.521Z", "fetchedAt": "2026-11-03T03:39:51.755Z",
"label": "贡献蛇", "label": "贡献蛇",
"theme": "any" "theme": "any"
} }
+1 -1
View File
@@ -1 +1 @@
<svg viewBox="-16 -32 880 192" width="880" height="192" xmlns="http://www.w3.org/2000/svg"><desc>Generated with https://github.com/Platane/snk</desc><style>:root{--cb:#1b1f230a;--cs:purple;--ce:#ebedf0;--c0:#ebedf0;--c1:#9be9a8;--c2:#40c463;--c3:#30a14e;--c4:#216e39}.c{shape-rendering:geometricPrecision;fill:var(--ce);stroke-width:1px;stroke:var(--cb);animation:none 35300ms linear infinite;width:12px;height:12px}@keyframes c0{4.52%{fill:var(--c1)}4.54%,100%{fill:var(--ce)}}.c.c0{fill:var(--c1);animation-name:c0}@keyframes c1{66.85%{fill:var(--c2)}66.87%,100%{fill:var(--ce)}}.c.c1{fill:var(--c2);animation-name:c1}@keyframes c2{3.67%{fill:var(--c1)}3.69%,100%{fill:var(--ce)}}.c.c2{fill:var(--c1);animation-name:c2}@keyframes c3{1.97%{fill:var(--c1)}1.99%,100%{fill:var(--ce)}}.c.c3{fill:var(--c1);animation-name:c3}@keyframes c4{2.82%{fill:var(--c1)}2.84%,100%{fill:var(--ce)}}.c.c4{fill:var(--c1);animation-name:c4}@keyframes c5{3.39%{fill:var(--c1)}3.41%,100%{fill:var(--ce)}}.c.c5{fill:var(--c1);animation-name:c5}@keyframes c6{2.26%{fill:var(--c1)}2.28%,100%{fill:var(--ce)}}.c.c6{fill:var(--c1);animation-name:c6}@keyframes c7{10.19%{fill:var(--c1)}10.21%,100%{fill:var(--ce)}}.c.c7{fill:var(--c1);animation-name:c7}@keyframes c8{9.06%{fill:var(--c1)}9.08%,100%{fill:var(--ce)}}.c.c8{fill:var(--c1);animation-name:c8}@keyframes c9{41.63%{fill:var(--c1)}41.65%,100%{fill:var(--ce)}}.c.c9{fill:var(--c1);animation-name:c9}@keyframes ca{12.74%{fill:var(--c1)}12.76%,100%{fill:var(--ce)}}.c.ca{fill:var(--c1);animation-name:ca}@keyframes cb{13.59%{fill:var(--c1)}13.61%,100%{fill:var(--ce)}}.c.cb{fill:var(--c1);animation-name:cb}@keyframes cc{14.15%{fill:var(--c1)}14.17%,100%{fill:var(--ce)}}.c.cc{fill:var(--c1);animation-name:cc}@keyframes cd{14.44%{fill:var(--c1)}14.46%,100%{fill:var(--ce)}}.c.cd{fill:var(--c1);animation-name:cd}@keyframes ce{21.8%{fill:var(--c1)}21.82%,100%{fill:var(--ce)}}.c.ce{fill:var(--c1);animation-name:ce}@keyframes cf{19.82%{fill:var(--c1)}19.84%,100%{fill:var(--ce)}}.c.cf{fill:var(--c1);animation-name:cf}@keyframes cg{20.67%{fill:var(--c1)}20.69%,100%{fill:var(--ce)}}.c.cg{fill:var(--c1);animation-name:cg}@keyframes ch{14.72%{fill:var(--c1)}14.74%,100%{fill:var(--ce)}}.c.ch{fill:var(--c1);animation-name:ch}@keyframes ci{21.52%{fill:var(--c1)}21.54%,100%{fill:var(--ce)}}.c.ci{fill:var(--c1);animation-name:ci}@keyframes cj{19.25%{fill:var(--c1)}19.27%,100%{fill:var(--ce)}}.c.cj{fill:var(--c1);animation-name:cj}@keyframes ck{15%{fill:var(--c1)}15.02%,100%{fill:var(--ce)}}.c.ck{fill:var(--c1);animation-name:ck}@keyframes cl{15.29%{fill:var(--c1)}15.31%,100%{fill:var(--ce)}}.c.cl{fill:var(--c1);animation-name:cl}@keyframes cm{44.18%{fill:var(--c2)}44.2%,100%{fill:var(--ce)}}.c.cm{fill:var(--c2);animation-name:cm}@keyframes cn{18.4%{fill:var(--c1)}18.42%,100%{fill:var(--ce)}}.c.cn{fill:var(--c1);animation-name:cn}@keyframes co{18.69%{fill:var(--c1)}18.71%,100%{fill:var(--ce)}}.c.co{fill:var(--c1);animation-name:co}@keyframes cp{16.14%{fill:var(--c1)}16.16%,100%{fill:var(--ce)}}.c.cp{fill:var(--c1);animation-name:cp}@keyframes cq{15.85%{fill:var(--c1)}15.87%,100%{fill:var(--ce)}}.c.cq{fill:var(--c1);animation-name:cq}@keyframes cr{23.5%{fill:var(--c1)}23.52%,100%{fill:var(--ce)}}.c.cr{fill:var(--c1);animation-name:cr}@keyframes cs{18.12%{fill:var(--c1)}18.14%,100%{fill:var(--ce)}}.c.cs{fill:var(--c1);animation-name:cs}@keyframes ct{89.79%{fill:var(--c4)}89.81%,100%{fill:var(--ce)}}.c.ct{fill:var(--c4);animation-name:ct}@keyframes cu{16.99%{fill:var(--c1)}17.01%,100%{fill:var(--ce)}}.c.cu{fill:var(--c1);animation-name:cu}@keyframes cv{76.19%{fill:var(--c4)}76.21%,100%{fill:var(--ce)}}.c.cv{fill:var(--c4);animation-name:cv}@keyframes cw{47.02%{fill:var(--c2)}47.04%,100%{fill:var(--ce)}}.c.cw{fill:var(--c2);animation-name:cw}@keyframes cx{35.4%{fill:var(--c1)}35.42%,100%{fill:var(--ce)}}.c.cx{fill:var(--c1);animation-name:cx}@keyframes cy{35.12%{fill:var(--c1)}35.14%,100%{fill:var(--ce)}}.c.cy{fill:var(--c1);animation-name:cy}@keyframes cz{26.34%{fill:var(--c1)}26.36%,100%{fill:var(--ce)}}.c.cz{fill:var(--c1);animation-name:cz}@keyframes c10{33.7%{fill:var(--c1)}33.72%,100%{fill:var(--ce)}}.c.c10{fill:var(--c1);animation-name:c10}@keyframes c11{33.98%{fill:var(--c1)}34%,100%{fill:var(--ce)}}.c.c11{fill:var(--c1);animation-name:c11}@keyframes c12{50.41%{fill:var(--c2)}50.43%,100%{fill:var(--ce)}}.c.c12{fill:var(--c2);animation-name:c12}@keyframes c13{78.46%{fill:var(--c4)}78.48%,100%{fill:var(--ce)}}.c.c13{fill:var(--c4);animation-name:c13}@keyframes c14{49.28%{fill:var(--c2)}49.3%,100%{fill:var(--ce)}}.c.c14{fill:var(--c2);animation-name:c14}@keyframes c15{27.47%{fill:var(--c1)}27.49%,100%{fill:var(--ce)}}.c.c15{fill:var(--c1);animation-name:c15}@keyframes c16{80.44%{fill:var(--c4)}80.46%,100%{fill:var(--ce)}}.c.c16{fill:var(--c4);animation-name:c16}@keyframes c17{27.75%{fill:var(--c1)}27.77%,100%{fill:var(--ce)}}.c.c17{fill:var(--c1);animation-name:c17}@keyframes c18{32.28%{fill:var(--c1)}32.3%,100%{fill:vLine truncated <svg viewBox="-16 -32 880 192" width="880" height="192" xmlns="http://www.w3.org/2000/svg"><desc>Generated with https://github.com/Platane/snk</desc><style>:root{--cb:#1b1f230a;--cs:purple;--ce:#ebedf0;--c0:#ebedf0;--c1:#9be9a8;--c2:#40c463;--c3:#30a14e;--c4:#216e39}.c{shape-rendering:geometricPrecision;fill:var(--ce);stroke-width:1px;stroke:var(--cb);width:12px;height:12px}.c.c0{fill:var(--c1);animation-name:c0}.c.c1{fill:var(--c2);animation-name:c1}.c.c2{fill:var(--c1);animation-name:c2}.c.c3{fill:var(--c1);animation-name:c3}.c.c4{fill:var(--c1);animation-name:c4}.c.c5{fill:var(--c1);animation-name:c5}.c.c6{fill:var(--c1);animation-name:c6}.c.c7{fill:var(--c1);animation-name:c7}.c.c8{fill:var(--c1);animation-name:c8}.c.c9{fill:var(--c1);animation-name:c9}.c.ca{fill:var(--c1);animation-name:ca}.c.cb{fill:var(--c1);animation-name:cb}.c.cc{fill:var(--c1);animation-name:cc}.c.cd{fill:var(--c1);animation-name:cd}.c.ce{fill:var(--c1);animation-name:ce}.c.cf{fill:var(--c1);animation-name:cf}.c.cg{fill:var(--c1);animation-name:cg}.c.ch{fill:var(--c1);animation-name:ch}.c.ci{fill:var(--c1);animation-name:ci}.c.cj{fill:var(--c1);animation-name:cj}.c.ck{fill:var(--c1);animation-name:ck}.c.cl{fill:var(--c1);animation-name:cl}.c.cm{fill:var(--c2);animation-name:cm}.c.cn{fill:var(--c1);animation-name:cn}.c.co{fill:var(--c1);animation-name:co}.c.cp{fill:var(--c1);animation-name:cp}.c.cq{fill:var(--c1);animation-name:cq}.c.cr{fill:var(--c1);animation-name:cr}.c.cs{fill:var(--c1);animation-name:cs}.c.ct{fill:var(--c4);animation-name:ct}.c.cu{fill:var(--c1);animation-name:cu}.c.cv{fill:var(--c4);animation-name:cv}.c.cw{fill:var(--c2);animation-name:cw}.c.cx{fill:var(--c1);animation-name:cx}.c.cy{fill:var(--c1);animation-name:cy}.c.cz{fill:var(--c1);animation-name:cz}.c.c10{fill:var(--c1);animation-name:c10}.c.c11{fill:var(--c1);animation-name:c11}.c.c12{fill:var(--c2);animation-name:c12}.c.c13{fill:var(--c4);animation-name:c13}.c.c14{fill:var(--c2);animation-name:c14}.c.c15{fill:var(--c1);animation-name:c15}.c.c16{fill:var(--c4);animation-name:c16}.c.c17{fill:var(--c1);animation-name:c17}.c.c18{fill:var(--c1);animation-name:c18}.c.c19{fill:var(--c1);animation-name:c19}.c.c1a{fill:var(--c2);animation-name:c1a}.c.c1b{fill:var(--c1);animation-name:c1b}.c.c1c{fill:var(--c1);animation-name:c1c}.c.c1d{fill:var(--c4);animation-name:c1d}.c.c1e{fill:var(--c4);animation-name:c1e}.u{transform-origin:0 0;transform:scale(0,1);animation-name:u0;transform-origin:0.0px 0}.u.u1{fill:var(--c2);animation-name:u1;transform-origin:648.5px 0}.u.u2{fill:var(--c4);animation-name:u2;transform-origin:748.2px 0}.s{shape-rendering:geometricPrecision;fill:var(--cs);animation-name:s0}.s.s1{transform:translate(16px,-16px);animation-name:s1}.s.s2{transform:translate(32px,-16px);animation-name:s2}.s.s3{transform:translate(48px,-16px);animation-name:s3}</style><rect class="c" x="2" y="2" rx="2" ry="2"/><rect class="c" x="2" y="18" rx="2" ry="2"/><rect class="c" x="2" y="34" rx="2" ry="2"/><rect class="c" x="2" y="50" rx="2" ry="2"/><rect class="c" x="2" y="66" rx="2" ry="2"/><rect class="c" x="2" y="82" rx="2" ry="2"/><rect class="c" x="2" y="98" rx="2" ry="2"/><rect class="c" x="18" y="2" rx="2" ry="2"/><rect class="c" x="18" y="18" rx="2" ry="2"/><rect class="c" x="18" y="34" rx="2" ry="2"/><rect class="c" x="18" y="50" rx="2" ry="2"/><rect class="c" x="18" y="66" rx="2" ry="2"/><rect class="c" x="18" y="82" rx="2" ry="2"/><rect class="c c0" x="18" y="98" rx="2" ry="2"/><rect class="c" x="34" y="2" rx="2" ry="2"/><rect class="c" x="34" y="18" rx="2" ry="2"/><rect class="c" x="34" y="34" rx="2" ry="2"/><rect class="c" x="34" y="50" rx="2" ry="2"/><rect class="c" x="34" y="66" rx="2" ry="2"/><rect class="c" x="34" y="82" rx="2" ry="2"/><rect class="c" x="34" y="98" rx="2" ry="2"/><rect class="c" x="50" y="2" rx="2" ry="2"/><rect class="c" x="50" y="18" rx="2" ry="2"/><rect class="c" x="50" y="34" rx="2" ry="2"/><rect class="c" x="50" y="50" rx="2" ry="2"/><rect class="c c1" x="50" y="66" rx="2" ry="2"/><rect class="c c2" x="50" y="82" rx="2" ry="2"/><rect class="c" x="50" y="98" rx="2" ry="2"/><rect class="c" x="66" y="2" rx="2" ry="2"/><rect class="c" x="66" y="18" rx="2" ry="2"/><rect class="c c3" x="66" y="34" rx="2" ry="2"/><rect class="c c4" x="66" y="50" rx="2" ry="2"/><rect class="c" x="66" y="66" rx="2" ry="2"/><rect class="c c5" x="66" y="82" rx="2" ry="2"/><rect class="c" x="66" y="98" rx="2" ry="2"/><rect class="c" x="82" y="2" rx="2" ry="2"/><rect class="c" x="82" y="18" rx="2" ry="2"/><rect class="c c6" x="82" y="34" rx="2" ry="2"/><rect class="c" x="82" y="50" rx="2" ry="2"/><rect class="c" x="82" y="66" rx="2" ry="2"/><rect class="c" x="82" y="82" rx="2" ry="2"/><rect class="c" x="82" y="98" rx="2" ry="2"/><rect class="c" x="98" y="2" rx="2" ry="2"/><rect class="c" x="98" y="18" rx="2" ry="2"/><rect class="c" x="98" y="34" rx="2" ry="2"/><rect class="c" x="98" y="50" rx="2" ry="2"/><rect class="c" x="98" y="66" rx="2" ry="2"/><rect class="cLine truncated

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 20 KiB

+13 -20
View File
@@ -1,15 +1,8 @@
<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' <svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'
style='isolation: isolate' viewBox='0 0 495 195' width='495px' height='195px' direction='ltr'> style='isolation: isolate' viewBox='0 0 495 195' width='495px' height='195px' direction='ltr'>
<style> <style>
@keyframes currstreak {
0% { font-size: 3px; opacity: 0.2; }
80% { font-size: 34px; opacity: 1; }
100% { font-size: 28px; opacity: 1; }
}
@keyframes fadein {
0% { opacity: 0; }
100% { opacity: 1; }
}
</style> </style>
<defs> <defs>
<clipPath id='outer_rectangle'> <clipPath id='outer_rectangle'>
@@ -32,21 +25,21 @@
<g style='isolation: isolate'> <g style='isolation: isolate'>
<!-- Total Contributions big number --> <!-- Total Contributions big number -->
<g transform='translate(82.5, 48)'> <g transform='translate(82.5, 48)'>
<text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#c9ccd4' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='700' font-size='28px' font-style='normal' style='opacity: 0; animation: fadein 0.5s linear forwards 0.6s'> <text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#c9ccd4' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='700' font-size='28px' font-style='normal' style='opacity: 1'>
344 344
</text> </text>
</g> </g>
<!-- Total Contributions label --> <!-- Total Contributions label -->
<g transform='translate(82.5, 84)'> <g transform='translate(82.5, 84)'>
<text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#9aa0aa' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='400' font-size='14px' font-style='normal' style='opacity: 0; animation: fadein 0.5s linear forwards 0.7s'> <text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#b9bec8' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='400' font-size='14px' font-style='normal' style='opacity: 1'>
Total Contributions Total Contributions
</text> </text>
</g> </g>
<!-- Total Contributions range --> <!-- Total Contributions range -->
<g transform='translate(82.5, 114)'> <g transform='translate(82.5, 114)'>
<text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#9aa0aa' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='400' font-size='12px' font-style='normal' style='opacity: 0; animation: fadein 0.5s linear forwards 0.8s'> <text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#b9bec8' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='400' font-size='12px' font-style='normal' style='opacity: 1'>
May 24, 2019 - Present May 24, 2019 - Present
</text> </text>
</g> </g>
@@ -54,31 +47,31 @@
<g style='isolation: isolate'> <g style='isolation: isolate'>
<!-- Current Streak label --> <!-- Current Streak label -->
<g transform='translate(247.5, 108)'> <g transform='translate(247.5, 108)'>
<text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#3454d1' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='700' font-size='14px' font-style='normal' style='opacity: 0; animation: fadein 0.5s linear forwards 0.9s'> <text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#3454d1' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='700' font-size='14px' font-style='normal' style='opacity: 1'>
Current Streak Current Streak
</text> </text>
</g> </g>
<!-- Current Streak range --> <!-- Current Streak range -->
<g transform='translate(247.5, 145)'> <g transform='translate(247.5, 145)'>
<text x='0' y='21' stroke-width='0' text-anchor='middle' fill='#9aa0aa' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='400' font-size='12px' font-style='normal' style='opacity: 0; animation: fadein 0.5s linear forwards 0.9s'> <text x='0' y='21' stroke-width='0' text-anchor='middle' fill='#b9bec8' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='400' font-size='12px' font-style='normal' style='opacity: 1'>
Nov 3 Nov 3
</text> </text>
</g> </g>
<!-- Ring around number --> <!-- Ring around number -->
<g mask='url(#mask_out_ring_behind_fire)'> <g mask='url(#mask_out_ring_behind_fire)'>
<circle cx='247.5' cy='71' r='40' fill='none' stroke='#3454d1' stroke-width='5' style='opacity: 0; animation: fadein 0.5s linear forwards 0.4s'></circle> <circle cx='247.5' cy='71' r='40' fill='none' stroke='#3454d1' stroke-width='5' style='opacity: 1'></circle>
</g> </g>
<!-- Fire icon --> <!-- Fire icon -->
<g transform='translate(247.5, 19.5)' stroke-opacity='0' style='opacity: 0; animation: fadein 0.5s linear forwards 0.6s'> <g transform='translate(247.5, 19.5)' stroke-opacity='0' style='opacity: 1'>
<path d='M -12 -0.5 L 15 -0.5 L 15 23.5 L -12 23.5 L -12 -0.5 Z' fill='none'/> <path d='M -12 -0.5 L 15 -0.5 L 15 23.5 L -12 23.5 L -12 -0.5 Z' fill='none'/>
<path d='M 1.5 0.67 C 1.5 0.67 2.24 3.32 2.24 5.47 C 2.24 7.53 0.89 9.2 -1.17 9.2 C -3.23 9.2 -4.79 7.53 -4.79 5.47 L -4.76 5.11 C -6.78 7.51 -8 10.62 -8 13.99 C -8 18.41 -4.42 22 0 22 C 4.42 22 8 18.41 8 13.99 C 8 8.6 5.41 3.79 1.5 0.67 Z M -0.29 19 C -2.07 19 -3.51 17.6 -3.51 15.86 C -3.51 14.24 -2.46 13.1 -0.7 12.74 C 1.07 12.38 2.9 11.53 3.92 10.16 C 4.31 11.45 4.51 12.81 4.51 14.2 C 4.51 16.85 2.36 19 -0.29 19 Z' fill='#3454d1' stroke-opacity='0'/> <path d='M 1.5 0.67 C 1.5 0.67 2.24 3.32 2.24 5.47 C 2.24 7.53 0.89 9.2 -1.17 9.2 C -3.23 9.2 -4.79 7.53 -4.79 5.47 L -4.76 5.11 C -6.78 7.51 -8 10.62 -8 13.99 C -8 18.41 -4.42 22 0 22 C 4.42 22 8 18.41 8 13.99 C 8 8.6 5.41 3.79 1.5 0.67 Z M -0.29 19 C -2.07 19 -3.51 17.6 -3.51 15.86 C -3.51 14.24 -2.46 13.1 -0.7 12.74 C 1.07 12.38 2.9 11.53 3.92 10.16 C 4.31 11.45 4.51 12.81 4.51 14.2 C 4.51 16.85 2.36 19 -0.29 19 Z' fill='#3454d1' stroke-opacity='0'/>
</g> </g>
<!-- Current Streak big number --> <!-- Current Streak big number -->
<g transform='translate(247.5, 48)'> <g transform='translate(247.5, 48)'>
<text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#c9ccd4' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='700' font-size='28px' font-style='normal' style='animation: currstreak 0.6s linear forwards'> <text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#c9ccd4' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='700' font-size='28px' font-style='normal' >
0 0
</text> </text>
</g> </g>
@@ -87,21 +80,21 @@
<g style='isolation: isolate'> <g style='isolation: isolate'>
<!-- Longest Streak big number --> <!-- Longest Streak big number -->
<g transform='translate(412.5, 48)'> <g transform='translate(412.5, 48)'>
<text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#c9ccd4' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='700' font-size='28px' font-style='normal' style='opacity: 0; animation: fadein 0.5s linear forwards 1.2s'> <text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#c9ccd4' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='700' font-size='28px' font-style='normal' style='opacity: 1'>
6 6
</text> </text>
</g> </g>
<!-- Longest Streak label --> <!-- Longest Streak label -->
<g transform='translate(412.5, 84)'> <g transform='translate(412.5, 84)'>
<text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#9aa0aa' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='400' font-size='14px' font-style='normal' style='opacity: 0; animation: fadein 0.5s linear forwards 1.3s'> <text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#b9bec8' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='400' font-size='14px' font-style='normal' style='opacity: 1'>
Longest Streak Longest Streak
</text> </text>
</g> </g>
<!-- Longest Streak range --> <!-- Longest Streak range -->
<g transform='translate(412.5, 114)'> <g transform='translate(412.5, 114)'>
<text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#9aa0aa' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='400' font-size='12px' font-style='normal' style='opacity: 0; animation: fadein 0.5s linear forwards 1.4s'> <text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#b9bec8' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='400' font-size='12px' font-style='normal' style='opacity: 1'>
Oct 19 - Oct 24 Oct 19 - Oct 24
</text> </text>
</g> </g>

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

+13 -20
View File
@@ -1,15 +1,8 @@
<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' <svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'
style='isolation: isolate' viewBox='0 0 495 195' width='495px' height='195px' direction='ltr'> style='isolation: isolate' viewBox='0 0 495 195' width='495px' height='195px' direction='ltr'>
<style> <style>
@keyframes currstreak {
0% { font-size: 3px; opacity: 0.2; }
80% { font-size: 34px; opacity: 1; }
100% { font-size: 28px; opacity: 1; }
}
@keyframes fadein {
0% { opacity: 0; }
100% { opacity: 1; }
}
</style> </style>
<defs> <defs>
<clipPath id='outer_rectangle'> <clipPath id='outer_rectangle'>
@@ -32,21 +25,21 @@
<g style='isolation: isolate'> <g style='isolation: isolate'>
<!-- Total Contributions big number --> <!-- Total Contributions big number -->
<g transform='translate(82.5, 48)'> <g transform='translate(82.5, 48)'>
<text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#33343a' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='700' font-size='28px' font-style='normal' style='opacity: 0; animation: fadein 0.5s linear forwards 0.6s'> <text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#33343a' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='700' font-size='28px' font-style='normal' style='opacity: 1'>
344 344
</text> </text>
</g> </g>
<!-- Total Contributions label --> <!-- Total Contributions label -->
<g transform='translate(82.5, 84)'> <g transform='translate(82.5, 84)'>
<text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#8b8f98' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='400' font-size='14px' font-style='normal' style='opacity: 0; animation: fadein 0.5s linear forwards 0.7s'> <text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#3f424a' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='400' font-size='14px' font-style='normal' style='opacity: 1'>
Total Contributions Total Contributions
</text> </text>
</g> </g>
<!-- Total Contributions range --> <!-- Total Contributions range -->
<g transform='translate(82.5, 114)'> <g transform='translate(82.5, 114)'>
<text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#8b8f98' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='400' font-size='12px' font-style='normal' style='opacity: 0; animation: fadein 0.5s linear forwards 0.8s'> <text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#3f424a' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='400' font-size='12px' font-style='normal' style='opacity: 1'>
May 24, 2019 - Present May 24, 2019 - Present
</text> </text>
</g> </g>
@@ -54,31 +47,31 @@
<g style='isolation: isolate'> <g style='isolation: isolate'>
<!-- Current Streak label --> <!-- Current Streak label -->
<g transform='translate(247.5, 108)'> <g transform='translate(247.5, 108)'>
<text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#3454d1' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='700' font-size='14px' font-style='normal' style='opacity: 0; animation: fadein 0.5s linear forwards 0.9s'> <text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#3454d1' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='700' font-size='14px' font-style='normal' style='opacity: 1'>
Current Streak Current Streak
</text> </text>
</g> </g>
<!-- Current Streak range --> <!-- Current Streak range -->
<g transform='translate(247.5, 145)'> <g transform='translate(247.5, 145)'>
<text x='0' y='21' stroke-width='0' text-anchor='middle' fill='#8b8f98' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='400' font-size='12px' font-style='normal' style='opacity: 0; animation: fadein 0.5s linear forwards 0.9s'> <text x='0' y='21' stroke-width='0' text-anchor='middle' fill='#3f424a' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='400' font-size='12px' font-style='normal' style='opacity: 1'>
Nov 3 Nov 3
</text> </text>
</g> </g>
<!-- Ring around number --> <!-- Ring around number -->
<g mask='url(#mask_out_ring_behind_fire)'> <g mask='url(#mask_out_ring_behind_fire)'>
<circle cx='247.5' cy='71' r='40' fill='none' stroke='#3454d1' stroke-width='5' style='opacity: 0; animation: fadein 0.5s linear forwards 0.4s'></circle> <circle cx='247.5' cy='71' r='40' fill='none' stroke='#3454d1' stroke-width='5' style='opacity: 1'></circle>
</g> </g>
<!-- Fire icon --> <!-- Fire icon -->
<g transform='translate(247.5, 19.5)' stroke-opacity='0' style='opacity: 0; animation: fadein 0.5s linear forwards 0.6s'> <g transform='translate(247.5, 19.5)' stroke-opacity='0' style='opacity: 1'>
<path d='M -12 -0.5 L 15 -0.5 L 15 23.5 L -12 23.5 L -12 -0.5 Z' fill='none'/> <path d='M -12 -0.5 L 15 -0.5 L 15 23.5 L -12 23.5 L -12 -0.5 Z' fill='none'/>
<path d='M 1.5 0.67 C 1.5 0.67 2.24 3.32 2.24 5.47 C 2.24 7.53 0.89 9.2 -1.17 9.2 C -3.23 9.2 -4.79 7.53 -4.79 5.47 L -4.76 5.11 C -6.78 7.51 -8 10.62 -8 13.99 C -8 18.41 -4.42 22 0 22 C 4.42 22 8 18.41 8 13.99 C 8 8.6 5.41 3.79 1.5 0.67 Z M -0.29 19 C -2.07 19 -3.51 17.6 -3.51 15.86 C -3.51 14.24 -2.46 13.1 -0.7 12.74 C 1.07 12.38 2.9 11.53 3.92 10.16 C 4.31 11.45 4.51 12.81 4.51 14.2 C 4.51 16.85 2.36 19 -0.29 19 Z' fill='#3454d1' stroke-opacity='0'/> <path d='M 1.5 0.67 C 1.5 0.67 2.24 3.32 2.24 5.47 C 2.24 7.53 0.89 9.2 -1.17 9.2 C -3.23 9.2 -4.79 7.53 -4.79 5.47 L -4.76 5.11 C -6.78 7.51 -8 10.62 -8 13.99 C -8 18.41 -4.42 22 0 22 C 4.42 22 8 18.41 8 13.99 C 8 8.6 5.41 3.79 1.5 0.67 Z M -0.29 19 C -2.07 19 -3.51 17.6 -3.51 15.86 C -3.51 14.24 -2.46 13.1 -0.7 12.74 C 1.07 12.38 2.9 11.53 3.92 10.16 C 4.31 11.45 4.51 12.81 4.51 14.2 C 4.51 16.85 2.36 19 -0.29 19 Z' fill='#3454d1' stroke-opacity='0'/>
</g> </g>
<!-- Current Streak big number --> <!-- Current Streak big number -->
<g transform='translate(247.5, 48)'> <g transform='translate(247.5, 48)'>
<text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#33343a' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='700' font-size='28px' font-style='normal' style='animation: currstreak 0.6s linear forwards'> <text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#33343a' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='700' font-size='28px' font-style='normal' >
0 0
</text> </text>
</g> </g>
@@ -87,21 +80,21 @@
<g style='isolation: isolate'> <g style='isolation: isolate'>
<!-- Longest Streak big number --> <!-- Longest Streak big number -->
<g transform='translate(412.5, 48)'> <g transform='translate(412.5, 48)'>
<text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#33343a' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='700' font-size='28px' font-style='normal' style='opacity: 0; animation: fadein 0.5s linear forwards 1.2s'> <text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#33343a' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='700' font-size='28px' font-style='normal' style='opacity: 1'>
6 6
</text> </text>
</g> </g>
<!-- Longest Streak label --> <!-- Longest Streak label -->
<g transform='translate(412.5, 84)'> <g transform='translate(412.5, 84)'>
<text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#8b8f98' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='400' font-size='14px' font-style='normal' style='opacity: 0; animation: fadein 0.5s linear forwards 1.3s'> <text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#3f424a' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='400' font-size='14px' font-style='normal' style='opacity: 1'>
Longest Streak Longest Streak
</text> </text>
</g> </g>
<!-- Longest Streak range --> <!-- Longest Streak range -->
<g transform='translate(412.5, 114)'> <g transform='translate(412.5, 114)'>
<text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#8b8f98' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='400' font-size='12px' font-style='normal' style='opacity: 0; animation: fadein 0.5s linear forwards 1.4s'> <text x='0' y='32' stroke-width='0' text-anchor='middle' fill='#3f424a' stroke='none' font-family='"Segoe UI", Ubuntu, sans-serif' font-weight='400' font-size='12px' font-style='normal' style='opacity: 1'>
Oct 19 - Oct 24 Oct 19 - Oct 24
</text> </text>
</g> </g>

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

+7 -1
View File
@@ -16,6 +16,11 @@ if (MODE === 'skip') {
const SKIP_PREFIX = ['/admin/', '/photos-originals/']; // admin=CMS 路由;photos-originals=部署期 rsync 叠加层(不在 dist,另有部署链保障) const SKIP_PREFIX = ['/admin/', '/photos-originals/']; // admin=CMS 路由;photos-originals=部署期 rsync 叠加层(不在 dist,另有部署链保障)
const SKIP_FILE = new Set(['/404.html']); // Astro 404 页不参与配对 const SKIP_FILE = new Set(['/404.html']); // Astro 404 页不参与配对
// 完全不参与检查的产物(连站内链接也不查):仅用于一次性预览路由。
// 书架方案预览页没有 en 对应页,而 Header 的语言切换会自动生成 /en/… 链接,
// 会误报「断链 + 配对缺」;该路由在用户选定方案后即删除。
const SKIP_ALL = new Set(['/books-preview/index.html']);
function walk(dir, out = []) { function walk(dir, out = []) {
for (const e of readdirSync(dir)) { for (const e of readdirSync(dir)) {
const p = join(dir, e); const p = join(dir, e);
@@ -48,8 +53,9 @@ const fails = [];
const warns = []; const warns = [];
for (const f of files) { for (const f of files) {
const html = readFileSync(f, 'utf8');
const rel = f.slice(DIST.length) || '/'; const rel = f.slice(DIST.length) || '/';
if (SKIP_ALL.has(rel)) continue;
const html = readFileSync(f, 'utf8');
// 站内 <a href>:仅根相对路径;#锚点/mailto/tel/外部/data: 全跳过 // 站内 <a href>:仅根相对路径;#锚点/mailto/tel/外部/data: 全跳过
for (const m of html.matchAll(/<a\s[^>]*href="([^"]*)"/g)) { for (const m of html.matchAll(/<a\s[^>]*href="([^"]*)"/g)) {
+24 -3
View File
@@ -18,8 +18,9 @@ const UA = 'wait4x-blog-assets/1.0 (+https://wait4x.online)';
const ACCENT = '3454d1'; const ACCENT = '3454d1';
const INK_LIGHT = '33343a'; const INK_LIGHT = '33343a';
const INK_DARK = 'c9ccd4'; const INK_DARK = 'c9ccd4';
const MUTED_LIGHT = '8b8f98'; // 次级文字(Total Contributions / dates)也用 ink 档:旧的 #8b8f98/#9aa0aa 缩小后对比不足 4.5:1
const MUTED_DARK = '9aa0aa'; const MUTED_LIGHT = '3f424a';
const MUTED_DARK = 'b9bec8';
/** 同一张卡按明暗两套参数各存一份(组件内用 CSS 切换,零运行时请求) */ /** 同一张卡按明暗两套参数各存一份(组件内用 CSS 切换,零运行时请求) */
const ASSETS = [ 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 */ /** 10s 超时抓取;返回 SVG 文本或 null */
async function grab(url) { async function grab(url) {
try { try {
@@ -78,7 +99,7 @@ async function grab(url) {
console.warn(`[assets] 非 SVG 响应,已跳过: ${url.slice(0, 90)}…`); console.warn(`[assets] 非 SVG 响应,已跳过: ${url.slice(0, 90)}…`);
return null; return null;
} }
return text; return freezeAnimations(text);
} catch (err) { } catch (err) {
console.warn(`[assets] 抓取失败(${err.name === 'TimeoutError' ? '超时' : err.message}): ${url.slice(0, 90)}…`); console.warn(`[assets] 抓取失败(${err.name === 'TimeoutError' ? '超时' : err.message}): ${url.slice(0, 90)}…`);
return null; return null;
+19 -6
View File
@@ -82,6 +82,8 @@ for (const [key, label] of [
dark: hasAsset(`${key}-dark.svg`) ? assetPath(`${key}-dark.svg`) : undefined, dark: hasAsset(`${key}-dark.svg`) ? assetPath(`${key}-dark.svg`) : undefined,
}); });
} }
// 只有一张非通栏卡时让它占满整行(否则右半空档;stats/top-langs 上游 503 时就是这种情形)
const soloCard = boardCards.length === 1;
const snakeCard = hasAsset('snake.svg') const snakeCard = hasAsset('snake.svg')
? { label: locale === 'zh' ? '贡献蛇' : 'Contribution snake', src: assetPath('snake.svg') } ? { label: locale === 'zh' ? '贡献蛇' : 'Contribution snake', src: assetPath('snake.svg') }
: null; : null;
@@ -130,6 +132,7 @@ const L = {
</div> </div>
</div> </div>
{/* 统计三格:关注者 / 公开仓库 / 总星标(贡献数走热力图,不在此重复) */}
{(stats || totalStars !== null) && ( {(stats || totalStars !== null) && (
<dl class="pf-stats mono"> <dl class="pf-stats mono">
{stats && ( {stats && (
@@ -165,7 +168,12 @@ const L = {
<p class="pf-board-title mono">{locale === 'zh' ? '数据板' : 'Data board'}</p> <p class="pf-board-title mono">{locale === 'zh' ? '数据板' : 'Data board'}</p>
<div class="pf-board-grid"> <div class="pf-board-grid">
{boardCards.map((card) => ( {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" /> <img class="pf-card-img only-light" src={card.light} alt={card.label} loading="lazy" decoding="async" />
{card.dark && ( {card.dark && (
<img class="pf-card-img only-dark" src={card.dark} alt={card.label} loading="lazy" decoding="async" /> <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; display: flex;
gap: var(--space-2); gap: var(--space-2);
list-style: none; list-style: none;
margin: var(--space-2) 0 0; /* 光学对齐:.tap 的 44px 热区带左右 padding,整体左移使图标视觉左缘对齐正文列 */
margin: var(--space-2) 0 0 -0.55rem;
padding: 0; padding: 0;
} }
.pf-socials a { color: var(--ink-3); display: inline-flex; } .pf-socials a { color: var(--ink-3); display: inline-flex; }
@@ -281,7 +290,6 @@ const L = {
padding: 0; padding: 0;
display: grid; display: grid;
gap: 0.35rem; gap: 0.35rem;
max-width: 32rem;
} }
.pf-lang { .pf-lang {
display: grid; display: grid;
@@ -323,9 +331,11 @@ const L = {
letter-spacing: 0.04em; letter-spacing: 0.04em;
margin-bottom: var(--space-2); margin-bottom: var(--space-2);
} }
/* 自适应列:只有 1 张卡时自动占满整行(避免右半空档),≥2 张时并排;
320px 下限保证车卡内 SVG 不被压到字号过小 */
.pf-board-grid { .pf-board-grid {
display: grid; display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: var(--space-2); gap: var(--space-2);
} }
.pf-card { .pf-card {
@@ -339,7 +349,7 @@ const L = {
align-items: center; align-items: center;
gap: 4px; 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-img { max-width: 100%; height: auto; display: block; }
.pf-card figcaption { .pf-card figcaption {
font-size: 11px; font-size: 11px;
@@ -349,9 +359,12 @@ const L = {
/* 明暗两版自托管 SVG:默认只显示亮色,暗色主题切到暗色版 */ /* 明暗两版自托管 SVG:默认只显示亮色,暗色主题切到暗色版 */
/* 贡献蛇:SVG 内部用自带 CSS 变量(绿阶 + #ebedf0 空格),<img> 无法改写内部配色 → /* 贡献蛇: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); } .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 { :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; } .only-dark { display: none; }
+2 -1
View File
@@ -88,8 +88,9 @@ try {
.repo-desc { .repo-desc {
font-size: 13px; font-size: 13px;
color: var(--ink-3); color: var(--ink-3);
/* 居中后长描述限行宽,避免整行摊平散架 */ /* 居中后长描述限行宽,避免整行摊平散架;pretty 减少末行孤词 */
max-width: 34em; max-width: 34em;
text-wrap: pretty;
} }
.repo-meta { display: flex; gap: var(--space-3); font-size: 12px; color: var(--ink-3); margin-top: 4px; } .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; } .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); margin: 0 0 var(--space-2);
min-height: 1.6em; /* 固定行高,打字期间不推挤下方内容 */ min-height: 1.6em; /* 固定行高,打字期间不推挤下方内容 */
display: flex; display: flex;
align-items: center; /* baseline:光标与末行基线对齐(center 会在两行时把光标拉到中间) */
gap: 1px; align-items: baseline;
gap: 2px;
} }
.typing-text { white-space: pre-wrap; } .typing-text { white-space: pre-wrap; }
.typing-caret { .typing-caret {
display: inline-block; display: inline-block;
width: 0.5em; width: 2px; /* 细竖条:不参与折行计算 */
height: 1em; height: 1em;
align-self: baseline;
transform: translateY(0.12em);
background: var(--accent); background: var(--accent);
animation: caret-blink 1.05s steps(1) infinite; animation: caret-blink 1.05s steps(1) infinite;
} }
@@ -100,6 +103,10 @@ const first = safe[0] ?? '';
0%, 49% { opacity: 1; } 0%, 49% { opacity: 1; }
50%, 100% { opacity: 0; } 50%, 100% { opacity: 0; }
} }
/* 窄屏预留两行高度:长句折行时不再每轮下推再弹回(实测 390 视口曾推挤 ~21px) */
@media (max-width: 560px) {
.typing { min-height: calc(1.6em * 2); }
}
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.typing-caret { animation: none; opacity: 0.6; } .typing-caret { animation: none; opacity: 0.6; }
} }