artículos / Cómo construimos a Maia (3/3): de la tar...

Cómo construimos a Maia (3/3): de la tarima a producción

AWONG 9 minutos de lectura 33 vistas

Un video corrido no sirve para dar una plática. Parte 3: el reproductor con control remoto desde el teléfono que usé frente a 300 personas, y por qué ese mismo corto terminó convertido en el sitio de Purple AI.

Cómo construimos a Maia (3/3): de la tarima a producción
Serie: Cómo construimos a Maia · Parte 3 de 3

Cómo construimos a Maia (3/3): de la tarima a producción

Tenía los 22 frames, las 21 transiciones y los loops. Tenía un corto. Lo que no tenía, a días del evento, era cómo presentarlo — porque un video corrido no sirve para dar una plática. Esta última parte va de lo que pasó de ahí hasta el escenario, y de por qué ese mismo corto terminó siendo el sitio de Purple AI.

Un video no es una presentación

Parece obvio dicho así, pero me tomó un rato aceptarlo. Un corto corre a su ritmo; una plática corre al tuyo. Necesitas parar donde quieras, quedarte cuarenta segundos en un dato si alguien frunce el ceño, y avanzar cuando decides. Un .mp4 en pantalla completa no hace nada de eso.

Así que el corto no se exportó como video: se armó un reproductor. La estructura quedó así, y de aquí sale todo lo demás:

loop_f00 → transición 0→1 → loop_f01 → transición 1→2 → loop_f02 → …

El loop corre en bucle mientras hablo —por eso todo el trabajo de la parte 2— y al avanzar dispara la transición al siguiente frame. La imagen nunca se congela y yo nunca voy contra reloj.

La segunda decisión fue igual de importante: el texto de cada slide no está quemado en la imagen, se dibuja encima con tipografía y layout propios. Suena a detalle técnico y es un seguro de vida — corregir un dato la noche anterior es editar una línea, no regenerar un frame.

Maqueta de un slide: un número grande sobre la mitad izquierda en negro absoluto, con la imagen viviendo en la mitad derecha
Maqueta de composición. Aquí se ve para qué sirvió toda la disciplina de espacio negativo de la parte 1: la mitad izquierda es negro absoluto y el número cae sobre el vacío. El frame se generó sabiendo qué texto le iba encima. (Composición de una versión anterior del deck — la regla no cambió.)

El reproductor, y la regla de que todo falla

Node con Express y socket.io. Sirve el deck y hace de relay del control remoto. Dos detalles que valen más que el resto del código:

HTTP Range. Sin soporte de range requests el navegador no puede hacer seek dentro de un video: se descarga entero o no se mueve. Media hora perdida hasta caer en eso. Si vas a servir video propio, el servidor estático tiene que soportar Range — no todos lo hacen.

Una sola réplica, a propósito. El relay vive en memoria del proceso. Con dos réplicas necesitaría sticky sessions o un backend compartido para que el teléfono y la pantalla se vieran. Para un evento de una hora, escalar horizontalmente era la respuesta equivocada.

Control remoto en el teléfono: campo de PIN, número de slide en matriz de puntos, botones grandes de Siguiente, Anterior y Entrar
El control, en el teléfono. Botones grandes —porque en el escenario no ves la pantalla del celular—, el número de slide en matriz de puntos (el mismo motivo LED del moodboard, cerrando el círculo) y un PIN, porque la URL es pública y no quería que alguien del público me pasara las diapositivas.

La decisión de la que estoy más orgulloso: el display degrada con elegancia. Si el socket se cae o el wifi del recinto se satura —que pasa siempre—, el teclado del laptop sigue controlando el deck. El control remoto es una comodidad, no una dependencia. Ningún sistema que corra en vivo puede tener una sola vía.

Lo que costó ponerlo en línea

Es una app chiquita en Kubernetes, pero con websockets todo se vuelve más sensible. Tres cosas que hay que recordar:

  • El ingress necesita timeouts largos (proxy-read-timeout y proxy-send-timeout en 3600). Con el default, la conexión se corta a la mitad de la plática.
  • El nginx de enfrente tiene que reenviar los headers de Upgrade y Connection, o socket.io se queda en polling —o directamente no conecta—. Es la diferencia entre este vhost y el de la landing: aquel no los necesita, éste sí.
  • Recargar nginx sin tirar los otros sitios que viven en la misma máquina. Y un tropiezo que me hizo reír después: mi pgrep para hallar el proceso maestro también encontraba mi propio comando, porque el string que buscaba estaba literalmente en el comando. El kill se mataba a sí mismo.

El día del evento no hubo drama. Abrí la pantalla, entré desde el teléfono, y las 300 personas vieron el corto avanzar al ritmo de la plática. El teclado de respaldo nunca hizo falta — pero lo que me dejó tranquilo toda la hora fue saber que estaba ahí.

Y entonces: ¿por qué el corto se volvió la página?

Esta es la parte que a mí más me interesa, y la que menos tiene que ver con generación de imágenes.

El corto fue un piloto. Uno con todo lo que suele tener un piloto: hecho contra reloj, con herramientas nuevas, para un solo uso. Y funcionó — la sala lo notó y la conversación después del evento no fue sobre las diapositivas, fue sobre el personaje.

En Purple AI la reacción no fue "qué bonito quedó el video". Fue: la página tiene que hablar así. Y ahí el proyecto cambió de naturaleza — dejó de ser una pieza de evento y pasó a ser el lenguaje visual de la marca, en producción, todos los días.

Lo que me da gracia es que ese salto es literalmente el argumento de la charla. Toda la plática iba de que la diferencia no está en tener un piloto lindo, sino en ponerlo en producción. Terminamos haciéndole eso al propio corto.

Del corto al sitio

La ventaja de haber construido un lenguaje y no un video es que se reusa. Los assets nuevos del sitio salen del mismo Soul y del mismo estilo, así que son consistentes sin esfuerzo adicional: es el mismo mundo, otra locación.

El sitio es Nuxt 3 con atomic design, y la apuesta fue meter cine dentro del scroll sin volverlo un juguete pesado.

El hero que despierta

Al entrar, el scroll se bloquea unos segundos y el movimiento del dedo mueve la secuencia —121 cuadros dibujados en un canvas—: la cámara se acerca por la calle y Maia entra en cuadro hasta quedar de frente. Cuando termina, el scroll se libera y ya estás en la página.

Cuatro cuadros del hero: la cámara se acerca por la calle hasta llegar al rostro del personaje
El hero, en cuatro cuadros de 121. No es un video con autoplay: es scrub — cada pixel de scroll adelanta cuadros. El usuario no ve una animación, la maneja. Y el último cuadro es exactamente el frame de portada del corto.

Más abajo hay una segunda secuencia, de 145 cuadros: Maia se voltea y camina hacia el fondo, y el último cuadro se funde con el diagrama de proceso de la sección de implementación. La transición es el índice — te lleva de "quién somos" a "cómo trabajamos" sin un título que lo anuncie.

Cuatro cuadros: el personaje se voltea y camina alejándose por el corredor
La segunda transición. Arranca exactamente donde terminó la primera —de frente— y ella se va caminando. Es la misma gramática del corto: una cosa se mueve a la vez, y el cambio de sección se esconde en el movimiento.

Seis servicios, seis piezas

Cada página de servicio tiene su propio hero en video. La decisión de composición: cinco van en diagonal, recortadas con clip-path, respetando la escala natural de la pieza en vertical; solo una va a sangre completa en 16:9. Mezclar formatos evita que seis páginas se sientan la misma plantilla con distinto texto.

Cuadrícula con los seis heroes de las páginas de servicio
Los seis heroes. Mismo mundo, mismo personaje, seis escenas distintas. Esto es lo que compra tener un Soul y un estilo fijos: agregar una página no es empezar de cero.
Uno de los loops del sitio. Misma receta de la parte 2 —ambiente en movimiento, personaje anclada— pero aquí corre en bucle para siempre detrás del texto, en vez de esperar mi clic.

Tres reglas de marca que sostienen todo

  • Nada redondeado. border-radius: 0 global, sin excepciones. Es lo que hace que la interfaz se lea como el corto y no como un dashboard.
  • Violeta como único color saturado. Todo lo demás vive en grises y negros. Un solo acento manda; dos compiten.
  • Texto sobre imagen con sombra nítida, nunca con velo. Nada de una capa negra semitransparente encima de la pieza para "que se lea el texto" — eso mata la imagen. Se resuelve con text-shadow ajustado. Por lo mismo la barra de navegación no tiene fondo: flota sobre el video.

Y un gotcha que me costó una tarde: en el scrub del canvas, no intentes asignarle canvas.clientWidth — es de solo lectura y en módulos ES en modo estricto no falla en silencio, truena. Lo que se asigna es canvas.width.


Lo que me llevo de todo esto

1. Las restricciones son dirección, no obstáculos. "Una cosa se mueve a la vez" nació de una limitación del modelo y terminó siendo la gramática del corto. Casi todo lo que quedó bien salió de un "no se puede".

2. El entregable no era el video. Era el lenguaje — el personaje anclado, el estilo, las reglas de composición. El video se usó una tarde; el lenguaje lleva meses trabajando y va a seguir. Cuando arranques algo así, pregúntate qué te queda cuando la pieza ya se usó.

3. Un piloto que no llega a producción es una demo cara. Lo dije en la tarima con datos de otras empresas y me lo terminé aplicando a mí mismo. La distancia entre "quedó increíble" y "está en producción" es donde se decide todo.

Todo el material técnico —los 20 prompts con planta de set, las recetas, los scripts del grade, los bloques de los loops— está público en github.com/andyeswong/maia-prompts. Y si llegaste directo a esta parte: la parte 1 es el personaje y los moodboards, la parte 2 es cómo se mueve.

artículos_relacionados

posts_recientes