Communitygithub.com

walenso/3D-포트폴리오

3D 히어로, 관심사별 애니메이션 객체, 시각적 케이스 스터디, 작동하는 문의 폼(tRPC, Resend, Turnstile, Postgres)을 갖춘 개인 포트폴리오 사이트를 구축하고 Vercel에 배포합니다. 템플릿보다 더 독창적인 포트폴리오, 개인 사이트 또는 '3D 포트폴리오'를 요청받을 때 사용하세요.

3D-포트폴리오란 무엇인가요?

이 스킬은 사용자의 고유한 정체성을 반영한 원페이지 3D 포트폴리오 사이트를 구축하는 AI 에이전트용 지침입니다. 기존 템플릿을 복사하지 않고, 사용자의 프로젝트, 경험, 기술, 취미 등 실제 데이터를 수집하여 가장 특징적인 컨셉을 찾아내고, 이를 Three.js 기반의 실제 3D 객체로 표현합니다. Vite, React, TypeScript, tRPC, Resend, Cloudflare Turnstile, Neon Postgres를 기술 스택으로 사용하며, 모든 콘텐츠는 하나의 TypeScript 파일에 보관되어 추후 수정이 용이합니다. 이 스킬은 Claude Code, Cursor, Codex와 같은 AI 코딩 어시스턴트에서 사용할 수 있도록 설계되었으며, AI 에이전트가 사용자와 협력하여 단계별로 포트폴리오를 완성하고 Vercel에 배포하는 전체 워크플로우를 제공합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/walenso/3d-portfolio-skill/tree/HEAD/skills/3d-portfolio

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

3D 포트폴리오

이 사람에게만 속할 수 있는 원페이지 포트폴리오를 만드세요. 이 스킬을 생성한 사이트는 DJ의 레코드 크레이트를 중심으로 구축되었습니다. 모든 프로젝트는 넘겨볼 수 있는 레코드 슬리브이며, 앞에 있을 때 레코드가 회전합니다. 예시: https://alessio-simoya.vercel.app

해당 컨셉을 복사하지 마세요. 프로세스를 따르고 그들만의 컨셉을 찾으세요.

1. 먼저 입력 수집

디자인하기 전에 다음을 요청하세요. 처음부터 실제 콘텐츠로 구축하세요.

  • 프로젝트: 각 프로젝트에 대해 간략한 사실: 무엇인지, 역할, 날짜, 성과, 스택, 라이브 링크, 주장해서는 안 되는 것들의 목록.
  • 경력: 회사, 직책, 날짜, 장소, 각 직무당 하나의 결과.
  • 기술: 그룹화하고, 각 기술이 입증된 위치.
  • 직장 외 삶: 취미, 팀, 요리하는 것, 가본 곳. 컨셉이 여기서 나오므로, 그들이 제시하는 것보다 더 많이 요청하세요.
  • 에셋: 로고, 제품 스크린샷, 비디오, Rive 또는 Lottie 파일. 프로젝트 폴더가 어디에 있는지 묻고 검색하세요.
  • 링크: LinkedIn, GitHub 등. 그들이 허락하지 않는 한 이메일 주소를 공개하지 마세요.

2. 하나의 컨셉 찾기

그 사람의 세계에서 가장 특징적인 것을 골라 히어로로 삼고, 사람들이 가지고 놀 수 있는 실제 3D 객체로 만드세요. DJ는 레코드 크레이트를, 클라이머는 홀드 벽을, 제빵사는 빵 트레이를 얻을 수 있습니다. 각 프로젝트는 그 객체의 한 항목이 됩니다.

거기서 대담함을 사용하세요. 페이지의 나머지는 조용하게 유지하세요: 평평한 색상 필드, 개성이 있는 하나의 디스플레이 서체, 하나의 평범한 본문 서체, 4~5개의 명명된 색상.

3. 스택

  • 엄격 모드의 Vite, React 및 TypeScript
  • 3D용 three.js, 직접 작성(씬 프레임워크 불필요)
  • 사용자가 이미 .riv 파일을 가지고 있는 경우에만 Rive 사용; wasm을 지연 로드
  • API용 tRPC 및 zod, api/trpc/[trpc].ts의 Vercel 함수로
  • 이메일용 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. 효과가 있었던 섹션

  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. reply_to로 발신자를 설정하여 Resend를 통해 소유자에게 이메일을 보냅니다.
  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. 진행하면서 작업 확인

변경 사항이 있을 때마다:

  • 빌드를 실행합니다.
  • 1440px 및 390px 너비에서 모든 섹션의 Playwright 스크린샷을 찍습니다(WebGL이 헤드리스로 렌더링되도록 --use-gl=swiftshader로 Chromium 실행).
  • document.documentElement.scrollWidth - innerWidth가 0인지 확인합니다.
  • 완료되었다고 말하기 전에 직접 스크린샷을 확인하세요.

확인되지 않은 것을 명확히 말하세요: 실제 휴대폰, Safari, 모션의 느낌, 실제 이메일 전달.

9. 배포

  • git init, .env* 및 .vercel은 무시됩니다. 첫 커밋 전에 스테이징된 파일에서 키를 스캔하세요.
  • vercel link, 환경 변수 추가, vercel --prod.
  • 라이브 페이지가 실패한 요청 없이 로드되고 API가 Turnstile 토큰 없는 요청을 거부하는지 확인하세요.
  • 소유자에게 라이브 폼을 통해 실제 메시지를 하나 보내도록 요청하세요.

시작 프롬프트

사용자에게 다음을 작성하도록 제공하세요:

3d-portfolio 스킬을 사용하여 내 3D 포트폴리오를 만들어 주세요.

내 이름과 한 줄 설명:
히어로에서 순환할 역할(4~6개):
프로젝트(각각: 사실, 내 역할, 날짜, 링크, 주장하지 말아야 할 것):
경력(회사, 직책, 날짜, 각각 하나의 결과):
기술, 그룹화:
학교 또는 이정표:
직장 외 관심사(구체적으로):
내가 가본 곳:
내 컴퓨터에서 로고, 스크린샷 및 애니메이션이 있는 위치:
표시할 링크(LinkedIn, GitHub 등):
사이트에 절대 원하지 않는 것:

관련 스킬