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
- 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.
- 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".
- Experiencia: una fila por empleo con un número grande cada uno.
- Trayectoria: escuelas o hitos, cada uno con su objeto animado.
- Habilidades: filas de texto en movimiento, no una tabla.
- Fuera del horario laboral: un objeto animado por interés.
- Viajes: lugares como imanes de nevera en una puerta de acero que se pueden arrastrar.
- 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:
- devuelve éxito silenciosamente si el campo oculto honeypot está lleno;
- aplica un límite de velocidad pequeño por dirección;
- verifica el token de Turnstile con Cloudflare y lo rechaza si falla;
- envía un correo al propietario a través de Resend, con el remitente como
reply_to; - guarda cada mensaje en una tabla
messages; - 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
fromya puede serNombre <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 conserver.ssrLoadModuley le pase unRequest. - 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=swiftshaderpara que WebGL se renderice sin cabeza); - confirma que
document.documentElement.scrollWidth - innerWidthsea 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.vercelignorados. 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: