Communitygithub.com

demo-reel

Graba un video demo de 60 fps suave y preciso de cualquier aplicación web a partir de un guión corto: cursor guionizado con un halo brillante, ondas al hacer clic, leyendas y una insignia en la esquina, renderizado sobre un reloj virtual para que nunca tartamudee, codificado en un MP4 que se reproduce en bucle en PowerPoint, Keynote o la web. Úsalo cuando el usuario quiera un video demo, un recorrido por el producto, una grabación de pantalla, un tour de funciones, un video de comparación antes/después, un clip para una presentación o una página de aterrizaje, o lo diga.

¿Qué es demo-reel?

demo-reel es una habilidad especializada que transforma un guión que recorre una aplicación web en un video de aspecto profesional, con movimiento suave a 60 fps, un cursor visible con halo y ondas al hacer clic. A diferencia de las grabaciones de pantalla convencionales, utiliza un **reloj virtual** inyectado en el navegador Chromium sin cabeza para avanzar fotograma a fotograma de forma precisa, eliminando tartamudeos y variabilidad del hardware. La herramienta maneja la navegación, escritura, desplazamiento y clics, y superpone leyendas e insignias personalizables. Está diseñada para desarrolladores que necesitan crear demos pulidos para presentaciones, páginas de aterrizaje o documentación de productos, y es compatible con Node 18+ y ffmpeg. El flujo de trabajo incluye servir la aplicación, escribir un escenario en JavaScript, grabarlo y verificar la calidad con una hoja de contactos.

Compatible con~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/hasuwini77/demo-reel/tree/main/skills/demo-reel

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Vista previa

Del README del skill

Documentación

¿Qué hace demo-reel?

Convierte un recorrido guionizado por una aplicación web en un video que parece una grabación de pantalla profesional: movimiento suave a 60 fps, un cursor visible con halo, ondas al hacer clic, leyendas. Maneja la aplicación real en Chromium sin cabeza; el metraje es el producto, no una maqueta.

Por qué el video es suave

Una grabación de pantalla normal captura fotogramas cuando el navegador pinta; en una máquina lenta (o WebGL por software) eso es ~10–15 fps y tartamudea. demo-reel inyecta un reloj virtual (scripts/inject.js): requestAnimationFrame, performance.now y Date solo avanzan cuando el grabador lo indica, y las animaciones CSS/Web se avanzan paso a paso mediante document.getAnimations(). Cada fotograma se avanza exactamente 1/fps, se captura una captura de pantalla y se envía a ffmpeg. Por mucho que tarde un fotograma en renderizarse, el resultado se reproduce perfectamente suave.

Los temporizadores (setTimeout/setInterval) se dejan deliberadamente reales — falsificarlos hace que las cadenas de temporizadores con retardo cero en cargadores y planificadores se ejecuten para siempre (por eso el page.clock de Playwright se cuelga en muchas páginas WebGL).

Requisitos

  • Node 18+, ffmpeg en PATH.
  • Una vez, en el directorio de esta habilidad: npm install && npx playwright install chromium.

Flujo de trabajo

  1. Sirve la aplicación como la ven los usuarios — idealmente una compilación de producción (vite build && vite preview, next build && next start) contra datos locales o simulados. Los servidores de desarrollo funcionan pero son más lentos y pueden mostrar superposiciones de desarrollo. Nunca grabes datos reales o sensibles — siembra datos de demostración.
  2. Escribe un escenario (*.scenario.mjs, ver API debajo). Empieza desde examples/tasks.scenario.mjs. Planifícalo como una historia: 20–60 s, una idea por leyenda, pausas de 1–2 s después de cada resultado para que los espectadores puedan leerlo.
  3. Graba: node scripts/record.mjs mi.escenario.mjs --out demo.mp4 Un clip de 60 s a 1080p toma aproximadamente 3–10 min (las páginas WebGL pesadas son las más lentas). Ejecútalo en segundo plano.
  4. QA: scripts/sheet.sh demo.mp4 escribe una hoja de contactos (un fotograma cada 2.5 s). Mírala: las leyendas coinciden con lo que está en pantalla, no hay indicadores de carga, nada cubierto por la leyenda, insignia/cursor visibles. Verifica la suavidad extrayendo algunos fotogramas consecutivos — cada uno debería diferir ligeramente durante el movimiento.
  5. Entrega (ver "Uso del video").

API del escenario

export default {
  size: "1920x1080",          // viewport = tamaño del video
  fps: 60,
  theme: { captionPosition: "bottom", haloFill: "rgba(250,204,21,.30)" }, // opcional
  async run(d) {
    await d.open("http://localhost:4173/");     // navegar + estabilizar (no grabado)
    d.badge("NUEVO", "#15803d");                  // insignia en la esquina (null oculta)
    d.caption("El nuevo panel");             // píldora de leyenda (null oculta)
    await d.hold(1500);                          // grabar 1.5 s tal cual
    await d.click(d.page.getByRole("button", { name: "Filtros" }));
    d.caption("Los filtros viven en un panel");     // establecer leyendas DESPUÉS de la acción
    await d.hold(1200);
    await d.type("Estocolmo");                  // escritura a velocidad humana
    await d.press("Enter");
    await d.scroll(600);                        // desplazamiento suave con rueda
    await d.step("parte opcional", async () => { /* los errores se registran, la grabación continúa */ });
  },
};
LlamadaHace
d.open(url, { settle })Navegar, dejar que la página se estabilice (no grabado, predeterminado 2500 ms).
d.hold(ms)Grabar la página tal como está.
d.settle(ms)Avanzar el tiempo sin grabar — saltar estados de carga, partes perezosas, animaciones de diseño que no quieras mostrar.
d.move(target, { ms }) / d.hoverDeslizar el cursor (con easing) hacia un objetivo.
d.click(target, { ms, button })Moverse, hacer clic con una onda. button: "right" para menús contextuales.
d.doubleClick(target)Doble clic.
d.type(text, { delay })Escribir una tecla a la vez (predeterminado 90 ms/tecla).
d.press(key)Atajo de teclado, ej. "Escape", "Control+K".
d.scroll(dy, { ms })Desplazamiento suave con rueda.
d.caption(text) / d.badge(text, color) / d.cursor(bool)Estado de la superposición; sobrevive a navegaciones completas de la página.
d.step(name, fn)Paso con nombre; los errores se registran, no son fatales.
d.page, d.point(target), d.width, d.heightVías de escape.

Objetivos pueden ser un Locator de Playwright, una cadena de selector CSS, { x, y }, o una función asíncrona (page) => ({ x, y }).

UIs renderizadas en canvas (gráficos, mapas, gráficos de barras) no tienen un elemento DOM por elemento — pasa una función que pida a la biblioteca las coordenadas de pantalla. Ejemplo para Cytoscape.js:

const node = (label) => (page) => page.evaluate((label) => {
  const el = [...document.querySelectorAll("div")].find((e) => e._cyreg?.cy);
  const n = el._cyreg.cy.nodes().filter((n) => n.data("label") === label)[0];
  const r = el.getBoundingClientRect(), p = n.renderedPosition();
  return { x: r.left + p.x, y: r.top + p.y };
}, label);
await d.click(node("Servidor A"));

Escribir buenas demos

  • Las leyendas dicen la verdad. Solo afirma lo que muestra el metraje. Si un error que quieres mostrar no se reproduce en la toma, cambia la leyenda — no la mantengas.
  • Leyenda después de la acción, no antes — de lo contrario, la leyenda de la siguiente pantalla aparece sobre la anterior.
  • Muestra resultados, no clics. Mantén 1–2 s en cada resultado; corta la carga con d.settle.
  • Antes/Después: graba dos escenarios con la misma historia y pasos, insignia ANTES/DESPUÉS (rojo/verde), y mantenlos en archivos separados más una concatenación opcional.
  • Momentos estelares (una página de aterrizaje, una animación): dales 5–10 s — los espectadores necesitan un momento para asimilarlo.
  • Mantén el cursor tranquilo: 600–900 ms por movimiento, apárcarlo lejos del contenido mientras se mantiene.
  • Los temporizadores reales siguen funcionando mientras se renderizan los fotogramas (~50–300 ms de tiempo real por fotograma), por lo que las interfaces basadas en temporizadores — toasts, búsqueda con debounce, tooltips que se cierran solos — pueden desaparecer antes en el video que en la vida real. Dispáralos justo antes de que quieras que aparezcan en pantalla, o mantén menos.

Uso del video

  • PowerPoint: Insertar → Video → Este dispositivo. Pestaña Reproducción: Inicio: Automáticamente y Repetir hasta detener. H.264 yuv420p MP4 (lo que escribe demo-reel) se reproduce en Windows y macOS.
  • Keynote: arrastrar; Película → Repetir: Bucle.
  • Web: <video src="demo.mp4" autoplay loop muted playsinline></video>.
  • GIF (README, chat): ffmpeg -i demo.mp4 -vf "fps=20,scale=960:-1:flags=lanczos,split[a][b];[a]palettegen[p];[b][p]paletteuse" demo.gif
  • Concatenar clips del mismo tamaño/fps: printf "file 'a.mp4'\nfile 'b.mp4'\n" > l.txt && ffmpeg -f concat -safe 0 -i l.txt -c copy both.mp4

Solución de problemas

SíntomaSolución
Fotogramas en blanco / "Cargando…"Mayor settle en d.open, o await d.settle(1500) después de un clic de navegación.
Objetivo no visibleEstá fuera de pantalla o dentro de un menú cerrado — abre/desplázate primero; para elementos de canvas usa una función de punto.
WebGL/3D se renderiza en negroMantén las banderas de lanzamiento predeterminadas (--use-angle=swiftshader); es lento (~0.25 s/fotograma) pero correcto.
Video demasiado corto/largo vs planLas duraciones son exactas: suma de hold + movimientos. El tiempo de settle nunca se graba.
Colores se ven deslavados en PowerPointNo re-codifiques sin -pix_fmt yuv420p; la salida de demo-reel ya es correcta.
Texto ligeramente borroso--png captura fotogramas sin pérdida (más lento, más grande).

Skills relacionados