# AI 꾸러미 — 마스크 와이프 · 원 조리개 — Wipe transition
> 다음 화면을 비스듬한 띠 · 커지는 원 모양으로 잘라(clip) 그 안에만 그려, 띠가 쓸고 원이 열리며 화면이 바뀌게 한다.  
> 견본: https://ai-techstudio.web.app/#t/i99

이 문서를 코드 도우미(Claude Code · Cursor · ChatGPT 등)에 그대로 주면 돼요. 「## 주문서」 가 할 일, 나머지는 참고 자료예요.

## 주문서

### 만들어 줘: 마스크 와이프 · 원 조리개 — Wipe transition

#### 1. 목표
문제 → 정답 화면 바꾸기에 마스크 와이프 전환을 넣어 줘 — 비스듬한 띠가 쓸며 새 화면이 드러나고, 원 조리개가 열리며 돌아오게. 분위기는 밝은 만화 (흰 테두리 선).

#### 2. 핵심 기술 용어
- **Wipe transition** — 쓸고 지나가며 바뀌는 장면 전환
- **Iris transition (circle reveal)** — 원 조리개가 열리며 드러남
- **Canvas clip() / CSS clip-path** — 그릴 영역을 모양으로 자르기

#### 3. 환경
- 플랫폼: HTML Canvas 2D (TypeScript), requestAnimationFrame 루프, 라이브러리 없이
- 화면: 2D · 브라우저 — PC 와 폰(390px 폭) 모두, devicePixelRatio 맞춰 또렷하게, 60fps 목표

#### 4. 조건
- 장면은 「그리기 함수」로 두고 clip 안에서 다시 부를 수 있게
- 원 조리개 반지름은 화면 대각선 기준 (√(w²+h²) × 0.55) — 모서리까지 다 덮이게
- 이징은 inOut (천천히 시작 · 천천히 끝) — 전환 0.9초 안팎
- 경계에 흰 선(4px)을 그려 움직임을 또렷하게

#### 5. 완성 기준 (이게 보이면 성공)
- 문제 화면 위로 비스듬한 흰 띠가 왼쪽에서 오른쪽으로 쓸며 정답 화면이 드러난다
- 정답 화면 가운데서 원이 커지며 문제 화면이 다시 드러난다
- 전환 중에도 두 화면 모두 계속 움직인다 (멈춘 그림이 아님)

#### 6. 진행 방식
- 핵심 코드 위주로, 설명은 짧게. 내 프로젝트에 끼워 넣기 쉬운 함수 · 클래스로 나눠 줘.
- 처음 화면에 바로 결과가 보이게, 그리고 켬/끔(또는 전/후) 비교를 할 수 있게 만들어 줘.
- 그림 · 소리 · 모델 파일이 필요하면 코드로 만든 임시 대체물로 먼저 돌아가게 하고, 진짜 파일로 바꿀 자리를 표시해 줘.
- 마지막에 「확인 방법」(무엇을 보면 성공인지)과 「조절할 값」 목록을 짧게 정리해 줘.
- 답변과 코드 주석은 한국어로 해 줘.

## 원리
- 두 장면을 함수로 두고(이전 · 다음), 이전 장면을 다 그린 뒤 다음 장면은 잘린 모양 안에만 그린다.
- 띠 와이프: 비스듬한 선 x + 0.5·y = 경계 의 왼쪽을 다각형으로 잘라 그 안에 다음 장면. 경계를 −0.5h 에서 w 까지 옮긴다.
- 원 조리개: 반지름 R = √(w² + h²) × 0.55 × k 인 원으로 잘라 그 안에 다음 장면 (k = 0 → 1).
- 자른 경계에 흰 선을 그려 주면 「쓸고 지나간다」 가 또렷해진다.

## 핵심 코드 — 띠 와이프 · 원 조리개 (캔버스 clip)
(발췌: demos/demosMotionA.ts i99 의 draw 를 정리)
```ts
const inOut = (x: number) => { const v = Math.max(0, Math.min(1, x)); return v < 0.5 ? 4 * v * v * v : 1 - Math.pow(-2 * v + 2, 3) / 2; };

/** 띠 와이프 — 비스듬한 경계 왼쪽에만 다음 장면 */
function bandWipe(g: CanvasRenderingContext2D, w: number, h: number, k: number) {
  drawPrev(g, w, h);
  const edge = -h * 0.5 - 20 + (w + 20 - (-h * 0.5 - 20)) * inOut(k);
  g.save();
  g.beginPath();
  g.moveTo(-1, -1); g.lineTo(edge + h * 0.25, -1); g.lineTo(edge - h * 0.25, h + 1); g.lineTo(-1, h + 1);
  g.closePath();
  g.clip();
  drawNext(g, w, h);
  g.restore();
  g.strokeStyle = '#fff'; g.lineWidth = 4;   // 쓸고 가는 선
  g.beginPath(); g.moveTo(edge + h * 0.25, 0); g.lineTo(edge - h * 0.25, h); g.stroke();
}

/** 원 조리개 — 가운데서 열리는 원 안에만 다음 장면 */
function irisOpen(g: CanvasRenderingContext2D, w: number, h: number, k: number) {
  drawPrev(g, w, h);
  const R = Math.max(0.1, Math.hypot(w, h) * 0.55 * inOut(k)); // 대각선 기준 — 모서리까지
  g.save();
  g.beginPath(); g.arc(w / 2, h / 2, R, 0, Math.PI * 2); g.clip();
  drawNext(g, w, h);
  g.restore();
  g.strokeStyle = '#fff'; g.lineWidth = 4;
  g.beginPath(); g.arc(w / 2, h / 2, R, 0, Math.PI * 2); g.stroke();
}
```

## 흔한 실수 · 확인 목록
- [ ] **원 반지름을 화면 폭의 반으로 하면 모서리가 끝까지 안 덮인다** — 대각선 √(w²+h²) 의 반보다 조금 크게 (× 0.55).
- [ ] **save/restore 없이 clip 하면 뒤에 그리는 것까지 잘린다** — clip 은 save 와 restore 사이에서만.
- [ ] **반지름 0 으로 arc 를 그리면 브라우저에 따라 오류 · 깜박임** — Math.max(0.1, R) 로 아주 작은 값을 둔다.

## 완성 기준 체크리스트
- [ ] 문제 화면 위로 비스듬한 흰 띠가 왼쪽에서 오른쪽으로 쓸며 정답 화면이 드러난다
- [ ] 정답 화면 가운데서 원이 커지며 문제 화면이 다시 드러난다
- [ ] 전환 중에도 두 화면 모두 계속 움직인다 (멈춘 그림이 아님)

## 이 기술 정보
- id: `i99` · 분류: 이펙트 · 연출 › 모션 그래픽 · 2D · 난이도 쉬움 · 폰 부담 가벼움 (폰 OK) — 전환하는 동안만 두 장면을 함께 그린다 (그리기 2배). 끝나면 하나.
- 라이브 견본 (브라우저에서 직접 조작): https://ai-techstudio.web.app/#t/i99
- 쓰면 좋을 때: 화면 하나를 통째로 바꿀 때 (문제 → 정답) / 바뀌는 순간에 눈길을 모으고 싶을 때
- 쓰지 말 때: 자주 오가는 탭 · 쪽 넘김 — 매번 쓸면 느리다. 대신 바뀌는 부분만 바로 갈아 끼우기 / 3D 장면 전체 — 대신 후처리 패스로 같은 원리

## 견본 실제 코드 (라이브 견본이 돌리는 코드 — three.js · TypeScript)
### i99 — `src/demos/demosMotionA.ts:1124`
```ts
const i99 = {
  kind: '2d' as const,
  caption: '비스듬한 띠가 쓸며 정답 화면이 드러나고, 원 조리개가 열리며 문제 화면으로 돌아와요',
  make() {
    const o = { s: 1 };
    let tt = 0;
    return {
      controls: [speedCtl(o)],
      draw(g: G, w: number, h: number, _t: number, dt: number) {
        tt += Math.max(0, dt) * o.s;
        const T = 6;
        const p = tt % T;
        const band = inOut(seg(p, 1.1, 2.0));
        const iris = inOut(seg(p, 4.1, 5.0));
        let label = '';
        if (p < 1.1 || p >= 5.0) sceneQ(g, w, h, tt);
        else if (p < 2.0) {
          label = '띠 와이프';
          sceneQ(g, w, h, tt);
          // 비스듬한 가장자리: x + y*0.5 < 경계
          const edge = lerp(-h * 0.5 - 20, w + 20, band);
          g.save();
          g.beginPath();
          g.moveTo(-1, -1);
          g.lineTo(edge + h * 0.25, -1);
          g.lineTo(edge - h * 0.25, h + 1);
          g.lineTo(-1, h + 1);
          g.closePath();
          g.clip();
          sceneA(g, w, h, tt);
          g.restore();
          g.strokeStyle = '#fff';
          g.lineWidth = 4;
          g.beginPath();
          g.moveTo(edge + h * 0.25, 0);
          g.lineTo(edge - h * 0.25, h);
          g.stroke();
        } else if (p < 4.1) sceneA(g, w, h, tt);
        else {
          label = '원 조리개';
          sceneA(g, w, h, tt);
          const R = Math.hypot(w, h) * 0.55 * iris;
          g.save();
          g.beginPath();
          g.arc(w / 2, h / 2, Math.max(0.1, R), 0, TAU);
          g.clip();
          sceneQ(g, w, h, tt);
          g.restore();
          g.strokeStyle = '#fff';
          g.lineWidth = 4;
          g.beginPath();
          g.arc(w / 2, h / 2, Math.max(0.1, R), 0, TAU);
          g.stroke();
        }
        if (label) {
          const u = view(g, w, h);
          void u;
          pill(g, label, VW / 2, 16, 10, 'rgba(20,20,50,.75)');
          g.restore();
        }
      },
    };
  },
};
```

## 관련 기술
- 같은 방법으로 만든 효과 예시: [살 · 범람 · 시계 와이프](https://ai-techstudio.web.app/ai/t/i107.md) `i107` · [점 속으로 빨려 들기 · 렌즈 포털](https://ai-techstudio.web.app/ai/t/i126.md) `i126`
- 먼저 알면 좋은 기술: [캔버스 그러데이션 · 흐림 · 빛 섞기](https://ai-techstudio.web.app/ai/t/u52.md) `u52`
- 다음에 해 볼 기술: [휩 팬 · 줌 통과 · 매치 컷](https://ai-techstudio.web.app/ai/t/i106.md) `i106` · [2D 시차 층 · 무한 확대](https://ai-techstudio.web.app/ai/t/i104.md) `i104`
- 참고 문서: [MDN — CanvasRenderingContext2D.clip()](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/clip) · [MDN — CSS clip-path](https://developer.mozilla.org/en-US/docs/Web/CSS/clip-path)
