Communitygithub.com

walenso/3d-portfolio

建立一個具備 3D 主視覺、每個興趣對應的動態物件、視覺化案例研究以及可運作聯絡表單(tRPC、Resend、Turnstile、Postgres)的個人作品集網站,並部署到 Vercel。當有人要求製作比模板更具特色的作品集、個人網站或「3D 作品集」時使用。

3d-portfolio 是什麼?

此技能用於構建一個獨一無二的單頁式個人作品集網站。核心概念是將使用者的某個最個人化的特質(例如 DJ 的唱片箱、攀岩者的岩點牆)轉化為一個可由使用者互動的 3D 物體作為主視覺。每個專案或興趣都會成為該物體中的一個獨立項目。整個網站的技術棧包括 Vite、React、TypeScript、three.js(用於 3D 渲染)、tRPC 和 Zod(用於 API)、Resend(郵件發送)、Cloudflare Turnstile(防垃圾郵件)以及 Neon Postgres(資料庫)。它透過一個全螢幕的 WebGL canvas 和 DOM 佔位符來高效地渲染多個 3D 場景,而不需要載入外部模型檔案。此外,它還整合了一個功能完整的聯絡表單,包含蜜罐、速率限制、Turnstile 驗證、透過 Resend 發送郵件並記錄訊息,以及可選的訂閱者管理。建置者必須嚴格遵守「僅陳述事實」、「不虛榮計數」、「尊重他人作品」和「支援無障礙(減少動畫)」等規則。所有內容資料都集中在一個 TypeScript 檔案中,方便使用者後續編輯。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/walenso/3d-portfolio-skill/tree/HEAD/skills/3d-portfolio

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

3D 作品集

建立一個只能屬於這個人的單頁式作品集。這個技能所產生的網站是以一個 DJ 的唱片箱為核心:每個專案都是一張你可以翻閱的唱片封套,而當唱片在前方時,它會旋轉。範例:https://alessio-simoya.vercel.app

請不要複製那個概念。遵循這個流程,並找到屬於他們的獨特概念。

1. 首先收集輸入資料

在開始設計之前先詢問這些資訊。從一開始就用真實內容來建構。

  • 專案: 對於每個專案,提供一份簡短的事實摘要:它是什麼、你的角色、日期、成果、使用的技術棧、一個線上連結,以及一份「不得聲稱」的清單。
  • 經歷: 公司、職稱、日期、地點,以及每份工作的一項具體成果。
  • 技能: 分組列出,並說明每項技能在哪裡應用過。
  • 工作之外的生活: 嗜好、參與的團隊、擅長的料理、去過的地方。這是創意概念的來源,所以問得比他們願意提供的還要多。
  • 素材: 標誌、產品截圖、影片、Rive 或 Lottie 檔案。詢問專案資料夾的位置並搜尋它們。
  • 連結: LinkedIn、GitHub 等。除非對方明確同意,否則絕不公開他們的電子郵件地址。

2. 找到一個獨特概念

選取此人世界中最具代表性的事物,並將其作為主視覺,製作成一個可以讓使用者互動的真實 3D 物體。DJ 會有一個唱片箱。攀岩者可能會有一面岩點牆;麵包師傅則可能有一個麵包托盤。每個專案都變成這個物體中的一個項目。

把大膽的創意用在這裡。頁面的其餘部分保持簡潔:純色色塊、一個具有個性的顯示字體、一個樸素的正文字體、四到五種命名顏色。

3. 技術棧

  • Vite、React 和 TypeScript(嚴格模式)
  • three.js 用於 3D 渲染,直接撰寫(不需要場景框架)
  • 僅在對方已有 .riv 檔案時才使用 Rive;並延遲載入其 wasm
  • tRPC 和 zod 用於 API,作為 Vercel Function 位於 api/trpc/[trpc].ts
  • Resend 用於電子郵件,Cloudflare Turnstile 用於防垃圾郵件,Neon Postgres 用於資料記錄
  • 透過 @fontsource-variable/* 自託管可變字型

4. 3D 的組合方式

使用一個固定的全螢幕 WebGL canvas,設定 pointer-events: none,並將每個 3D 視圖繪製到 DOM 佔位符的矩形區域中:

const r = el.getBoundingClientRect();
renderer.setViewport(r.left, innerHeight - r.bottom, r.width, r.height);
renderer.setScissor(r.left, innerHeight - r.bottom, r.width, r.height);
renderer.render(scene, camera);

每影格清除一次,開啟裁剪測試,並跳過任何在畫面外的元素。這樣做可以只花費一個 context 的資源,就在頁面下方呈現多個 3D 物體,而且它們會隨著版面滾動。

為每個小物體建立一個「骨架」:一個群組、一個攝影機位置、一個注視點,以及一個由共同時鐘驅動的 tick(t) 函式。懶散地建立骨架,並一次只顯示一個。在 2D canvas 上繪製紋理(標誌、標籤、圖案),這樣就不需要載入模型檔案。

過去曾發生的錯誤以及如何避免:

  • 比元素本身還大的地面陰影平面會顯示為一個硬邊矩形。請將其大小設定為適合在視圖內。
  • 當你需要「繞著自身軸心旋轉」時,先旋轉一個群組,再旋轉其子物件。
  • Sprite 和透明平面會在實心網格之後繪製;物體下方的光暈應該是一個位於其下方的平面,而不是一個面向攝影機的 sprite。

5. 效果良好的區塊

  1. 主視覺區: 姓名很大,一個會循環顯示其角色的貼紙,一句話的介紹,以及可互動的物體。背後一個彩色圓盤會根據焦點項目變化。
  2. 作品區: 每個案例研究以該品牌顏色的色帶打開,帶有真實標誌,然後是一個橫向的圖片、影片和動畫捲軸,三個重點摘要,幾個重要數字,以及技術棧標籤。長篇文字放在「閱讀完整故事」後面。
  3. 經歷區: 每份工作一行,並附上一項重要數字。
  4. 旅程區: 學校或里程碑,每個都有其自己的動畫物體。
  5. 技能區: 移動的文字行,而非表格。
  6. 下班時間區: 每個興趣一個動畫物體。
  7. 旅行區: 地點像冰箱磁鐵一樣貼在鋼門上,可以拖曳。
  8. 打招呼區: 聯絡表單。

一個浮動的藥丸狀頁首會使用 IntersectionObserver 標記當前區塊。

6. 基本規則

  • 僅陳述事實。 使用對方提供的資訊。如果某個區塊缺少對方未提供的內容,就省略它。將每個專案的「不得聲稱」清單以註解形式保留在內容檔案中。
  • 不虛榮計數。 提交次數、程式碼行數、頁面數量或已讀取模型數量會顯得不成熟。展示產品及其成果。
  • 文字要精簡。 如果一個畫面大部分是段落,請將其轉換為視覺化內容。
  • 尊重他人作品。 不要從預覽中重建付費模板,也不要複製某個品牌的產品設計(例如,一個具名的比賽用球);以相同的精神繪製原創作品。只使用自由授權的圖片,並在頁面上標示來源。
  • 螢幕截圖 不得顯示真實客戶的名字、金額或面孔。
  • 動畫要有關閉開關。 尊重 prefers-reduced-motion,加入一個「暫停動畫」按鈕,並在 WebGL 不可用時提供一個靜態的備用方案。
  • 將所有內容放在一個型別檔案中(src/content/portfolio.ts),以便使用者可以在不接觸元件的情況下編輯文字。

7. 聯絡表單

一個 tRPC 程序,contact.send,使用 zod 進行輸入驗證。依序執行以下動作:

  1. 如果隱藏的蜜罐欄位已填寫,則默默地回傳成功;
  2. 對每個地址施加一個小的速率限制;
  3. 使用 Cloudflare 驗證 Turnstile 令牌,如果驗證失敗則拒絕;
  4. 透過 Resend 發送電子郵件給網站擁有者,並將寄件者設為 reply_to;
  5. 將每條訊息儲存到 messages 資料表中;
  6. 只有在訪客勾選了一個預設未勾選的「通知我」核取方塊時,才將他們儲存到 subscribers 資料表(包含同意文字和時間)以及 Resend 的受眾清單中。

未經對方勾選,絕不將任何人加入郵件清單。

環境變數:RESEND_API_KEY、ENQUIRY_TO_EMAIL、ENQUIRY_FROM_EMAIL、RESEND_AUDIENCE_ID、DATABASE_URL、TURNSTILE_SECRET,以及給瀏覽器使用的公開網站金鑰。

過去曾發生的錯誤:

  • from 的值可能已經是 Name <address> 格式。再次對其進行包裝會導致 Resend 回應 422。當它包含 < 時,請直接使用該值。
  • Vite 只會暴露以 envPrefix 中定義的前綴開頭的變數。如果網站金鑰是以其他名稱傳入,請新增該前綴。
  • Turnstile 小工具只能在 Cloudflare 中為其設定的主機名稱上運作。
  • 在正式環境中,如果沒有 Turnstile 密鑰,則拒絕發送。僅在本地開發環境中使用 Cloudflare 發布的測試金鑰。
  • 加入一個 CONTACT_DRY_RUN=1 開關,在正式環境中忽略,以便可以在不發送電子郵件的情況下測試整個流程。
  • 為了在 vite dev 下執行相同的處理程式,加入一個小的外掛程式,該外掛程式使用 server.ssrLoadModule 載入函式,並將 Request 物件傳遞給它。
  • 建立包含表格和行內樣式的電子郵件 HTML,並跳脫每個欄位。

8. 邊做邊檢查

每次進行修改後:

  • 執行建置;
  • 使用 Playwright 在 1440 和 390 像素寬度下為每個區塊截圖(啟動 Chromium 時加上 --use-gl=swiftshader 參數,以便 WebGL 能在無頭模式下渲染);
  • 確認 document.documentElement.scrollWidth - innerWidth 為 0;
  • 在你說「完成」之前,自己先檢查截圖。

明確說明未檢查的項目:真實手機、Safari 瀏覽器、動畫的流暢度、真實的郵件傳遞。

9. 部署

  • git init,並在 .gitignore 中加入 .env* 和 .vercel。在第一次提交前掃描暫存檔案中是否有金鑰。
  • vercel link,新增環境變數,vercel --prod。
  • 檢查線上頁面是否正常載入沒有失敗請求,以及 API 是否拒絕了沒有 Turnstile 令牌的請求。
  • 要求網站擁有者透過線上表單發送一封真實的訊息進行測試。

起始提示

給對方填寫以下內容:

使用 3d-portfolio 技能建立我的 3D 作品集。

姓名和一行自我介紹:
要在主視覺區循環顯示的角色(4 到 6 個):
專案(針對每個專案:事實、我的角色、日期、連結、不得聲稱的內容):
經歷(公司、職稱、日期、每份工作的一項具體成果):
技能(分組):
學校或里程碑:
工作以外的興趣(請具體說明):
我去過的地方:
我的標誌、截圖和動畫檔案在此電腦上的位置:
要顯示的連結(LinkedIn、GitHub 等):
我永遠不想在網站上看到的東西:

相關技能