Replaces the generated isometric diagram with the supplied 2752x1536
render (engineer at a multi-monitor desk, container streams flowing into
server racks). Re-exported at 1024/1280/2048 widths; alt text and the
lightbox caption now describe what the image actually shows.
The previous hero-banner was composed as a wide banner with an empty left
40% reserved for overlay text. In the 50/50 hero column it rendered as a
shrunken half-width picture with dead space on both sides.
New illustration fills the whole 16:9 frame edge to edge (code/CI/CD left,
container + host cluster centre, observability right) so the right hero
column reads as a system diagram at its native 518px width, with the
2048w lightbox for detail reading.
Also add a 1280w WebP so DPR2 desktops do not pull the 2048w asset on
first paint.
- Homepage (EN + ZH) featured section becomes a 2x2 Cyber-Bento grid: OG
thumbnail at 65%/140px behind a scrim, category badge + metric pill,
3-line-clamped description, arrow CTA; 1 column under 680px.
- Bento scrim/pill/dot now use theme tokens (--card-veil, --pill-bg,
--bento-visual-opacity). The original hardcoded dark blend measured
1.96:1 pill contrast on the light theme; now 6.56 dark / 6.10 light.
- Add the missing @keyframes bento-pulse (the doc claimed a pulsing dot,
none existed), disabled under prefers-reduced-motion.
- Tag chips: mono face, hairline border, 4px radius; dashed border for
non-link tags.
- ZH pages: brand links /zh/, in-post category tag links
/zh/categories/<cat>/ instead of the EN route.
- project-state: B6 shipped note with measured readings; correct the
prompts-catalog path claim (file is outside the repo).
Measured in headless Chrome at 1280px and 390px, EN + ZH: 4 cards,
2 cols / 1 col, scrollWidth-clientWidth 0, no card overflow, all four
-800.webp thumbs loaded at 800x420.
The photo is the identity evidence the external review flagged as the biggest
gap. One source image (Pictures/Professional Portrait with Blue Gradient
Background.png, 1254x1254), two sizes, WebP first with a JPEG fallback —
continuous tone is the case where WebP/JPEG beat PNG:
640x640 42 KB JPG / 26 KB WebP About page (190 CSS px)
128x128 3 KB JPG / 2 KB WebP author card + home hero avatar
It now appears in the About hero, the author card at the end of every post, the
home hero (both languages), and JSON-LD Person.image — the node search engines
read to associate the name with a face.
About went from 1,024 characters to five sections: identity with photo, "By the
numbers" (162 containers / 3 hosts / 71 essays / 0 hosting companies between me
and the servers), the real tech stack in four columns, the Hoelee Enterprise
block (website design and development is the main business, email hosting
secondary), and a contact split between hiring and business.
Also: the headshot's dark-blue background sits close to the page background, so
the photo, hero avatar and card avatar get a faint brand ring plus shadow.
Pictures/ keeps the converted JPG master next to the untouched PNG.
All four featured posts are case studies, so the badge repeated the same word
four times; the card grid already sits under a 'Featured' heading. Search pill
gets a little horizontal room so 'Search /' does not read as cramped.
Found a real bug while verifying the nav search: /pagefind/pagefind.js is an ES
module that defines no window.Pagefind, but Search.astro loaded it with a classic
<script src> and read window.Pagefind — so every query answered "Search is
unavailable." (confirmed live before the fix). Now a dynamic import; the specifier
must be a variable or Rollup resolves it at build time and the build fails.
Measured before/after in a real browser:
- archive /posts/ + /zh/posts/: 18,220px -> 3,788px (71 rows at 44px, grouped by
year) with a client-side filter (title/tags/description text, category chips,
14 tag chips, zero requests). Tested: "nginx" -> 6 rows, chip devops -> 28.
- tag pages for tags with >= 3 posts. Measured 235 distinct tags (an earlier brief
claimed 74), 156 used exactly once — pages only where a topic exists: 40 tags ->
80 pages, build 165 -> 245 pages. Pills link only when a page exists.
- homepage + ZH homepage: hero (288px) with the real scale claim, three CTAs, a
fixed Featured 4 (scale -> automation -> observability -> a delivered result),
Latest 10 -> 6. ZH hero had linked "关于我" to the English /about/.
- long code blocks collapse above 420px with an Expand button + gradient, copy
button still copies the whole block (verified on the 1010px block).
- search is visible ("⌕ Search /" pill, "/" plus the existing Ctrl/Cmd+K) and the
zero-result screen offers topic chips instead of a dead end.
Docs: project-state B3 entry, ops-runbook Pagefind gotcha.
Listings render `pubDate` but sorted by `updatedDate ?? pubDate`, so any
post carrying both dates sorted by a date it never displayed. After the
backdate commit the two diverged by years and the list read out of order
("January 7, 2026" above "September 13, 2026", "March 11, 2026" below
"December 10, 2025").
Replace `sortByUpdated` with `sortForListing`, which orders by `pubDate`
and uses `updatedDate` only as a tiebreak. This matches the RSS feed,
which already sorted by `pubDate`.
Verified all three listings (EN, EN homepage, ZH) are monotonically
non-increasing across the full 2024-09 -> 2026-09 span.
robots.txt was absent at the origin, so Cloudflare served its Free-plan
Content Signals Policy placeholder — a file with no User-agent, no Sitemap
and no directives. Adds src/pages/robots.txt.ts (origin 200 → CF merges
instead of substituting) pointing at sitemap-index.xml, disallowing
/pagefind/, and expressing search=yes / ai-input=yes / ai-train=no in line
with the two-lane SEO+GEO strategy in docs/seo-reference.md. Citation
crawlers (Google-Extended, OAI-SearchBot, PerplexityBot, ClaudeBot) stay
allowed; only training-only harvesters blocked.
hreflang was never emitted despite full EN/ZH i18n: both language versions
sat in the sitemap with no cross-reference. BaseLayout gains altLocaleUrl
and emits en/zh/x-default per page; every post pair, category pair, and
landing page now cross-links.
Also adds BlogPosting JSON-LD (@id-linked to the site-wide Person node)
so Google has a rich-result-eligible node per article, and creates the
missing /zh/about/ page — the EN about page pointed its language switch at
a 404, and /about/ was the only unpaired page after hreflang landed.
Update hello-world post, homepage, about page, and zh landing to say
website design & development (primary) + email hosting (side offering),
not 'email-hosting business'.
- Derive lang from folder (posts/zh/* -> zh, else en); remove lang: frontmatter
- Change post route to [...slug] to capture nested zh paths
- Add nav language switcher (EN <-> 中文) + /zh/ landing page
- Default theme to dark (light opt-in via toggle)
- Translate hello-world-zh placeholder into English intro post
- Update case study: docker-compose sample + real install gotchas (commit history)
- Add docs/project-state.md (Tier 1/2/3 backlog)