Communitygithub.com

walenso/3d-portfolio

Crea un sitio web de portafolio personal con un héroe 3D, objetos animados para cada interés, estudios de caso visuales y un formulario de contacto funcional (tRPC, Resend, Turnstile, Postgres), y despliéguelo en Vercel. Úsalo cuando alguien pida un portafolio, sitio personal o 'portafolio 3D' y quiera algo más distintivo que una plantilla.

¿Qué es 3d-portfolio?

Este skill guía la creación de un portafolio de una sola página, altamente personalizado y centrado en un concepto único que refleje la identidad del usuario. A diferencia de plantillas genéricas, el skill enfatiza la recopilación de contenido real (proyectos, experiencia, habilidades, intereses) para inspirar un objeto 3D principal que los visitantes puedan interactuar. Utiliza tecnologías modernas como Vite, React, TypeScript, three.js para gráficos 3D, y una API backend con tRPC, Zod, Resend para correo, Turnstile para protección contra spam, y Neon Postgres para almacenamiento. El skill proporciona instrucciones detalladas para implementar múltiples vistas 3D en una sola pantalla, construir un formulario de contacto funcional que incluye verificación de Turnstile y envío de correo real (con modo de prueba seco), y desplegar el sitio en Vercel. Además, incluye reglas de diseño como respetar preferencias de movimiento reducido, usar datos verificables y evitar métricas vanidosas, y proporciona un prompt inicial para que el usuario proporcione su información fácilmente.

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/walenso/3d-portfolio-skill/tree/HEAD/skills/3d-portfolio

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Portafolio 3D

Construye un portafolio de una página que solo pueda pertenecer a esta persona. El sitio que produjo este skill se construyó alrededor de la caja de discos de un DJ: cada proyecto es una funda de disco que hojeas, y el disco gira cuando está al frente. Ejemplo: https://alessio-simoya.vercel.app

No copies ese concepto. Sigue el proceso y encuentra el suyo.

1. Recopila los datos primero

Pide estos antes de diseñar. Construye con contenido real desde el inicio.

  • Proyectos: para cada uno, una breve descripción de hechos: qué es, tu rol, fechas, qué logró, el stack, un enlace en vivo y una lista de cosas que no se deben reclamar.
  • Experiencia: empresa, cargo, fechas, lugar y un resultado por empleo.
  • Habilidades: agrupadas, con dónde se demostró cada una.
  • Vida fuera del trabajo: pasatiempos, equipos, qué cocinan, dónde han estado. De aquí surge el concepto, así que pide más de lo que ofrezcan.
  • Recursos: logotipos, capturas de pantalla de productos, videos, archivos Rive o Lottie. Pregunta dónde están las carpetas de los proyectos y búscalas.
  • Enlaces: LinkedIn, GitHub, etc. Nunca publiques su dirección de correo electrónico a menos que lo digan.

2. Encuentra el concepto único

Elige la cosa más característica del mundo de la persona y conviértela en el héroe, como un objeto 3D real con el que se pueda jugar. Un DJ obtiene una caja de discos. Un escalador podría obtener un muro de presas; un panadero, una bandeja de panes. Cada proyecto se convierte en un elemento de ese objeto.

Invierte la audacia allí. Mantén el resto de la página tranquila: campos de color plano, una tipografía display con carácter, una tipografía de cuerpo simple, cuatro o cinco colores con nombre.

3. Stack

  • Vite, React y TypeScript en modo estricto
  • three.js para 3D, escrito directamente (no se necesita un framework de escena)
  • Rive solo si la persona ya tiene archivos .riv; cárgalo de forma diferida con su wasm
  • tRPC y zod para la API, como una función de Vercel en api/trpc/[trpc].ts
  • Resend para correo electrónico, Cloudflare Turnstile para spam, Neon Postgres para registros
  • Fuentes variables autoalojadas a través de @fontsource-variable/*

4. Cómo se monta el 3D

Usa un lienzo WebGL fijo de pantalla completa con pointer-events: none, y dibuja cada vista 3D dentro del rectángulo de un marcador de posición del 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);

Limpia una vez por fotograma, activa la prueba de recorte y omite cualquier elemento que esté fuera de la pantalla. Esto proporciona muchos objetos 3D en la página al coste de un solo contexto, y se desplazan con el diseño.

Asigna a cada objeto pequeño un "rig": un grupo, una posición de cámara, un punto de mira y una función tick(t) impulsada por un reloj compartido. Construye los rigs de forma diferida y muéstralos uno a la vez. Dibuja las texturas en un lienzo 2D (logotipos, etiquetas, patrones) para que no haya archivos de modelo que cargar.

Cosas que salieron mal y cómo evitarlas:

  • Un plano de sombra en el suelo más grande que el elemento se muestra como un rectángulo duro. Ajústalo para que quepa dentro de la vista.
  • Rota un grupo, luego gira un hijo, cuando necesites "girar alrededor de su propio eje".
  • Los sprites y los planos transparentes se dibujan después de los sólidos; un brillo debajo de un objeto debe ser un plano plano debajo de él, no un sprite orientado a la cámara.

5. Secciones que funcionaron

  1. Héroe: nombre muy grande, una pegatina que recorre sus roles, una oración y el objeto interactivo. Un disco de color detrás cambia con el elemento enfocado.
  2. Trabajo: cada estudio de caso se abre en una banda del color de esa marca con su logotipo real, luego un carrusel lateral de imágenes, video y animación, tres aspectos destacados breves, algunas cifras grandes y el stack como chips. El texto largo va detrás de "Leer la historia completa".
  3. Experiencia: una fila por empleo con un número grande cada uno.
  4. Trayectoria: escuelas o hitos, cada uno con su objeto animado.
  5. Habilidades: filas de texto en movimiento, no una tabla.
  6. Fuera del horario laboral: un objeto animado por interés.
  7. Viajes: lugares como imanes de nevera en una puerta de acero que se pueden arrastrar.
  8. Saluda: el formulario de contacto.

Un encabezado de píldora flotante marca la sección actual con un IntersectionObserver.

6. Reglas de la casa

  • Solo hechos. Usa lo que la persona proporcionó. Si una sección necesita algo que no dieron, omítela. Mantén la lista de "no reclamar" de cada proyecto en el archivo de contenido como comentario.
  • Sin métricas vanidosas. Los commits, líneas de código, número de páginas o modelos se leen como inmaduros. Muestra el producto y lo que logró.
  • Muestra menos texto. Si una pantalla tiene principalmente párrafos, conviértela en un elemento visual.
  • Respeta el trabajo de otros. No reconstruyas una plantilla de pago a partir de su vista previa, ni copies el diseño de un producto de una marca (por ejemplo, un balón de partido con nombre); dibuja un original con el mismo espíritu. Usa solo imágenes que tengan licencia libre y acredítalas en la página.
  • Las capturas de pantalla no deben mostrar nombres reales de clientes, cantidades ni rostros.
  • El movimiento tiene un interruptor de apagado. Honra prefers-reduced-motion, agrega un botón "Pausar movimiento" y proporciona una alternativa plana cuando WebGL falte.
  • Pon todo el contenido en un archivo tipado (src/content/portfolio.ts) para que la persona pueda editar palabras sin tocar componentes.

7. El formulario de contacto

Un procedimiento tRPC, contact.send, con entrada zod. En orden:

  1. devuelve éxito silenciosamente si el campo oculto honeypot está lleno;
  2. aplica un límite de velocidad pequeño por dirección;
  3. verifica el token de Turnstile con Cloudflare y lo rechaza si falla;
  4. envía un correo al propietario a través de Resend, con el remitente como reply_to;
  5. guarda cada mensaje en una tabla messages;
  6. solo si el visitante marcó una casilla "mantenerme al tanto" (no marcada por defecto), los guarda en una tabla subscribers (con el texto de consentimiento y la hora) y en una audiencia de Resend.

Nunca agregues personas a una lista de correo sin esa marca.

Variables de entorno: RESEND_API_KEY, ENQUIRY_TO_EMAIL, ENQUIRY_FROM_EMAIL, RESEND_AUDIENCE_ID, DATABASE_URL, TURNSTILE_SECRET y la clave pública del sitio para el navegador.

Cosas que salieron mal:

  • El valor from ya puede ser Nombre <dirección>. Envolverlo de nuevo hace que Resend responda 422. Úsalo como se proporciona cuando contenga <.
  • Vite solo expone variables que comienzan con un prefijo en envPrefix. Si la clave del sitio llega con otro nombre, agrega ese prefijo.
  • Un widget de Turnstile solo funciona en nombres de host listados para él en Cloudflare.
  • En producción, sin un secreto de Turnstile, rechaza el envío. Usa las claves de prueba publicadas de Cloudflare solo en desarrollo local.
  • Agrega un interruptor CONTACT_DRY_RUN=1, ignorado en producción, para que se pueda probar toda la ruta sin enviar correo.
  • Para ejecutar el mismo controlador bajo vite dev, agrega un pequeño plugin que cargue la función con server.ssrLoadModule y le pase un Request.
  • Construye el HTML del correo con tablas y estilos en línea, y escapa cada campo.

8. Verifica tu trabajo a medida que avanzas

Después de cada ronda de cambios:

  • ejecuta la compilación;
  • toma capturas de pantalla de Playwright de cada sección a 1440 y 390 píxeles de ancho (inicia Chromium con --use-gl=swiftshader para que WebGL se renderice sin cabeza);
  • confirma que document.documentElement.scrollWidth - innerWidth sea 0;
  • mira las capturas de pantalla tú mismo antes de decir que está terminado.

Di claramente lo que no se verificó: teléfonos reales, Safari, la sensación del movimiento, la entrega real de correo.

9. Envíalo

  • git init, con .env* y .vercel ignorados. Escanea los archivos preparados en busca de claves antes del primer commit.
  • vercel link, agrega las variables de entorno, vercel --prod.
  • Verifica que la página en vivo se cargue sin solicitudes fallidas y que la API rechace una solicitud sin token de Turnstile.
  • Pide al propietario que envíe un mensaje real a través del formulario en vivo.

Prompt inicial

Dale a la persona esto para que lo complete:

Construye mi portafolio 3D usando el skill 3d-portfolio.

Nombre y descripción de una línea de mí:
Roles para recorrer en el héroe (4 a 6):
Proyectos (para cada uno: hechos, mi rol, fechas, enlace, qué no reclamar):
Experiencia (empresa, cargo, fechas, un resultado cada uno):
Habilidades, agrupadas:
Escuelas o hitos:
Intereses fuera del trabajo (sé específico):
Lugares donde he estado:
Dónde están mis logotipos, capturas de pantalla y animaciones en esta máquina:
Enlaces para mostrar (LinkedIn, GitHub, etc.):
Cosas que nunca quiero en el sitio:

Skills relacionados