
和 AI 智能體花五個回合做出部落格 logo:留下來的是一個 /logo skill 與三支指令碼
用 AI 智能體重做這個部落格的 logo 與程式碼字型:壞掉的 SVG 字形、用正規表達式算出的邊界框、猜錯的漢字,以及最後長出來的 /logo skill。
本頁目錄
引言
我坐下來,打算修掉一個我不滿意很久的 logo:一個純文字的標記,是這個專案開工第一天自己生出來的,之後沒有人為它做過任何決定。
用 AI 智能體設計 logo 不是一次就能收工的事,我也不希望它是。我真正想知道的,是這個迴圈本身值不值得寫成一個 Claude Code skill:智能體給出候選,我看、我砍、我說明理由,然後再跑一輪,直到我滿意為止。智能體在這幾個回合裡有五次自信滿滿地弄錯,而我不認為其中任何一次是浪費。 每一次走錯路都長出一條規則,這些規則現在成了一個 /logo skill 與三支指令碼,下一個標記可以直接從那裡開始。
最後出貨的,是一枚寫著 桜 的朱紅印章,旁邊是用 Fraunces 排的「oharu」,再加上只在文章頁生效的 JetBrains Mono。本文會走完這五個回合、每一個回合各自花了什麼代價才被發現,以及它們最後變成的那個 skill。
這個部落格沒有設計,而我一直將就著用
當時的 favicon 是這樣:
rect,加上一個指定 Helvetica,Arial,sans-serif 的 <text>,以 favicon 實際會被用到的三種尺寸並列。那個字母是訪客的作業系統給什麼就是什麼。那不是 logo,是一個我一直將就著用的佔位符。頁首連標記都沒有,只有純文字的網站標題。
排版也是同一個故事:
--font-sans: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, …;--font-mono: ui-monospace, 'SF Mono', SFMono-Regular, Menlo, Consolas, …;純系統堆疊。所以字型並不是無聊。根本沒有字型。 每一位訪客看到的都是自己作業系統的臉。這是一個正當的取捨(零位元組、零版面位移),但它不是任何人做出的決定。
錯誤一:同一個想法的二十四種變化
我先要的是廣度:跨越純圖示、文字商標與組合標記的二十四個概念,並算繪成一張同時以 128px、32px 與 16px 呈現的清單。
它產出了二十四個。它們全都是同一個 logo。
![]()
這是整張表的圖示那一半。光圈、軌道、終端機、圓弧、鳥居、方圓形、提示符、分裂。八個名字,指向同一張圖。每一個都是單線的幾何筆畫:圓、弧、均勻線寬、圓形端點。不管是圖示還是文字商標,骨架都一樣。那副骨架正是語言模型最先伸手拿的預設值,也正是網路上每一個 AI logo 產生器的招牌風格。我要的是幅度,拿到的是戴了八頂帽子的重複。
解法不是更好的提示詞,而是先去看真正的作品。六個識別,逐一截圖並實際端詳:
| 參考對象 | 它做了什麼 | 第一輪做了什麼 |
|---|---|---|
| Linear、Astro | 實心填色,標記小而安靜 | 全是線條,到 16px 就糊成一團 |
| Rauno Freiberg、Josh Comeau | 完全沒有標記。 只有字,加一個下定決心的顏色 | 預設標記是必要的 |
| Maggie Appleton | 展示用襯線體。 因為那是一個寫作的網站 | 幾何無襯線,範本的預設值 |
| Anthony Fu | 個人字母組合,手寫的字 | 純幾何長不出「個人」 |
第二輪回來的東西是真的不一樣了,因為這次的限制來自真實存在的地方:

這個教訓不只適用於 logo:向 AI 要 N 個選項,你拿到的是同一個分布裡的 N 個樣本。 幅度只能從外部強加進去,而去看真正的作品,就是找出「外部」長什麼樣子的方法。
錯誤二:從來沒有被算繪過的字形
第一輪是手工搭出來的幾何,而且一次都沒有算繪就直接進了清單。三個 bug 就攤在那裡。
最糟的是字母 u:
M9,49 L9,99 A41,41 0 0 0 91,99 L91,140把它當成一支筆來讀。左邊字幹從 x-height 往下畫到 y=99。碗狀弧線橫過去到 (91,99)。然後一條線往下到基線。弧線結束在 y=99,所以右邊的字幹只從 99 走到 140:整個上半截不見了。 這個字會被算繪成右側發育不良的 u。
修法是拆成獨立的子路徑:
M9,49 L9,99 A41,41 0 0 0 91,99 ← bowlM91,49 L91,140 ← full-height right stem
另外兩個是同一類錯誤。一行放在 y=160、縮放 0.26 的副標題,上伸部會落在 160 + 0.26 × (−700) = −22,比它底下那個字的基線還高,於是 t、h、b、l 直接刺穿了名字。還有一個標記畫成 220 單位高,旁邊的字母卻是 700 單位高,看起來不像 logo,比較像一個走失的項目符號。
這些在瀏覽器裡都一目了然,在路徑資料裡則完全看不見。這些幾何被推理過,卻從來沒有被看過。
錯誤三:智能體自己假設的一個字
第二輪跑出來最強的概念是日式的判子(hanko),也就是個人印章,這和部落格的本質相當接近。朱紅、一個字、白色反白。
名字是「oharu」,所以智能體用了 春(haru,春天)。看起來不錯,差一點就做下去了。
然後它問了,因為把一個字放進別人的識別裡,不是可以憑假設默默做掉的事。我更正它:「oharu」是 桜明,完全不是 春。
這個答案改動的不只是一個字符,而是整個設計。桜明 是兩個字,而兩個字撐不過 16px 的 favicon:在那個尺寸下,十畫的漢字是紋理,不是文字。

16px 的算繪結果就把事情定了。直排的 桜明 是這個名字正確的判子,卻是瀏覽器標籤頁裡表現最差的一個;朱文外框版本更是整個消失。所以印章變成圓形、單字的 桜,而這個選擇是由筆畫數決定的,不是品味。
一般化的版本是:AI 很樂意在一個關於你的猜測之上,做出自信、合理而且完成度很高的成品。 那個猜測在輸出裡看不出來,只能靠問才會浮現。
錯誤四:用正規表達式算出來的邊界框
到了真正要用的資產,手繪字母讓位給從真實字型烘焙出來的外框:文字商標用 Fraunces,桜 用 Noto Serif JP,透過 fontTools 與 HarfBuzz 處理。正確的字距與對比被凍結成路徑資料,所以文字商標是以幾何形狀出貨的,沒有網頁字型請求,也沒有備用字型閃現。
要擺放一個字符,就需要它的實際墨水邊界。那支程式把路徑字串裡的每一個數字都抓出來,當成 x、y 交替的座標對。
這是錯的,而且錯得很安靜。SVGPathPen 會輸出 H 與 V 這兩個簡寫,而它們攜帶的座標只有一個。只要出現一條水平線,後面每一個數字的奇偶位置都會翻掉。
這個 bug 以「看起來只是有點怪」的樣子撐過了兩輪清單。它變得無法否認,是在一個應該大約 3200 單位寬的組合標記算出來只有 1802 的時候。
修法是別再解析文字,讓函式庫去量:
from fontTools.pens.boundsPen import BoundsPen
bounds = BoundsPen(glyph_set)for info, pos in zip(buf.glyph_infos, buf.glyph_positions): xform = (scale, 0, 0, -scale, (x + pos.x_offset) * scale, 0) glyph_set[name].draw(TransformPen(pen, xform)) glyph_set[name].draw(TransformPen(bounds, xform)) # same transform文字商標真正的邊界是 2456.4 × 758.6。正規表達式從第一張清單開始,就一直很有自信地回報別的數字。
這是最值得單獨拿出來講的失敗形式:它沒有崩潰。 它產出了看起來合理的結果,而人類瞄了一眼就通過,還通過了兩次。
錯誤五:每一頁都掛著的 preload
這次工作階段的另一半,是給程式碼自己的字型。程式碼區塊是技術部落格真正被閱讀的表面,而我的程式碼區塊當時是用 Menlo 算繪的,和內文一樣處在「沒有人做過決定」的狀態。
JetBrains Mono Variable,只取 Latin 子集,40KB,自架在固定路徑上,好讓它可以用 URL 預先載入。這在日文頁與中文頁上完全不花成本:程式碼是 Latin,所以 CJK 堆疊完全不動,也不會有任何 CJK 字型被抓下來。
preload 被放進了共用版面。瀏覽器直接說明了那為什麼是錯的:
The resource http://localhost:4321/fonts/jetbrains-mono-latin.woff2 waspreloaded using link preload but not used within a few seconds from thewindow's load event. Please make sure it has an appropriate `as` value andit is preloaded intentionally.首頁、標籤列表頁,以及兩個 CJK 索引頁上一行程式碼都沒有。它們每一頁都在抓 40KB 來算繪出零。 把它收在只有文章頁會設定的 prop 後面:
| 頁面 | rel="preload" 連結數 |
|---|---|
dist/index.html |
0 |
dist/ja/index.html |
0 |
dist/tags/index.html |
0 |
dist/blog/<article>/index.html |
1 |
一則主控台警告,換到一份真實的節省。這是五次裡唯一一次工具主動說出來的錯誤。 其餘全部都是因為有人跑去看,才浮上檯面。
這些回合留下的東西:一個 /logo skill
每一次走錯路,都長出一條下一次不能跳過的規則。那些規則就是這個 skill 的不可妥協事項,並且照著「跳過它會造成多大損害」由大到小排列:
| 規則 | 它從哪裡來 |
|---|---|
| 在生成任何東西之前,先研究真正的作品 | 同一個分布裡的二十四個概念 |
| 拿給任何人看之前,先算繪出來親眼看 | 只剩一半字幹的 u |
| 用 16px 來判斷 | 在瀏覽器標籤頁裡糊成一團的 桜明 |
| 絕不把關於這個人的假設烘進去 | 春 |
| 用真實字型的外框,不要手工畫字形 | 手工搭出來的幾何,以及量它的那段正規表達式 |
| 顏色靠比較決定,不靠理論 | 紅色印章旁邊的藍色文字商標 |
| 每一道關卡都由使用者決定 | 迴圈本身 |
最後一條讓其餘幾條成立。這個 skill 被要求:提出方案、附理由推薦,然後停下來,因為意見有用,決定不是你的。本文裡的每一個回合,都結束在一道由我來選的關卡上。 這也是為什麼五次走錯路的代價是半天,而不是一個建立在猜測之上的完成品。
機械的那一半由三支指令碼負責。gallery.mjs 組出對照清單並在本機連接埠上提供服務,outline.py 透過 fontTools 與 HarfBuzz 把字型外框烘成路徑資料,build-brand-lockup.mjs 則是產出 favicon 與頁首元件的示範。概念與清單都寫進暫存目錄,絕不寫進儲存庫:一輪設計產出的大多是被淘汰的東西,而儲存庫不是放淘汰品的地方。
錯誤五沒有在這裡留下規則。在沒有程式碼的頁面上發動的 preload 是版面的 bug,不是設計的 bug,它屬於這次工作階段的字型那一半,而不是標記。
出貨的東西
一枚寫著 桜 的朱紅圓印,旁邊是用 Fraunces 排的「oharu」。SVG 是兩個而不是一個,因為印章刻意做得比文字商標的墨水高度矮,所以沒辦法靠把整個組合標記裁成正方形來生出窄螢幕版本。低於 50rem 時只出現印章,這順手解決了舊的純文字商標留下的版面問題:在手機上,標題會和搜尋控制項互相搶位置。
有一個顏色決定是靠看、不是靠推論做出來的。網站的強調色是 #1d4ed8,印章是 #d8452a。這兩個在色相上幾乎正對面,藍色文字商標擺在紅色印章旁邊會明顯打架。解法不是換一個紅色。 而是讓文字商標吃主題的文字色,把藍色留給連結,這樣兩者永遠不會並排。
成本是量出來的,不是估的:
| 文字商標路徑資料 | 3569 位元組(從 5145 取整數) |
| 印章路徑資料 | 1677 位元組 |
favicon.svg |
1878 位元組 |
| JetBrains Mono,Latin 子集 | 40KB,只在文章頁 |
/ja/ 上的網頁字型請求 |
1 個,且沒有 CJK 字型 |
astro check 對 53 個檔案回報 0 errors、0 warnings、0 hints。
總結
五次走錯路。沒有任何一次是靠對程式碼進行推理抓到的。
那些通用到不行的概念,需要真實的參考對象才會顯出它有多通用。壞掉的字形需要一個瀏覽器。猜錯的漢字需要一個提問。邊界框的 bug 需要一個荒謬到足以被注意的數字。多餘的 preload 需要一個主控台。
這份清單正是把它當成迴圈、而不是當成一句提示詞來跑的理由。產出二十四個打磨過的概念現在只要幾分鐘,這讓人非常容易把數量誤認成幅度,也非常容易接受一個建立在猜測之上的自信成品。抵銷它的不是更好的提示詞,而是把東西算繪出來、把東西量出來,並且在它取決於一個我掌握、而智能體並不掌握的事實時,開口問。
這五次在當時不像浪費,現在讀起來也不像,因為每一次都以一條規則的形式回來了。下一個標記不是從一張空白提示詞開始,而是從七條規則與三支指令碼開始,那才是值得留下的部分。