COURSE 章節目錄 · 課程首頁 · 成品 App

第 10 章 — 進階 9×9 與發佈:展開 81 格,然後讓全世界(和 AI)看見

學完能做什麼

核心觀念:81 格的勸退門檻,AI 把它變成一鍵

大谷翔平那張目標達成表流傳了十幾年,看過的人成千上萬,真正填完 81 格的人極少。不是方法不好,是門檻設計使然:中央 3×3 填完正有成就感,接著要為八個子目標各想八個具體行動——64 個空格瞪著你,多數人在這裡合上筆記本。這正是 AI 該站的位置:不是替你想得更好,是把「64 個空格」這個心理門檻變成一鍵,生出一版夠好的草稿,你只負責改與刪。改一個字的門檻,遠低於想一個詞。

本章的後半是發佈。做完的東西沒被看見等於沒做(前課模組 10 的開場白,在這裡依然成立),而這門課自己的發佈流程就是教材:dev 私有 repo 是工作檯,公開 repo 是櫥窗——設計文件、驗證截圖、Worker 原始碼、管理 token 全留在工作檯,一支 publish.sh 只把櫥窗該有的東西搬出去。分離的好處不只安全,還有自由:工作檯可以亂,櫥窗永遠乾淨。

步驟

1. 資料模型:actions 是 8×8

3×3 的九格存在 state.cells(第 02 章);展開後的 64 個行動存在 state.actions——八列,每列對應一個子目標的八個行動,沿用「null = 留給 AI、有物件 = 使用者鎖定」的同一套模型:

// localStorage 'goal-grid-state-v1'
state.cells   = Array(9);  // null | {text, source:'user'|'ai'},index 4 = 核心目標
state.actions = Array(8);  // null | Array(8) of (null | {text, source})

2. AI 展開:每個子目標一次呼叫

Worker 的 /fill 端點第 03 章就建好了,展開只是它的第二種模式(mode:'expand'):送核心目標、一個子目標、該列已填的行動,拿回八個行動。prompt 把方法論直接寫進規則——行動要具體到能每日檢核,並拿大谷當示範:

// worker-deploy/src/lib/prompts.js(節錄)
規則:
1. 行動要具體可執行、最好可每日/每週檢核
   (例:大谷在「運」底下寫「撿垃圾」「打招呼」這種日常小事)。
2. 每格 2-10 個字,繁體中文(台灣用語),彼此不重複,不用標點結尾,不用 emoji。
3. 已填的原樣保留,一字不改。

3. 序列展開:八次呼叫,一個迴圈

「展開 81 格」按下去是八次 API 呼叫。直覺會想用 Promise.all 八個一起發,這裡刻意用序列:

// app/js/main.js — runExpand(節錄)
const coreGoal = state.cells[4].text;
for (let s = 0; s < 8; s++) {
  aiProgressEl.textContent = `展開中 ${s + 1}/8`;
  const existing = (state.actions[s] || Array(8).fill(null)).map((a) => (a ? a.text : null));
  if (existing.every(Boolean)) continue;          // 這個子目標已滿,不重抽
  const subGoal = state.cells[posOfSubIndex(s)].text;
  const actions = await api.expandSub(coreGoal, subGoal, existing);
  actions.forEach((text, j) => {
    if (!existing[j]) setAction(state, s, j, text, 'ai');
  });
  persist();                                       // 每完成一列就存檔
}

序列的三個理由,都跟長流程的體驗有關:

注意展開的前提:3×3 必須先填滿(自己填或 AI 補),因為每一列展開都要拿「核心目標 + 該子目標」當脈絡。程式裡有守門,文案直接告訴使用者先做哪一步。

4. 9×9 桌布版型:主體與全圖分離

81 格直接鋪滿手機桌布,每格字只剩幾個像素——不可讀的桌布沒人會設。本課的版型把「情感主體」和「完整資訊」分開:中央 3×3 放大當視覺主角,81 全圖縮小放下方(直式)或右側(橫式),想細看時放大圖片看得到,平時鎖定畫面看到的是九個大字:

// app/js/wallpaper.js — layoutWallpaper(節錄)
if (mode === '9x9') {
  // 直式:3×3 中心移到 0.30H(騰出下方);橫式:3×3 置左 0.27W
  if (orientation === 'portrait') { cx = 0.5 * W; cy = 0.3 * H; }
  else { cx = 0.27 * W; cy = 0.5 * H; }
}
// 81 全圖:直式寬 0.92W、中心 0.74H;橫式寬 0.5W、中心 0.72W

色彩上沿用大谷表轉載圖的慣例:八個子目標各有一色,81 全圖裡每個外圍區塊的中心格用同一色呼應,視線能立刻對上「這個區塊在展開哪個子目標」:

// drawGrid81(節錄):外圍區塊中心格 = 子目標,同色呼應
drawCell(ctx, rect, g.radius, {
  fill: hexA(subColor, 0.32),
  border: hexA(subColor, 0.9),
  text: cellText(cells[subPos]),
  ...
});

文字縮放交給第 06 章寫好的 fitText(二分搜尋字級 + CJK 逐字斷行),81 格的字長差異再大也不會爆框。

5. 發佈:dev 私有 + 公開雙 repo,一支腳本同步

repo 有兩個:ai-goal-grid-course-dev(私有,日常工作都在這)和 ai-goal-grid-course(公開,GitHub Pages 從這裡服務)。同步靠一支三十行的腳本,值得整段讀懂:

#!/usr/bin/env bash
# scripts/publish.sh — 把教材從 dev repo 發佈到公開 repo
set -euo pipefail
cd "$(dirname "$0")/.."

PUB_REPO="https://github.com/yazelin/ai-goal-grid-course.git"
PUBLISH_PATHS=(index.html .nojekyll slides demos course app \
               assets sitemap.xml llms.txt robots.txt)

TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT
git clone -q --depth 1 "$PUB_REPO" "$TMP/pub"

# 清空(留 .git)後重放發佈集
find "$TMP/pub" -mindepth 1 -maxdepth 1 ! -name .git -exec rm -rf {} +
for p in "${PUBLISH_PATHS[@]}"; do
  [ -e "$p" ] && cp -r "$p" "$TMP/pub/"
done
# app 的 node 測試屬開發資產,不發佈
rm -rf "$TMP/pub/app/test"
cp README-public.md "$TMP/pub/README.md"

cd "$TMP/pub"
git add -A
if git diff --cached --quiet; then
  echo "publish: 沒有變更,跳過"
else
  git commit -q -m "publish: $(date +%Y-%m-%d) 教材更新"
  git push -q origin main
  echo "publish: 已推送 $(git rev-parse --short HEAD)"
fi

三個設計決定:

公開 repo 的 README 也是發佈品(README-public.md 改名而成)——dev repo 的 README 寫給自己,公開的寫給訪客,兩份各司其職。

6. AEO:讓 AI 搜尋引擎讀懂你的課

越來越多人問 ChatGPT / Perplexity 而不是 Google,你的課要讓 AI 講得出來(完整原理在前課模組 10,這裡列本課的實際配備):

llms.txt ——放在站點根目錄、專門寫給 LLM 的純文字說明書:一句話講清楚你是誰,然後把重要連結攤開:

# AI 互動行銷頁實戰 — 目標九宮格

> 教行銷人員把 AI 行銷頁零件整合成完整產品的實戰課程。
> 範例產品:訪客用 AI 填好「目標九宮格」⋯⋯生成專屬桌布下載,
> 並以 30 天行動追蹤模板為誘因留下 email。

## 成品
- [目標九宮格 App](https://yazelin.github.io/ai-goal-grid-course/app/)
## 課程章節(course/)
- 08 防刷與額度(Turnstile + D1 原子配額)
⋯⋯

sitemap.xml ——列出所有公開頁(首頁、app、slides、五個 demo),提交 Google Search Console。

JSON-LD(Course schema) ——課程首頁 <head> 裡給機器讀的身分證,AI 和 Google 都吃:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Course",
  "name": "AI 互動行銷頁實戰 — 目標九宮格",
  "description": "教行銷人員把 AI 行銷頁零件組成完整產品的實戰課程。",
  "provider": { "@type": "Person", "name": "Yaze Lin", "url": "https://yazelin.github.io/" },
  "isAccessibleForFree": true,
  "inLanguage": "zh-Hant"
}
</script>

robots.txt 的誠實註記 ——GitHub Pages 專案頁(帳號.github.io/repo名/)的 robots.txt 與 llms.txt 放在子路徑,爬蟲實際讀的是網域根。本課的 robots.txt 開頭就註明這件事,真正的站根檔案由 yazelin.github.io 主 repo 管理(在它的 llms.txt 列出所有子專案)。這是前課模組 10 用檢測工具實測過的坑:AEO 分數低,先確認工具掃的是哪個 URL。

另外兩個本課一直在做、容易忘記歸進 AEO 的事:後台與模板頁都加了 <meta name="robots" content="noindex">(admin.html、tracker.html 不該出現在搜尋結果);關鍵資訊全用文字寫在頁面上,不藏在圖裡。

7. OG 圖:分享出去的第一印象

課程連結被貼到 LINE / FB 時長什麼樣,由 OG tags 決定。本課的 assets/og-course.jpg 是 1200×630、壓在 200KB 內的 AI 生成圖(深夜藍加金的九宮格意象,不含文字——中文字交給 og:title 顯示,不讓 AI 畫)。鐵則跟前課一樣:og:image 必須是完整 https 網址,相對路徑抓不到;改圖後用 FB Sharing Debugger 強制重抓快取。

8. 驗收

給 AI 的 prompt 範本

展開 81 格與 9×9 桌布(接在你既有的九宮格 App 上):

我的九宮格 App 已有 3×3 填寫、AI 補格、桌布合成。幫我加「進階 9×9」:

【資料模型】localStorage state 加 actions:長度 8 的陣列,每項是 null 或
長度 8 的 (null | {text, source:'user'|'ai'});actions[i] = 子目標 i 的八個行動。

【AI 展開】「展開完整 81 格」按鈕:
- 前提:3×3 九格都有字,否則提示「先補滿空格再展開」
- 對八個子目標逐一(序列,不要並發)呼叫 AI:送核心目標、該子目標、
  該列已填的行動,要求回 JSON {"actions":[8 個字串]};已填的原樣保留
- 行動要具體可每日檢核、每格 2-10 個字、繁體中文(台灣用語)、不用 emoji
- 顯示進度「展開中 x/8」;每完成一列就存進 localStorage
- 已填滿的列跳過不重抽 —— 失敗後再按一次要能從斷點續跑

【9×9 檢視】巢狀 grid:外圈 8 區塊各 3×3,區塊中心 = 對應子目標的唯讀鏡像,
其餘 8 格可編輯寫回 actions;中央區塊 = 整張 3×3 的唯讀鏡像。
每個區塊用該子目標的代表色標示。

【9×9 桌布版型】直式(1170x2532):中央 3×3 放大置於上方(中心約 0.30 高),
81 全圖縮小置於下方(寬 0.92,中心約 0.74 高);橫式(1920x1080):3×3 置左、
81 全圖置右。81 全圖中,每個外圍區塊的中心格用對應子目標的顏色呼應。
文字用「二分搜尋字級 + 中文逐字斷行」塞進格子,最小 14px。

【驗收】展開到一半模擬失敗(斷網),再按一次能續跑;9×9 直式桌布輸出 PNG 後,
中央九格在手機螢幕上可讀。

發佈與 AEO 資產:

幫我的課程 repo 做發佈系統與 SEO/AEO 資產:

【雙 repo】私有 dev repo 是工作檯;公開 repo 只放發佈品,GitHub Pages 從它服務。

【publish.sh】bash 腳本,set -euo pipefail:
- 白名單陣列 PUBLISH_PATHS 列出要公開的路徑(index.html、course、demos、app、
  assets、sitemap.xml、llms.txt、robots.txt⋯),絕不用「排除法」
- 流程:clone 公開 repo 到暫存資料夾 → 清空(保留 .git)→ 複製白名單路徑 →
  刪掉 app 裡的測試資料夾 → README-public.md 改名 README.md →
  有變更才 commit push,沒變更就印「跳過」
- 可重複執行,跑幾次結果都一樣

【AEO 資產】
- llms.txt:站點一句話定位 + 成品/章節/demo 連結清單(純文字、給 LLM 讀)
- sitemap.xml:列出所有公開頁
- 課程首頁 <head> 加 JSON-LD,@type 用 Course,內容必須與頁面一致
- 後台頁與贈品頁加 meta robots noindex
- robots.txt 註明:GitHub Pages 專案頁的 robots/llms 在子路徑,
  爬蟲讀的是網域根,真正站根檔案由主 repo 管理

【OG】og:title / og:description / og:image(1200x630、完整 https 網址、200KB 內)

【驗收】publish.sh 連跑兩次,第二次顯示無變更;公開 repo 裡 grep 不到
dev 專用資料夾;把首頁網址貼給 AI 問「這在講什麼」,要答得出課程主題。

常見坑

  1. 八個展開請求一起發:撞速率限制整批失敗、沒有進度感、失敗全毀。序列 + 逐列存檔 + 已滿跳過,才有「斷點續跑」。
  2. 展開前沒守門:3×3 還有空格就展開,AI 拿不到脈絡,生出來的行動空泛。先補滿再展開,程式擋 + 文案引導。
  3. 81 格直接鋪滿桌布:手機上每格字小到不可讀。主體(3×3 放大)與全圖(縮小)分離,是版型的核心決定。
  4. 手動複製資料夾來「發佈」:漏刪舊檔、誤帶私有檔案,遲早出事。白名單 + 清空重放 + 腳本化。
  5. 直接改公開 repo 的檔案:下次 publish 整個被覆蓋。公開 repo 是輸出物,所有修改回 dev repo 再發佈。
  6. og:image 用相對路徑:LINE / FB 抓不到,分享變破圖卡。完整 https 網址,改圖後用 Sharing Debugger 重抓。
  7. JSON-LD 跟頁面內容對不上:亂填會被搜尋引擎降權。schema 寫的必須是頁面上真的有的東西。
  8. 以為子路徑的 robots/llms 全站生效:爬蟲讀網域根。要嘛自訂網域,要嘛把根域站的 llms.txt 做好並列出子專案(前課模組 10 的實測教訓)。
  9. sitemap 忘了更新:新增 demo 或頁面後,sitemap.xml 與 llms.txt 要跟著加,發佈前看一眼。

對照成品