Communitygithub.com

demo-reel

Nehmen Sie eine flüssige, frame-genaue Demo-Video mit 60 fps jeder Web-App von einem kurzen Skript auf — skriptgesteuerter Cursor mit Highlight-Halo, Klick-Wellen, Untertiteln und einem Ecken-Badge, gerendert auf einer virtuellen Uhr, damit es nie ruckelt, kodiert als MP4, das in PowerPoint, Keynote oder im Web läuft.

Was ist demo-reel?

demo-reel ist ein KI-gestütztes Tool, das ein kurzes Skript in ein poliertes Demo-Video einer Web-App umwandelt – mit flüssigen 60 fps, einem sichtbaren Cursor mit Halo, Klick-Wellen und Untertiteln. Es treibt die echte App im Headless-Chromium: Das Filmmaterial ist das Produkt, kein Mockup. Die besondere Technik ist eine virtuelle Uhr (in scripts/inject.js), die requestAnimationFrame, performance.now und Date nur dann fortschreiten lässt, wenn der Recorder es bestimmt, wodurch das Video immer perfekt flüssig ist, unabhängig von der Rendering-Geschwindigkeit des Rechners. Es unterstützt Node 18+ und erfordert ffmpeg (auf dem PATH) sowie die Installation von Abhängigkeiten (npm install && npx playwright install chromium). Der Workflow umfasst das Bereitstellen der App (am besten als Produktions-Build), das Schreiben eines Szenarios (z. B. *.scenario.mjs, mit API wie d.open, d.hold, d.click, d.caption), die Aufnahme über node scripts/record.mjs my.scenario.mjs --out demo.mp4, die Qualitätssicherung mittels Kontaktbogen (scripts/sheet.sh) und schließlich die Auslieferung (PowerPoint, Keynote, Web, GIF).

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

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Vorschau

Aus dem Skill-README

Dokumentation

Was macht demo-reel?

Verwandle einen skriptgesteuerten Durchlauf durch eine Web-App in ein Video, das wie eine polierte Bildschirmaufnahme aussieht: flüssige 60 fps, ein sichtbarer Cursor mit Halo, Klick-Wellen, Untertitel. Es steuert die echte App im Headless-Chromium — das Filmmaterial ist das Produkt, kein Mockup.

Warum das Video flüssig ist

Eine normale Bildschirmaufnahme zeichnet Frames auf, wenn der Browser zufällig malt; auf einem langsamen Rechner (oder mit Software-WebGL) sind das ca. 10–15 fps und es ruckelt. demo-reel injiziert stattdessen eine virtuelle Uhr (scripts/inject.js): requestAnimationFrame, performance.now und Date schreiten nur fort, wenn der Recorder es sagt, und CSS-/Web-Animationen werden über document.getAnimations() schrittweise ausgeführt. Jeder Frame wird um exakt 1/fps weitergeschaltet, als Screenshot erfasst und an ffmpeg übergeben. Egal wie lange das Rendern eines Frames dauert, das Ergebnis ist perfekt flüssig.

Timer (setTimeout/setInterval) bleiben bewusst echt — das Vortäuschen würde dazu führen, dass Timer-Ketten mit Nullverzögerung in Loadern und Schedulern ewig laufen (deswegen hängt Playwrights eigene page.clock auf vielen WebGL-Seiten).

Voraussetzungen

  • Node 18+, ffmpeg im PATH.
  • Einmalig, im Verzeichnis dieses Skills: npm install && npx playwright install chromium.

Arbeitsablauf

  1. Bereitstellen der App so, wie Nutzer sie sehen — idealerweise ein Produktions-Build (vite build && vite preview, next build && next start) gegen lokale oder Mock-Daten. Dev-Server funktionieren, sind aber langsamer und zeigen möglicherweise Dev-Overlays. Niemals echte oder sensible Daten aufnehmen — Demo-Daten einspielen.
  2. Schreiben eines Szenarios (*.scenario.mjs, siehe API unten). Beginne mit examples/tasks.scenario.mjs. Plane es als Geschichte: 20–60 s, eine Idee pro Untertitel, 1–2 s Halten nach jedem Ergebnis, damit Zuschauer es lesen können.
  3. Aufnehmen: node scripts/record.mjs my.scenario.mjs --out demo.mp4 Ein 60 s Clip in 1080p dauert grob 3–10 min (schwere WebGL-Seiten sind am langsamsten). Im Hintergrund laufen lassen.
  4. Qualitätssicherung: scripts/sheet.sh demo.mp4 schreibt ein Kontaktblatt (ein Frame alle 2,5 s). Prüfe: Untertitel passen zum Bildschirminhalt, keine Lade-Spinner, nichts vom Untertitel verdeckt, Badge/Cursor sichtbar. Stichprobenartig die Flüssigkeit prüfen, indem man ein paar aufeinanderfolgende Frames extrahiert — jeder sollte sich während einer Bewegung leicht unterscheiden.
  5. Ausliefern (siehe „Video verwenden").

Szenario-API

export default {
  size: "1920x1080",          // Viewport = Videogröße
  fps: 60,
  theme: { captionPosition: "bottom", haloFill: "rgba(250,204,21,.30)" }, // optional
  async run(d) {
    await d.open("http://localhost:4173/");     // Navigieren + abwarten (nicht aufgenommen)
    d.badge("NEU", "#15803d");                 // Ecken-Badge (null blendet aus)
    d.caption("Das neue Dashboard");            // Untertitel (null blendet aus)
    await d.hold(1500);                          // 1,5 s so aufnehmen
    await d.click(d.page.getByRole("button", { name: "Filter" }));
    d.caption("Filter leben in einem Panel");   // Untertitel NACH der Aktion setzen
    await d.hold(1200);
    await d.type("Stockholm");                  // Tippen in menschlicher Geschwindigkeit
    await d.press("Enter");
    await d.scroll(600);                        // Sanftes Scrollen mit dem Rad
    await d.step("optionaler Teil", async () => { /* Fehler werden protokolliert, Aufnahme läuft weiter */ });
  },
};
AufrufWirkung
d.open(url, { settle })Navigieren, Seite abwarten lassen (nicht aufgenommen, Standard 2500 ms).
d.hold(ms)Seite so aufnehmen, wie sie ist.
d.settle(ms)Zeit ohne Aufnahme fortschreiten lassen — Ladezustände, Lazy Chunks, Layout-Animationen überspringen, die du nicht zeigen willst.
d.move(target, { ms }) / d.hoverCursor (easing) zu einem Ziel gleiten lassen.
d.click(target, { ms, button })Hinbewegen, klicken mit Welle. button: "right" für Kontextmenüs.
d.doubleClick(target)Doppelklicken.
d.type(text, { delay })Zeichen für Zeichen tippen (Standard 90 ms/Zeichen).
d.press(key)Tastaturkürzel, z.B. "Escape", "Control+K".
d.scroll(dy, { ms })Sanftes Scrollen mit dem Rad.
d.caption(text) / d.badge(text, color) / d.cursor(bool)Overlay-Zustand; überlebt vollständige Seitennavigationen.
d.step(name, fn)Benannter Schritt; Fehler werden protokolliert, nicht fatal.
d.page, d.point(target), d.width, d.heightNotausstiege.

Ziele können ein Playwright-Locator, ein CSS-Selektor-String, { x, y } oder eine asynchrone Funktion (page) => ({ x, y }) sein.

Canvas-gerenderte UIs (Graphen, Karten, Diagramme) haben kein DOM-Element pro Element — verwende eine Funktion, die die Bibliothek nach Bildschirmkoordinaten fragt. Beispiel für 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("Server A"));

Gute Demos schreiben

  • Untertitel sagen die Wahrheit. Behaupten nur, was das Filmmaterial zeigt. Wenn ein Fehler, den du zeigen willst, im Take nicht reproduziert wird, ändere den Untertitel — behalte ihn nicht.
  • Untertitel nach der Aktion, nicht davor — sonst erscheint der Untertitel für den nächsten Bildschirm über dem vorherigen.
  • Zeige Ergebnisse, nicht Klicks. Halte 1–2 s auf jedem Ergebnis; schneide Ladezeiten mit d.settle heraus.
  • Vorher/Nachher: Nimm zwei Szenarien mit derselben Geschichte und denselben Schritten auf, kennzeichne sie mit Badge VORHER/NACHHER (rot/grün) und behalte sie als separate Dateien plus einer optionalen Verkettung.
  • Heldenmomente (eine Landing Page, eine Animation): Gib ihnen 5–10 s — Zuschauer brauchen einen Moment, um sie aufzunehmen.
  • Halte den Cursor ruhig: 600–900 ms pro Bewegung, parke ihn während des Haltens abseits des Inhalts.
  • Echte Timer laufen während des Renderns der Frames weiter (~50–300 ms Echtzeit pro Frame), daher können timer-gesteuerte UIs — Toasts, debounced Suche, automatisch ausblendende Tooltips — im Video schneller verschwinden als im echten Leben. Löse sie direkt vor dem gewünschten Bildschirm aus oder halte kürzer.

Video verwenden

  • PowerPoint: Einfügen → Video → Dieses Gerät. Registerkarte Wiedergabe: Start: Automatisch und Bis zum Anhalten wiederholen. H.264 yuv420p MP4 (was demo-reel schreibt) läuft auf Windows und macOS.
  • Keynote: Ziehen; Film → Wiederholung: Schleife.
  • 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
  • Verketten von Clips gleicher Größe/fps: printf "file 'a.mp4'\nfile 'b.mp4'\n" > l.txt && ffmpeg -f concat -safe 0 -i l.txt -c copy both.mp4

Fehlerbehebung

SymptomBehebung
Leere / „Lädt…"-FramesLängeres settle in d.open, oder await d.settle(1500) nach einem Navigationsklick.
Ziel nicht sichtbarEs ist außerhalb des Bildschirms oder in einem geschlossenen Menü — zuerst öffnen/scrollen; für Canvas-Elemente eine Punkt-Funktion verwenden.
WebGL/3D rendert schwarzDie Standard-Launch-Flags behalten (--use-angle=swiftshader); es ist langsam (~0,25 s/Frame), aber korrekt.
Video zu kurz/lang im Vergleich zum PlanDauern sind exakt: Summe aus hold + Bewegungen. settle-Zeit wird nie aufgenommen.
Farben in PowerPoint verwaschenNicht ohne -pix_fmt yuv420p neu kodieren; demo-reels Ausgabe ist bereits korrekt.
Text leicht unscharf--png erfasst verlustfreie Frames (langsamer, größer).

Verwandte Skills