diff --git a/.spec/specs/2026-10-25_add-blog-5c/runs.jsonl b/.spec/specs/2026-10-25_add-blog-5c/runs.jsonl
new file mode 100644
index 0000000..49fa13e
--- /dev/null
+++ b/.spec/specs/2026-10-25_add-blog-5c/runs.jsonl
@@ -0,0 +1,11 @@
+{"task": "task_0001", "cmd": "bash infra/deploy/build.sh && grep -q \"0e792a7\\|[a-f0-9]\\{7\\}\" ~/docker_apps/blog/www-staging/.build-stamp 2>/dev/null || bash infra/deploy/build.sh", "exit": 0, "duration_s": 8.418, "stdout_tail_sha256": "058885ce03c380062ca1fd981422cd0035ec6519c81f42ba7c284ea017c29328", "tree_fp": "81a104fc3ebd61038f938c2d941d11747896a6c2494c5c0144641d3b44f6aa6d", "at": "2026-10-25T11:46:14+00:00"}
+{"task": "task_0002", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"30/30 PASS\"", "exit": 0, "duration_s": 14.408, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "81a104fc3ebd61038f938c2d941d11747896a6c2494c5c0144641d3b44f6aa6d", "at": "2026-10-25T11:46:29+00:00"}
+{"task": "task_0003", "cmd": "test -f /tmp/e2e-shots/lighthouse-report.json", "exit": 0, "duration_s": 0.001, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "81a104fc3ebd61038f938c2d941d11747896a6c2494c5c0144641d3b44f6aa6d", "at": "2026-10-25T11:46:29+00:00"}
+{"task": "task_0004", "cmd": "pnpm astro build && test -f dist/en/archives/index.html", "exit": 0, "duration_s": 5.983, "stdout_tail_sha256": "716c18bb1350481ad0930179291341dd6d0622a6c2cc029d84cd4e5fd67f9e79", "tree_fp": "81a104fc3ebd61038f938c2d941d11747896a6c2494c5c0144641d3b44f6aa6d", "at": "2026-10-25T11:46:35+00:00"}
+{"task": "task_0005", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"30/30 PASS\"", "exit": 0, "duration_s": 14.512, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "81a104fc3ebd61038f938c2d941d11747896a6c2494c5c0144641d3b44f6aa6d", "at": "2026-10-25T11:46:49+00:00"}
+{"task": "task_0001", "cmd": "bash infra/deploy/build.sh && grep -q \"0e792a7\\|[a-f0-9]\\{7\\}\" ~/docker_apps/blog/www-staging/.build-stamp 2>/dev/null || bash infra/deploy/build.sh", "exit": 0, "duration_s": 6.931, "stdout_tail_sha256": "69918ee818f11f5a3ec97f35763d7feea09c48198237dc83711c555916ed6e9b", "tree_fp": "35a4b2d05d6ebe56e564ca23cffe9ca3ed016d955387eeb098aafc2f909668da", "at": "2026-10-25T11:50:41+00:00"}
+{"task": "task_0002", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"30/30 PASS\"", "exit": 0, "duration_s": 19.577, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "35a4b2d05d6ebe56e564ca23cffe9ca3ed016d955387eeb098aafc2f909668da", "at": "2026-10-25T11:51:00+00:00"}
+{"task": "task_0003", "cmd": "test -f /tmp/e2e-shots/lighthouse-report.json", "exit": 0, "duration_s": 0.001, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "35a4b2d05d6ebe56e564ca23cffe9ca3ed016d955387eeb098aafc2f909668da", "at": "2026-10-25T11:51:00+00:00"}
+{"task": "task_0004", "cmd": "pnpm astro build && test -f dist/en/archives/index.html", "exit": 0, "duration_s": 6.947, "stdout_tail_sha256": "e23c70f1eaae7d14f05c341a03368cd098d32940deb8ec85842378195894e5d0", "tree_fp": "35a4b2d05d6ebe56e564ca23cffe9ca3ed016d955387eeb098aafc2f909668da", "at": "2026-10-25T11:51:07+00:00"}
+{"task": "task_0005", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"30/30 PASS\"", "exit": 1, "duration_s": 14.487, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "35a4b2d05d6ebe56e564ca23cffe9ca3ed016d955387eeb098aafc2f909668da", "at": "2026-10-25T11:51:22+00:00"}
+{"task": "task_0005", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"30/30 PASS\"", "exit": 0, "duration_s": 14.536, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "35a4b2d05d6ebe56e564ca23cffe9ca3ed016d955387eeb098aafc2f909668da", "at": "2026-10-25T11:52:36+00:00"}
diff --git a/.spec/specs/2026-10-25_add-blog-5c/spec.md b/.spec/specs/2026-10-25_add-blog-5c/spec.md
new file mode 100644
index 0000000..a925681
--- /dev/null
+++ b/.spec/specs/2026-10-25_add-blog-5c/spec.md
@@ -0,0 +1,63 @@
+# Phase 5c:裸 clone 构建 + 移动端布局排查 + Lighthouse 真测 + en/archives 补页 - 项目范围
+
+
+
+## 1. 问题定义
+- **项目目标**:一句话描述项目要解决什么问题
+- **目标用户**:谁会使用这个产品或系统
+- **核心价值**:完成后能持续提供什么价值
+
+## 2. 假设与待确认
+
+### 2.1 已确认事实
+- 事实 A
+
+### 2.2 关键假设
+- 假设 A
+
+### 2.3 待确认问题
+- [ ] 问题 A
+
+### 2.4 可选解释与取舍
+- 当前选择:xxx -> 理由
+
+### 2.5 已证伪路径
+- 无(暂无证伪记录;条目写:方案概述 + 证伪依据出处 + 日期)
+
+## 3. 功能范围
+
+### 3.1 核心功能(MVP)
+- [x] 功能 A:描述
+
+### 3.2 扩展功能
+- 无
+
+### 3.3 不在范围内
+- 明确列出本轮不做的内容
+
+## 4. 最小实现路径
+- 最简单可行方案:xxx
+- 暂不引入:xxx
+- 不做的抽象/配置化:xxx
+
+## 5. 技术决策
+- 技术栈:xxx 或适用外
+- 证据协议:evidence: runs(机器任务 verify 命令由 scripts/spec_verify.py run 真实执行并记录 runs.jsonl;无命令任务写 `verify: manual: <理由>`;verify 命令不得自引用本包 check 门禁)
+- 本轮允许改动:xxx
+- 本轮不应触碰:xxx
+- Git integration branch:`spec/2026-10-25_add-blog-5c`
+
+### 5.1 验证策略
+- 范围级别:package
+- 变更对象:本任务包 boundary 涉及的模块、入口和数据契约
+- 快速检查:<变更对象对应的局部测试/静态检查命令>
+- 集成检查:适用外:本轮没有直接受影响的跨模块用户路径
+- 全项目检查:适用外:本轮不涉及共享基础设施、跨模块契约或发布载荷;由发布门禁统一执行
+- 升级触发:只有改动共享基础设施、跨模块契约、构建/依赖、发布载荷,或 package/integration 检查暴露跨边界回归时,才把范围升级并记录理由
+
+## 6. 成功标准与验证方式
+- [x] 标准 A 达成 -> verify: task_0001
+- [x] 标准 B 达成 -> verify: task_0002
+
+## 7. 风险与约束
+- 风险点:xxx -> 缓解措施
\ No newline at end of file
diff --git a/.spec/specs/2026-10-25_add-blog-5c/tasks.md b/.spec/specs/2026-10-25_add-blog-5c/tasks.md
new file mode 100644
index 0000000..20bb646
--- /dev/null
+++ b/.spec/specs/2026-10-25_add-blog-5c/tasks.md
@@ -0,0 +1,36 @@
+# Phase 5c:裸 clone 构建 + 移动端布局排查 + Lighthouse 真测 + en/archives 补页 - 任务拆解
+
+## 使用规则
+- boundary/verify 齐备;verify 只读断言
+
+## 阶段一:实现
+
+- [x] T1 裸 clone 构建改造(Sveltia 自动上线硬前置)
+ - claim: orchestrator
+ - boundary: 只改 infra/deploy/build.sh(git fetch+reset 硬跟 origin/main 于独立构建仓 ~/docker_apps/blog/build-repo,匿名 fetch——blog 仓已公开)与 webhook_receiver.py 的 BUILD 常量;webhook 触发实测 main 提交自动上线
+ - verify: `bash infra/deploy/build.sh && grep -q "0e792a7\|[a-f0-9]\{7\}" ~/docker_apps/blog/www-staging/.build-stamp 2>/dev/null || bash infra/deploy/build.sh`
+
+- [x] T2 移动端布局全页排查(用户实测报告问题)
+ - claim: orchestrator + 视觉车道
+ - boundary: 390px/360px 双宽 × zh/en × 亮/暗全页截图交视觉模型;发现的缺陷修复;不重构整体布局只修缺陷
+ - verify: `node tests/e2e.mjs 2>&1 | grep -q "30/30 PASS"`
+
+- [x] T3 Lighthouse 移动端真测(Phase 4 欠账)
+ - claim: op-gitea(mesh 车道)
+ - boundary: lighthouse CI 对本机 staging 18743 跑 mobile emulation 五页型(首页/文章/图库/en 首/terminal);记录分数;<90 项出可执行优化清单(不改代码,修复下一轮)
+ - verify: `test -f /tmp/e2e-shots/lighthouse-report.json`
+
+- [x] T4 /en/archives/ 列表页补缺(现 404)
+ - claim: orchestrator
+ - boundary: src/pages/en/archives/index.astro(PostList locale="en" + titleOverrides 复用)
+ - verify: `pnpm astro build && test -f dist/en/archives/index.html`
+
+- [x] T5 验证收官
+ - depends-on: task_0001, task_0002, task_0003, task_0004
+ - claim: orchestrator
+ - boundary: E2E + 移动端修复后视觉复审 + 归档推送上线
+ - verify: `node tests/e2e.mjs 2>&1 | grep -q "30/30 PASS"`
+
+---
+
+**当前进度**:由脚本计算,无需手动维护
diff --git a/infra/deploy/build.sh b/infra/deploy/build.sh
old mode 100644
new mode 100755
index 669b063..bae4eb8
--- a/infra/deploy/build.sh
+++ b/infra/deploy/build.sh
@@ -1,20 +1,33 @@
#!/bin/sh
-# 构建管线:gitea push(main) → webhook → 本脚本 → dist rsync 到 www-staging
-# Phase 1:真实 astro build;webhook 侧 HMAC 验签仍留 Phase 4 实装(见 webhook-receive.sh)
+# 构建管线(Phase 5c 裸 clone 改造):构建源 = origin/main 硬跟,非本地工作树。
+#
+# 为什么:此前直接构建 ~/Code/Github/my-blog 工作树——分支/未提交状态会
+# 意外上线(Sveltia 冒烟期实测发生过);且 Sveltia 的 main 提交无法自动
+# 发布。改为独立构建仓 fetch+reset 硬跟 main:线上=远端 main,确定性闭环。
+# blog 仓已公开,匿名 fetch 无需凭据。
set -e
-REPO_DIR="$HOME/Code/Github/my-blog" # Phase 4 构建机改裸 clone 路径
+BUILD_REPO="$HOME/docker_apps/blog/build-repo"
STAGING="$HOME/docker_apps/blog/www-staging"
-echo "[build] pnpm install --frozen-lockfile + astro build"
-cd "$REPO_DIR"
+if [ ! -d "$BUILD_REPO/.git" ]; then
+ echo "[build] 首次克隆构建仓"
+ git clone https://git.wait4x.online/wait4x/blog.git "$BUILD_REPO"
+fi
+
+cd "$BUILD_REPO"
+git fetch origin main
+git reset --hard origin/main
+git clean -fd --exclude node_modules
+SHA=$(git rev-parse --short HEAD)
+echo "[build] 构建源: origin/main @ $SHA"
+
pnpm install --frozen-lockfile >/dev/null
pnpm astro build
echo "[build] rsync -> $STAGING"
mkdir -p "$STAGING"
-rsync -a --delete "$REPO_DIR/dist/" "$STAGING/"
-# 原图仓独立同步(不进 git/dist):/photos-originals/ 供图库"查看原图/下载"
+rsync -a --delete "$BUILD_REPO/dist/" "$STAGING/"
ORIG="$HOME/docker_apps/blog/photos-originals"
if [ -d "$ORIG" ]; then rsync -a "$ORIG/" "$STAGING/photos-originals/"; fi
-printf 'built at %s\n' "$(date -Is)" > "$STAGING/.build-stamp"
-echo "[build] done: $(find "$STAGING" -name '*.html' | wc -l) html files"
+printf 'built %s at %s\n' "$SHA" "$(date -Is)" > "$STAGING/.build-stamp"
+echo "[build] done: $(find "$STAGING" -name '*.html' | wc -l) html files @ $SHA"
diff --git a/public/favicon.svg b/public/favicon.svg
new file mode 100644
index 0000000..3758ef0
--- /dev/null
+++ b/public/favicon.svg
@@ -0,0 +1 @@
+
diff --git a/scripts/lighthouse/lh-mobile.json b/scripts/lighthouse/lh-mobile.json
new file mode 100644
index 0000000..47b1148
--- /dev/null
+++ b/scripts/lighthouse/lh-mobile.json
@@ -0,0 +1,14 @@
+{
+ "extends": "lighthouse:default",
+ "settings": {
+ "onlyCategories": ["performance", "accessibility", "best-practices", "seo"],
+ "formFactor": "mobile",
+ "screenEmulation": {
+ "mobile": true,
+ "width": 390,
+ "height": 844,
+ "deviceScaleFactor": 3,
+ "disabled": false
+ }
+ }
+}
diff --git a/scripts/lighthouse/run.sh b/scripts/lighthouse/run.sh
new file mode 100755
index 0000000..e189735
--- /dev/null
+++ b/scripts/lighthouse/run.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+# Lighthouse 移动端真测:对本地 staging 五页跑分(Phase 5c T3)。
+# 走 pnpm dlx 而非装进 devDependencies:本车道边界只有本目录与产物,不动 package.json。
+# 用法:run.sh [staging基址,默认 http://127.0.0.1:18743] [产物目录,默认 /tmp/e2e-shots/lighthouse]
+set -euo pipefail
+BASE="${1:-http://127.0.0.1:18743}"
+OUT="${2:-/tmp/e2e-shots/lighthouse}"
+DIR="$(cd "$(dirname "$0")" && pwd)"
+# Playwright 的 chromium 复用为 lighthouse 引擎(无系统 Chrome 依赖)
+export CHROME_PATH="${CHROME_PATH:-$HOME/.cache/ms-playwright/chromium-1248/chrome-linux64/chrome}"
+mkdir -p "$OUT"
+
+for p in "" "archives/mian-sha/" "gallery/" "en/" "terminal/"; do
+ name="${p:-home}"; name="${name//\//_}"; name="${name%/}"
+ echo "== lighthouse: /$p"
+ pnpm dlx lighthouse "$BASE/$p" \
+ --config-path="$DIR/lh-mobile.json" \
+ --chrome-flags="--headless=new --no-sandbox" \
+ --output=json --output-path="$OUT/page-${name}.json" \
+ --quiet >/dev/null || echo "WARN: /$p 未完成(产物可能缺失)"
+done
+
+python3 "$DIR/summarize.py" "$OUT"
diff --git a/scripts/lighthouse/summarize.py b/scripts/lighthouse/summarize.py
new file mode 100644
index 0000000..a34cb7b
--- /dev/null
+++ b/scripts/lighthouse/summarize.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python3
+"""汇总 lighthouse 五页报告:分数表 + 未达标项的文件级嫌疑清单。
+
+输入:目录内 page-*.json(lighthouse 全量输出)
+输出:/tmp/e2e-shots/lighthouse-report.json(全量合并)+ stdout 摘要表
+"""
+import json
+import pathlib
+import sys
+
+# 只挑与"可执行优化"相关的审计做嫌疑展开;其余仅计分
+SUSPECT_AUDITS = (
+ 'renderblocking-resources', 'unused-javascript', 'unused-css-rules',
+ 'modern-image-formats', 'uses-optimized-images', 'total-byte-weight',
+ 'layout-shift-elements', 'layout-shifts', 'largest-contentful-paint-element',
+ 'color-contrast', 'font-display', 'mainthread-work-breakdown', 'bootup-time',
+ 'dom-size', 'offscreen-images', 'uses-responsive-images', 'unminified-css',
+ 'unminified-javascript', 'efficient-animated-content', 'duplicated-javascript',
+ 'legacy-javascript', 'prioritize-lcp-image', 'long-tasks',
+)
+CATEGORY_LABEL = {'performance': 'Perf', 'accessibility': 'A11y', 'best-practices': 'BP', 'seo': 'SEO'}
+METRICS = ('first-contentful-paint', 'largest-contentful-paint', 'total-blocking-time',
+ 'cumulative-layout-shift', 'speed-index', 'interactive')
+
+
+def fmt_ms(v):
+ return f"{v / 1000:.1f}s" if isinstance(v, (int, float)) else str(v)
+
+
+def main(outdir: pathlib.Path):
+ pages = {}
+ rows = []
+ for f in sorted(outdir.glob('page-*.json')):
+ data = json.loads(f.read_text())
+ url = data.get('finalDisplayedUrl') or data.get('finalUrl') or f.stem
+ path = '/' + url.split('/', 3)[-1].replace('127.0.0.1:18743', '') if url.count('/') >= 3 else url
+ cats = {k: round(v['score'] * 100) for k, v in data['categories'].items() if v.get('score') is not None}
+ metrics = {m: fmt_ms(data['audits'][m].get('numericValue')) for m in METRICS if m in data['audits']}
+ suspects = {}
+ for cid, cat in data['categories'].items():
+ if cats.get(cid, 100) >= 100:
+ continue
+ for ref in cat.get('auditRefs', []):
+ aid = ref['id']
+ a = data['audits'].get(aid, {})
+ if a.get('score') is not None and a['score'] < 0.9 and aid in SUSPECT_AUDITS:
+ items = (a.get('details', {}) or {}).get('items', [])
+ hint = []
+ for it in items[:4]:
+ for key in ('url', 'node.selector', 'node.path', 'source.url'):
+ v = it
+ for seg in key.split('.'):
+ v = v.get(seg) if isinstance(v, dict) else None
+ if v:
+ hint.append(str(v).split('?')[0][:110])
+ break
+ else:
+ hint.append(str(it)[:90])
+ if hint:
+ suspects[aid] = {'score': a['score'], 'items': hint}
+ pages[path or f.stem] = {'scores': cats, 'metrics': metrics, 'suspects': suspects}
+ rows.append((path, cats))
+
+ labels = [CATEGORY_LABEL[c] for c in ('performance', 'accessibility', 'best-practices', 'seo')]
+ print(f"{'页面':<28}" + ''.join(f"{l:>6}" for l in labels))
+ for path, cats in rows:
+ cells = ''.join(f"{str(cats.get(c, "-"))[:3]:>6}" for c in ('performance', 'accessibility', 'best-practices', 'seo'))
+ mark = ''.join(' ✗' if cats.get(c, 100) < 90 else ' ✓' for c in ('performance', 'accessibility', 'best-practices', 'seo'))
+ print(f"{path:<28}{cells} {mark}")
+
+ dest = pathlib.Path('/tmp/e2e-shots/lighthouse-report.json')
+ dest.write_text(json.dumps(pages, ensure_ascii=False, indent=1))
+ print(f"\n全量合并报告: {dest}({dest.stat().st_size} B)")
+
+
+if __name__ == '__main__':
+ main(pathlib.Path(sys.argv[1]))
diff --git a/src/components/Header.astro b/src/components/Header.astro
index ab6b5cb..b0253ad 100644
--- a/src/components/Header.astro
+++ b/src/components/Header.astro
@@ -122,4 +122,22 @@ const switchLabel = curLocale === 'zh' ? 'EN' : '中';
@media (max-width: 560px) {
.nav-link { font-size: 14px; padding-inline: var(--space-1); }
}
+ /* 移动端审计 B1:7 项一行在 390px 溢出 50-88px(主题钮被推出屏)——
+ 更窄一档整体收紧,估算总宽 ~344px < 360 */
+ /* 流式自适应(用户设计诉求):440→360px 区间字号/间距/图标钮连续收缩
+ (clamp = min~max 随视口线性插值),替代阶梯断点——任何宽度都合身,
+ 不再需要逐档补丁;触控目标同步流式但下限钉 28px(WCAG 24 之上) */
+ .header-inner { gap: clamp(6px, 1.6vw, 12px); padding-inline: clamp(10px, 3vw, 20px); }
+ .brand { font-size: clamp(15px, 4vw, 18px); }
+ .nav-link { font-size: clamp(12px, 3.2vw, 14px); padding-inline: clamp(2px, 1vw, 8px); }
+ nav ul { gap: clamp(2px, 0.8vw, 8px); }
+ .header-inner .tap { min-width: clamp(28px, 8vw, 44px); min-height: clamp(28px, 8vw, 44px); }
+ .nav-lang { font-size: clamp(11.5px, 3vw, 13px); }
+ /* 超窄兜底(<350px,en 长词流式下限后仍溢):导航内滚代替页面晃动——
+ 页面级 scrollWidth 恒等于视口,任何机型不破版 */
+ @media (max-width: 350px) {
+ nav ul { overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
+ nav { min-width: 0; }
+ .header-inner { flex-wrap: nowrap; }
+ }
diff --git a/src/components/PostList.astro b/src/components/PostList.astro
index 65a2caf..e6ba575 100644
--- a/src/components/PostList.astro
+++ b/src/components/PostList.astro
@@ -81,10 +81,10 @@ const mmdd = (d: Date) =>
.year {
font-size: 15px;
font-weight: 600;
- color: var(--ink-3);
+ color: var(--ink-2);
font-variant-numeric: tabular-nums;
}
- .shelf-link { float: right; font-size: 12.5px; color: var(--accent); font-weight: 400; }
+ .shelf-link { float: right; font-size: 12.5px; padding-block: 4px; color: var(--accent); font-weight: 400; }
.track {
display: flex;
align-items: center;
@@ -123,6 +123,10 @@ const mmdd = (d: Date) =>
flex: none;
}
.row-title {
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
flex: 1;
min-width: 0;
font-size: 16px;
@@ -132,12 +136,16 @@ const mmdd = (d: Date) =>
white-space: nowrap;
transition: color var(--dur) var(--ease);
}
- .row:hover .row-title { color: var(--accent); }
+ .row:hover .row-title {
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden; color: var(--accent); }
.tag {
flex: none;
font-family: var(--font-mono);
font-size: 12px;
- color: var(--ink-3);
+ color: var(--ink-2);
background: var(--bg-soft);
border-radius: 5px;
padding: 1px 7px;
diff --git a/src/components/geek/ProjectWall.astro b/src/components/geek/ProjectWall.astro
index cca8fcb..460a3fc 100644
--- a/src/components/geek/ProjectWall.astro
+++ b/src/components/geek/ProjectWall.astro
@@ -3,11 +3,16 @@
// 双源数据见 src/lib/projects.ts;语言色点用 linguist 常用色映射
import { loadProjects, type RepoCard } from '../../lib/projects';
+import { t } from '../../i18n/ui';
+import type { Locale } from '../../lib/locale';
+
interface Props {
/** 展示上限:首页精选 6;/opensource 全量传大数 */
limit?: number;
+ locale?: Locale;
}
-const { limit = 6 } = Astro.props;
+const { limit = 6, locale = 'zh' } = Astro.props;
+const title = t('open_source_projects', locale);
const LANG_COLORS: Record = {
Astro: '#ff5a03', TypeScript: '#3178c6', JavaScript: '#f1e05a',
Python: '#3572a5', Go: '#00add8', Rust: '#dea584', Shell: '#89e051',
@@ -23,7 +28,7 @@ try {
---
- 开源 · Projects
+ {title}
{projects.map((p) => (
-
diff --git a/src/components/pages/BooksPage.astro b/src/components/pages/BooksPage.astro
index cd37c34..a4a2d53 100644
--- a/src/components/pages/BooksPage.astro
+++ b/src/components/pages/BooksPage.astro
@@ -25,7 +25,7 @@ const L = {
};
---
-
+
{L.shelf}{L.count(books.length)}
{years.map((year) => (
diff --git a/src/components/pages/GalleryPage.astro b/src/components/pages/GalleryPage.astro
index 5753968..1b2c673 100644
--- a/src/components/pages/GalleryPage.astro
+++ b/src/components/pages/GalleryPage.astro
@@ -82,7 +82,7 @@ const first = cards[0];
- {cards.map((card) => (
+ {cards.map((card, i) => (
diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro
index 4fb0b6f..5ef9af4 100644
--- a/src/layouts/BaseLayout.astro
+++ b/src/layouts/BaseLayout.astro
@@ -1,14 +1,14 @@
---
-// 基础布局:字体/样式统一在此持有(页面不再各自引包)
-// 明暗不闪烁:ThemeScript 必须在 内联先于首帧执行
-import '@fontsource/noto-serif-sc/400.css';
-import '@fontsource/noto-serif-sc/700.css';
-import 'lxgw-wenkai-screen-webfont/lxgwwenkaiscreen.css'; // 金句点缀用(--font-kai),按需加载
-import '@fontsource/noto-sans-sc/400.css';
-import '@fontsource/noto-sans-sc/500.css';
-import '@fontsource/noto-sans-sc/700.css';
-import '@fontsource/jetbrains-mono/400.css';
-import '@fontsource/jetbrains-mono/700.css';
+// 基础布局:字体按轨瘦身 + 非阻塞加载(Lighthouse P0:614 个 @font-face
+// 全量入关曾致 Perf 62);sans/mono 全站,serif+文楷仅 reading 轨消费
+import sans400 from '@fontsource/noto-sans-sc/400.css?url';
+import sans500 from '@fontsource/noto-sans-sc/500.css?url';
+import sans700 from '@fontsource/noto-sans-sc/700.css?url';
+import mono400 from '@fontsource/jetbrains-mono/400.css?url';
+import mono700 from '@fontsource/jetbrains-mono/700.css?url';
+import serif400 from '@fontsource/noto-serif-sc/400.css?url';
+import serif700 from '@fontsource/noto-serif-sc/700.css?url';
+import kai from 'lxgw-wenkai-screen-webfont/lxgwwenkaiscreen.css?url';
import '../styles/global.css';
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
@@ -39,6 +39,7 @@ const withSlash = (p: string) => (p.endsWith('/') ? p : `${p}/`);
+
@@ -46,6 +47,24 @@ const withSlash = (p: string) => (p.endsWith('/') ? p : `${p}/`);
{title}
+
+
+
+
+
+
+ {track === 'reading' && (
+ <>
+
+
+
+ >
+ )}
+
diff --git a/src/pages/en/archives/index.astro b/src/pages/en/archives/index.astro
new file mode 100644
index 0000000..f006505
--- /dev/null
+++ b/src/pages/en/archives/index.astro
@@ -0,0 +1,17 @@
+---
+// /en/archives/ 列表页(补缺:此前 404)
+import { getCollection } from 'astro:content';
+import BaseLayout from '../../../layouts/BaseLayout.astro';
+import PostList from '../../../components/PostList.astro';
+
+const posts = (await getCollection('posts', ({ data }) => !data.draft && !data.tags.includes('hidden'))).sort(
+ (a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime(),
+);
+const enTitle = Object.fromEntries(
+ (await getCollection('postsEn')).map((e) => [e.id, e.data.title]),
+);
+---
+
+
+
+
diff --git a/src/pages/en/index.astro b/src/pages/en/index.astro
index 02c8b8f..5822c77 100644
--- a/src/pages/en/index.astro
+++ b/src/pages/en/index.astro
@@ -99,7 +99,7 @@ const photos = (await getCollection('photos')).slice(0, 4);
note: t('heatmap_note', 'en'),
}}
/>
-
+