diff --git a/docs/consultant-review-2026-10-27-posts-and-casestudies-plan.md b/docs/consultant-review-2026-10-27-posts-and-casestudies-plan.md
index 29609b9..fa32261 100644
--- a/docs/consultant-review-2026-10-27-posts-and-casestudies-plan.md
+++ b/docs/consultant-review-2026-10-27-posts-and-casestudies-plan.md
@@ -120,3 +120,18 @@ node scripts/probe-layout.mjs # 退出码 1 = 有横向溢出或折叠
(`devops` 出现两次:分类 30 / 话题 6)→ 加 *Category* / *Topic* 标签、≤700px 时标签上移;
列表卡片标签数封顶 4 个 + `+N`,修复卡片元信息行高不一致;导航补 `aria-current="page"`
(此前四个栏目链接在任何页面都没有当前位置提示)。
+
+---
+
+## 复审修复 — 2026-10-27(第二轮)
+
+独立复核实测发现四处遗漏,已修并重新量过(详见 `project-state.md` §4e-bis):
+
+| # | 问题 | 实测(前 → 后) | 改法 |
+| --- | --- | --- | --- |
+| P1 | 移动端 `/posts/` 首条归档行在 2.36 屏之外 | 精选块 1235→**523px**,卡片 392→**153px**,首行 1996→**1309px(1.55 屏)** | ≤700px 精选卡改左图右文、隐藏摘要、标题 3 行截断;块头加 `All 91 posts ↓`(含 `scroll-margin-top`) |
+| P2 | 两处新增小字不达 WCAG AA | `.feat-min` 3.04→**5.78**;`.archive-status` 3.67→**6.99**(AA 需 4.5:1) | `--faint` → `--muted`(仅新增元素;既有 91 行日期未动,避免整页重排) |
+| P3 | 同一分类两套视觉 | `/categories/` 瓦片原用旧 `case-studies.webp` | 改用 `case-studies-banner-1200.webp`(14KB)+ `object-fit:contain` on `#0b1613` 信箱式留边(16:9 居中裁切会切掉两侧主机面板,已实测) |
+| P4 | `6min read` 缺空格 | `6min read` → **`6 min read` / `6 分钟`** | 改为模板字符串(同款写法在文章页正常,此处在嵌套 span 内被吞掉) |
+
+另:`/posts/`、`/zh/posts/` 导语不再声称"全部按时间倒序"。验收:`probe-layout` **72/72**,折叠在 EN/ZH × 1280/390 仍为 425→0→425 / 523→0→523。
diff --git a/docs/project-state.md b/docs/project-state.md
index fe10f94..b431790 100644
--- a/docs/project-state.md
+++ b/docs/project-state.md
@@ -464,6 +464,33 @@ reports **72/72** page×viewport checks with `scrollWidth == clientWidth`, plus
stops breaking; the nav gained `aria-current="page"` (it had no active state at all).
- **Docs:** `scripts/probe-layout.mjs` is committed as the repeatable gate for this class of bug.
+### 4e-bis. Review pass on the delivery (same day) — what measurement changed
+
+An independent read of the shipped pages turned up four things the first pass missed. Fixed, remeasured:
+
+- **Mobile archive was 2.4 screens away from its own list.** At 390×844 the featured block ran
+ 688→1923 px (3 × 392 px stacked cards) plus 435 px of controls, so the first `2026` row sat at
+ **1996 px = 2.36 screens** — a reader who came to browse had to scroll past everything.
+ Below 700 px each featured card is now horizontal (thumb 38 %, excerpt dropped, title clamped to
+ three lines): card 392 → **153 px**, block 1235 → **523 px**, first row at **1309 px = 1.55
+ screens**. The block header gained `All 91 posts ↓` (→ `#archive-list`, `scroll-margin-top`
+ clears the sticky header), which doubles as the bypass-block affordance.
+- **Two new small-text colours failed WCAG AA.** Measured against their own backgrounds:
+ `.feat-min` **3.04:1** and `.archive-status` **3.67:1** (`--faint`), where AA wants 4.5:1 under
+ 18.66 px. Both moved to `--muted` → **5.78:1** and **6.99:1**; the featured block's own heading
+ moved with them. Pre-existing `--faint` dates (3.67:1) were left alone rather than restyling 91 rows.
+- **One category, two art languages.** `/categories/` still showed the old Gemini
+ `/categories/case-studies.webp` while the category page had the new 21:9 topology banner.
+ The tile now points at `case-studies-banner-1200.webp` (14 KB) and letterboxes it
+ (`object-fit: contain` on `#0b1613`, measured seamless) instead of centre-cropping — a 16:9 crop
+ sliced the outer host panels in half.
+- **`6min read` had no space.** `{f.minutes} {labels.minutes}` inside the nested `feat-cta` span is
+ emitted without the separating space (the same pattern elsewhere in the repo *does* keep it, so
+ this is compiler-adjacent, not universal). Replaced with a template literal → `6 min read` /
+ `6 分钟`, verified in `dist/`.
+
+`/posts/` and `/zh/posts/` ledes no longer claim the page is purely newest-first.
+
## 5. Conventions (non-negotiable)
- Push git.hoelee.com first, then GitHub
diff --git a/src/components/Archive.astro b/src/components/Archive.astro
index 644fd3e..c86008b 100644
--- a/src/components/Archive.astro
+++ b/src/components/Archive.astro
@@ -56,6 +56,7 @@ const labels = isZh
readArticle: '阅读文章',
readCase: '阅读案例复盘',
minutes: '分钟',
+ skip: `全部 ${posts.length} 篇 ↓`,
statusAll: `共 ${posts.length} 篇`,
statusSome: `命中 {n} / ${posts.length} 篇`,
clear: '清除筛选',
@@ -70,6 +71,7 @@ const labels = isZh
readArticle: 'Read article',
readCase: 'Read case study',
minutes: 'min read',
+ skip: `All ${posts.length} posts ↓`,
statusAll: `All ${posts.length} posts`,
statusSome: `Showing {n} of ${posts.length} posts`,
clear: 'Clear filters',
@@ -131,9 +133,15 @@ const ctaLabel = (category: string) =>
data-collapsed="false"
>
-
- {labels.featured}
-
+
+
+ {labels.featured}
+
+ {/* Jump past the cards. On a phone the block used to put the first archive
+ row ~2.4 screens down, so a reader who came to browse had no way out of the
+ flagships; this is also the bypass-block affordance for keyboard users. */}
+ {labels.skip}
+
{featured.map((f) => (
@@ -157,7 +165,7 @@ const ctaLabel = (category: string) =>
{f.title}{f.description}
- {ctaLabel(f.category)} → {f.minutes} {labels.minutes}
+ {ctaLabel(f.category)} → {`${f.minutes} ${labels.minutes}`}
@@ -314,6 +322,12 @@ const ctaLabel = (category: string) =>
pointer-events: none;
}
.archive-featured-inner { overflow: hidden; min-height: 0; }
+ .af-head {
+ display: flex;
+ align-items: baseline;
+ gap: 0.6rem;
+ margin: 0.2rem 0 0.7rem;
+ }
.archive-featured-title {
display: flex;
align-items: center;
@@ -321,9 +335,19 @@ const ctaLabel = (category: string) =>
font-size: 0.78rem;
letter-spacing: 0.08em;
text-transform: uppercase;
- color: var(--faint);
- margin: 0.2rem 0 0.7rem;
+ /* --faint measured 3.67:1 at this size; AA needs 4.5:1 for small text. */
+ color: var(--muted);
+ margin: 0;
}
+ .af-skip {
+ margin-left: auto;
+ font-family: var(--font-mono);
+ font-size: 0.72rem;
+ color: var(--muted);
+ text-decoration: none;
+ white-space: nowrap;
+ }
+ .af-skip:hover { color: var(--brand); text-decoration: underline; }
.af-dot {
width: 6px;
height: 6px;
@@ -393,7 +417,35 @@ const ctaLabel = (category: string) =>
gap: 0.35rem;
flex-wrap: wrap;
}
- .feat-min { color: var(--faint); font-weight: 400; }
+ /* --faint measured 3.04:1 at 12 px; AA needs 4.5:1 for small text. */
+ .feat-min { color: var(--muted); font-weight: 400; }
+
+ /* Phones: three stacked 16:9 cards put the first archive row 2.4 screens down
+ (measured 1235 px of featured + 435 px of controls at 390x844). Lay each card out
+ horizontally instead and drop the excerpt, so the block costs ~1/3 of the height
+ and the year list is reachable in about one screen. */
+ @media (max-width: 700px) {
+ .feat-card { flex-direction: row; align-items: stretch; }
+ .feat-thumb {
+ flex: 0 0 38%;
+ aspect-ratio: auto;
+ align-self: stretch;
+ border-right: 1px solid var(--border);
+ }
+ .feat-thumb img { height: 100%; }
+ .feat-body { flex: 1 1 auto; min-width: 0; padding: 0.55rem 0.65rem 0.6rem; gap: 0.2rem; }
+ .feat-desc { display: none; }
+ .feat-title {
+ font-size: 0.9rem;
+ display: -webkit-box;
+ -webkit-line-clamp: 3;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ }
+ .feat-cta { padding-top: 0.15rem; font-size: 0.72rem; }
+ .feat-top { gap: 0.3rem; }
+ .feat-metric { font-size: 0.64rem; }
+ }
.archive-status {
display: flex;
@@ -402,7 +454,8 @@ const ctaLabel = (category: string) =>
flex-wrap: wrap;
font-family: var(--font-mono);
font-size: 0.74rem;
- color: var(--faint);
+ /* --faint measured 3.67:1 at 11.8 px; AA needs 4.5:1 for small text. */
+ color: var(--muted);
margin: 0 0 0.5rem;
min-height: 28px;
}
@@ -419,7 +472,7 @@ const ctaLabel = (category: string) =>
}
.archive-clear:hover { border-color: var(--brand); }
- .archive { list-style: none; padding: 0; margin: 0; }
+ .archive { list-style: none; padding: 0; margin: 0; scroll-margin-top: 5.5rem; }
.archive-year {
font-size: 0.8rem;
letter-spacing: 0.08em;
diff --git a/src/components/CategoryArt.astro b/src/components/CategoryArt.astro
index 77f967a..cbfe34e 100644
--- a/src/components/CategoryArt.astro
+++ b/src/components/CategoryArt.astro
@@ -141,14 +141,18 @@ const LINE_COLORS: Record = {
{
hasImage ? (
+ // case-studies has its own 21:9 topology banner (the portfolio hero art). At the
+ // 16:9 tile ratio a centre crop would slice the outer host panels in half, so the
+ // card letterboxes it on the same dark ground instead — measured invisible at
+ // #0b1613. One art language for one category, no clipped hardware.
) : (