Install any skill in seconds. Free to start, no credit card required.
Get Started Free →화면 녹화 도구 없이 웹사이트·웹앱의 제품 데모 영상(SNS용 1:1, 9:16, 16:9)을 만든다. 실제 사이트를 iframe에 띄워 진짜 마우스 이벤트로 조작하며 프레임을 캡처하고 ffmpeg로 합성한다.
.claude/skills/bam-bam-2-web-demo-video/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | 88% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 90% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 112% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 68% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 52% | 0% |
밤밤의 웹 프로젝트(모자이크, 겟백앱, 카공메이트 등) 소개 영상을 만들 때 쓰는 절차. 목업 이미지를 이어붙이는 게 아니라 실제 사이트를 실제로 조작한 화면을 찍는 게 핵심이다.
영상은 어디 쓰느냐로 비율·길이가 갈린다. 만들기 전에 반드시 확인한다.
| 용도 | 비율 | 길이 | |---|---|---| | 스레드·릴스·쇼츠 | 9:16 (1080x1920) | 15~25초 | | 블로그·유튜브 임베드·랜딩 | 16:9 (1920x1080) | 30~40초 | | 인스타 피드·카톡 공유 | 1:1 (1080x1080) | ~20초 |
밤밤은 시각 산출물의 형식에 구체적인 기대가 있다. 레퍼런스가 있으면 먼저 달라고 한다.
bashcd <프로젝트> && python3 -m http.server 8899
무대(stage) 페이지와 대상 사이트는 반드시 같은 오리진이어야 한다. 포트가 다르면 iframe.contentDocument 접근이 막혀서 조작이 불가능하다. → 무대 파일을 프로젝트 폴더 안에 임시로 두고, 작업이 끝나면 반드시 빼낸다(안 그러면 다음 배포에 딸려 올라간다).
assets/stage-template.html를 복사해서 고친다. 구조:
#stage — 출력 비율 그대로의 고정 크기 div (1:1이면 540x540, dpr 2로 캡처하면 1080x1080)#cap / #foot — 큰 자막(상단) / 보조 문구(하단)#win — 맥 브라우저 창 목업(신호등 3개 + URL) 안에 <iframe>을 transform: scale()로 축소해 넣음#cursor — 가짜 마우스 커서 SVG. 이게 있어야 조작 영상처럼 보인다#ring — 클릭·브러시 크기를 보여주는 하이라이트 원#end — 엔딩 카드(제품명 / 한줄 설명 / 셀링포인트 3개 / URL)window.__tick(frameIndex) — 프레임 번호 하나를 받아 그 시점의 화면 상태를 만드는 결정론적 함수__tick(frame) 안에서 t = frame / FPS로 시간을 계산하고, 모든 상태를 t로부터 다시 계산한다. 실시간 setTimeout을 쓰면 캡처 속도와 어긋나서 망가진다.
once(key, fn) 으로 중복 실행을 막는다drag(t, 시작초, 끝초, ptFn) — 시작 프레임에 mousedown, 매 프레임 mousemove, 끝나면 mouseupW.MouseEvent로 만들어 clientX/clientY(iframe 기준 좌표)로 dispatch한다.mousedown은 대상 요소에, mousemove/mouseup은 보통 window에 붙어 있으니 확인하고 각각 맞는 타깃에 쏜다
getBoundingClientRect() 또는 그려넣은 목업의 rect 비율로 계산한다. 창 크기를 바꿔도 안 깨진다iframe load 시점에 <style> 주입으로 숨긴다jsfor (let i = from; i <= to; i++) { await page.evaluate(f => __tick(f), i); const buf = await page.screenshot({ clip: { x:0, y:0, width:540, height:540 } }); await fs.writeFile(`${DIR}/f${String(i).padStart(4,'0')}.png`, buf); }
clip은 CSS 픽셀 단위다. devicePixelRatio를 곱하면 안 된다. dpr 2면 540 클립이 1080px 이미지로 나온다__tick(0) 후 이미지 로드가 끝날 시간을 준다ffmpeg는 PATH에 없다. export PATH="$PATH:/opt/homebrew/bin" 를 붙인다.
bashffmpeg -y -framerate 20 -i frames/f%04d.png \ -vf "scale=1080:1080:flags=lanczos,fps=30" \ -c:v libx264 -profile:v high -pix_fmt yuv420p -crf 20 -movflags +faststart out.mp4
-pix_fmt yuv420p는 SNS 호환 필수fps=10,scale=420:-1:flags=lanczos,palettegen=stats_mode=diff → paletteuse=dither=bayer:bayer_scale=3
select='eq(n\,20)+...',scale=340:340,tile=3x3lsof -ti:8899 | xargs kill -9SNS 영상과 목적이 다르다. 블로그 독자는 "이 도구가 뭘 하는지"를 3초 안에 봐야 하므로, 맥 브라우저 창 목업·엔딩 카드 없이 작업 대상만 크게 보여주는 편이 낫다. 무대(stage) 페이지도 로컬 서버도 필요 없다. 배포된 사이트를 직접 조작하고 캔버스를 그대로 뜬다.
js await page.evaluate(async (b64) => { const blob = await (await fetch('data:image/png;base64,'+b64)).blob(); const dt = new DataTransfer(); dt.items.add(new File([blob],'s.png',{type:'image/png'})); document.dispatchEvent(new ClipboardEvent('paste',{clipboardData:dt,bubbles:true})); }, b64);
adInCanvas 같은 광고 영역).찍혀서 세 번 다시 찍었다. 캔버스 픽셀을 직접 훑어 대상 글자의 bbox를 구한다. js const bbox = (x0,x1,y0,y1) => { /* 어두운 픽셀(r,g,b<110)의 min/max */ }; 노란 말풍선 같은 색 블록은 해당 색 범위로 같은 방식으로 찾는다.
page.screenshot({clip})은 스크롤/DPR 때문에좌표가 어긋나기 쉽다. 자막·커서·선택 박스·브러시 원도 이 합성 단계에서 그려 넣는다.
넣어야 눈이 따라간다.
차이가 안 보여서 뺐다. 블러는 디테일이 있는 대상에만 쓴다.
살짝 비치면 여백을 더 줘서 한 번 더 덮고 뒤쪽 정지 프레임만 다시 찍으면 된다.
사진 버튼 → 파일 선택으로 올린다 (GIF도 그대로 애니메이션된다)첨부 → 사진. 업로드하면 [##_Image|...|_##] 문법이 본문에 들어가므로,그걸 읽어서 전체 HTML을 조립한 뒤 setContent() 하면 이미지 위치를 원하는 곳에 둘 수 있다
<figure> + loading="lazy" + 캡션에 "예시 데이터는 전부 가짜"를 명시| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 28,972 | 9,664 | -67% | 1 | 1 | 0% | 5,238 | 3,012 | -42% | 0 | 0 | — |
case-02 | fail→fail | 17,964 | 32,518 | +81% | 1 | 1 | 0% | 2,980 | 6,518 | +119% | 0 | 0 | — |
case-03 | fail→fail | 23,996 | 23,913 | -0% | 1 | 1 | 0% | 4,152 | 7,486 | +80% | 0 | 0 | — |
case-04 | fail→pass | 13,059 | 7,651 | -41% | 1 | 1 | 0% | 2,008 | 3,770 | +88% | 0 | 0 | — |
case-05 | fail→pass | 13,090 | 8,548 | -35% | 1 | 1 | 0% | 1,983 | 3,761 | +90% | 0 | 0 | — |
case-06 | pass→pass | 12,755 | 9,048 | -29% | 1 | 1 | 0% | 2,115 | 3,667 | +73% | 0 | 0 | — |
case-07 | fail→pass | 9,625 | 4,232 | -56% | 1 | 1 | 0% | 1,483 | 3,146 | +112% | 0 | 0 | — |
case-08 | fail→pass | 14,607 | 4,635 | -68% | 1 | 1 | 0% | 2,009 | 3,378 | +68% | 0 | 0 | — |
case-09 | pass→pass | 14,865 | 7,196 | -52% | 1 | 1 | 0% | 2,427 | 3,591 | +48% | 0 | 0 | — |
case-10 | fail→pass | 15,458 | 7,046 | -54% | 1 | 1 | 0% | 2,276 | 3,454 | +52% | 0 | 0 | — |
case-11 | fail→pass | 10,067 | 10,144 | +1% | 1 | 1 | 0% | 1,636 | 3,855 | +136% | 0 | 0 | — |
case-12 | pass→pass | 6,970 | 5,941 | -15% | 1 | 1 | 0% | 1,234 | 3,342 | +171% | 0 | 0 | — |
case-13 | pass→pass | 15,444 | 8,010 | -48% | 1 | 1 | 0% | 2,272 | 3,555 | +56% | 0 | 0 | — |
case-14 | pass→pass | 13,669 | 11,263 | -18% | 1 | 1 | 0% | 2,086 | 4,298 | +106% | 0 | 0 | — |
case-15 | pass→pass | 11,263 | 8,271 | -27% | 1 | 1 | 0% | 1,777 | 3,842 | +116% | 0 | 0 | — |
case-16 | fail→pass | 13,215 | 12,301 | -7% | 1 | 1 | 0% | 1,576 | 4,320 | +174% | 0 | 0 | — |
case-17 | fail→pass | 13,816 | 13,807 | -0% | 1 | 1 | 0% | 2,488 | 5,108 | +105% | 0 | 0 | — |
case-18 | fail→fail | 15,079 | 7,212 | -52% | 1 | 1 | 0% | 2,424 | 3,593 | +48% | 0 | 0 | — |
case-19 | fail→pass | 15,019 | 12,491 | -17% | 1 | 1 | 0% | 2,457 | 4,364 | +78% | 0 | 0 | — |
case-20 | fail→pass | 13,551 | 11,730 | -13% | 1 | 1 | 0% | 1,860 | 4,375 | +135% | 0 | 0 | — |
case-21 | pass→pass | 26,989 | 12,105 | -55% | 1 | 1 | 0% | 2,719 | 4,729 | +74% | 0 | 0 | — |
case-22 | pass→pass | 15,041 | 15,401 | +2% | 1 | 1 | 0% | 2,938 | 5,295 | +80% | 0 | 0 | — |
case-23 | pass→pass | 20,506 | 18,235 | -11% | 1 | 1 | 0% | 3,937 | 6,095 | +55% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted, and 22 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +43 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.