B7 review-response batch: derived about counter, CJK-aware reading time, ZH copy fixes, hero leads with capability
Deploy / build (push) Successful in 26s

This commit is contained in:
hoelee committed 2026-10-19 14:15:59 +08:00
1 parent 0b23312948
commit 5941e858cc
10 files changed
+87 -27

No files matched your search

+8 -2
View File
@@ -1,7 +1,13 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';
import AuthorCard from '../components/AuthorCard.astro';
import { getCollection } from 'astro:content';
import { isEn } from '../lib/lang';
import { SITE } from '../config';
// Counted from the content collection, never typed by hand — a hardcoded number
// here already drifted once (71 while the homepage rendered 72).
const writeUps = (await getCollection('posts', isEn)).length;
---
<BaseLayout
@@ -47,8 +53,8 @@ import { SITE } from '../config';
<div class="proof-grid">
<div class="proof-card"><span class="n">162</span><span class="l">containers in production</span></div>
<div class="proof-card"><span class="n">3</span><span class="l">self-managed hosts (78 compose stacks)</span></div>
<div class="proof-card"><span class="n">71</span><span class="l">write-ups, each in English and 中文</span></div>
<div class="proof-card"><span class="n">0</span><span class="l">hosting companies between me and the servers</span></div>
<div class="proof-card"><span class="n">{writeUps}</span><span class="l">write-ups, each in English and 中文</span></div>
<div class="proof-card"><span class="n">0</span><span class="l">managed hosting layers — I run all three hosts myself</span></div>
</div>
<p>
That footprint is client websites, a mail platform, CI/CD, workflow automation,
+6 -3
View File
@@ -47,9 +47,10 @@ const featuredMetrics: Record<string, string> = {
<h1>{SITE.author} — full-stack developer &amp; DevOps engineer</h1>
</div>
<p class="hero-sub">
I run <strong>162 containers across three self-managed hosts</strong> — client sites, a
mail platform, CI/CD, workflow automation and this blog — and I build web systems for
Malaysian SMEs. I write up what broke, what the evidence said, and how I fixed it.
I build and operate production web systems — from the code down to the servers.
<strong>162 containers across three self-managed hosts</strong> run client sites, a mail
platform, CI/CD, workflow automation and this blog. I write up what broke, what the
evidence said, and how I fixed it.
</p>
<p class="hero-cta">
<a class="btn btn-primary" href="#featured">Read the engineering case studies ↓</a>
@@ -72,6 +73,8 @@ const featuredMetrics: Record<string, string> = {
<div class="bento-visual" aria-hidden="true">
<img
src={`/og/thumb/${cleanSlug}-800.webp`}
srcset={`/og/thumb/${cleanSlug}-400.webp 400w, /og/thumb/${cleanSlug}-800.webp 800w`}
sizes="(max-width: 640px) 60vw, 341px"
alt=""
loading="lazy"
decoding="async"
+7 -7
View File
@@ -3,6 +3,7 @@ import BaseLayout from '../../layouts/BaseLayout.astro';
import AuthorCard from '../../components/AuthorCard.astro';
import { getCollection, render } from 'astro:content';
import { postLang } from '../../lib/lang';
import { countWords, readingMinutes } from '../../lib/reading-time';
import { tagCounts, tagLink } from '../../lib/tags';
import { SITE } from '../../config';
@@ -19,15 +20,14 @@ const { Content } = await render(post);
const lang = postLang(post.slug);
// Reading time (~200 wpm)
const words = post.body?.trim().split(/\s+/).length ?? 0;
const minutes = Math.max(1, Math.round(words / 200));
// Reading time — CJK-aware (src/lib/reading-time.ts). A plain whitespace split
// made every Chinese post read "1 min" while its English twin read five.
const words = countWords(post.body);
const minutes = readingMinutes(post.body);
const fmt = (d: Date) =>
d.toLocaleDateString(lang === 'zh' ? 'zh-CN' : 'en-US', { year: 'numeric', month: 'long', day: 'numeric' });
const minutesOf = (body?: string) => Math.max(1, Math.round((body?.trim().split(/\s+/).length ?? 0) / 200));
// Related posts: same language, same category or overlapping tags, excluding self
const all = await getCollection('posts', ({ data }) => !data.draft);
// Tag pills: a tag with its own page becomes a link, a one-off tag stays plain text.
@@ -97,7 +97,7 @@ const jsonLd = {
<div class="meta">
<time datetime={post.data.pubDate.toISOString()}>{fmt(post.data.pubDate)}</time>
<span class="sep">·</span>
<span>{minutes} min read</span>
<span>{minutes} {lang === 'zh' ? '分钟' : 'min read'}</span>
<span class="sep">·</span>
<a class="tag" href={lang === 'zh' ? `/zh/categories/${post.data.category}/` : `/categories/${post.data.category}/`}>{post.data.category}</a>
{post.data.tags.map((t) => {
@@ -135,7 +135,7 @@ const jsonLd = {
<span class="related-badge">{p.data.category}</span>
<h3>{p.data.title}</h3>
<time datetime={p.data.pubDate.toISOString()}>
{fmt(p.data.pubDate)} · {minutesOf(p.body)} {lang === 'zh' ? '分钟' : 'min read'}
{fmt(p.data.pubDate)} · {readingMinutes(p.body)} {lang === 'zh' ? '分钟' : 'min read'}
</time>
</div>
</a>
+8 -2
View File
@@ -1,7 +1,13 @@
---
import BaseLayout from '../../layouts/BaseLayout.astro';
import AuthorCard from '../../components/AuthorCard.astro';
import { getCollection } from 'astro:content';
import { isZh } from '../../lib/lang';
import { SITE } from '../../config';
// Counted from the content collection, never typed by hand — a hardcoded number
// here already drifted once (71 while the homepage rendered 72).
const writeUps = (await getCollection('posts', isZh)).length;
---
<BaseLayout
@@ -46,8 +52,8 @@ import { SITE } from '../../config';
<div class="proof-grid">
<div class="proof-card"><span class="n">162</span><span class="l">个生产环境容器</span></div>
<div class="proof-card"><span class="n">3</span><span class="l">台自管服务器(78 个 compose 栈)</span></div>
<div class="proof-card"><span class="n">71</span><span class="l">篇长文,每篇中英双版</span></div>
<div class="proof-card"><span class="n">0</span><span class="l">夹在我和服务器之间的托管商</span></div>
<div class="proof-card"><span class="n">{writeUps}</span><span class="l">篇长文,每篇中英双版</span></div>
<div class="proof-card"><span class="n">0</span><span class="l">层托管服务——三台主机都我自己运维</span></div>
</div>
<p>
这套环境里有客户网站、邮件平台、CI/CD、流程自动化、统计和这个博客,全靠手工拴在一起。
+3 -3
View File
@@ -51,9 +51,7 @@ const featuredMetrics: Record<string, string> = {
<h1>{SITE.author} —— 全栈开发者兼 DevOps 工程师</h1>
</div>
<p class="hero-sub">
我独自运维 <strong>三台自管服务器上的 162 个容器</strong>(客户站点、邮件平台、
CI/CD、流程自动化与这个博客),并为马来西亚中小企业构建 Web 系统。这里写的是
「坏了什么、证据在哪、怎么修好」。
从代码到服务器,生产环境的 Web 系统我自己从头做到尾。<strong>三台自管服务器上跑着 162 个容器</strong>:客户站点、邮件平台、CI/CD、流程自动化,还有这个博客。这里写的是「坏了什么、证据在哪、怎么修好」。
</p>
<p class="hero-cta">
<a class="btn btn-primary" href="#featured">先看工程案例 ↓</a>
@@ -76,6 +74,8 @@ const featuredMetrics: Record<string, string> = {
<div class="bento-visual" aria-hidden="true">
<img
src={`/og/thumb/${cleanSlug}-800.webp`}
srcset={`/og/thumb/${cleanSlug}-400.webp 400w, /og/thumb/${cleanSlug}-800.webp 800w`}
sizes="(max-width: 640px) 60vw, 341px"
alt=""
loading="lazy"
decoding="async"