3Dポートフォリオ は何をしますか?
この人にしか作れない、ワンページのポートフォリオを構築しましょう。このスキルを生み出したサイトは、DJのレコードケースをモチーフに作られました。各プロジェクトはめくるレコードジャケットで、手前にあるレコードは回転します。例: https://alessio-simoya.vercel.app
そのコンセプトをコピーしてはいけません。プロセスに従い、その人自身のコンセプトを見つけてください。
1. まず入力を収集する
デザインの前にこれらを尋ねてください。最初から実際のコンテンツを基に構築します。
- プロジェクト: 各プロジェクトについて、概要、自分の役割、日付、成果、使用スタック、公開リンク、そして主張してはいけないことのリストを簡潔に。
- 職歴: 会社名、役職、期間、場所、各職場でのひとつの成果。
- スキル: グループ化し、それぞれをどこで証明したか。
- 仕事外の活動: 趣味、チーム、料理、訪れた場所。ここからコンセプトが生まれます。相手が提供する以上の情報を引き出しましょう。
- アセット: ロゴ、製品スクリーンショット、動画、RiveまたはLottieファイル。プロジェクトフォルダがどこにあるか尋ね、検索してください。
- リンク: LinkedIn、GitHubなど。特に許可がない限り、メールアドレスを公開してはいけません。
2. ひとつのコンセプトを見つける
その人の世界で最も特徴的なものを選び、それをヒーローにします。触って遊べる本物の3Dオブジェクトとして表現します。DJにはレコードのケース、クライマーにはホールドの壁、パン屋にはパンのトレイ。各プロジェクトはそのオブジェクトの中の一品になります。
その大胆さに集中しましょう。ページの他の部分は落ち着いたものにします。フラットなカラーフィールド、個性のある表示用書体、シンプルな本文用書体、四つか五つの名前付きカラー。
3. スタック
- Vite、React、TypeScript(strictモード)
- three.jsを直接使用(シーンフレームワーク不要)
- Riveは、その人が既に
.rivファイルを持っている場合のみ使用。wasmは遅延ロード - APIにtRPCとzod、Vercel Functionとして
api/trpc/[trpc].tsに配置 - メールにResend、スパム対策にCloudflare Turnstile、レコード保存にNeon Postgres
- セルフホストの可変フォントを
@fontsource-variable/*で使用
4. 3Dの構成方法
ひとつの固定フルスクリーンWebGLキャンバスを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);
フレームごとに一度クリアし、シザーテストを有効にし、画面外の要素はスキップします。これにより、ひとつのコンテキストでページ内に複数の3Dオブジェクトを配置でき、レイアウトに合わせてスクロールします。
各小さなオブジェクトに「リグ」を与えます。グループ、カメラ位置、注視点、そしてひとつの共有クロックで駆動するtick(t)関数です。リグは遅延構築し、一度にひとつだけ表示します。テクスチャは2Dキャンバスに描画します(ロゴ、ラベル、パターンなど)。これにより、読み込むモデルファイルは不要です。
うまくいかなかった点とその回避方法:
- 要素より大きな地面影の平面は、硬い矩形として表示されます。ビュー内に収まるようにサイズ調整してください。
- 「自身の軸で回転」させたい場合は、グループを回転させ、その子を回転させます。
- スプライトと透明平面は、ソリッドメッシュの後に描画されます。オブジェクトの下にあるグローは、カメラ向きスプライトではなく、その下に配置したフラットな平面にします。
5. 機能したセクション
- ヒーロー: 名前を大きく表示し、役割を循環表示するステッカー、ひとつの文、インタラクティブなオブジェクト。背後にある色付きの円盤は、フォーカスしているアイテムに応じて変化します。
- 作品: 各ケーススタディは、そのブランドカラーのバンドと実際のロゴで始まり、画像、動画、アニメーションの横スクロールリール、3つの短いハイライト、いくつかの大きな数値、スタックチップが続きます。長文は「すべてのストーリーを読む」の背後に配置します。
- 職歴: 各職場を一行で、大きな数字を一つずつ表示します。
- 経歴: 学校やマイルストーンを、それぞれアニメーションオブジェクトとともに表示します。
- スキル: テーブルではなく、動くタイプの行として表示します。
- 仕事外の活動: 各関心事にアニメーションオブジェクトをひとつずつ表示します。
- 旅: 訪問した場所を、スチールドアに貼られた冷蔵庫マグネットとして表示し、ドラッグ可能にします。
- お問い合わせ: 問い合わせフォーム。
フローティングピルヘッダーがIntersectionObserverで現在のセクションをマークします。
6. ルール
- 事実のみ 提供された情報のみを使用します。セクションに必要な情報がない場合は、そのセクションを省略します。各プロジェクトの「主張してはいけないこと」リストは、コンテンツファイルにコメントとして残します。
- 無駄なカウントはしない コミット数、コード行数、ページ数やモデル数は未熟に見えます。プロダクトとその成果を示しましょう。
- テキストを減らす 画面が段落で埋め尽くされている場合は、ビジュアルに変換します。
- 他者の作品を尊重する 有料テンプレートをプレビューから再構築したり、ブランドのプロダクトデザイン(例:特定の試合球)をコピーしてはいけません。同じ精神でオリジナルを描きましょう。自由にライセンスされた画像のみを使用し、ページ上でクレジットを表示します。
- スクリーンショット 実際の顧客名、金額、顔を表示してはいけません。
- モーションにはオフスイッチ
prefers-reduced-motionを尊重し、「モーションを一時停止」ボタンを追加し、WebGLがない場合のフラットなフォールバックを提供します。 - すべてのコンテンツをひとつの型付きファイルに
src/content/portfolio.tsに集約し、ユーザーがコンポーネントに触れずにテキストを編集できるようにします。
7. 問い合わせフォーム
ひとつのtRPCプロシージャ contact.send を、zod入力とともに使用します。順に以下の処理を行います:
- 隠しハニーポットフィールドが入力されていれば、成功を黙って返します。
- アドレスごとの小さなレート制限を適用します。
- TurnstileトークンをCloudflareで検証し、失敗した場合は拒否します。
- Resendを通じてオーナーにメールを送信し、送信者を
reply_toに設定します。 - すべてのメッセージを
messagesテーブルに保存します。 - 訪問者がデフォルトでオフの「最新情報を受け取る」チェックボックスをオンにした場合のみ、同意文言と時間とともに
subscribersテーブルに保存し、Resendのオーディエンスにも追加します。
そのチェックなしに、誰かをメーリングリストに追加してはいけません。
環境変数: RESEND_API_KEY, ENQUIRY_TO_EMAIL, ENQUIRY_FROM_EMAIL, RESEND_AUDIENCE_ID, DATABASE_URL, TURNSTILE_SECRET、およびブラウザ用の公開サイトキー。
うまくいかなかった点:
fromの値が既に名前 <アドレス>の形式になっている場合があります。これをさらにラップすると、Resendが422を返します。<が含まれている場合はそのまま使用します。- Viteは
envPrefixで指定されたプレフィックスで始まる変数のみを公開します。サイトキーが別の名前で渡される場合は、そのプレフィックスを追加します。 - Turnstileウィジェットは、Cloudflareで許可されたホスト名でのみ動作します。
- 本番環境でTurnstileのシークレットがない場合は、送信を拒否します。Cloudflareの公開テストキーはローカル開発でのみ使用します。
CONTACT_DRY_RUN=1スイッチを追加します。本番環境では無視され、メールを送信せずに全パスをテストできます。vite devで同じハンドラーを実行するには、関数をserver.ssrLoadModuleで読み込み、Requestを渡す小さなプラグインを追加します。- メールHTMLはテーブルとインラインスタイルで構築し、すべてのフィールドをエスケープします。
8. 作業の都度チェック
変更のたびに:
- ビルドを実行します。
- Playwrightで各セクションのスクリーンショットを1440pxと390pxの幅で撮影します(Chromiumを
--use-gl=swiftshaderで起動し、WebGLがヘッドレスでレンダリングされるようにします)。 document.documentElement.scrollWidth - innerWidthが0であることを確認します。- 完了と言う前に、自分でスクリーンショットを確認します。
確認しなかったことを明確に述べます。実機のスマートフォン、Safari、モーションのフィーリング、実際のメール配信。
9. 公開
git initし、.env*と.vercelを無視リストに追加。最初のコミットの前に、ステージングされたファイルにキーがないかスキャンします。vercel linkを実行し、環境変数を追加し、vercel --prodを実行します。- 公開ページが失敗したリクエストなく読み込まれ、APIがTurnstileトークンなしのリクエストを拒否することを確認します。
- オーナーに実際のメッセージをライブフォームから送信してもらいます。
スタータープロンプト
この人に以下の入力を依頼します:
3d-portfolioスキルを使って、私の3Dポートフォリオを構築してください。
名前と一言説明:
ヒーローで循環表示する役割(4~6個):
プロジェクト(各プロジェクトの概要、役割、日付、リンク、主張してはいけないこと):
職歴(会社名、役職、期間、それぞれのひとつの成果):
スキル(グループ化して):
学校やマイルストーン:
仕事外の関心事(具体的に):
訪れた場所:
ロゴ、スクリーンショット、アニメーションの保存場所(このマシン上で):
表示するリンク(LinkedIn、GitHubなど):
サイトに絶対に載せたくないもの: