Communitygithub.com

ui-demo

Grabe videos de demostración de UI profesionales usando Playwright. Úsalo cuando el usuario pida crear un demo, tutorial, grabación de pantalla o video instructivo de una aplicación web. Produce videos WebM con cursor visible, ritmo natural y aspecto profesional.

¿Qué es ui-demo?

Esta habilidad automatiza la grabación de videos de demostración de interfaces de usuario (UI) para aplicaciones web, utilizando Playwright. Sigue un proceso estructurado de tres fases: Descubrir, Ensayar y Grabar, para asegurar que el video final sea pulido y profesional. En la fase de Descubrimiento, se inspeccionan los elementos interactivos de la página para entender su estructura real (campos, botones, menús desplegables). En la fase de Ensayo, se verifica que todos los selectores de la UI sean válidos, evitando fallos silenciosos durante la grabación. Finalmente, en la fase de Grabación, se inyecta un cursor SVG que sigue los movimientos del ratón, se añade una barra de subtítulos para guiar al espectador, y se implementan retrasos tanto en las acciones (clics, escritura) como entre pasos para lograr un ritmo natural. El resultado es un video WebM (1280x720) que puede usarse para documentación, onboarding o presentaciones. Esta habilidad es ideal para desarrolladores, equipos de producto o cualquier persona que necesite crear videos de demostración de alta calidad para sus aplicaciones web.

Compatible con~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/ui-demo

Preguntar en tu IA favorita

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

Documentación

Grabador de Videos de Demostración UI

Graba videos de demostración pulidos de aplicaciones web usando la grabación de video de Playwright con un cursor superpuesto inyectado, ritmo natural y flujo narrativo.

Cuándo Usarlo

  • El usuario pide un "video demo", "grabación de pantalla", "tutorial" o "walkthrough"
  • El usuario quiere mostrar visualmente una característica o flujo de trabajo
  • El usuario necesita un video para documentación, onboarding o presentación a interesados

Proceso de Tres Fases

Cada demo pasa por tres fases: Descubrir -> Ensayar -> Grabar. Nunca saltes directamente a la grabación.


Fase 1: Descubrir

Antes de escribir cualquier script, explora las páginas de destino para entender qué hay realmente.

Por qué

No puedes escribir un script para algo que no has visto. Los campos pueden ser <input> no <textarea>, los desplegables pueden ser componentes personalizados no <select>, y las cajas de comentarios pueden soportar @menciones o #etiquetas. Las suposiciones rompen las grabaciones silenciosamente.

Cómo

Navega a cada página en el flujo y vuelca sus elementos interactivos:

// Ejecuta esto para cada página en el flujo ANTES de escribir el script demo
const fields = await page.evaluate(() => {
  const els = [];
  document.querySelectorAll('input, select, textarea, button, [contenteditable]').forEach(el => {
    if (el.offsetParent !== null) {
      els.push({
        tag: el.tagName,
        type: el.type || '',
        name: el.name || '',
        placeholder: el.placeholder || '',
        text: el.textContent?.trim().substring(0, 40) || '',
        contentEditable: el.contentEditable === 'true',
        role: el.getAttribute('role') || '',
      });
    }
  });
  return els;
});
console.log(JSON.stringify(fields, null, 2));

Qué Buscar

  • Campos de formulario: ¿Son <select>, <input>, desplegables personalizados o comboboxes?
  • Opciones de selección: Vuelca los valores Y los textos de las opciones. Los placeholders a menudo tienen value="0" o value="" que parece no vacío. Usa Array.from(el.options).map(o => ({ value: o.value, text: o.text })). Omite opciones donde el texto incluya "Seleccionar" o el valor sea "0".
  • Texto enriquecido: ¿La caja de comentarios soporta @menciones, #etiquetas, markdown o emojis? Revisa el texto del placeholder.
  • Campos obligatorios: ¿Qué campos bloquean el envío del formulario? Revisa required, * en las etiquetas e intenta enviar vacío para ver errores de validación.
  • Contenido dinámico: ¿Aparecen campos después de llenar otros?
  • Etiquetas de botones: Texto exacto como "Enviar", "Enviar Solicitud" o "Mandado".
  • Encabezados de columna de tabla: Para modales basados en tablas, mapea cada input[type="number"] a su encabezado de columna en lugar de asumir que todas las entradas numéricas significan lo mismo.

Salida

Un mapa de campos para cada página, usado para escribir selectores correctos en el script. Ejemplo:

/purchase-requests/new:
  - Código Presupuesto: <select> (primer select en la página, 4 opciones)
  - Entrega Deseada: <input type="date">
  - Contexto: <textarea> (no input)
  - Tabla BOM: celdas editables en línea con span.cursor-pointer -> patrón input
  - Enviar: <button> texto="Enviar"

/purchase-requests/N (detalle):
  - Comentario: <input placeholder="Escribe un mensaje..."> soporta @usuario y #PR etiquetas
  - Enviar: <button> texto="Enviar" (deshabilitado hasta que el input tenga contenido)

Fase 2: Ensayar

Ejecuta todos los pasos sin grabar. Verifica que cada selector resuelva.

Por qué

Los fallos silenciosos de selectores son la principal razón por la que se rompen las grabaciones demo. El ensayo los detecta antes de que desperdicies una grabación.

Cómo

Usa ensureVisible, un envoltorio que registra y falla con estruendo:

async function ensureVisible(page, locator, label) {
  const el = typeof locator === 'string' ? page.locator(locator).first() : locator;
  const visible = await el.isVisible().catch(() => false);
  if (!visible) {
    const msg = `ENSAYO FALLÓ: "${label}" no encontrado - selector: ${typeof locator === 'string' ? locator : '(objeto locator)'}`;
    console.error(msg);
    const found = await page.evaluate(() => {
      return Array.from(document.querySelectorAll('button, input, select, textarea, a'))
        .filter(el => el.offsetParent !== null)
        .map(el => `${el.tagName}[${el.type || ''}] "${el.textContent?.trim().substring(0, 30)}"`)
        .join('\n  ');
    });
    console.error('  Elementos visibles:\n  ' + found);
    return false;
  }
  console.log(`ENSAYO OK: "${label}"`);
  return true;
}

Estructura del Script de Ensayo

const steps = [
  { label: 'Campo email de inicio de sesión', selector: '#email' },
  { label: 'Enviar inicio de sesión', selector: 'button[type="submit"]' },
  { label: 'Botón Nueva Solicitud', selector: 'button:has-text("Nueva Solicitud")' },
  { label: 'Selector Código Presupuesto', selector: 'select' },
  { label: 'Fecha de entrega', selector: 'input[type="date"]:visible' },
  { label: 'Campo Descripción', selector: 'textarea:visible' },
  { label: 'Botón Añadir Artículo', selector: 'button:has-text("Añadir Artículo")' },
  { label: 'Botón Enviar', selector: 'button:has-text("Enviar")' },
];

let allOk = true;
for (const step of steps) {
  if (!await ensureVisible(page, step.selector, step.label)) {
    allOk = false;
  }
}
if (!allOk) {
  console.error('ENSAYO FALLÓ - corrige los selectores antes de grabar');
  process.exit(1);
}
console.log('ENSAYO APROBADO - todos los selectores verificados');

Cuando el Ensayo Falla

  1. Lee el volcado de elementos visibles.
  2. Encuentra el selector correcto.
  3. Actualiza el script.
  4. Re-ejecuta el ensayo.
  5. Solo procede cuando todos los selectores pasen.

Fase 3: Grabar

Solo después de que las fases de descubrimiento y ensayo pasen, debes crear la grabación.

Principios de Grabación

1. Flujo Narrativo

Planifica el video como una historia. Sigue el orden especificado por el usuario, o usa este predeterminado:

  • Entrada: Inicia sesión o navega al punto de partida
  • Contexto: Recorre los alrededores para que los espectadores se orienten
  • Acción: Realiza los pasos principales del flujo de trabajo
  • Variación: Muestra una característica secundaria como configuración, tema o localización
  • Resultado: Muestra el resultado, confirmación o nuevo estado

2. Ritmo

  • Después de inicio de sesión: 4s
  • Después de navegación: 3s
  • Después de hacer clic en un botón: 2s
  • Entre pasos principales: 1.5-2s
  • Después de la acción final: 3s
  • Retraso de escritura: 25-40ms por carácter

3. Superposición de Cursor

Inyecta un cursor de flecha SVG que siga los movimientos del ratón:

async function injectCursor(page) {
  await page.evaluate(() => {
    if (document.getElementById('demo-cursor')) return;
    const cursor = document.createElement('div');
    cursor.id = 'demo-cursor';
    cursor.innerHTML = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
      <path d="M5 3L19 12L12 13L9 20L5 3Z" fill="white" stroke="black" stroke-width="1.5" stroke-linejoin="round"/>
    </svg>`;
    cursor.style.cssText = `
      position: fixed; z-index: 999999; pointer-events: none;
      width: 24px; height: 24px;
      transition: left 0.1s, top 0.1s;
      filter: drop-shadow(1px 1px 2px rgba(0,0,0,0.3));
    `;
    cursor.style.left = '0px';
    cursor.style.top = '0px';
    document.body.appendChild(cursor);
    document.addEventListener('mousemove', (e) => {
      cursor.style.left = e.clientX + 'px';
      cursor.style.top = e.clientY + 'px';
    });
  });
}

Llama a injectCursor(page) después de cada navegación de página porque la superposición se destruye al navegar.

4. Movimiento del Ratón

Nunca teletransportes el cursor. Muévelo al objetivo antes de hacer clic:

async function moveAndClick(page, locator, label, opts = {}) {
  const { postClickDelay = 800, ...clickOpts } = opts;
  const el = typeof locator === 'string' ? page.locator(locator).first() : locator;
  const visible = await el.isVisible().catch(() => false);
  if (!visible) {
    console.error(`AVISO: moveAndClick omitido - "${label}" no visible`);
    return false;
  }
  try {
    await el.scrollIntoViewIfNeeded();
    await page.waitForTimeout(300);
    const box = await el.boundingBox();
    if (box) {
      await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2, { steps: 10 });
      await page.waitForTimeout(400);
    }
    await el.click(clickOpts);
  } catch (e) {
    console.error(`AVISO: moveAndClick falló en "${label}": ${e.message}`);
    return false;
  }
  await page.waitForTimeout(postClickDelay);
  return true;
}

Cada llamada debe incluir un label descriptivo para depuración.

5. Escritura

Escribe visiblemente, no con llenado instantáneo:

async function typeSlowly(page, locator, text, label, charDelay = 35) {
  const el = typeof locator === 'string' ? page.locator(locator).first() : locator;
  const visible = await el.isVisible().catch(() => false);
  if (!visible) {
    console.error(`AVISO: typeSlowly omitido - "${label}" no visible`);
    return false;
  }
  await moveAndClick(page, el, label);
  await el.fill('');
  await el.pressSequentially(text, { delay: charDelay });
  await page.waitForTimeout(500);
  return true;
}

6. Desplazamiento

Usa desplazamiento suave en lugar de saltos:

await page.evaluate(() => window.scrollTo({ top: 400, behavior: 'smooth' }));
await page.waitForTimeout(1500);

7. Recorrido de Panel

Al mostrar un panel de control o página de resumen, mueve el cursor sobre los elementos clave:

async function panElements(page, selector, maxCount = 6) {
  const elements = await page.locator(selector).all();
  for (let i = 0; i < Math.min(elements.length, maxCount); i++) {
    try {
      const box = await elements[i].boundingBox();
      if (box && box.y < 700) {
        await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2, { steps: 8 });
        await page.waitForTimeout(600);
      }
    } catch (e) {
      console.warn(`AVISO: panElements omitió elemento ${i} (selector: "${selector}"): ${e.message}`);
    }
  }
}

8. Subtítulos

Inyecta una barra de subtítulos en la parte inferior del viewport:

async function injectSubtitleBar(page) {
  await page.evaluate(() => {
    if (document.getElementById('demo-subtitle')) return;
    const bar = document.createElement('div');
    bar.id = 'demo-subtitle';
    bar.style.cssText = `
      position: fixed; bottom: 0; left: 0; right: 0; z-index: 999998;
      text-align: center; padding: 12px 24px;
      background: rgba(0, 0, 0, 0.75);
      color: white; font-family: -apple-system, "Segoe UI", sans-serif;
      font-size: 16px; font-weight: 500; letter-spacing: 0.3px;
      transition: opacity 0.3s;
      pointer-events: none;
    `;
    bar.textContent = '';
    bar.style.opacity = '0';
    document.body.appendChild(bar);
  });
}

async function showSubtitle(page, text) {
  await page.evaluate((t) => {
    const bar = document.getElementById('demo-subtitle');
    if (!bar) return;
    if (t) {
      bar.textContent = t;
      bar.style.opacity = '1';
    } else {
      bar.style.opacity = '0';
    }
  }, text);
  if (text) await page.waitForTimeout(800);
}

Llama a injectSubtitleBar(page) junto con injectCursor(page) después de cada navegación.

Patrón de uso:

await showSubtitle(page, 'Paso 1 - Iniciando sesión');
await showSubtitle(page, 'Paso 2 - Resumen del panel');
await showSubtitle(page, '');

Directrices:

  • Mantén el texto del subtítulo corto, idealmente menos de 60 caracteres.
  • Usa el formato Paso N - Acción para consistencia.
  • Limpia el subtítulo durante pausas largas donde la interfaz pueda hablar por sí misma.

Plantilla de Script

'use strict';
const { chromium } = require('playwright');
const path = require('path');
const fs = require('fs');

const BASE_URL = process.env.QA_BASE_URL || 'http://localhost:3000';
const VIDEO_DIR = path.join(__dirname, 'screenshots');
const OUTPUT_NAME = 'demo-CARACTERÍSTICA.webm';
const REHEARSAL = process.argv.includes('--rehearse');

// Pega injectCursor, injectSubtitleBar, showSubtitle, moveAndClick,
// typeSlowly, ensureVisible, y panElements aquí.

(async () => {
  const browser = await chromium.launch({ headless: true });

  if (REHEARSAL) {
    const context = await browser.newContext({ viewport: { width: 1280, height: 720 } });
    const page = await context.newPage();
    // Navega a través del flujo y ejecuta ensureVisible para cada selector.
    await browser.close();
    return;
  }

  const context = await browser.newContext({
    recordVideo: { dir: VIDEO_DIR, size: { width: 1280, height: 720 } },
    viewport: { width: 1280, height: 720 }
  });
  const page = await context.newPage();

  try {
    await injectCursor(page);
    await injectSubtitleBar(page);

    await showSubtitle(page, 'Paso 1 - Iniciando sesión');
    // acciones de inicio de sesión

    await page.goto(`${BASE_URL}/dashboard`);
    await injectCursor(page);
    await injectSubtitleBar(page);
    await showSubtitle(page, 'Paso 2 - Resumen del panel');
    // recorrer panel

    await showSubtitle(page, 'Paso 3 - Flujo de trabajo principal');
    // secuencia de acciones

    await showSubtitle(page, 'Paso 4 - Resultado');
    // revelación final
    await showSubtitle(page, '');
  } catch (err) {
    console.error('ERROR DE DEMO:', err.message);
  } finally {
    await context.close();
    const video = page.video();
    if (video) {
      const src = await video.path();
      const dest = path.join(VIDEO_DIR, OUTPUT_NAME);
      try {
        fs.copyFileSync(src, dest);
        console.log('Video guardado:', dest);
      } catch (e) {
        console.error('ERROR: No se pudo copiar el video:', e.message);
        console.error('  Origen:', src);
        console.error('  Destino:', dest);
      }
    }
    await browser.close();
  }
})();

Uso:

# Fase 2: Ensayar
node demo-script.cjs --rehearse

# Fase 3: Grabar
node demo-script.cjs

Lista de Verificación Antes de Grabar

  • Fase de descubrimiento completada
  • Ensayo aprobado con todos los selectores OK
  • Modo headless habilitado
  • Resolución configurada a 1280x720
  • Superposiciones de cursor y subtítulos reinyectadas después de cada navegación
  • showSubtitle(page, 'Paso N - ...') usado en transiciones principales
  • moveAndClick usado para todos los clics con etiquetas descriptivas
  • typeSlowly usado para entrada visible
  • [ } Sin capturas silenciosas;

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

Skills relacionados