Product Demo Video
Demostraciones de producto que parecen videos de lanzamiento de SaaS, no screencasts.
La idea central: no grabes la pantalla. Captura screenshots en alta resolución + las coordenadas reales de los elementos con Playwright, y luego anima ese material en Remotion. Esto te da control total sobre la cámara, el ritmo y los subtítulos — y permite re-renderizar en 10s cuando el usuario diga "más rápido", "zoom más grande", "quita este paso", "haz una versión de 10s". Regrabar sería lento y daría un resultado peor.
Flujo
- Setup (una vez por proyecto) →
node <skill>/scripts/setup.mjs - Guión → escribe
demo.json(verreferences/roteiro.md) - Captura →
node .demo-video/tools/capture.mjs demo.json capture/(Playwright) - Render →
node .demo-video/tools/render.mjs --capture capture/ --out out/(Remotion) - Entrega →
out/<nombre>.mp4(1080p) +out/<nombre>.gif(embedable en HTML) - Iterar → edita
demo.jsony ejecuta solo el paso que cambió
¿Cambió tiempo/cámara/subtítulo? Solo re-render (paso 3, ~30s). ¿Cambió el flujo en la app? Re-captura también (paso 2).
Del pedido en portugués al guión
El usuario describe el flujo en lenguaje natural ("muestra cómo crear un agente"); quien traduce eso en selectores eres tú, mirando la app — nunca adivinando. Adivinar #submit cuesta una captura entera que falla en la escena 4.
- Abre la pantalla con las herramientas de navegador y lee el DOM.
- Recolecta los selectores en orden de robustez:
[data-testid=...]>#id> texto visible (text=Nuevo agente) > ruta CSS. La ruta CSS se rompe al primer cambio de layout; el texto se rompe cuando traducen la interfaz. - Confirma el flujo con el usuario antes de capturar — muéstrale el guión y pregúntale si el orden de los pasos es ese. Él conoce el producto; tú no.
- Si la app requiere inicio de sesión, pídele que ejecute
auth.mjsprimero.
Cuando no sea posible abrir la app (producto interno, sin acceso), pide los selectores o una captura de pantalla con el HTML — es más rápido que adivinar.
El guión es la fuente de la verdad
Un único JSON dirige Playwright y Remotion. Antes de capturar cualquier cosa, escribe el guión y muéstraselo al usuario — es barato corregir ahí y caro corregir después. Esquema completo en references/roteiro.md.
{
"name": "crear-agente",
"url": "https://app.ejemplo.com",
"viewport": { "width": 1600, "height": 900 },
"scenes": [
{ "action": "goto", "value": "/dashboard", "camera": "wide", "caption": "Tu panel", "duration": 2 },
{ "action": "click", "target": "[data-testid='new-agent']", "camera": "zoom", "caption": "Crea tu agente", "duration": 2 },
{ "action": "type", "target": "#agent-name", "value": "Analista Financiero", "camera": "focus", "duration": 2.5 },
{ "action": "click", "target": "button[type=submit]", "camera": "zoom", "duration": 1.5 },
{ "action": "wait", "value": 1200, "camera": "wide", "caption": "Listo en 3 clics", "duration": 2.5 }
]
}
Setup (primera vez en el proyecto)
node "$HOME/.claude/skills/product-demo-video/scripts/setup.mjs"
Crea .demo-video/ (composición Remotion + las herramientas en .demo-video/tools/), instala las dependencias y descarga Chromium — ~300MB. Confirma con el usuario antes de ejecutar; después de eso, capturar y renderizar toman segundos. Sugiere agregar .demo-video/, capture/ y .auth/ al .gitignore.
Para afinar el resultado viéndolo en vivo, cd .demo-video && npx remotion studio abre una vista previa con scrubber — mucho mejor que renderizar para comprobar el timing.
Dirección de cámara
Lo que separa una demo buena de una mala es el ritmo, no el efecto. Reglas que funcionan:
- Abre y cierra en
wide. El espectador necesita situarse antes del zoom y ver el resultado después. Zoom en toda la película cansa y desorienta. - Un zoom por intención, no por clic. Clics seguidos en la misma región = una sola escena en
zoom. Zoom-in/zoom-out en cada clic da mareo. focussolo para escritura. El campo de texto debe ser legible; un botón no.- 2 a 3 segundos por escena. Menos de 1.5s nadie lee el subtítulo; más de 4s se convierte en tiempo muerto. Una demo buena tiene 15–25s y 5–8 escenas.
- El subtítulo es etiqueta, no frase. "Crea tu agente", "Conecta tus datos". Máximo ~4 palabras, verbo al inicio, sin punto final. No toda escena necesita uno.
- Corta tiempo muerto en el origen. Loading, redirect, animación de entrada: usa
settleen la escena en vez de dejar segundos parados en el video.
La cámara es continua entre escenas (interpolada con easing), por lo que el movimiento ya sale suave — no intentes compensarlo en el guión con escenas extra.
La capa de texto es la narración
No existe audio (GIF no tiene, y el video en feed se reproduce mudo). Quien cuenta la historia es la tipografía, y solo funciona si tiene jerarquía: si todo el texto tiene el mismo peso, el espectador no sabe qué es paso y qué es argumento.
| estilo | rol | cuándo |
|---|---|---|
title (escena) | apertura/cierre en pantalla completa, con sub | 1 al inicio, a veces 1 al final |
hero | el giro — grande, centrado, sobre gradiente | como máximo 1 por demo |
label | la etiqueta del paso (por defecto), con sub opcional | en la mayoría de las escenas |
stat | número que justifica el producto: "22s", "3 clics" | 1 al cierre |
note | detalle secundario, discreto | cuando el paso necesita una aclaración |
Dos reglas que sostienen el resto: un hero por demo (dos se convierten en ninguno), y el stat cierra — el número grande es lo último que queda en la cabeza de quien ve. Para una demo ejecutiva, el arco suele ser title → labels → hero en el momento decisivo → stat al final.
"highlight": true oscurece toda la pantalla excepto el elemento de la escena. Es el arma más fuerte del conjunto y por eso la más fácil de arruinar: úsala cuando el espectador necesita llevarse ese elemento, no como decoración. Entra después de que la cámara se asienta y sale solo en el instante de la acción, para no ocultar el resultado.
Credenciales
Nunca pongas contraseña en el demo.json. Dos opciones, en este orden de preferencia:
"auth": { "storageState": ".auth/state.json" }— sesión ya iniciada, generada una vez connode .demo-video/tools/auth.mjs <url>(abre el navegador, el usuario inicia sesión manualmente)."auth": { "login": { "user": "#email", "pass": "#password", "submit": "button[type=submit]" } }con las credenciales enDEMO_USER/DEMO_PASSen el entorno.
Si la demo muestra datos reales, usa "redact": ["selector"] en el guión para difuminar elementos sensibles antes de la captura — sale difuminado ya en el PNG, no se puede recuperar.
Salidas
| Formato | Uso | Cómo |
|---|---|---|
demo.mp4 1920×1080 | landing, LinkedIn, YouTube | por defecto |
demo.gif 960×540 | README, docs, correo, HTML | --formats mp4,gif (por defecto) |
demo-square.mp4 1080×1080 | Instagram/feed | --formats mp4,square |
GIF pesa. El render.mjs ya usa palettegen (paleta dedicada al video) a través del ffmpeg que viene integrado en Remotion — no necesitas instalar nada. Aún así, 15s a 960px dan ~3.5MB; para README prefiere --gif-width 720 --gif-fps 12, que reduce a menos de 1.5MB.
Para embeber en HTML: <img src="demo.gif" width="720" alt="...">. Si el usuario acepta MP4, <video autoplay loop muted playsinline> pesa ~10x menos.
Probar la instalación sin tocar el producto
assets/ejemplo/ trae una app falsa (app-ejemplo.html) y el guión correspondiente, ejercitando todas las acciones — title, shot, click, type, select, hold, redact. Ejecutar esto primero separa "el skill está roto" de "el selector de la app está mal", que es la duda cara cuando algo falla en la primera demo real.
Cuando algo sale mal
- Selector no encontrado →
capture.mjsfalla en la escena y dice cuál. Pídele al usuario la URL/pantalla e inspecciona con el navegador antes de adivinar otro selector. - Zoom borroso → aumenta
scalea 3 en el guión (screenshots 3×) o reduce el zoom. - Video desincronizado del subtítulo →
durationde la escena es lo que manda; ajústalo ahí. - Detalles de composición, cámara, cursor y personalización visual:
references/remotion.md.
