Communitygithub.com

demo-reel

스크립트를 통해 웹 앱의 부드러운 60fps 데모 비디오를 제작합니다. 커서 하이라이트, 클릭 웨이브, 캡션, 모서리 배지를 추가하고, 가상 시계를 사용하여 끊김 없는 MP4를 생성합니다. 파워포인트, 키노트, 웹에서 매끄럽게 재생됩니다.

demo-reel란 무엇인가요?

demo-reel은 실제 웹 애플리케이션을 헤드리스 Chromium에서 구동하며, 마치 실제 화면 녹화와 같은 고품질 데모 비디오를 만들어내는 기술 스킬입니다. 이 도구는 일반 화면 녹화가 겪는 성능 저하 문제를 해결하기 위해 가상 시계를 주입하여 프레임 재생 속도를 정확히 제어합니다. `requestAnimationFrame`, `performance.now`, `Date`와 같은 시간 관련 API를 조작하여 CSS 및 웹 애니메이션을 각 프레임에 맞춰 강제로 진전시키고, 최종적으로 끊김 없는 60fps 비디오를 생성합니다. `setTimeout`/`setInterval`은 실제 시간을 유지하여 로더와 스케줄러가 멈추지 않도록 합니다. 스킬은 Playwright 기반으로 작동하며, 개발자는 `.scenario.mjs` 파일을 작성하여 내비게이션, 클릭, 타이핑, 스크롤 등 복잡한 사용자 시나리오를 스크립트화할 수 있습니다. 캡션, 배지, 커서 하이라이트 및 클릭 리플을 오버레이로 추가하여 시청자의 이해를 돕습니다. 최종 출력은 H.264 `yuv420p` MP4 파일로, 파워포인트, 키노트, 웹 페이지에서 루프 재생이 가능합니다.

지원 대상~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/hasuwini77/demo-reel/tree/main/skills/demo-reel

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

미리보기

스킬 README에서

문서

demo-reel은(는) 무엇을 하나요?

웹 앱을 스크립트로 워크스루하여 실제 화면 녹화처럼 보이는 비디오로 만듭니다: 부드러운 60fps 모션, 하이라이트가 있는 가시적 커서, 클릭 웨이브, 캡션. 이 도구는 실제 앱을 헤드리스 Chromium에서 구동합니다 — 푸티지는 제품 자체이며 모형이 아닙니다.

비디오가 부드러운 이유

일반 화면 녹화는 브라우저가 페인트를 할 때마다 프레임을 캡처합니다. 느린 머신(또는 소프트웨어 WebGL)에서는 약 10–15fps이고 끊깁니다. 대신 demo-reel은 가상 시계(scripts/inject.js)를 주입합니다: requestAnimationFrame, performance.now 및 Date는 리코더가 지정할 때만 진행되며, CSS/웹 애니메이션은 document.getAnimations()를 통해 단계적으로 진행됩니다. 각 프레임은 정확히 1/fps만큼 진행되고 스크린샷으로 캡처된 후 ffmpeg로 전달됩니다. 프레임 렌더링에 시간이 얼마나 걸리든 결과는 완벽하게 부드럽게 재생됩니다.

타이머(setTimeout/setInterval)는 의도적으로 실제 시간을 유지합니다. 이를 가짜로 만들면 로더와 스케줄러의 제로-딜레이 타이머 체인이 영원히 회전합니다(이것이 Playwright 자체 page.clock이 많은 WebGL 페이지에서 멈추는 이유입니다).

요구 사항

  • Node 18+, ffmpeg가 PATH에 있어야 함.
  • 이 스킬 디렉토리에서 한 번 실행: npm install && npx playwright install chromium

워크플로우

  1. 앱을 제공합니다 사용자가 보는 방식으로 — 이상적으로는 프로덕션 빌드(vite build && vite preview, next build && next start)를 로컬 또는 모의 데이터와 함께 사용합니다. 개발 서버도 작동하지만 느리고 개발 오버레이가 표시될 수 있습니다. 실제 또는 민감한 데이터를 기록하지 마십시오 — 데모 데이터를 시드하세요.
  2. 시나리오를 작성합니다 (*.scenario.mjs, 아래 API 참조). examples/tasks.scenario.mjs에서 시작하세요. 이야기로 계획하세요: 20–60초, 캡션당 하나의 아이디어, 각 결과 후 1–2초 머뭄으로 시청자가 읽을 수 있게 합니다.
  3. 녹화: node scripts/record.mjs my.scenario.mjs --out demo.mp4 1080p에서 60초 클립은 대략 3–10분이 걸립니다(무거운 WebGL 페이지가 가장 느림). 백그라운드에서 실행하세요.
  4. QA: scripts/sheet.sh demo.mp4는 접촉 시트(2.5초마다 한 프레임)를 작성합니다. 확인하세요: 캡션이 화면의 내용과 일치하는지, 로딩 스피너가 없는지, 캡션에 가려진 부분이 없는지, 배지/커서가 보이는지. 연속된 프레임 몇 개를 추출하여 부드러움을 점검하세요 — 움직임 중에는 각 프레임이 약간씩 달라야 합니다.
  5. 전달 ("비디오 사용" 참조).

시나리오 API

export default {
  size: "1920x1080",          // 뷰포트 = 비디오 크기
  fps: 60,
  theme: { captionPosition: "bottom", haloFill: "rgba(250,204,21,.30)" }, // 선택 사항
  async run(d) {
    await d.open("http://localhost:4173/");     // 탐색 + 안정화 (기록되지 않음)
    d.badge("NEW", "#15803d");                  // 모서리 배지 (null이면 숨김)
    d.caption("새로운 대시보드");                 // 캡션 필 (null이면 숨김)
    await d.hold(1500);                          // 1.5초 그대로 기록
    await d.click(d.page.getByRole("button", { name: "Filters" }));
    d.caption("필터는 하나의 패널에 있습니다");     // 작업 후에 캡션 설정
    await d.hold(1200);
    await d.type("Stockholm");                  // 사람 속도로 타이핑
    await d.press("Enter");
    await d.scroll(600);                        // 부드러운 휠 스크롤
    await d.step("선택적 부분", async () => { /* 실패는 기록, 녹화는 계속 */ });
  },
};
호출기능
d.open(url, { settle })탐색, 페이지 안정화 (기록되지 않음, 기본값 2500ms)
d.hold(ms)현재 페이지를 그대로 기록
d.settle(ms)시간을 기록 없이 진행 — 로딩 상태, 지연 청크, 보여주고 싶지 않은 레이아웃 애니메이션 건너뛰기
d.move(target, { ms }) / d.hover커서를 대상으로 (이즈 적용되어) 이동
d.click(target, { ms, button })이동, 웨이브와 함께 클릭. button: "right"는 컨텍스트 메뉴용
d.doubleClick(target)더블 클릭
d.type(text, { delay })한 번에 한 키씩 타이핑 (기본값 90ms/키)
d.press(key)키보드 단축키, 예: "Escape", "Control+K"
d.scroll(dy, { ms })부드러운 휠 스크롤
d.caption(text) / d.badge(text, color) / d.cursor(bool)오버레이 상태; 전체 페이지 탐색 후에도 유지
d.step(name, fn)이름이 있는 단계; 오류는 기록되지만 치명적이지 않음
d.page, d.point(target), d.width, d.height이스케이프 해치

대상은 Playwright Locator, CSS 선택자 문자열, { x, y }, 또는 비동기 함수 (page) => ({ x, y })일 수 있습니다.

Canvas 렌더링 UI(그래프, 지도, 차트)는 항목별 DOM 요소가 없습니다 — 라이브러리에 화면 좌표를 요청하는 함수를 전달하세요. 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"));

좋은 데모 작성하기

  • 캡션은 진실을 말해야 합니다. 푸티지가 보여주는 것만 주장하세요. 보여주고 싶은 버그가 촬영본에서 재현되지 않으면 캡션을 변경하세요 — 유지하지 마세요.
  • 작업 후에 캡션을 설정하고, 전에 하지 마세요 — 그렇지 않으면 다음 화면용 캡션이 이전 화면 위에 나타납니다.
  • 결과를 보여주고 클릭을 보여주지 마세요. 각 결과에서 1–2초 머물고, d.settle로 로딩을 잘라내세요.
  • Before/After: 동일한 이야기와 단계로 두 시나리오를 기록하고, 배지 BEFORE/AFTER(빨강/초록)를 사용하며, 별도 파일과 선택적 연결본을 유지하세요.
  • 히어로 순간(랜딩 페이지, 애니메이션): 5–10초를 주세요 — 시청자가 받아들일 시간이 필요합니다.
  • 커서를 조용히 유지: 이동당 600–900ms, 머무는 동안 콘텐츠에서 멀리 두세요.
  • 실제 타이머는 프레임 렌더링 중(~50–300ms 실시간/프레임) 계속 실행되므로, 타이머 기반 UI(토스트, 디바운스된 검색, 자동 사라짐 툴팁)는 실제보다 비디오에서 더 빨리 사라질 수 있습니다. 화면에 표시되기 직전에 트리거하거나 덜 머무세요.

비디오 사용

  • 파워포인트: 삽입 → 비디오 → 이 장치. 재생 탭: 시작: 자동 및 중지될 때까지 반복. H.264 yuv420p MP4(demo-reel이 작성하는 형식)는 Windows와 macOS에서 재생됩니다.
  • 키노트: 드래그 앤 드롭; 동영상 → 반복: 반복.
  • 웹: <video src="demo.mp4" autoplay loop muted playsinline></video>.
  • GIF(README, 채팅): ffmpeg -i demo.mp4 -vf "fps=20,scale=960:-1:flags=lanczos,split[a][b];[a]palettegen[p];[b][p]paletteuse" demo.gif
  • 같은 크기/fps의 클립 연결: printf "file 'a.mp4'\nfile 'b.mp4'\n" > l.txt && ffmpeg -f concat -safe 0 -i l.txt -c copy both.mp4

문제 해결

증상해결 방법
빈 / "로딩 중…" 프레임d.open에서 더 긴 settle, 또는 탐색 클릭 후 await d.settle(1500)
대상이 보이지 않음화면 밖이거나 닫힌 메뉴 안에 있음 — 먼저 열거나 스크롤; 캔버스 항목은 포인트 함수 사용
WebGL/3D가 검게 렌더링됨기본 실행 플래그 유지(--use-angle=swiftshader); 느리지만(~0.25s/프레임) 올바름
비디오가 계획보다 너무 짧거나 긺지속 시간은 정확함: hold + 이동의 합계. settle 시간은 기록되지 않음
파워포인트에서 색상이 바래 보임-pix_fmt yuv420p 없이 재인코딩하지 마세요; demo-reel의 출력은 이미 올바름
텍스트가 약간 흐림--png는 무손실 프레임을 캡처(느리지만 더 큼)

관련 스킬