Deploy / build (push) Successful in 34s
The YuE2 and subtitles posts shipped text-only: render_from_art.mjs only READS ogImage/banner, and after the 2026-10-19 bulk migration no script had to write them, so both pages advertised /og-default.jpg and rendered no hero banner. - new metaphors (in build_captions.py, frozen here via sync_prompts_catalog.py), captions, art-meta evidence, banner plan and the five derived files per slug - frontmatter: ogImage + banner in EN and ZH for both posts - scripts/check-assets.mjs is now step 1 of npm run build, so CI fails when a published post lacks a language-correct og card, banner, banner .webp or 400/800 list thumb. It reported 16 problems on the unfixed tree, all here.
90 lines
6.0 KiB
Markdown
90 lines
6.0 KiB
Markdown
# Design Guide — what the blog UI must have & must avoid
|
||
|
||
Read this **before** touching theme, layout, typography, color, or SEO markup. Priority order is fixed: **reading experience → SEO → brand → everything else.**
|
||
|
||
---
|
||
|
||
## 1. Non-negotiables (reading experience first)
|
||
|
||
| Rule | Spec |
|
||
|---|---|
|
||
| **Measure (line length)** | 65–75 characters per line. Single biggest readability win. |
|
||
| **Body typography** | 16–17px, `line-height: 1.6–1.7`, a clean sans (Inter / system-ui / Source Sans). |
|
||
| **Headings** | A character font (Plus Jakarta Sans / Space Grotesk) or keep Inter for minimalism. |
|
||
| **Code** | Proper monospace (JetBrains Mono / Fira Code) + distinct background block + **syntax highlighting + copy button + language label**. Non-negotiable for technical posts. |
|
||
| **Light + dark mode** | Both. Dev audience splits ~50/50. |
|
||
| **Color** | 2-accent palette (primary + semantic green/red for correct/wrong in tutorials). Avoid pure black / pure white. |
|
||
|
||
---
|
||
|
||
## 2. Required components
|
||
|
||
- **Sticky header** — name/logo + nav (Blog / Categories / About / RSS).
|
||
- **Article header** — title, pub date, reading time, category + tags, author card (photo, 1–2 line bio, LinkedIn + GitHub + git.hoelee.com links).
|
||
- **Author box / ProfilePage schema** on every post (see SEO below).
|
||
- **Related posts** (6 photo cards: 2 across on desktop, 1 across on phones with only the first 4 shown) — internal linking + keeps readers on-site. Each card shows the post's own language-specific share card, so an EN page never shows ZH art.
|
||
- **RSS link** in footer — how the technical audience follows me.
|
||
- **Custom og:image per post** — 1200×630, never the reused logo.
|
||
|
||
---
|
||
|
||
## 3. Responsive & media
|
||
|
||
- **Mobile-first**: design 390px, then desktop 1280px.
|
||
- **Astro Image** for automatic optimization/WebP (Core Web Vitals win).
|
||
- **Image format policy (measured 2026-10-16 — do not guess this one):**
|
||
|
||
| Asset | Served as | Why |
|
||
|---|---|---|
|
||
| `og:image` / `twitter:image` | **PNG** (1200×630) | Social scrapers (WhatsApp / LinkedIn / FB / Telegram) do not reliably render WebP share cards. Compatibility beats bytes here. |
|
||
| Article banner in-page | **WebP q78** + PNG fallback via `<picture>` | 6.02 MB → 2.52 MB across 70 banners (42%); JPEG would be *larger* (q85 = 129% of PNG) because the art is flat terminal-style with sharp text edges. |
|
||
| List thumbnails | **WebP q72 at 400w + 800w** (`/og/thumb/<name>-400.webp`) | Rows display at ~398px; they used to fetch the 1200×630 PNG (~45 KB). 400w is 5.8 KB, 800w is 16.1 KB. The homepage went from ~450 KB of thumbnails to ~62 KB. |
|
||
| Related-post cards | The post's **own share card** (`/og/<slug>.jpg`, ZH `/og/zh-<slug>.jpg`), `loading="lazy"` | The card must match the page language, and the thumb WebPs are keyed by base slug — using them would print EN art on ZH pages. 6 cards ≈ 390 KB, deferred below the fold. |
|
||
| Real photos (author, hardware) | **WebP q82** + JPEG fallback | Continuous-tone images are the opposite case from the flat art above. |
|
||
|
||
Regenerate all derivatives after adding a post: `uv run --with pillow python scripts/optimize-images.py`.
|
||
|
||
- **Every published post must point at its own art.** `scripts/check-assets.mjs` runs as the first
|
||
step of `npm run build` (so CI fails, not just warns) and rejects a post whose `ogImage` or
|
||
`banner` is missing or misnamed, or whose banner `.webp` / `-400` / `-800` thumbs are absent.
|
||
The renderers only *read* frontmatter — nothing writes these two fields for a new post, which is
|
||
how two 2026-10-20 posts shipped on `/og-default.jpg` with no hero banner.
|
||
- **Declare `og:image:width/height` explicitly** — mis-declared dimensions get preview cards rejected by WhatsApp/Telegram/LinkedIn.
|
||
- **Favicon**: reference all sizes (`.ico` + 32×32 + 192×192 PNG + apple-touch 180×180). "Favicon not showing" is usually browser cache, not a missing asset — `curl` the live files before "fixing".
|
||
|
||
---
|
||
|
||
## 4. Theme / stack decisions (settled)
|
||
|
||
- **Astro 5 static** (already scaffolded). Rationale: TypeScript/React skill surface, true static output, low attack surface, first-class i18n routing, and the repo itself is a portfolio artifact.
|
||
- **Theme base:** AstroPaper-style minimalism (light/dark, fuzzy search, RSS + sitemap defaults). Don't rebuild from scratch.
|
||
- **Skip for v1:** WordPress-headless (two systems), Next.js SSR/ISR (heavier than needed), self-built blog engine (the classic time sink).
|
||
|
||
---
|
||
|
||
## 5. SEO checklist (2026 — two lanes)
|
||
|
||
Optimize for **both** classic SEO *and* GEO (getting cited by Google AI Overviews / ChatGPT Search / Perplexity).
|
||
|
||
- **E-E-A-T first:** real `Person`/`ProfilePage` schema around the author bio, with `sameAs` → LinkedIn, GitHub, git.hoelee.com.
|
||
- **Per-post meta:** unique title (≤60 chars), description (≤155), canonical, og:image (1200×630) + `twitter:card`.
|
||
- **Sitemap.xml + RSS + robots.txt** — generated automatically.
|
||
- **Core Web Vitals:** static site → 90+ LCP/CLS/INP easy. Verify with headless-Chrome screenshot + Lighthouse on 390px **and** 1280px.
|
||
- **Internal linking:** related-posts module + link pillar posts (case studies) from every tutorial.
|
||
- **Question-shaped headings:** write H2/H3 as the actual question a learner types. Rewarded by Google *and* AI engines.
|
||
- **Name consistency:** same name ("Lee Teong Hoe" / "Mr Hoelee") + photo + handles across blog, LinkedIn, GitHub, git.hoelee.com. Fragmented identity is the #1 personal-brand SEO killer.
|
||
|
||
---
|
||
|
||
## 6. What to AVOID in design
|
||
|
||
| Anti-pattern | Why |
|
||
|---|---|
|
||
| Heavy JS / client-side rendering | Static output is the point — don't reintroduce runtime cost. |
|
||
| Reused logo as og:image | Already an issue on hoelee.com. Custom per post. |
|
||
| Anonymous/absent author card | Kills E-E-A-T. |
|
||
| Wall-of-text without code blocks | Technical posts need formatted, copyable code. |
|
||
| No RSS / sitemap | Both are free in Astro; omitting them is pure loss. |
|
||
| Orphaned pages / thin content | hoelee.com already has ~400; keep this sitemap clean. |
|
||
| Over-styled "agency look" | This is a personal engineering blog — credibility over flash. |
|