[effort: high]
볼 베어링 2단계: 그림을 움직이게 한다(설계서 §7). 1·1b단계는 정지 장면만 그렸다.

먼저: out/ball-bearing/design.prev.md에 지금 판을 남겨 두었다. cp /tmp/claude-1000/-home-namenu-src-github-com-namenu-isowiki/c0557bfd-7c20-45bf-a3c9-ce3c1f39ab97/scratchpad/design-ball-bearing.md out/ball-bearing/design.md 하고 diff로 바뀐 곳(§11-4 비율 목표 16.5%, 카드 460 — 네가 이미 한 것)을 본다. 본보기: pages/differential/figure/live.js. docs/knowhow.md "휴대폰"(그립은 HTML 요소로 — Chromium은 SVG 자식의 touch-action을 무시한다), docs/delegation.md "검증은 조정자가"(헤드리스 터치로 스크롤을 검증하지 말 것). 이 저장소 밖 파일(특히 ~/.claude/)은 읽지 않는다. 브라우저 시험은 drive.mjs.
Target: pages/ball-bearing/figure/live.js와, 상태를 받아 다시 그리는 데 필요한 만큼 scene.mjs·index.mjs. model.mjs의 물리 값은 바꾸지 않는다.
Change (§7):
- 상태 kind(ball/plain), rpm, 각(축·케이지·볼 자전). 매 프레임 rpm을 M.torque(kind)/M.INERTIA로 감속(dω/dt 일정), 각은 실제의 1/10로 그린다: 축 각 += ω·dt/10, 케이지 각 = 축 각 × M.CAGE, 볼 자전 각 = 축 각 × M.BALL_SPIN(볼 위 점, 보이는 쪽에 있을 때만). 미끄럼 모드에서는 부시 안에서 축만 돈다.
- 판독값: "볼 · 287 rpm · 12분 뒤 멈춤" / "미끄럼 · 210 rpm · 8초 뒤 멈춤" / "볼 · 멈춤". 남은 시간은 M.stopTime(kind, rpm), 60 s 넘으면 분, 아니면 초.
- 버튼: "돌리기" = rpm 300(0.4 s에 걸쳐), "멈추기" = 1 s에 0(손으로 잡아 세움), 베어링 버튼: rpm > 0이면 먼저 멈추기 후 바꾼다. aria-pressed 동기화.
- 끌기: 플라이휠 테두리 위 HTML 투명 그립(data-grip, 화면 44 px 이상, touch-action: none). 끈 속도(화면 접선 방향 px/s)를 rpm으로(최대 300). 무대는 touch-action: pan-y. 마우스는 무대 어디서나 플라이휠을 끌 수 있게.
- 투어(손대지 않으면): 볼 돌리기 → 6 s 보기 → 멈추기 → 미끄럼 → 돌리기 → 멈출 때까지(약 12 s) → 볼 … 조작하면 멈추고 8 s 뒤 재개. 화면 밖 정지, reduced motion이면 투어 없음·즉시 반영.
Constraints: 기하·그리는 순서(1b 결과)는 바꾸지 않는다. 설계서와 다르게 해야 하면 ask. 커밋 금지.
Acceptance: preview·check(두 테마 line ends 0)·build. drive.mjs로 (1) 볼 돌리기 후 2 s·5 s의 rpm(거의 그대로 — 볼은 13분 걸림), (2) 미끄럼 돌리기 후 2 s·6 s의 rpm(300 → 250 → 150 근처, 12 s에 0), 각 장면 스크린샷·판독값·lines 0. 케이지가 축보다 느리게(0.382배) 도는지 각도 두 시점으로 수치 확인. 그립: 마우스 drag로 rpm이 오르는지, computed touch-action(그립 none, 무대 pan-y), 390 px에서 그립 크기. worker_done: 바꾼 파일(git status), 장면별 표, report 갱신(--report-path).
