Detrás del portafolio
Cómo hice este portafolio
Un sistema solar en 3D donde cada planeta es una sección. Aquí explico las decisiones técnicas: qué hace cada pieza y por qué está hecha así.
01Un solo archivo, sin herramientas de build
Todo el portafolio es un index.html con su CSS y su JavaScript dentro, más las imágenes. Three.js se carga como módulo ES desde un CDN con un import map, así que no hay npm, bundler ni paso de compilación: lo que se edita es lo que se publica.
02Texturas: primero pintadas, luego reales
Cada planeta arranca con una textura generada por código: ruido fractal (fBm) evaluado sobre la esfera y pasado por una rampa de colores propia de cada planeta. Se pinta en un Web Worker, así la página nunca se congela mientras se generan.
En paralelo se descargan los mapas reales de Solar System Scope (CC BY 4.0) en WebP: a 2048 px en escritorio y a 1024 px en móvil, unos 1,5 MB y 0,4 MB en total. Cuando cada uno llega, sustituye al pintado sin recompilar el shader, porque todos los materiales empiezan con un mapa blanco de 1×1 que solo se intercambia.
03Los planetas están donde están hoy
La posición de cada planeta sale de los elementos orbitales que publica la JPL de la NASA (Approximate Positions of the Planets). Para una fecha se calcula la anomalía media, se resuelve la ecuación de Kepler y se obtiene la longitud heliocéntrica real:
function helioLon([e0, e1, L0, L1, w0, w1], days) {
const T = days / 36525; // siglos desde J2000
const e = e0 + e1 * T, w = (w0 + w1 * T) * d2r;
const M = (L0 + L1 * T) * d2r - w; // anomalía media
let E = M;
for (let k = 0; k < 8; k++) E = M + e * Math.sin(E); // Kepler
return 2 * Math.atan2(Math.sqrt(1 + e) * Math.sin(E / 2),
Math.sqrt(1 - e) * Math.cos(E / 2)) + w;
}
La dirección es real; las distancias no, porque a escala real los planetas serían puntos invisibles. La barra de tiempo mueve ese reloj hacia delante o hacia atrás, hasta un año por segundo. La Luna también empieza en su posición real, y las lunas de los demás planetas giran con sus periodos reales, limitados en velocidad para que Fobos no parpadee cuando el reloj va rápido.
04El Sol
El Sol no tiene textura: es un shader que mezcla varias capas de ruido animado para la granulación y oscurece el borde del disco (limb darkening), como se ve en las fotos reales. Encima hay una corona animada en un plano que siempre mira a la cámara. En escritorio se añade un bloom (UnrealBloomPass) con un umbral alto, para que solo brille lo que de verdad es muy luminoso. Se carga después de la escena; si falla, el sistema se dibuja igual sin él.
05Un material para todos los planetas
Los planetas usan el MeshStandardMaterial de Three.js, extendido con onBeforeCompile para inyectar GLSL propio sin perder la iluminación física:
- Luces de las ciudades en la Tierra, que solo aparecen en la cara nocturna.
- Océanos con brillo: los texeles azul oscuro se vuelven más pulidos y reflejan el Sol.
- Sombra de los anillos sobre Saturno: para cada punto se lanza un rayo hacia el Sol, se corta con el plano de los anillos y se mira la densidad del anillo en ese radio.
- Relieve en Mercurio, Marte y las lunas, usando el propio mapa de color como mapa de relieve.
- Suelo de cerca: al aterrizar, el ruido se refina y aparece el terreno propio de cada cuerpo, con niebla de horizonte.
Los anillos hacen lo contrario: un rayo desde cada punto del anillo hacia el Sol que choca con la esfera del planeta dibuja su sombra, y la cara que no mira al Sol se oscurece porque solo le llega luz a través.
06Aterrizar en cada planeta
Al abrir una sección la cámara vuela hasta el planeta, gira hacia su lado iluminado y aterriza mirando al horizonte. Alrededor del punto de aterrizaje se construye el relieve de ese cuerpo (montañas, rocas, hielo, hierba en la Tierra) y se activa una luz direccional con sombras. El mapa de sombras solo se recalcula mientras estás en tierra.
07Rendimiento
- Todos los shaders se compilan durante la pantalla de carga, y el relieve de cada planeta se prepara en segundo plano cuando la página está quieta, para que el primer viaje no dé tirones.
- La resolución se adapta: si la media baja de unos 45 fps, se reduce un paso cada dos segundos (nunca por debajo de 1×).
- La barra de carga es real: cuenta las imágenes de la página y las texturas de los planetas.
- Hay un modo lectura sin 3D, y con
prefers-reduced-motionel sistema se queda quieto.
08Lo que no es 3D
El texto está en español e inglés con un diccionario que traduce los nodos de texto de la página. El formulario de propuestas envía los mensajes a un canal de Discord, y la tarjeta de estado de Discord se actualiza en directo por WebSocket con Lanyard.