今天要解的問題
Day16 照著設計 handoff 修了部落格,之後 /projects/ 又收進鐵人賽的文章,補了 Art-Tracking、LLM Wiki 和 kiwi-walk 三個專案頁:每個專案頁底下有一份「開發紀錄」,列出屬於它的文章。
專案頁變多之後,/projects/ 的卡片列表開始不好看。我列了幾件事給 Claude:
- Art-Tracking 的截圖太長(主線)
- 專案的順序要重新安排
- 卡片上的「最新」要連到最新一篇文章,而且往上放
- 已經 archived 的專案移到最下面,自成一區
- 手機版的選單收成漢堡按鈕
這五件事做完、合併上線之後,我看著成品又改了第二輪:
- 小卡再精簡:狀態標籤、技術標籤、「網站/GitHub」按鈕都拿掉
- 專案頁的封面縮小,移到標題旁邊
- 封面是截圖的專案改用另外畫的 icon,截圖移進內文

第一件事奇怪的地方在於,卡片封面的 CSS 本來就寫了固定比例和裁切:
.project-card-cover {
display: block;
aspect-ratio: 16 / 9;
background: var(--border-warm);
}
.project-card-cover img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
照這段,每張封面都應該是 16:9。但在 1024px 寬的正式站上量出來是這樣:
| 專案(圖片原始尺寸) | 封面實際大小 | 卡片高度 |
|---|---|---|
| Art-Tracking(430×1100) | 256×654 | 985 |
| LLM Wiki(1840×1240) | 256×172 | 985 |
| Commute(512×512) | 256×256 | 985 |
| kiwi-walk(492×784) | 256×407 | 770 |
沒有任何一張是 16:9,每張圖都是照自己的原比例顯示。 Art-Tracking 的封面是一張手機的整頁截圖,於是它那一列的三張卡片全部被拉到 985px 高。kiwi-walk 的截圖也是直式的,只是沒那麼長。
想法與取捨
封面比例:16:9 還是 4:3
把比例修到真的生效之後,才會第一次發生裁切。在那之前,方形的 icon(Coffee Review、Commute、Daily 是 512×512,Claude Monitor、Manga Monitor 是 640×640)一直是完整顯示的。
所以比例要重新選。原本寫的 16:9 套在方形圖上,會裁掉 44% 的高度;4:3 只裁上下各 12.5%。這個列表裡方形圖占了一半以上,所以改成 4:3。
裁切之後看不到重點:交給各專案的 frontmatter
固定比例之後,預設是裁掉上下、留中間。Art-Tracking 那張截圖的中段是一張展覽的照片,看不出這是什麼 app;頁面標題、搜尋框和快捷 chip 都在最上面。
所以卡片多讀兩個選填的 frontmatter:
cover.position:裁切時要留哪一塊。兩張直式截圖(Art-Tracking、kiwi-walk)設成topcover.fit: contain:完整顯示、不裁切
第二個是修完 4:3 之後才加的。計劃裡原本就寫了一條退路:如果 Daily 的 icon 被切得難看,就對它單獨用 contain。實際看畫面,Daily 的 icon 有一圈外框,上下兩邊的框被切掉、左右還在;LLM Wiki 的封面是一張圖表,左右被裁掉之後,標題的第一個字被切掉一截。這兩個專案就設成 fit: contain,代價是封面的上下或左右會露出底色的邊。
這一節提到的兩張截圖和 LLM Wiki 的圖表,到第二輪都不在封面上了,見後面「封面放截圖,還是另外畫 icon」。
順序:依最近活動自動排,還是手動 weight
原本的順序是各專案頁的 date 由新到舊。Claude 給了三個選項:依最近活動自動排(用最新一篇文章的日期,沒文章的用專案 date)、手動 weight、維持現狀。它推薦第一個,我選了手動 weight。
初始值是 Claude 依最近活動排的,以 10 為間隔:LLM Wiki 10、Coffee Review 20、Art-Tracking 30……之後想調就改數字。
Archived:先問哪些算
九個專案頁的 status 全都寫著 active,Claude 沒辦法從資料判斷哪些已經停了。它列出比較久沒動的幾個讓我選,我勾了 Claude Monitor 和 Manga Monitor;Commute 也在候選名單裡,我留在 active。
做法是把這兩個專案頁的 status 改成 archived,列表模板依這個欄位把專案分成兩區。
「最新」放在卡片、專案頁,還是都放
原本卡片 footer 的最底下有一行「最新:2026-09-18」,只有日期,不能點。
我的要求是「改成最新的文章,然後放到上面」。這句話可以指 /projects/ 的卡片,也可以指各專案自己的頁面:專案頁的「開發紀錄」本來就是新到舊,但排在整頁最底下。Claude 問了要做在哪裡,我選兩邊都做:
- 卡片:日期換成最新一篇文章的標題連結,移到描述下方
- 專案頁:封面下方、內文上方加同一個區塊,底部的「開發紀錄」保留
標題不是手動輸入的。build 的時候找出這個專案最新的一篇文章,讀它的 title,再把開頭的 Day4 [art-tracking] 去掉,因為在專案的脈絡下這是重複資訊。代價是去前綴的規則只認 DayN [專案名] 這個格式,標題寫成別的樣子就會原樣顯示。
第二輪把這個區塊的標籤從「最新」改成「最新開發紀錄」,跟專案頁底部的「開發紀錄」用同一個詞。
漢堡選單:複製一份 theme 的 header
Day16 加了 Series 之後,手機上的選單換成兩行,當時的處理是讓它整齊地換行。今天要把它收成一顆按鈕。
按鈕得放在 <nav> 裡面,但 PaperMod 沒有給 header 留擴充點,所以把 theme 的 header.html 整份複製到自己的 layouts/partials/,再加上按鈕和一小段 inline script。theme 的 submodule 沒有動。代價是這份複本跟 theme 脫鉤了,之後如果升級 PaperMod,要重新對一次。
另外兩個決定:
- 沒有 JS 的時候不收合。 「把選單藏起來」的 CSS 掛在
.has-menu-toggle這個 class 底下,而這個 class 是 script 加上去的。script 沒跑的話,選單就維持 Day16 的換行顯示,不會出現一顆按不動的按鈕和一份打不開的選單 - 只在 768px 以下收合。 769–1000px 還是用 Day16 的換行規則,更寬就是原本的橫排
小卡上要留什麼:只留封面、標題、描述和最新一篇
以下三個是上線之後第二輪的決定。
第一輪做完,小卡上有六樣東西:封面、標題加狀態標籤、描述、最新一篇、技術標籤、網站與 GitHub 按鈕。我拿掉了三樣:
- 狀態標籤:列表已經分成上面的 active 和底部的 Archived 兩區,每張卡片再標一次 ACTIVE 或 ARCHIVED 是重複的
- 技術標籤:我不需要在這裡列出用了哪些技術
- 網站與 GitHub 按鈕:這兩個連結放在專案自己的頁面就好,小卡上不用
Claude 一開始只改了小卡,專案頁的狀態標籤和技術標籤都留著。我在專案頁上再指了一次,兩樣也拿掉,專案頁只留網站與 GitHub 按鈕。status 這個欄位現在只用來分區。
專案頁的封面:照原比例縮小,還是裁成方形
專案頁的封面原本放在標題和按鈕下面,最高 360px。我要它縮小、放到標題旁邊。
Claude 的做法是不裁切,照原比例縮進一個 88px 的方框(手機 72px)。理由是桌機上的文章圖片可以點擊放大,縮圖維持原比例,點下去還是能看到完整的原圖。
代價在直式截圖上很明顯。Coffee Review 的方形 icon 是完整的 88×88,但 Art-Tracking 的截圖變成 34×88 的細長條,kiwi-walk 是 55×88,LLM Wiki 的圖表是 88×59。Claude 回報了這件事,也給了另一個做法:全部裁成方形,但這樣點擊放大只會放大裁剩的那一塊。另外點擊放大只在 769px 以上啟用,手機上就只剩那條縮圖可看。
封面放截圖,還是另外畫 icon
看到細長條之後,我沒有在上面兩個做法裡選,而是換掉封面本身:封面是截圖的專案各畫一張 icon,原本的截圖放進專案頁的介紹裡。
- Art-Tracking、LLM Wiki:Claude 各畫一張 512×512 的 icon
- kiwi-walk:直接用部落格的吉祥物,不另外畫
- 三張原本的圖(Art-Tracking 首頁截圖、LLM Wiki 的三層分工圖、部落格首頁截圖)都移到各自專案頁「這是什麼」的段落後面
這樣封面只負責代表這個專案,截圖放在它被文字介紹的地方。第一輪為直式截圖加的 cover.position: top,現在沒有任何專案在用;cover.fit: contain 還有 Daily 和 kiwi-walk 的吉祥物在用。
實作
封面:把圖片拿出文件流
.project-card-cover {
position: relative;
display: block;
flex-shrink: 0;
aspect-ratio: 4 / 3;
overflow: hidden;
background: var(--border-warm);
}
.project-card-cover img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
圖片改成絕對定位之後不再參與封面的高度計算,封面的高度只由 aspect-ratio 決定,圖片再去填滿它。為什麼原本的寫法沒用,見「踩到的坑」。
裁切位置和不裁切,由卡片模板從 frontmatter 組成 inline style:
{{- $coverStyle := slice }}
{{- with .Params.cover }}
{{- with .image }}{{ $cover = . }}{{ end }}
{{- with .alt }}{{ $coverAlt = . }}{{ end }}
{{- with .position }}{{ $coverStyle = $coverStyle | append (printf "object-position: %s" .) }}{{ end }}
{{- with .fit }}{{ $coverStyle = $coverStyle | append (printf "object-fit: %s" .) }}{{ end }}
{{- end }}
<article class="post-entry project-card">
<a class="project-card-cover" href="{{ .RelPermalink }}" rel="noopener" aria-label="{{ .Title }}">
<img src="{{ $cover }}" alt="{{ $coverAlt }}" loading="lazy"
{{- with $coverStyle }} style="{{ delimit . "; " | safeCSS }}"{{ end }} />
</a>
$cover 和 $coverAlt 在前面有預設值(/images/<代號>.png 和專案名稱)。最後的 safeCSS 是 Day16 踩過的坑:html/template 會把 style 屬性裡它不放心的值換成 ZgotmplZ,這次直接標成安全的 CSS。
專案頁那邊只要加一行,例如 Daily:
cover:
image: /images/daily-tracking.png
alt: Daily app icon
fit: contain
weight: 60
最新開發紀錄:卡片與專案頁共用一個 partial
{{- $slug := .Params.projectSlug -}}
{{- if $slug -}}
{{- $devlogs := where site.RegularPages "Params.project" $slug -}}
{{- range first 1 $devlogs.ByDate.Reverse }}
<a class="project-latest" href="{{ .RelPermalink }}" rel="noopener">
<span class="project-latest-label">最新開發紀錄</span>
<time class="project-latest-date" datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "2006-01-02" }}</time>
<span class="project-latest-title">{{ replaceRE `^Day\d+\s*\[[^\]]+\]\s*` "" .Title }}</span>
</a>
{{- end }}
{{- end -}}
傳進來的是專案頁。找出 frontmatter 有 project: <代號> 的文章,取日期最新的一篇;range first 1 讓沒有文章的專案什麼都不輸出。查詢的寫法跟「開發紀錄」那個 partial 一樣,原本卡片用的是另一種寫法(site.Pages),這次順便統一。
排序與 Archived 分區
卡片的 HTML 抽成 project-card.html,列表模板只剩分組:
{{- $active := where .Sections "Params.status" "ne" "archived" }}
{{- $archived := where .Sections "Params.status" "archived" }}
<div class="projects-list">
{{- range $active.ByWeight }}
{{- partial "project-card.html" . }}
{{- end }}
</div>
{{- with $archived }}
<h2 class="projects-archived-title">Archived</h2>
<div class="projects-list projects-list--archived">
{{- range .ByWeight }}
{{- partial "project-card.html" . }}
{{- end }}
</div>
{{- end }}
沒有 archived 專案的時候,with 讓整區連標題都不出現。Archived 區的封面另外用 CSS 降了一點飽和度。

小卡最後的樣子
第二輪拿掉標籤和按鈕之後,卡片的內容區只剩三塊:
<div class="entry-body">
<header class="entry-header">
<h2 class="project-card-title entry-hint-parent">
<a href="{{ .RelPermalink }}" rel="noopener">{{ .Title }}</a>
</h2>
</header>
{{- if .Description }}
<p class="project-card-desc">{{ .Description }}</p>
{{- end }}
{{- partial "project-latest.html" . }}
</div>
只給狀態標籤、技術標籤和卡片 footer 用的 CSS 也一起刪掉。
專案頁:縮圖放在標題旁邊,截圖放進內文
封面沿用 PaperMod 的 cover.html,只是從標題下方搬進標題那一列:
<div class="project-landing-title-row">
{{- $isHidden := (.Param "cover.hiddenInSingle") | default (.Param "cover.hidden") | default false }}
{{- partial "cover.html" (dict "cxt" . "IsSingle" true "isHidden" $isHidden) }}
<h1 class="project-landing-title">{{ .Title }}</h1>
</div>
.project-landing-title-row {
display: flex;
align-items: center;
gap: 16px;
}
.project-landing .entry-cover {
flex: 0 0 auto;
margin: 0;
}
.project-landing .entry-cover img {
display: block;
width: auto;
height: auto;
max-width: 88px;
max-height: 88px;
border-radius: 12px;
}
/* ... 768px 以下改成 72px */
.project-landing-about img {
width: auto;
max-height: 560px;
}
max-width 和 max-height 同時設、寬高都是 auto,圖片就會照原比例縮進 88px 的方框,不裁切。最後一條是給移進內文的截圖用的:Art-Tracking 那張 430×1100 的截圖不設上限會佔掉一整個螢幕,設了 560px 的高度上限之後是 219×560。

兩張 icon
icon 是 Claude 寫 SVG 再轉成 512×512 的 PNG,平面色塊,比照現有的 Coffee Review 和 Daily。LLM Wiki 那張是三個疊起來的菱形,顏色取自原本三層分工圖裡的 Raw、Wiki、Schema:
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
<rect width="512" height="512" fill="#F3E9D2"/>
<g stroke="#F3E9D2" stroke-width="12" stroke-linejoin="round">
<path d="M256 264 L416 328 L256 392 L96 328 Z" fill="#9A918B"/>
<path d="M256 192 L416 256 L256 320 L96 256 Z" fill="#A0553D"/>
<path d="M256 120 L416 184 L256 248 L96 184 Z" fill="#4A5560"/>
</g>
</svg>
外面那層 <g> 的描邊用的是底色,原本想在三層之間留一道縫。轉出來的 PNG 上沒有這道縫,三層直接疊在一起也看得懂,就沒有再處理。Art-Tracking 那張是深色牆面上的一幅畫,右下角加一個地點圖釘。
卡片上的 4:3 會裁掉方形圖上下各 12.5%,所以兩張的圖案都畫在中間,上下留白。kiwi-walk 的吉祥物是圓的、貼到圖的邊緣,裁了會缺角,所以它用 fit: contain。
![]()
漢堡選單
header 的複本裡,在 logo 和 <ul id="menu"> 之間加一顆按鈕(兩個 SVG 圖示省略),</nav> 後面接 script:
<button id="menu-toggle" class="menu-toggle" type="button" aria-label="選單" aria-controls="menu" aria-expanded="false">
<!-- ... 三條線與叉叉兩個 SVG -->
</button>
(function () {
var toggle = document.getElementById('menu-toggle');
if (!toggle) return;
toggle.parentNode.classList.add('has-menu-toggle');
function setOpen(open) {
toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
}
toggle.addEventListener('click', function () {
setOpen(toggle.getAttribute('aria-expanded') !== 'true');
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && toggle.getAttribute('aria-expanded') === 'true') {
setOpen(false);
toggle.focus();
}
});
})();
開合的狀態只存在按鈕的 aria-expanded 上,沒有另外的 class。CSS 直接讀這個屬性:
.menu-toggle {
display: none;
}
@media (max-width: 768px) {
.has-menu-toggle .menu-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
margin: auto var(--gap);
border-radius: 8px;
color: var(--ink-warm);
}
/* ... 兩個圖示的切換 */
.has-menu-toggle #menu {
display: none;
flex-basis: 100%;
flex-direction: column;
flex-wrap: nowrap;
row-gap: 0;
margin: 4px var(--gap) 8px;
padding-top: 4px;
border-top: 1px solid var(--border-warm);
line-height: 48px;
}
.has-menu-toggle .menu-toggle[aria-expanded="true"] ~ #menu {
display: flex;
}
}
按鈕在 #menu 前面,所以用 ~ 就能從按鈕的狀態控制選單。flex-basis: 100% 讓選單換到 logo 列的下一行,變成整寬的直排清單,會把頁面往下推,不是蓋在上面。按 Esc 會收合,並把焦點還給按鈕。

踩到的坑
CSS 寫了 aspect-ratio,Claude 就以為卡片有裁切
我只說了「Art-Tracking 截圖過長」。Claude 先讀程式碼,看到卡片封面寫著 aspect-ratio: 16 / 9 加 object-fit: cover,結論是「卡片上的截圖會被裁成 16:9」,所以推測我說的是專案頁上的封面。但專案頁的封面有 max-height: 360px,那張截圖在那裡是 141×360,並不長。
接著它打開正式站。第一張截圖裡,每張卡片的封面都是一塊灰色,量出來圖片的原始尺寸是 0×0:圖片是 lazy loading,還沒載入。把圖片改成立刻載入再量,才看到開頭那張表:Art-Tracking 的封面是 256×654。
原因在這三件事湊在一起:
- 卡片
.project-card是直向的 flex 容器,封面是它的子項目,min-height是預設的auto - 封面自己沒有寫死高度,高度要靠
aspect-ratio推出來 - 圖片的
height: 100%對不到一個確定的高度,等於沒寫,圖片就照自己的原比例長到 654px 高
圖片還在文件流裡,封面的高度就跟著內容走,aspect-ratio 讓位。修法是把圖片改成絕對定位,讓封面裡沒有東西能撐開它。我沒有試其他修法(例如只加 min-height: 0),直接用了這個。
之後看到「CSS 寫了固定比例」,會先量畫面上的實際尺寸,再判斷問題在哪一頁。
「最新」是算出來的,漏一行 frontmatter 就會指錯
為了確認每個專案的「最新」會連到哪一篇,Claude 把所有帶 project: 的文章列了一遍。LLM Wiki 的最新一篇是 Day17,但 Day18 的標題明明是 [llm-wiki]。原因是 Day18 的 frontmatter 漏了 project: llm-wiki 這一行,所以它既不在 LLM Wiki 的「開發紀錄」裡,「最新」也輪不到它。補上之後,開發紀錄從 6 篇變 7 篇,最新指到 Day18。
標題裡的 [llm-wiki] 和 tag 都只是給人看的,模板只認 project: 這個欄位。
描述浮在卡片中間,不是今天加的
加了「最新」區塊之後,沒有文章的卡片(例如 Commute)看起來很怪:標題下面空一大段,描述浮在卡片中間,按鈕在底部。
量了一下 Commute 卡片的內容區,高 348px,標題在 16px、描述在 144px、按鈕在 294px 的位置:三個元素被平均撐開了。原因是 custom.css 裡有一條給文章列表卡片用的 .entry-body,設了 justify-content: space-between,專案卡片用了同一個 class,也吃到它。同一列的卡片會拉成一樣高,內容少的卡片就被撐開。回頭看修正前的正式站,Commute 本來就是這樣,只是那時候沒有「最新」區塊對照,不顯眼。
.project-card .entry-body {
display: flex;
flex-direction: column;
justify-content: flex-start;
gap: 10px;
padding: 16px 18px 18px;
}
改成從上往下排。第一輪還讓按鈕那一列用 margin-top: auto 貼到卡片底部;第二輪按鈕整個拿掉,就只剩這一段。
量到 0×0、截到關著的選單:都是時機
驗證的時候有兩次拿到不合理的結果。
第一次是改完封面之後量卡片:封面全部是 0×0,視窗寬度也是 0,卡片高度兩千多。但同一時間的截圖是對的。那次的量測是在分頁還沒顯示出來的時候跑的,等分頁顯示之後重量一次,九張封面都是 256×192。
第二次是漢堡選單。點了按鈕之後,讀到 aria-expanded 是 true、header 的高度從 68px 變成 421px,截圖裡的選單卻是關著的。等一秒再截,選單就是開的。這跟 Day16 那張空白截圖是同一件事:截得太早,畫面還沒畫出來。
另外還有一次,在模擬 375px 的視窗裡點按鈕,狀態完全沒變,改用 script 直接觸發 click 就開了。這次我沒有往下查原因,後來換成在一個本來就夠窄的視窗(約 470px)裡點,是正常的。
這次比 Day16 好的地方是,判斷選單有沒有打開看的是 DOM 上的狀態,截圖只拿來看樣子。量出來的數字不合理的時候,先看視窗寬度是不是 0。
拿掉一層包裝,標題從 20px 變成 24px
第二輪拿掉狀態標籤之後,標題外面那層用來排「標題加標籤」的 div 也不需要了,Claude 把它一起刪掉,標題的 CSS 從 .project-card-title-row h2 改寫成 .project-card-title。
畫面看起來沒問題,但量出來標題是 24px,不是原本的 20px,卡片也高了 5px。原因是優先權:
- PaperMod 有一條
.entry-header h2 { font-size: 24px },優先權是 (0,1,1) - 舊的
.project-card-title-row h2也是 (0,1,1),同分時寫在後面的贏,所以一直是 20px - 新的
.project-card-title只有一個 class,(0,1,0),輸給 PaperMod
/* Two classes so this outranks PaperMod's `.entry-header h2` (24px). */
.project-card .project-card-title {
margin: 0;
font-size: 20px;
line-height: 1.3;
}
改成兩個 class 之後回到 20px。這跟 Day16 的全黑按鈕是同一類問題:改 selector 的寫法會改變優先權,而主題的規則一直都在。重構 CSS 之後要量字級和高度,不能只看畫面像不像。
縮圖放大,圓角也放大了十倍
專案頁的縮圖有 12px 的圓角。點擊放大的做法是把原本那個 <img> 直接用 transform 放大,所以圓角也一起被放大:Art-Tracking 那條 34×88 的縮圖放大成 294×752,是 8.5 倍,圓角看起來有 100px 左右,截圖的四個角被削成像手機外框。
.project-landing .entry-cover img.imgzoom-on {
border-radius: 0;
}
放大的時候把圓角拿掉。改完用當時還是圖表的 LLM Wiki 封面驗證:88×59 放大到 976×658,圓角是 0。
小結
- 卡片封面:九張封面在 1024px 下都是 256×192,375px 下都是 317×238。第一列的卡片高度從 985px 降到第一輪的 676px,第二輪拿掉標籤和按鈕之後是 495px
- 順序與 Archived:改用
weight手動排序,Claude Monitor、Manga Monitor 移到底部的 Archived 區 - 最新開發紀錄:有文章的四個專案在卡片和專案頁上都多了最新一篇的連結(第一輪驗證時是 LLM Wiki → Day18、Coffee Review → Day15、Art-Tracking → Day4、kiwi-walk → Day16),沒有文章的專案不顯示
- 小卡:只剩封面、標題、描述、最新開發紀錄
- 專案頁:封面變成標題旁邊的縮圖(桌機 88px、手機 72px),狀態標籤和技術標籤拿掉,網站與 GitHub 按鈕保留
- 封面換成 icon:Art-Tracking、LLM Wiki 各畫一張,kiwi-walk 用吉祥物;三張原本的圖移進「這是什麼」,高度上限 560px
- 漢堡選單:768px 以下選單收在一顆 44×44 的按鈕後面,展開是七個各 48px 高的項目;900px 和 1280px 下按鈕不出現,選單照舊
- 另外修了描述浮在卡片中間的舊問題,補了 Day18 漏掉的
project:
Hugo build 沒有 warning。第一輪的兩個 commit 在 #26 合併。
有兩件事看到了但沒做:
- 卡片有兩層內距(PaperMod 給
.post-entry的 24px,加上自己寫的.entry-body18px),描述每行只排得下十個中文字左右。這不在今天列的項目裡,先留著 - 六個專案頁的 frontmatter 還留著
tech欄位,現在沒有任何模板在讀,資料先不刪
本文同步發表於 kiwi-walk.com:https://kiwi-walk.com/blogs/engineer/ironman-2026-day20-project-cards-and-mobile-menu/
