Communitygithub.com

wiggle-that-diagram: 3가지 스타일의 설명 모션

정적인 '작동 방식' 일러스트레이션을 짧고, 자막이 있는 루핑 애니메이션으로 변환하여 HTML 데크나 보고서에 삽입하고, 세 가지 형식으로 제공하여 독자가 선택할 수 있도록 합니다.

wiggle-that-diagram: 3가지 스타일의 설명 모션란 무엇인가요?

이 스킬은 슬라이드 데크나 HTML 보고서 내에서 메커니즘 다이어그램(예: 파일 흐름, 규칙 검사, 승인 프로세스)을 단계별로 설명하는 애니메이션을 만들기 위한 전문 기술입니다. 사용자가 '너무 빨라서 읽을 수 없다'고 느끼는 애니메이션에 자막을 추가하거나 속도를 늦추는 문제를 해결하기 위해 개발되었으며, 세 가지 고유한 형식(A: huashu 엔진 기반 다이어그램 클립, B: 화이트보드 스타일, C: GSAP 가상 카메라가 있는 라이브 SVG)으로 동일한 콘텐츠를 제공합니다. 이 스킬은 Claude Code와 같은 AI 에이전트가 사용하도록 설계되었으며, 캡션 스크립트 작성부터 스타일별 렌더링, 최종 임베드 테스트까지 전체 워크플로우를 안내합니다. 각 스타일의 애니메이션은 하나의 공유 `beats.json` 파일에서 캡션과 타이밍을 가져와 일관된 스토리텔링을 보장합니다. 최종 결과물은 iframe srcdoc에 바로 삽입 가능한 단일 HTML 파일이며, 슬라이드 전환에 따라 재생/일시 정지가 제어되고, 다양한 화면 크기에 적응하며, 접근성을 고려한 '움직임 줄이기' 모드를 지원합니다.

지원 대상✓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

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

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

문서

wiggle-that-diagram: 3가지 스타일의 설명 모션은(는) 무엇을 하나요?

정적인 '작동 방식' 일러스트레이션을 짧고, 자막이 있는 루핑 애니메이션으로 변환하여 HTML 데크나 보고서에 삽입하고, 세 가지 형식으로 제공하여 독자가 선택할 수 있도록 합니다. 이 방법은 실제 고객 데크를 통해 개발되었습니다(참고: "실제 예시"). huashu-art-motion을 방향 설정(샷 목록, 카메라 규칙, 실패하는 6가지 방법)과 스타일 A 및 B의 렌더링 엔진으로 사용합니다.

사용 시기와 사용하지 말아야 할 시기

다음과 같은 경우에 사용하세요:

  • 데크나 HTML 보고서에 메커니즘 다이어그램(파일 흐름, 규칙 검사, 승인)이 있고, 독자가 단계별로 실제로 작동하는 모습을 읽을 수 있는 텍스트와 함께 봐야 하는 경우
  • 사용자가 단순히 세 가지 색상 구성표가 아닌, 진정으로 다른 형식의 옵션을 원하는 경우
  • 이전 애니메이션이 "너무 빨라서 읽을 수 없었거나" 자막이 필요한 경우

다음과 같은 경우에는 사용하지 마세요: 단일 마이크로 인터랙션(animate 사용), 내레이션이 포함된 비디오(huashu-art-motion 직접 사용), 숫자 차트(dataviz 사용).

세 가지 스타일 (팔레트가 아닌 형식에서 차이가 있습니다)

A · 다이어그램 클립B · 화이트보드C · 라이브 SVG
그림huashu 엔진 캔버스 장면, t1_3b1b 문법, 텍스트 없는 H.264로 렌더링huashu y3_whiteboard 문법, 어두운 보드, 펜이 그리고 손글씨(Kalam), 정사각형 비디오가상 카메라(viewBox가 GSAP 3.12.5로 트위닝됨)로 촬영된 인라인 SVG 월드
그림 위의 텍스트덤프된 카메라 트랙에서 비디오 위에 배치된 HTML 레이블(항상 선명함)비디오에 그려짐(휴대폰에서도 읽을 수 있을 정도로 커야 함)실제 SVG 텍스트(선명하며, 카메라 프레임을 너비에 따라 변경하여 리플로우됨)
영화당 크기(실제 예시)360–820KB (예산 900KB)847–886KB (예산 900KB)21–34KB (예산 40KB)
느낌깔끔한 3Blue1Brown 스타일 다이어그램화이트보드에서 설명하는 누군가정교한 제품 애니메이션
주요 위험 요소장면이 재렌더링 없이 변경되면 레이블이 어긋남빡빡한 크기 예산; 휴대폰 레이블 12–14px까다로운 카메라 수학; GSAP 변환 문제

세 가지 모두 하나의 캡션 스크립트, 동일한 비트(beat), 동일한 임베드 계약을 공유하므로, 스타일 전환기를 사용하면 동일한 이야기가 세 가지 방식으로 전달되는 것을 보여줄 수 있습니다.

방법, 처음부터 끝까지

각 단계는 완료되었음을 증명하는 검사(check)로 끝납니다.

  1. 메커니즘과 구체적인 예시 하나를 찾으세요. 호스트 페이지를 읽습니다(읽기 전용). 표시할 순간과 모든 비트(beat)에 걸쳐 연결될 하나의 실제 예시(한 행, 한 레코드, 한 사례, 실제 값 포함)를 선택하세요. 발명해서는 안 되는 것(id, 수치, 이미지)을 목록으로 만드세요. → 확인: 각 비트(beat)를 "이 객체가 이것을 한다"라고 명사와 동사로 말할 수 있어야 합니다.

  2. 캡션 스크립트를 먼저 작성하세요. 영화당 비트(beat)당 캡션 문장 하나씩, 3–6비트, beats.json 파일에 작성합니다(형식: scripts/common/beats.example.json). 캡션은 모든 스타일에서 동일합니다. 그런 다음 페이싱 게이트(pacing gate)를 실행하고 출력된 기준을 읽습니다:

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

    → 확인: 모든 비트(beat)가 적절하고, 사용자가 결과 루프 길이(또는 더 짧은 캡션)에 동의했습니다. 캡션과 별도로 루프 대상을 설정하지 마십시오. "페이싱"을 참조하세요.

  3. 브리프(brief)를 작성하세요. references/brief-template.md를 SPEC.md로 채웁니다: 인용된 피드백, 토큰, 페이싱 규칙, 캡션 스크립트, 비트(beat), 그대로 포함된 임베드 계약, 예산, 결과물. → 확인: 다른 맥락이 없는 에이전트도 이 정보만으로 빌드할 수 있어야 합니다.

  4. 방향 설정 라운드(선택 사항, 사용자가 스타일을 보지 못한 경우). 세 에이전트가 병렬로, 각각 한 스타일을 담당하며, 동일한 영화의 한 순간(~8–12초)을 프로토타이핑하고 1280px 및 390px에서 스틸 이미지를 촬영합니다. 스틸 이미지를 나란히 보여주고, 사용자가 하나를 선택하거나 세 개를 모두 유지합니다. → 확인: 사용자의 선택이 기록에 남습니다.

  5. 빌드 라운드. 스타일당 하나의 에이전트(각자 자신의 프로토타입을 이어받음), 해당 스타일의 모든 영화를 <work>/<style>/final/ 아래에만 작성합니다. 브리프: references/agent-briefs.md. 실행하기 전에 에이전트 수와 티어를 사용자에게 알리세요. → 확인: 각각 파일, 스틸 이미지, NOTES.md 및 정직한 미해결 문제 목록을 반환합니다.

  6. 빌드 레시피(각 스타일 에이전트가 실제로 한 일; 스크립트는 scripts/style-a|b|c/에 있습니다): 두 개의 중립적인 영화가 세 가지 스타일 모두로 제공되며, 각각 beats.json과 style-a/, style-b/, style-c/ 폴더로 구성됩니다: examples/pizza/(5비트, 단순함) 및 examples/expenses/(6비트: 카드, 색상별 이유, 두 번째 및 세 번째 패널로의 화살표). 해당 스타일의 폴더에서 시작하세요.

    • A. cp -R ~/.claude/skills/huashu-art-motion/scripts/engine <A>/engine 및 touch <A>/engine/favicon.ico. engine/ways/way_make.js 추가(scripts/style-a/에서), beats_js.py beats.json --engine engine --var <VAR> 실행(캡션 및 타이밍은 공유 스크립트에서 가져오며, 수동 복사하지 않음), WAY.make({poster, beats, camBase, cam, labels})를 호출하고 SCENES['<film>']를 등록하는 장면 파일 engine/ways/<film>.js, 및 FILM_SIZE=[1280,720], PUNCH=0(그렇지 않으면 엔진이 전환에 0.03 확대 효과를 추가함), SCENE_LIBS=['ways/way_make.js','ways/<film>_beats.js','ways/<film>.js'], SCENE_DIR='ways', ERAS=[{id:'<film>',dur:1}]를 설정하는 engine/eras_<film>.js. 캔버스에 텍스트를 그리지 마십시오. 그리기 도우미(둥근 상자, 점, 선, 페이드, 경로를 따른 이동)는 영화별로 작성됩니다. examples/pizza/style-a/pizza.js에 복사할 수 있는 작은 세트가 있습니다. 그런 다음 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> (또는 한 번에: make.sh). 장면을 편집한 후에는 다시 렌더링하고 중간 이동 시점에 midmove.py를 실행하세요. 레이블이 해당 객체 위에 있어야 합니다.
    • B. 동일한 방법으로 엔진을 복사하고(빈 favicon.ico 사용: 실제 Chrome에서는 404가 페이지 오류로 간주되어 render.py가 좋은 비디오를 작성한 후 종료 코드 1로 종료됨), render.py를 chromium.launch(channel='chrome', …)로 패치하세요. kit.js를 engine/demos/_hb/에 넣고, engine/demos/<film>/board.js 작성(HB.make() 사용, BEATS[k].t0 + offset에 이벤트 연결) 및 engine/demos/<film>/eras.js 작성(FILM_SIZE=[1080,1080], PUNCH=0, Kalam 글꼴, SCENE_LIBS=['demos/<film>/beats.js','demos/_hb/kit.js','demos/<film>/board.js']). 그런 다음 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², 24fps, 맞는 가장 낮은 CRF) → test_contract.py final/<film>.html --beats beats.json. 각 영화는 전체 보드로의 후퇴, ≥ 1초 유지, 그 다음 지우개로 지우기로 끝납니다. 빈 보드는 루프가 다시 시작되는 곳입니다. beats.json의 마지막 비트(beat)의 still은 모든 스타일에서 공유되며 지우개 시작 전에 도달해야 합니다. 그렇지 않으면 포스터와 축소 모션 프레임이 반쯤 지워진 보드를 보여줍니다. 움직이는 객체(스쿠터, 상자)는 handed: false인 K.custom 항목이며 위치는 시간의 함수입니다. 각 객체에 종료 시간을 지정하세요. 그렇지 않으면 작업이 완료된 후에도 보드에 남아 있습니다.
    • C. examples/pizza/style-c/에서 시작합니다(SVG, 타임라인, 테마). src/<film>.svg 작성(하나의 <svg id="scene" class="scene">는 <g id="world">를 포함하며, 모든 <text> 또는 해당 그룹에 data-b="<beat>" 태그를 지정하고, 1부터 시작하여 카메라가 처음 표시하는 비트(beat)로 설정합니다) 및 src/<film>.js 작성(카메라 프레임 FR / ORDER, 각 프레임은 와이드 스크린용 w rect와 좁은 스크린용 t rect를 가짐; R(selector, beat, offset)로 공개, camTo(i, beat, offset)로 이동, draw()로 선 그리기). JS에서 beat는 0부터 시작합니다(B[0]은 비트 1). R()은 오프셋 ≥ 2.5s에서만 단어에 사용됩니다. 모든 R() 호출은 단어 공개로 기록되며 캡션은 오프셋 0에서 단어로 계산되므로, 단어 없는 모양이나 오프셋 0에서의 R()은 간격 검사에 실패합니다. 캡션과 함께 도착하는 모양, 움직임 및 단어는 일반 tl.to(…, T(beat, offset))를 사용합니다. 자체 transform이 있는 모든 요소는 상위 <g>로 감싸고 하위 요소를 애니메이션 처리하세요. 그런 다음 build.py beats.json --src src --out final [--css theme.css] → test_contract.py final/<film>.html --stills <scratch>/stills → widthcheck.py. 런타임(shell.html, shell.css, head.js, tail.js)은 그대로 재사용됩니다. 호스트 페이지와 일치시키려면 :root 토큰(--panel, --ink, --muted, --line, --gold, --cap-ink, --on-gold, …)을 테마 파일에 넣고 --css로 전달하세요. 영화의 README 또는 채팅 사진용: gif.py final/<film>.html --beat 4 --from 1.9 --to 8.6 --out film.gif (또는 PNG용 --still <s>).
  7. 다른 사람에게 보여주기 전에 확인하세요(세 가지 빌드 모두 마지막 세 단계를 건너뛰었습니다. 지금 수행하세요):

    • 페이싱 게이트 통과; 계약 테스트가 실제 Chrome에서 1100px 및 360px에서 모두 통과; 크기가 예산 미만;
    • 두 너비에서 모든 비트(beat)의 연락처 시트를 확인합니다(scripts/common/contact_sheet.py);
    • A/B mp4에 대해 huashu film_gate.py 실행(필요시 ffprobe shim 사용), traps.md에 설명된 대로 빨간색 읽기;
    • 빌드하지 않은 독립적인 검토자가 각 루프를 정상 속도로 시청하고 소견을 작성합니다;
    • 실제 호스트 페이지(references/embed-contract.md의 상위 측), 실제 휴대폰, Safari에서 테스트합니다(스타일 A/B 폴백: H.264를 재생할 수 없을 때 포스터 + 번호가 매겨진 캡션).
  8. 인계. 세 가지 스타일을 나란히(스틸 이미지 또는 전환기 페이지) 루프 길이, 크기, 그림에 발명된 내용(사용자 확인용), 확인되지 않은 내용과 함께 보여줍니다.

호스트 측: 전환기가 있는 하나의 슬라이드에 9개 모두 (scripts/deck/)

실제 데크가 한 일과 그 과정에서 발생한 문제:

  1. 링크가 아닌 임베드. 각 영화는 <script type="text/plain" id="anim-{style}-{way}">로 데크에 들어가며 </script → \</script 및 <!-- → \<!--로 이스케이프됩니다. JS는 슬라이드가 표시될 때만(주문형으로 빌드된 하나의 iframe) 이를 iframe.srcdoc으로 디코딩합니다. embed_anims.py는 <!--ANIM-DATA-START--> / <!--ANIM-DATA-END--> 마커 사이에서 이 작업을 멱등적으로 수행하므로, 재빌드 후 다시 실행하면 데이터만 교체됩니다. 데크 크기는 ~0.3MB에서 4.5MB로 증가했습니다.
  2. 하나의 슬라이드, 두 개의 분할 버튼 그룹(Way, Style; aria-pressed), 각 Way의 세부 패널 내부에 있는 "작동 보기" 링크(way를 설정하고 패널을 닫은 후 슬라이드로 이동).
  3. 재생/일시 정지는 슬라이드를 따릅니다: go()는 deck:slide 이벤트를 디스패치합니다. 전환기는 슬라이드가 현재 슬라이드가 될 때 anim-play를 게시하고(현재 슬라이드인 경우 iframe 로드 시에도), 그렇지 않으면 anim-pause를 게시합니다.
  4. iframe 위의 제스처: 데크의 휠/키/스와이프 핸들러는 onWheel(dy,mode), onKey(key), onSwipe(dy,dx)로 리팩토링되었으며, 데크 자체 리스너와 iframe의 전달된 메시지 모두에서 호출됩니다(e.source === frame.contentWindow만 허용). howtest2.py는 하나의 제스처 = 하나의 슬라이드를 증명합니다.
  5. 하나의 노트북 화면에 맞추기: 먼저 내부 레이아웃을 좁히고, 마지막으로 확대/축소합니다. 1280×800에서 영화는 전체 너비에서 740–840px 높이였으므로 캡션이 화면 아래로 떨어졌습니다. 작동하는 방법(embed_anims.py의 howFit): srcdoc iframe은 동일 출처이므로 데크가 contentDocument.body 높이를 직접 측정할 수 있습니다. 상자 너비에서 740px까지 내부 너비를 시도하고(이진 검색, 영화가 크기 조정 시 JS에서 레이아웃을 설정할 수 있으므로 너비당 ~90ms 대기, 그동안 프레임 숨김), 맞는 가장 넓은 너비를 유지합니다. 740px도 너무 높은 경우에만 frame.style.zoom을 설정합니다. 좁히기가 확대/축소보다 나은 이유는 캡션 크기를 유지하면서 그림이 줄어들기 때문입니다. 그 과정에서 만난 함정: (a) width:100%로 확대/축소하면 내부 레이아웃이 넓어지고(1126 → 1876px) 바닥까지 반복되므로 항상 frame.style.width를 px로 고정합니다. (b) documentElement.scrollHeight는 iframe의 현재 높이보다 작게 보고되지 않으므로 축소를 유도할 수 없습니다(대신 body 측정, 프레임을 충분히 높게 만든 상태에서). (c) 너비 변경과 동일한 틱에서 측정하면 오래된 JS 레이아웃을 읽습니다. 넓을 때 나란히 배치(정사각형 그림)하고 780px 너비에서 총 높이 ≤ 580px을 영화에 요청하세요. 휴대폰(<900px)은 맞지 않습니다. 슬라이드는 컴팩트 헤더와 함께 스크롤됩니다.
  6. 테스트: howtest.py(전환, 딥 링크, 화살표), howtest2.py(9개 모두 실제 Chrome에서 로드 및 재생, iframe 위의 휠/키/스와이프가 정확히 하나의 슬라이드 이동), 데크 자체의 one-gesture-one-slide 검사. 슬라이드 번호를 하드코딩하는 데크 테스트는 슬라이드가 삽입될 때 깨집니다. ID로 슬라이드를 찾으세요.

페이싱 (첫 번째 컷에 대한 사용자의 주요 불만)

첫 번째 프로토타입은 8–13초였습니다. 독자의 평가: 무슨 일이 일어나고 있는지 읽기에는 너무 빠르며 자막이 필요했습니다. 재구축에 사용된 규칙:

  • 각 비트(beat)는 최소 max(4.5초, (캡션 단어 수 + 새 그림 단어 수) / 3 + 1.5초) 동안 지속됩니다. 표준 계산은 scripts/common/pace_check.py(및 스타일 C의 build.py, SVG에서 동일한 방식으로 계산)입니다. 스타일 A 자체의 pace_check.py는 빌드된 영화의 실제 레이블 시간을 확인하고 한 가지 차이점이 있습니다. 이후 비트(beat)에서 새 레이블로 반복된 단어는 다시 계산됩니다.
  • 새로운 것은 이전 단어가 나온 후 ≥ 2.5초 후에만 나타납니다. 카메라는 단어를 읽는 동안 정지 상태를 유지합니다.
  • 비트(beat) 사이의 느린 카메라 이동(1.4–2.0초, 사인 이징), 영화당 최대 하나의 빠른 악센트, 판결 시점에.
  • 루프 전 마지막 프레임에서 ≥ 1초 유지.

사용자에게 미리 알려야 할 결과: 비트(beat)당 ~20단어의 캡션은 5–6비트 영화를 40–80초로 만듭니다. 실제 예시에서 브리프의 18–34초 목표는 불가능했습니다(캡션만으로 31.5–51.7초 필요). 세 에이전트 모두 읽기 규칙을 선택하고 35–82초 루프를 구축했으며, 이는 사용자와 확인되지 않았습니다. 더 짧은 루프는 더 짧은 캡션을 의미하며, beats.json에서 한 번 변경하면 모든 스타일에 적용됩니다.

임베드 계약, 크기 및 휴대폰 (요약; 전체 텍스트는 references/embed-contract.md 참조)

  • 영화당 하나의 자체 포함 HTML 파일, 호스트가 <iframe srcdoc>에 로드합니다. 그림 + 캡션 스트립 + 단계 레일 + 재생/일시 정지, 그 외에는 없습니다. 미디어는 base64 인라인입니다.

관련 스킬