diff --git a/content/site/opensource.yaml b/content/site/opensource.yaml index f937b34..5041226 100644 --- a/content/site/opensource.yaml +++ b/content/site/opensource.yaml @@ -6,8 +6,8 @@ typing: en: Self-hosting whatever can be self-hosted - zh: 拥抱开源,热爱开源 en: Embrace open source, love open source - - zh: 把读过的书、按下的快门都放进同一个工作台 - en: Books read and shutters pressed, gathered in one workbench + - zh: 书与快门都在一个工作台 + en: Books and shutters, one workbench blurb: zh: 本站本身即开源: diff --git a/public/github-assets/manifest.json b/public/github-assets/manifest.json index aaff915..eec4c46 100644 --- a/public/github-assets/manifest.json +++ b/public/github-assets/manifest.json @@ -1,20 +1,20 @@ { "assets": { "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", - "fetchedAt": "2026-11-03T03:07:28.521Z", + "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:39:51.755Z", "label": "连续贡献", "theme": "light" }, "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", - "fetchedAt": "2026-11-03T03:07:28.521Z", + "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:39:51.755Z", "label": "连续贡献", "theme": "dark" }, "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": "贡献蛇", "theme": "any" } diff --git a/public/github-assets/snake.svg b/public/github-assets/snake.svg index 2527054..0f5c918 100644 --- a/public/github-assets/snake.svg +++ b/public/github-assets/snake.svg @@ -1 +1 @@ -Generated with https://github.com/Platane/snk \ No newline at end of file +Generated with https://github.com/Platane/snk \ No newline at end of file diff --git a/public/github-assets/streak-dark.svg b/public/github-assets/streak-dark.svg index 8f62d84..060b351 100644 --- a/public/github-assets/streak-dark.svg +++ b/public/github-assets/streak-dark.svg @@ -1,15 +1,8 @@ @@ -32,21 +25,21 @@ - + 344 - + Total Contributions - + May 24, 2019 - Present @@ -54,31 +47,31 @@ - + Current Streak - + Nov 3 - + - + - + 0 @@ -87,21 +80,21 @@ - + 6 - + Longest Streak - + Oct 19 - Oct 24 diff --git a/public/github-assets/streak-light.svg b/public/github-assets/streak-light.svg index 29250c7..54aaa17 100644 --- a/public/github-assets/streak-light.svg +++ b/public/github-assets/streak-light.svg @@ -1,15 +1,8 @@ @@ -32,21 +25,21 @@ - + 344 - + Total Contributions - + May 24, 2019 - Present @@ -54,31 +47,31 @@ - + Current Streak - + Nov 3 - + - + - + 0 @@ -87,21 +80,21 @@ - + 6 - + Longest Streak - + Oct 19 - Oct 24 diff --git a/scripts/build-perf/content-lint.mjs b/scripts/build-perf/content-lint.mjs index 4d7aae2..a424580 100644 --- a/scripts/build-perf/content-lint.mjs +++ b/scripts/build-perf/content-lint.mjs @@ -16,6 +16,11 @@ if (MODE === 'skip') { const SKIP_PREFIX = ['/admin/', '/photos-originals/']; // admin=CMS 路由;photos-originals=部署期 rsync 叠加层(不在 dist,另有部署链保障) 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 = []) { for (const e of readdirSync(dir)) { const p = join(dir, e); @@ -48,8 +53,9 @@ const fails = []; const warns = []; for (const f of files) { - const html = readFileSync(f, 'utf8'); const rel = f.slice(DIST.length) || '/'; + if (SKIP_ALL.has(rel)) continue; + const html = readFileSync(f, 'utf8'); // 站内 :仅根相对路径;#锚点/mailto/tel/外部/data: 全跳过 for (const m of html.matchAll(/]*href="([^"]*)"/g)) { diff --git a/scripts/build-perf/fetch-readme-assets.mjs b/scripts/build-perf/fetch-readme-assets.mjs index f12763f..37b5f88 100644 --- a/scripts/build-perf/fetch-readme-assets.mjs +++ b/scripts/build-perf/fetch-readme-assets.mjs @@ -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; diff --git a/src/components/geek/GithubProfile.astro b/src/components/geek/GithubProfile.astro index ff19d4a..4ede507 100644 --- a/src/components/geek/GithubProfile.astro +++ b/src/components/geek/GithubProfile.astro @@ -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 = { + {/* 统计三格:关注者 / 公开仓库 / 总星标(贡献数走热力图,不在此重复) */} {(stats || totalStars !== null) && (
{stats && ( @@ -165,7 +168,12 @@ const L = {

{locale === 'zh' ? '数据板' : 'Data board'}

{boardCards.map((card) => ( -
+
{card.label} {card.dark && ( {card.label} @@ -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 空格), 无法改写内部配色 → 滤镜归一到站点灰阶;暗色下反相,避免浅灰格子贴在深底上刺眼(想去掉绿→灰只改这一行) */ + /* 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; } diff --git a/src/components/geek/ProjectWall.astro b/src/components/geek/ProjectWall.astro index bcc1f38..68afdb2 100644 --- a/src/components/geek/ProjectWall.astro +++ b/src/components/geek/ProjectWall.astro @@ -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; } diff --git a/src/components/geek/TypingLines.astro b/src/components/geek/TypingLines.astro index e2af038..a12145c 100644 --- a/src/components/geek/TypingLines.astro +++ b/src/components/geek/TypingLines.astro @@ -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; } }