Communitygithub.com

wiggle-that-diagram

Úsalo cuando te pidan animar una ilustración en una presentación HTML o informe HTML, mostrar paso a paso cómo funciona un mecanismo, flujo de trabajo o flujo de datos. Crea un archivo autónomo por mecanismo con subtítulos, bucle e incrustable en iframe srcdoc, en tres formatos (A, B y C).

¿Qué es wiggle-that-diagram?

Esta habilidad transforma una ilustración estática en una animación corta con subtítulos y bucle, diseñada para integrarse en diapositivas HTML o informes, ofreciendo tres estilos distintos de animación para que el usuario elija. Está basada en un caso real y utiliza el motor `huashu-art-motion` para la dirección (guiones de tomas, reglas de cámara, seis formas de fallar). Los tres estilos son: A (diagrama clip: escena del motor huashu con etiquetas HTML), B (pizarra: dibujo a mano y letras con Kalam) y C (SVG en vivo con cámara virtual y GSAP). Cada animación comparte el mismo guión de subtítulos y ritmo, pero se genera en un archivo HTML autónomo que se incrusta en un iframe srcdoc. La habilidad guía al usuario paso a paso: desde identificar el mecanismo y escribir el guión de subtítulos, hasta la construcción, verificación y entrega final en una diapositiva con un conmutador de estilos. Incluye reglas de ritmo para que la animación no sea demasiado rápida de leer, y un contrato de incrustación que asegura compatibilidad con distintos navegadores y dispositivos.

Compatible con✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/wan-huiyan/wiggle-that-diagram/tree/HEAD/plugins/wiggle-that-diagram/skills/wiggle-that-diagram

Preguntar en tu IA favorita

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

Documentación

wiggle-that-diagram: animación explicativa en tres estilos

Convierte una ilustración estática de "cómo funciona" en una animación corta, con subtítulos y en bucle, que se inserta dentro de una presentación HTML o informe, y ofrécela en tres formatos para que el lector pueda elegir. El método se desarrolló en una presentación real de un cliente (consulta "Ejemplo práctico"). Utiliza huashu-art-motion para la dirección (guiones de tomas, reglas de cámara, las seis formas de fallar) y, para los estilos A y B, como motor de renderizado.

Cuándo usarlo y cuándo no

Úsalo cuando:

  • una presentación o informe HTML tiene un diagrama de mecanismo (flujo de archivos, verificación de reglas, aprobación por una persona) y el lector debería verlo ocurrir, paso a paso, con palabras que pueda leer;
  • el usuario quiere opciones en formatos genuinamente diferentes, no tres esquemas de color de uno;
  • una animación anterior era "demasiado rápida para leer" o necesita subtítulos.

No lo uses para: una sola microinteracción (usa animate), un video narrado con voz en off (usa huashu-art-motion directamente), o un gráfico de números (usa dataviz).

Los tres estilos (difieren en formato, no en paleta)

A · diagrama clipB · pizarraC · SVG en vivo
ImagenEscena del lienzo del motor huashu, gramática t1_3b1b, renderizada a H.264 sin textoGramática huashu y3_whiteboard, pizarra oscura, bolígrafo dibuja y escribe a mano (Kalam), video cuadradoMundo SVG en línea filmado por una cámara virtual (viewBox animado con GSAP 3.12.5)
Palabras en la imagenEtiquetas HTML colocadas sobre el video a partir de una pista de cámara volcada (siempre nítidas)Dibujadas en el video (deben ser lo suficientemente grandes para un teléfono)Texto SVG real (nítido, se reajusta cambiando los fotogramas de la cámara según el ancho)
Tamaño por película (ejemplo práctico)360–820 KB (presupuesto 900 KB)847–886 KB (presupuesto 900 KB)21–34 KB (presupuesto 40 KB)
Sensaciónun diagrama limpio al estilo 3Blue1Brownalguien explicando en una pizarrauna animación de producto pulida
Riesgo principallas etiquetas se desvían si la escena cambia sin volver a renderizarpresupuesto de tamaño ajustado; etiquetas de teléfono 12–14 pxmatemáticas de cámara complicadas; rarezas de transformación de GSAP

Los tres comparten UN guión de subtítulos, los mismos tiempos y el mismo contrato de incrustación, por lo que un conmutador de estilos muestra la misma historia contada de tres maneras.

El método, de principio a fin

Cada paso termina con la verificación que demuestra que está completo.

  1. Encuentra el mecanismo y un ejemplo concreto. Lee la página anfitriona (solo lectura). Elige el momento a mostrar y UN ejemplo real que recorra cada momento (una fila, un registro, un caso, con sus valores reales). Enumera lo que no se debe inventar (ids, cifras, imágenes). → Verificación: puedes decir cada momento como "este objeto hace esto", con un sustantivo y un verbo.

  2. Escribe primero el guión de subtítulos. Una oración de subtítulo por momento, de 3 a 6 momentos por película, en beats.json (formato: scripts/common/beats.example.json). Los subtítulos son idénticos en todos los estilos. Luego ejecuta la compuerta de ritmo y lee el piso que imprime:

    python3 ~/.claude/skills/wiggle-that-diagram/scripts/common/pace_check.py beats.json
    

    → Verificación: cada momento está bien, y el usuario ha acordado las duraciones de bucle resultantes (o subtítulos más cortos). Nunca establezcas un objetivo de bucle por separado de los subtítulos; consulta "Ritmo".

  3. Escribe el resumen. Completa references/brief-template.md en SPEC.md: comentarios citados, tokens, regla de ritmo, guión de subtítulos, momentos, contrato de incrustación con el fragmento textual, presupuestos, entregables. → Verificación: un agente sin otro contexto podría construirlo a partir de ahí.

  4. Ronda de direcciones (opcional, cuando el usuario no ha visto los estilos). Tres agentes en paralelo, uno por estilo, cada uno prototipando el MISMO momento de una película (~8–12 s) y tomando imágenes fijas a 1280 y 390 px. Muestra las imágenes fijas una al lado de la otra; el usuario elige una o conserva las tres. → Verificación: la elección del usuario está por escrito.

  5. Ronda de construcción. Un agente por estilo (cada uno continúa su propio prototipo), todas las películas en ese estilo, escribiendo solo bajo <work>/<style>/final/. Resúmenes: references/agent-briefs.md. Di al usuario la cantidad de agentes y el nivel antes de lanzarlos. → Verificación: cada uno devuelve archivos, imágenes fijas, NOTES.md y una lista honesta de problemas abiertos.

  6. Recetas de construcción (lo que realmente hizo cada agente de estilo; los scripts están en scripts/style-a|b|c/): Dos películas neutrales se entregan en los tres estilos, cada una como beats.json más style-a/, style-b/, style-c/: examples/pizza/ (5 momentos, simple) y examples/expenses/ (6 momentos: tarjetas, razones codificadas por colores, flechas a un segundo y tercer panel). Comienza desde la carpeta de tu estilo.

    • A. cp -R ~/.claude/skills/huashu-art-motion/scripts/engine <A>/engine y touch <A>/engine/favicon.ico. Agrega engine/ways/way_make.js (de scripts/style-a/), ejecuta beats_js.py beats.json --engine engine --var <VAR> (subtítulos y temporización del script compartido, nunca una copia manual), un archivo de escena engine/ways/<film>.js que llama a WAY.make({poster, beats, camBase, cam, labels}) y registra SCENES['<film>'], y engine/eras_<film>.js estableciendo FILM_SIZE=[1280,720], PUNCH=0 (de lo contrario, el motor agrega un zoom de 0.03 en las transiciones), SCENE_LIBS=['ways/way_make.js','ways/<film>_beats.js','ways/<film>.js'], SCENE_DIR='ways', ERAS=[{id:'<film>',dur:1}]. NO dibujes texto en el lienzo. Los ayudantes de dibujo (cajas redondeadas, puntos, líneas, desvanecimientos, movimientos a lo largo de una ruta) se escriben por película; examples/pizza/style-a/pizza.js tiene un pequeño conjunto para copiar. Luego render_way.py <film> --scale 0.75 --crf 30 → build.py <film> --meta meta.json → pace_check.py work/<film>_data.json → test_embed.py final/<film>.html:<verdict beat> (o todo a la vez: make.sh). Después de cualquier edición de escena, vuelve a renderizar y ejecuta midmove.py en los tiempos de movimiento medio: las etiquetas deben estar sobre sus objetos.
    • B. Copia el motor de la misma manera (con el favicon.ico vacío: en Chrome real, su 404 cuenta como un error de página y render.py sale con 1 después de escribir un buen video) y parchea render.py a chromium.launch(channel='chrome', …). Pon kit.js en engine/demos/_hb/, escribe engine/demos/<film>/board.js (usa HB.make(), ancla eventos a BEATS[k].t0 + offset) y engine/demos/<film>/eras.js (FILM_SIZE=[1080,1080], PUNCH=0, fuente Kalam, SCENE_LIBS=['demos/<film>/beats.js','demos/_hb/kit.js','demos/<film>/board.js']). Luego build_beats.py beats.json --engine engine → uv run --with playwright python engine/render.py --film demos/<film> --fps 30 --crf 12 --out work/<film>_master.mp4 → build_html.py <film> --beats beats.json (720², 24 fps, el CRF más bajo que quepa) → test_contract.py final/<film>.html --beats beats.json. Cada película termina con un alejamiento a toda la pizarra, una pausa de ≥ 1 s, luego un borrador; la pizarra vacía es donde se reinicia el bucle. El still del último momento en beats.json es compartido por todos los estilos y debe terminar antes de que comience el borrador, o el póster y el fotograma de movimiento reducido mostrarán una pizarra medio borrada. Los objetos que se mueven (un scooter, una caja) son elementos K.custom con handed: false cuya posición es una función del tiempo; asigna a cada uno un tiempo final, o se quedará en la pizarra después de su trabajo.
    • C. Comienza desde examples/pizza/style-c/ (SVG, línea de tiempo, tema). Escribe src/<film>.svg (un <svg id="scene" class="scene"> que contiene <g id="world">; etiqueta cada <text> o su grupo con data-b="<beat>", basado en 1, establecido en el momento en que la cámara lo muestra POR PRIMERA VEZ) y src/<film>.js (fotogramas de cámara FR / ORDER, cada fotograma con un rect w para pantallas anchas y un rect t para pantallas estrechas; revelaciones con R(selector, beat, offset), movimientos con camTo(i, beat, offset), dibujos de líneas con draw()). En el JS, beat está basado en 0 (B[0] es el momento 1). R() es solo para palabras, con offset ≥ 2.5 s: cada llamada R() se registra como una revelación de palabra y el subtítulo cuenta como palabras en el offset 0, por lo que R() en una forma sin palabras o en el offset 0 falla la verificación de espaciado. Las formas, el movimiento y las palabras que llegan con el subtítulo usan tl.to(…, T(beat, offset)). Envuelve cualquier elemento que tenga su propio transform en un <g> padre y anima al hijo. Luego build.py beats.json --src src --out final [--css theme.css] → test_contract.py final/<film>.html --stills <scratch>/stills → widthcheck.py. El tiempo de ejecución (shell.html, shell.css, head.js, tail.js) se reutiliza tal cual; para que coincida con la página anfitriona, coloca nuevos tokens :root (--panel, --ink, --muted, --line, --gold, --cap-ink, --on-gold, …) en un archivo de tema y pásalo con --css. Para una imagen de README o chat de una película: gif.py final/<film>.html --beat 4 --from 1.9 --to 8.6 --out film.gif (o --still <s> para un PNG).
  7. Verificar antes de mostrar a nadie (las tres construcciones se saltaron los últimos tres; hazlos):

    • compuerta de ritmo OK; todas las pruebas de contrato pasan a 1100 y 360 px en Chrome real; tamaños dentro del presupuesto;
    • mirar las hojas de contacto de cada momento en ambos anchos (scripts/common/contact_sheet.py);
    • huashu film_gate.py en los mp4 de A/B (con el shim ffprobe si es necesario), rojo leído como se explica en traps.md;
    • un revisor independiente que no lo construyó ve cada bucle a velocidad normal y escribe hallazgos;
    • probar dentro de la página anfitriona real (lado padre de references/embed-contract.md), en un teléfono real, y en Safari (respaldo de estilo A/B: póster + subtítulos numerados cuando H.264 no puede reproducirse).
  8. Entrega. Muestra los tres estilos uno al lado del otro (imágenes fijas o una página de conmutador), con las duraciones de bucle, tamaños, lo que se inventó en la imagen (para que el usuario lo confirme), y lo que no se verificó.

Lado anfitrión: los nueve en una diapositiva con un conmutador (scripts/deck/)

Lo que hace la presentación de ejemplo, y lo que se rompió en el camino:

  1. Incrustar, no enlazar. Cada película va en la presentación como <script type="text/plain" id="anim-{style}-{way}"> con </script → </script y <!-- → <!-- escapados; JS lo decodifica en iframe.srcdoc solo cuando se muestra la diapositiva (un iframe, construido bajo demanda). embed_anims.py hace esto de forma idempotente entre los marcadores <!--ANIM-DATA-START--> / <!--ANIM-DATA-END-->, por lo que volver a ejecutar después de una reconstrucción reemplaza solo los datos. La presentación creció de ~0.3 MB a 4.5 MB.
  2. Una diapositiva, dos grupos de botones segmentados (Forma, Estilo; aria-pressed), más un enlace "Míralo funcionar" dentro del panel de detalles de cada Forma que establece la forma, cierra el panel y salta a la diapositiva.
  3. Reproducir/pausar sigue la diapositiva: go() envía un evento deck:slide; el conmutador publica anim-play cuando su diapositiva se vuelve actual (y en la carga del iframe si está actual), anim-pause en caso contrario.
  4. Gestos sobre el iframe: los manejadores de rueda/tecla/deslizamiento de la presentación se refactorizaron en onWheel(dy,mode), onKey(key), onSwipe(dy,dx), llamados tanto por los propios oyentes de la presentación como por los mensajes reenviados del iframe (aceptar solo e.source === frame.contentWindow). howtest2.py demuestra que un gesto = una diapositiva.
  5. Ajustar a una pantalla de portátil: primero estrechar el diseño interno, hacer zoom al final. A 1280×800, las películas tenían 740–840 px de alto a ancho completo, por lo que el subtítulo caía debajo de la pantalla. Lo que funciona (howFit en embed_anims.py): el iframe srcdoc es del mismo origen, por lo que la presentación mide la altura de contentDocument.body por sí misma. Prueba anchos internos desde el ancho de la caja hasta 740 px (búsqueda binaria, esperando ~90 ms por ancho porque una película puede establecer su diseño en JS al cambiar de tamaño, fotograma oculto mientras tanto) y mantiene el más ancho que quepa; solo si 740 px sigue siendo demasiado alto, establece frame.style.zoom. Estrechar es mejor que hacer zoom porque el subtítulo mantiene su tamaño mientras la imagen se encoge. Problemas encontrados en el camino: (a) zoom con width:100% ensancha el diseño interno (1126 → 1876 px) y baja al mínimo, así que fija siempre frame.style.width en px; (b) documentElement.scrollHeight nunca informa menos que la altura actual del iframe, por lo que no puede impulsar la reducción (medir body en su lugar, con el fotograma hecho alto); (c) medir en el mismo tick que el cambio de ancho lee un diseño JS obsoleto. Pide a las películas un diseño lado a lado cuando sean anchas (imágenes cuadradas) y ≤ 580 px de altura total a 780 px de ancho. Los teléfonos (< 900 px) no caben; la diapositiva se desplaza, con un encabezado compacto.
  6. Pruebas: howtest.py (cambio, enlace profundo, flechas), howtest2.py (los 9 se cargan y reproducen en Chrome real, rueda/tecla/deslizamiento sobre el iframe mueven exactamente una diapositiva), más las comprobaciones de un gesto por diapositiva de la propia presentación. Cualquier prueba de presentación que codifique números de diapositiva se rompe cuando se inserta la diapositiva: buscar diapositivas por id.

Ritmo (la queja principal del usuario sobre el primer corte)

Los primeros prototipos tenían 8–13 s. El veredicto del lector: demasiado rápido para leer lo que sucedía, y necesitaba subtítulos. La regla utilizada para la reconstrucción:

  • cada momento dura ≥ max(4.5 s, (palabras del subtítulo + palabras nuevas de la imagen) / 3 + 1.5 s). El recuento canónico es scripts/common/pace_check.py (y build.py del estilo C, que cuenta de la misma manera desde el SVG). El pace_check.py propio del estilo A verifica los tiempos reales de las etiquetas de la película construida y difiere en una cosa: una palabra repetida por una nueva etiqueta en un momento posterior se cuenta nuevamente allí;
  • algo nuevo aparece solo ≥ 2.5 s después de las palabras anteriores; la cámara se mantiene quieta mientras se leen las palabras;
  • movimientos lentos de cámara entre momentos (1.4–2.0 s, interpolación sinusoidal), como máximo UN acento rápido por película, en el veredicto;
  • ≥ 1 s de pausa en el último fotograma antes del bucle.

Consecuencia que decir al usuario de antemano: los subtítulos de ~20 palabras por momento hacen películas de 5–6 momentos de 40–80 s. En el ejemplo práctico, los objetivos de 18–34 s del resumen eran imposibles (solo los subtítulos necesitaban 31.5–51.7 s); los tres agentes eligieron la regla de lectura y construyeron bucles de 35–82 s, que nadie había confirmado con el usuario. Los bucles más cortos significan subtítulos más cortos, cambiados una vez en beats.json para todos los estilos.

Contrato de incrustación, tamaño y teléfonos (resumen; texto completo en references/embed-contract.md)

  • Un archivo HTML autónomo por película, cargado por el anfitrión en <iframe srcdoc>. Imagen + tira de subtítulos + riel de pasos + Reproducir/Pausar, nada más. Medios en línea base64;

Skills relacionados