那是一個接近傍晚的下午。我拖著疲憊又有點低落的心情進來,眼前是一長串待辦事項,還有一堆等著我處理的問題。我不知道該從哪裡開始,所以就做了很多人在卡住時都會做的事:打開 DEV,開始滑文章。
然後我看到了 George Kobaidze 的這篇文章:
他把自己的 GitHub 個人檔案做成了一個賽博龐克控制台,用自己的貢獻蓋出了一座城市。我立刻就被吸引住了。我不只是想看看而已,我想要自己做一個。
我是 C#/.NET 遊戲開發者,所以一座城市對我來說不太像「我」。但地城就很像了。那股興奮感做到了待辦清單做不到的事:讓我開始動起來。我拿起工具開始做,而這股動能也延續到了那些我一直拖著沒做的任務上。
這篇文章就是我的建置紀錄:我做了什麼、怎麼做,以及一路上學到了什麼。
🙏 功勞先說清楚:核心概念(由層疊的 SVG「切片」組成一份 README,看起來像同一個控制台,並且每天由 GitHub Actions 重新繪製)來自 George 的文章。請去看看。下面的內容則是我自己的詮釋:roguelike 主題、C#/.NET 產生器,以及我想加到個人檔案上的功能。
👉 線上展示: github.com/G00dS0ul
<!-- 📸 SCREENSHOT 1: your WHOLE profile, top to bottom (zoom the browser out to ~50% so it fits) -->

現在我的個人檔案變成了一個復古終端機,並分成編號區塊。下面逐一介紹。
控制台用打字動畫「啟動」:> ./G00dS0ul.exe --boot,載入引擎核心、掛載渲染器,並生成玩家。
<!-- 📸 SCREENSHOT 2: the header (window bar + typed boot lines). Wait until the typing finishes -->

我最近 365 天的貢獻被做成地城格子:石牆、發光地板、寶箱、火把,以及一個在我目前連續貢獻天數上移動的小英雄。上方還有夜空,包含星星、月亮、城堡和地城入口。
<!-- 📸 SCREENSHOT 3: the dungeon section, including the 4 stat boxes (XP, STREAK, BEST DAY, TODAY) -->

GitHub 上的圖片不能回應滑鼠,所以點擊地城(或發光的 CLICK TO EXPLORE 按鈕)就會開啟 GitHub Pages 上的互動版本。把滑鼠移到任何格子上,都能看到當天的貢獻數。
<!-- 📸 SCREENSHOT 4: the explore page (g00ds0ul.github.io/G00dS0ul) with your mouse over a tile so the tooltip shows -->

一個裝著我常用語言的「背包」(依今年的 commit 加權),以及一個角色能力面板:星星、倉庫、PR、issue、公會與追隨者。
<!-- 📸 SCREENSHOT 5: the inventory section (backpack + character stats) -->

我釘選的儲存庫被做成任務卡,每張卡都有狀態:ACTIVE、IN PROGRESS 或 ON HOLD。對於我有參與的開源專案,卡片上會顯示金色的 CONTRIBUTOR 徽章、我的角色,以及我的 PR 有多少被合併。每張卡都會連到對應的儲存庫。
<!-- 📸 SCREENSHOT 6: the quest cards (all 6, or a close-up of a CONTRIBUTOR card) -->

用 RPG 的形式呈現「我是誰」:角色資料表、「持續效果」(我正在學什麼、在找什麼),以及我裝備的裝備與技能。
<!-- 📸 SCREENSHOT 7: the character section (character sheet, active effects, gear & skills) -->

像鍵帽風格的按鈕,讓人聯絡到我:Email、LinkedIn、X 和地城頁面。
<!-- 📸 SCREENSHOT 8: the party buttons + footer -->

GitHub 會把 README 裡的 JavaScript 和大部分 CSS 去掉。但透過 <img> 標籤載入的 SVG,仍然可以使用SVG 內部的 CSS 動畫。所以每個閃爍的游標、脈動的按鈕、閃爍的軌道和行走的英雄,全部都是純 SVG + CSS keyframes。
我沿用了 George 文章裡的技巧:這個「控制台」其實是一疊彼此對齊的獨立圖片。
<p align="center">
<img src="./assets/header.svg" width="100%" align="top">
<a href="https://g00ds0ul.github.io/G00dS0ul/">
<img src="./assets/dungeon.svg" width="100%" align="top">
</a>
<img src="./assets/inventory.svg" width="100%" align="top">
<!-- quest cards: 2 per row, 50% width each -->
<!-- connect buttons: 4 per row, 25% width each -->
<img src="./assets/footer.svg" width="100%" align="top">
</p>
為了讓這些切片看起來像同一個視窗,我訂了幾個規則:
我想用自己熟悉的語言來做,所以這個產生器是一個放在 tools/DungeonGen 裡的小型 .NET 10 主控台應用程式:
Program.cs -> 進入點與參數
GitHub.cs -> GraphQL + 貢獻日曆
DungeonRenderer.cs -> 地城地圖
NightSky.cs -> 星星、月亮、城堡、飛空艇
InventoryRenderer.cs -> 語言 + 數值
QuestRenderer.cs -> 釘選儲存庫卡片
AboutRenderer.cs -> 角色資料表(來自 about.json)
ConnectRenderer.cs -> 連結按鈕
ReadmeUpdater.cs -> 重寫 README 區塊 + 快取破壞
每個 renderer 都只是用 C# 組字串來產生 SVG。沒有函式庫,沒有魔法。
每天都會變成一個 14px 的格子:
在上方,我還加了一條夜空帶(星星、弦月、流星、城堡與地城入口),讓它更像一個真實場景。
GitHub Actions 每天執行一次產生器,只有在內容有變更時才提交:
on:
schedule:
- cron: "17 6 * * *" # daily
workflow_dispatch: # manual "Run workflow" button
push:
paths: ["tools/DungeonGen/**"]
# ...
- run: >
dotnet run --project tools/DungeonGen -c Release --
${{ github.repository_owner }}
--assets assets --html docs/index.html --readme README.md
env:
PROFILE_TOKEN: ${{ secrets.PROFILE_TOKEN || github.token }}
<!-- 📸 SCREENSHOT 9 (optional): a green "Update profile" run in your repo's Actions tab -->

步驟 1:開機畫面。 我先從小地方開始:一個帶打字動畫的頁首,透過每次一個字元地動畫化 clipPath 寬度來實作。看到它打出我的名字的那一刻,我就知道這個東西我做得完。
步驟 2:地城。 我把貢獻日曆對應到格子上,接著加入火把、寶箱、英雄和狀態框(XP、STREAK、BEST DAY、TODAY)。
步驟 3:README 沒有 hover,所以我加了一個頁面。 我把第二個互動版本產生到 docs/index.html,並用 GitHub Pages 發佈。地城圖片會連到那個頁面。
步驟 4:背包與任務。 語言、數值和釘選儲存庫都做成任務卡。
步驟 5:角色資料表與組隊按鈕。 所有文字都放在 about.json 檔案裡,所以我可以不用碰任何程式碼就更新自我介紹或連結。
步驟 6:夜空與最後修飾。 我加入天空,然後回頭替每個區塊加上編號分隔與留白。一開始全部堆得太緊,感覺很擠。
我的數字和個人檔案對不上。 GraphQL API 只有在你的 token 看得到私有貢獻時,才會計算私有貢獻。GitHub 自己的個人檔案圖表則已經包含了它們(前提是你有開啟「private contributions」)。所以現在產生器會讀取和個人檔案頁相同的日曆,並以 API 作為備援。
我的語言統計不對。 依照程式碼位元組數來排名語言,會讓單一大型儲存庫壓過一切。後來我改成依今年的 commit 加權的語言(來自 commitContributionsByRepository),這更能反映我實際在做什麼。
我的開源工作看不見。 我是 GS-SystemAnalyzer/core 的核心貢獻者,也在 metacall/core 裡負責 C# 載入器,但那些儲存庫不是我的。所以對於不是我擁有的釘選儲存庫,產生器會跑一個別名的 GraphQL search,統計我的 PR 以及其中有多少被合併,並在卡片上顯示 CONTRIBUTOR 徽章。
有一個組織擋掉了我的 token,整個流程就崩了。 GraphQL 可能會帶著 errors 陣列回傳部分資料。現在產生器會把這些當成警告記錄下來,並使用已拿到的資料繼續執行。
我的側邊軌條不見了。 我把它們畫成帶有光暈濾鏡的 <line> 元素。完全垂直的線會有零寬度的邊界框,所以濾鏡區域是空的,線就消失了。改成 3px 的 <rect> 元素後就解決了。
「它沒有更新!」 其實有更新,只是 GitHub 會非常積極地快取 README 圖片。現在產生器會把每個 SVG 的雜湊值加到網址上(dungeon.svg?v=1a2b3c4d),所以圖檔一變,網址也會跟著變。 (而且 Ctrl+Shift+R 是你的好朋友。)
那天下午,我是帶著壓力和低落開始的。結束時,我不只做出了一個能運作的專案,更重要的是,我找回了動能。當我完成第一個切片後,那些我一直害怕處理的任務,感覺也沒那麼沉重了。
有時候,擺脫卡住狀態最好的方法,就是單純因為它讓你興奮而去做一個作品。
再次感謝 George Kobaidze 帶來這個火花。如果他的文章啟發了我,也許我的文章也能啟發你。如果你也做出自己的版本,歡迎在留言裡貼連結給我。我很想看看!🗡️
原始碼: github.com/G00dS0ul/G00dS0ul(產生器在 tools/DungeonGen)