
Devanzire
portafolio y sistema visual
Resumen
El sitio que estás mirando. Un sistema de diseño propio sobre Next.js, con transiciones de ruta continuas y una esfera WebGL que persiste entre páginas.
El problema
Un portafolio que recarga y hace un fundido en cada navegación se siente como un documento, no como un producto. Entre una página y la siguiente hay un parpadeo en blanco, y ese parpadeo es la confesión de que no había nada continuo: dos documentos, servidos uno después del otro.
Quería lo contrario. Que las páginas se convirtieran unas en otras, y que algo sobreviviera al cambio para demostrarlo.
La esfera sobrevive a la ruta
La esfera del fondo vive en el layout, no en la página. Eso es todo el truco: como el layout no se desmonta al navegar, la esfera nunca se vuelve a crear. Cada ruta declara dónde quiere que esté —desplazamiento lateral, escala, dos rotaciones— y la esfera viaja hacia esa pose.
Las poses no son arbitrarias. articles y projects comparten exactamente la
misma, la de apertura del home, para que entrar a cualquiera de las dos deje la
esfera quieta y el morfismo del panel se lea solo. Las dos rutas de lectura
larga la empujan hacia afuera y la encogen, porque ahí su trabajo es no estorbar
a la columna de texto.
El home no declara pose, y esa ausencia es deliberada: mientras el home está montado, el hook del reveal es dueño del cuerpo de la esfera, y un segundo sistema interpolando los mismos valores pelearía con él.
/media/projects/devanzire-portfolio/01-poses.webpEl panel entrega su caja
El home tiene un panel que se abre. Al navegar desde él, ese panel mide dónde está en pantalla y le pasa el rectángulo a la página siguiente, cuyo marco crece desde ahí en vez de aparecer. Es un FLIP, pero el detalle interesante es dónde vive el dato.
No en un contexto de React: la captura ocurre dentro del manejador del clic y se lee después de un cambio de ruta, así que el que produce y el que consume nunca están montados a la vez. No hay subárbol compartido donde ponerlo. Vive en scope de módulo, que es exactamente el ámbito del problema.
Y caduca. Una captura sirve para la navegación para la que se hizo; sin una vida útil, un clic que al final no navegó dejaría un rectángulo olvidado y la siguiente carga —sin relación con aquella— crecería desde una caja que nadie vio nunca.
/media/projects/devanzire-portfolio/02-morph.webpUn solo escritor por propiedad
Esto fue lo difícil, y no era un bug sino una categoría de bug.
La primera versión tenía React renderizando geometría desde ternarios mientras GSAP escribía esas mismas propiedades. Las dos cosas eran correctas por separado. Juntas, el resultado dependía de cuál corriera último, que es otra forma de decir que no había resultado: había una carrera.
La regla que salió de ahí es que React decide en qué estado está algo, vía
data-state, y CSS o GSAP deciden cómo se mueve. Nunca los dos escribiendo
left, top o width. De ella se derivan las demás:
- El estado inicial va en CSS, no en
gsap.set, para que no haya un destello antes de la hidratación. - Solo
transformyopacityen lo que anime cada frame. - La interactividad no se condiciona al final de una animación. Los listeners se montan de inmediato; la animación es únicamente lo visual.
- Nada invisible sigue siendo operable. Una opacidad 0 deja el elemento clicable
y alcanzable por teclado, así que va acompañada de
inert.
Lo que quedó escrito
La capa de movimiento tiene una matriz de comportamiento documentada —qué pasa con el loader, la entrada de página y el deck del home en cada escenario— y se verifica en desarrollo y en producción por separado.
La razón es Strict Mode: solo corre en next dev, y monta, limpia y remonta
cada efecto en el primer render. Los efectos de animación no son idempotentes,
así que los dos entornos no se comportan igual. Un fallo que solo aparece en
desarrollo sigue siendo un fallo real. Strict Mode no lo causa, lo revela.
Resultado navegable
Devanzire


