feat(architecture): implement consultant review r4 (stream separation, nav upgrade, internal cta, schema & anchor fixes)
Deploy / build (push) Successful in 25s

This commit is contained in:
hoelee committed 2026-10-27 01:19:38 +08:00
1 parent cc28722c79
commit 419c962e2f
31 files changed
+286 -42

No files matched your search

+2 -2
View File
@@ -64,7 +64,7 @@ const writeUps = (await getCollection('posts', isEn)).length;
<div class="dual-track-grid">
<!-- Track A: For Business Owners & SMEs -->
<div class="track-card track-business">
<div class="track-card track-business" id="sme">
<div class="track-header">
<span class="track-badge">🏢 For Business Owners &amp; Founders</span>
<h3>Custom Web Systems, AI Automation &amp; Self-Hosted Infrastructure</h3>
@@ -102,7 +102,7 @@ const writeUps = (await getCollection('posts', isEn)).length;
</div>
<!-- Track B: For Tech Leads & Recruiters -->
<div class="track-card track-hiring">
<div class="track-card track-hiring" id="hiring">
<div class="track-header">
<span class="track-badge">👨‍💻 For Hiring Managers &amp; Tech Leads</span>
<h3>Full-Stack, DevOps &amp; AI Systems Engineering Roles</h3>
+46 -5
View File
@@ -7,6 +7,8 @@ import { SITE } from '../config';
import { sortForListing } from '../lib/sort';
const posts = sortForListing(await getCollection('posts', isEn));
const engineeringPosts = posts.filter((p) => p.data.stream !== 'creative-lab');
const creativeLabPosts = posts.filter((p) => p.data.stream === 'creative-lab');
// Featured picks: fixed slugs, not "newest 4", so the front page stays stable after a
// publish and keeps answering "who is this and what has he actually run" (scale →
@@ -112,7 +114,7 @@ const featuredMetrics: Record<string, string> = {
<div class="hero-cta">
<a class="btn btn-primary" href="#featured">Read the engineering case studies ↓</a>
<a class="btn" href="/about/">Hiring? Profile &amp; stack →</a>
<a class="btn" href={SITE.website} target="_blank" rel="noopener">Malaysian SME? How I work ↗</a>
<a class="btn" href="/about/#sme">Malaysian SME? How I work ↗</a>
</div>
<p class="hero-note">
{posts.length} posts in English and 中文 · <a href="/rss.xml">RSS</a>
@@ -179,7 +181,7 @@ const featuredMetrics: Record<string, string> = {
<section>
<div class="section-head-with-nav">
<h2>Latest posts</h2>
<h2>Latest engineering &amp; DevOps posts</h2>
<div class="section-nav-pills" aria-label="Category shortcuts">
<a class="section-nav-pill" href="/categories/case-studies/">case-studies</a>
<a class="section-nav-pill" href="/categories/devops/">devops</a>
@@ -188,11 +190,50 @@ const featuredMetrics: Record<string, string> = {
<a class="section-nav-pill section-nav-all" href="/posts/">All {posts.length} →</a>
</div>
</div>
{posts.length === 0 ? (
{engineeringPosts.length === 0 ? (
<p><em>No posts yet — check back soon.</em></p>
) : (
<PostList posts={posts} limit={6} />
<PostList posts={engineeringPosts} limit={6} />
)}
<p class="archive-meta"><a href="/posts/">All {posts.length} posts</a> · <a href="/tags/docker/">tag pages</a></p>
</section>
{creativeLabPosts.length > 0 && (
<section class="creative-lab-strip">
<div class="lab-strip-header">
<div class="lab-strip-title-wrap">
<span class="lab-strip-badge">⚡ GPU Node</span>
<h2>Local GPU &amp; Multimodal Pipelines (RTX 3060 Lab)</h2>
</div>
<p class="lab-strip-sub">
High-throughput audio synthesis, forced-alignment subtitling, and diffusion workflows running on our self-hosted GPU node.
</p>
</div>
<div class="lab-cards-grid">
{creativeLabPosts.slice(0, 3).map((post) => {
const cleanSlug = post.slug.replace(/^zh\//, '');
return (
<a class="lab-card" href={`/posts/${post.slug}/`}>
<div class="lab-card-thumb">
<img
src={`/og/thumb/${cleanSlug}-400.webp`}
alt=""
loading="lazy"
decoding="async"
width="400"
height="210"
onerror="this.style.display='none'"
/>
</div>
<div class="lab-card-body">
<span class="lab-card-tag">{post.data.category}</span>
<h3>{post.data.title}</h3>
<p>{post.data.description}</p>
</div>
</a>
);
})}
</div>
<p class="archive-meta"><a href="/posts/">All {posts.length} posts</a> · <a href="/tags/docker/">tag pages</a></p>
</section>
)}
</BaseLayout>
+14 -4
View File
@@ -8,6 +8,7 @@ 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 { categoryLabel } from '../../lib/categories';
import { SITE } from '../../config';
export async function getStaticPaths() {
@@ -57,12 +58,12 @@ const translation = post.data.translation
const switchHref = translation ? `/posts/${translation}/` : undefined;
const altLocaleUrl = translation ? new URL(`/posts/${translation}/`, SITE.url).href : undefined;
// BlogPosting structured data — gives Google a rich-result-eligible node and
// BlogPosting / TechArticle structured data — gives Google a rich-result-eligible node and
// links the article to the site-wide Person node (E-E-A-T).
const postUrl = new URL(`/posts/${post.slug}/`, SITE.url).href;
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
'@type': post.data.summary ? 'TechArticle' : 'BlogPosting',
mainEntityOfPage: { '@type': 'WebPage', '@id': postUrl },
headline: post.data.title,
description: post.data.description,
@@ -76,6 +77,15 @@ const jsonLd = {
articleSection: post.data.category,
keywords: post.data.tags.join(', '),
wordCount: words,
...(post.data.summary
? {
dependencies: post.data.summary.stack.join(', '),
audience: {
'@type': 'Audience',
audienceType: post.data.summary.audience,
},
}
: {}),
};
---
@@ -103,7 +113,7 @@ const jsonLd = {
<span class="sep">·</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>
<a class="tag" href={lang === 'zh' ? `/zh/categories/${post.data.category}/` : `/categories/${post.data.category}/`}>{categoryLabel(post.data.category, lang)}</a>
{post.data.tags.map((t) => {
const href = tagLink(t, tagCount, lang);
return href ? <a class="tag" href={href}>{t}</a> : <span class="tag">{t}</span>;
@@ -154,7 +164,7 @@ const jsonLd = {
decoding="async"
/>
<div class="related-body">
<span class="related-badge">{p.data.category}</span>
<span class="related-badge">{categoryLabel(p.data.category, lang)}</span>
<h3>{p.data.title}</h3>
<time datetime={p.data.pubDate.toISOString()}>
{fmt(p.data.pubDate)}
+2 -2
View File
@@ -63,7 +63,7 @@ const writeUps = (await getCollection('posts', isZh)).length;
<div class="dual-track-grid">
<!-- 轨道 A: 面向企业主与创业者 -->
<div class="track-card track-business">
<div class="track-card track-business" id="sme">
<div class="track-header">
<span class="track-badge">🏢 致企业主与业务负责人</span>
<h3>网站与业务系统定制 · 24/7 AI 获客客服 · 私有化部署降本</h3>
@@ -100,7 +100,7 @@ const writeUps = (await getCollection('posts', isZh)).length;
</div>
<!-- 轨道 B: 面向技术主管与招聘方 -->
<div class="track-card track-hiring">
<div class="track-card track-hiring" id="hiring">
<div class="track-header">
<span class="track-badge">👨‍💻 致技术主管与招聘方</span>
<h3>全栈开发、DevOps 运维与 AI 系统架构工程角色</h3>
+51 -9
View File
@@ -5,8 +5,11 @@ import { getCollection } from 'astro:content';
import { isZh } from '../../lib/lang';
import { SITE } from '../../config';
import { sortForListing } from '../../lib/sort';
import { categoryLabel } from '../../lib/categories';
const posts = sortForListing(await getCollection('posts', isZh));
const engineeringPosts = posts.filter((p) => p.data.stream !== 'creative-lab');
const creativeLabPosts = posts.filter((p) => p.data.stream === 'creative-lab');
// Same four topics as the English front page, in their Chinese versions, so the two
// front pages tell the same story (scale → automation → observability → a delivered
@@ -113,7 +116,7 @@ const featuredMetrics: Record<string, string> = {
<div class="hero-cta">
<a class="btn btn-primary" href="#featured">先看工程案例 ↓</a>
<a class="btn" href="/zh/about/">招聘方:技术栈与履历 →</a>
<a class="btn" href={SITE.website} target="_blank" rel="noopener">中小企业:合作方式 ↗</a>
<a class="btn" href="/zh/about/#sme">中小企业:合作方式 ↗</a>
</div>
<p class="hero-note">
共 {posts.length} 篇中文文章 · <a href="/">English</a> · <a href="/rss.xml">RSS</a>
@@ -162,7 +165,7 @@ const featuredMetrics: Record<string, string> = {
</div>
<div class="bento-content">
<div class="bento-top-meta">
<span class="featured-badge">{post.data.category}</span>
<span class="featured-badge">{categoryLabel(post.data.category, 'zh')}</span>
<span class="bento-metric-pill">
<span class="pulse-dot"></span>
{metric}
@@ -180,20 +183,59 @@ const featuredMetrics: Record<string, string> = {
<section>
<div class="section-head-with-nav">
<h2>最新文章</h2>
<h2>最新生产与运维文章</h2>
<div class="section-nav-pills" aria-label="分类快捷入口">
<a class="section-nav-pill" href="/zh/categories/case-studies/">工程案例</a>
<a class="section-nav-pill" href="/zh/categories/devops/">运维</a>
<a class="section-nav-pill" href="/zh/categories/tutorials/">教程</a>
<a class="section-nav-pill" href="/zh/categories/notes/">笔记</a>
<a class="section-nav-pill" href="/zh/categories/devops/">运维架构</a>
<a class="section-nav-pill" href="/zh/categories/tutorials/">实战教程</a>
<a class="section-nav-pill" href="/zh/categories/notes/">排障手记</a>
<a class="section-nav-pill section-nav-all" href="/zh/posts/">全部 {posts.length} 篇 →</a>
</div>
</div>
{posts.length === 0 ? (
{engineeringPosts.length === 0 ? (
<p><em>中文文章即将发布,敬请期待。</em></p>
) : (
<PostList posts={posts} lang="zh" limit={6} />
<PostList posts={engineeringPosts} lang="zh" limit={6} />
)}
<p class="archive-meta"><a href="/zh/posts/">全部 {posts.length} 篇</a></p>
</section>
{creativeLabPosts.length > 0 && (
<section class="creative-lab-strip">
<div class="lab-strip-header">
<div class="lab-strip-title-wrap">
<span class="lab-strip-badge">⚡ 本地 GPU 算力</span>
<h2>本地 GPU 算力与多模态工程实验室</h2>
</div>
<p class="lab-strip-sub">
基于本地 RTX 3060 生产节点的语音克隆、强制对齐字幕与音视频批量生成管线实录。
</p>
</div>
<div class="lab-cards-grid">
{creativeLabPosts.slice(0, 3).map((post) => {
const cleanSlug = post.slug.replace(/^zh\//, '');
return (
<a class="lab-card" href={`/posts/${post.slug}/`}>
<div class="lab-card-thumb">
<img
src={`/og/thumb/${cleanSlug}-400.webp`}
alt=""
loading="lazy"
decoding="async"
width="400"
height="210"
onerror="this.style.display='none'"
/>
</div>
<div class="lab-card-body">
<span class="lab-card-tag">{categoryLabel(post.data.category, 'zh')}</span>
<h3>{post.data.title}</h3>
<p>{post.data.description}</p>
</div>
</a>
);
})}
</div>
<p class="archive-meta"><a href="/zh/posts/">全部 {posts.length} 篇</a></p>
</section>
)}
</BaseLayout>