# AI 꾸러미 — 글자 속 그림 · 영상 — background-clip
> 큰 글자 모양 안으로 그림 · 영상이 보임 — background-clip:text · SVG 마스크  
> 견본: https://ai-techstudio.web.app/#t/i100

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

## 주문서

### 만들어 줘: 글자 속 그림 · 영상 — background-clip

#### 1. 목표
단원 제목 카드에 「글자 속 그림 · 영상」 기술을 깔끔한 기본 느낌으로 넣어 줘. — 큰 글자 모양 안으로 그림 · 영상이 보임 — background-clip:text · SVG 마스크

#### 2. 핵심 기술 용어
- **background-clip**
- **text**
- **SVG**

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

#### 4. 조건
- devicePixelRatio 를 맞춰 또렷하게
- 매 프레임 다시 그릴 필요 없는 그림은 화면 밖 캔버스에 미리 구워 두기
- 한 프레임 16ms 안 (폰 포함)

#### 5. 완성 기준 (이게 보이면 성공)
- 견본처럼 「큰 글자 모양 안으로 그림 · 영상이 보임 — background-clip:text · SVG 마스크」 이(가) 첫 화면에서 바로 보인다
- 켬/끔(또는 전/후) 비교 단추로 이 기술이 만드는 차이를 확인할 수 있다
- 중요한 값 1~3개를 슬라이더로 바꿔 볼 수 있다
- 콘솔 오류 0, 폰 폭(390px)에서도 동작한다

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

## 원리
- 큰 글자 모양 안으로 그림 · 영상이 보임 — background-clip:text · SVG 마스크


## 흔한 실수 · 확인 목록
- (아직 정리된 것이 없어요)

## 완성 기준 체크리스트
- [ ] 견본처럼 「큰 글자 모양 안으로 그림 · 영상이 보임 — background-clip:text · SVG 마스크」 이(가) 첫 화면에서 바로 보인다
- [ ] 켬/끔(또는 전/후) 비교 단추로 이 기술이 만드는 차이를 확인할 수 있다
- [ ] 중요한 값 1~3개를 슬라이더로 바꿔 볼 수 있다
- [ ] 콘솔 오류 0, 폰 폭(390px)에서도 동작한다

## 이 기술 정보
- id: `i100` · 분류: 이펙트 · 연출 › 모션 그래픽 · 2D · 난이도 보통 · 폰 부담 보통 (폰 주의)
- 라이브 견본 (브라우저에서 직접 조작): https://ai-techstudio.web.app/#t/i100
- 쓰면 좋을 때: 쓰면 좋을 곳 — 단원 제목 카드 · 게임 제목

## 견본 실제 코드 (라이브 견본이 돌리는 코드 — three.js · TypeScript)
### i100 — `src/demos/demosMotionA.ts:1190`
```ts
const i100 = {
  kind: '2d' as const,
  caption: '큰 글자 「수학」 모양 구멍으로 움직이는 그림이 보여요 — 끝에는 글자 속으로 쑥 들어가 그림 전체가 드러나요',
  make() {
    const o = { s: 1 };
    let tt = 0;
    let oc: HTMLCanvasElement | null = null;
    const paint = (c: G, t: number): void => {
      // 가상 280×175 그림 (하늘 · 해 · 언덕 · 떠다니는 기호)
      const gr = c.createLinearGradient(0, 0, 0, VH);
      gr.addColorStop(0, '#5ec8ff');
      gr.addColorStop(0.6, '#b8ecff');
      gr.addColorStop(1, '#fff1c9');
      c.fillStyle = gr;
      c.fillRect(-40, -40, VW + 80, VH + 80);
      const sx = 70 + Math.sin(t * 0.4) * 30;
      const sg = c.createRadialGradient(sx, 50, 2, sx, 50, 40);
      sg.addColorStop(0, '#fff6a0');
      sg.addColorStop(0.4, '#ffcf3a');
      sg.addColorStop(1, 'rgba(255,200,60,0)');
      c.fillStyle = sg;
      c.beginPath();
      c.arc(sx, 50, 40, 0, TAU);
      c.fill();
      for (let i = 0; i < 3; i++) cloud(c, mod(i * 110 + t * 12, VW + 80) - 50, 30 + i * 18, 0.9);
      const hill = (yb: number, amp: number, col: string, ph: number): void => {
        c.fillStyle = col;
        c.beginPath();
        c.moveTo(-40, VH + 40);
        for (let x = -40; x <= VW + 40; x += 8) c.lineTo(x, yb + Math.sin(x * 0.03 + ph + t * 0.3) * amp);
        c.lineTo(VW + 40, VH + 40);
        c.fill();
      };
      hill(96, 12, '#5fcf6f', 0);
      hill(116, 9, '#3eae5a', 2);
      for (let i = 0; i < 9; i++) {
        const x = mod(hsh(i) * 320 + t * (14 + i * 3), 320) - 20;
        const y = 85 + Math.sin(t * 0.8 + i * 1.3) * 32;
        c.fillStyle = ['#ff3d7f', '#6b4cff', '#ff7a1a'][i % 3]!;
        c.font = `900 ${24 + (i % 3) * 8}px ${F}`;
        c.textAlign = 'center';
        c.textBaseline = 'middle';
        c.fillText(['+', '×', 'π', '÷', '=', '7', '√', '3', '%'][i]!, x, y);
      }
    };
    return {
      controls: [speedCtl(o)],
      dispose() {
        oc = null;
      },
      draw(g: G, w: number, h: number, _t: number, dt: number) {
        tt += Math.max(0, dt) * o.s;
        const T = 7;
        const p = tt % T;
        const dpr = g.getTransform().a;
        const W = Math.max(1, Math.round(w * dpr));
        const H = Math.max(1, Math.round(h * dpr));
        if (!oc) oc = document.createElement('canvas');
        if (oc.width !== W || oc.height !== H) {
          oc.width = W;
          oc.height = H;
        }
        const c = oc.getContext('2d')!;
        c.setTransform(1, 0, 0, 1, 0, 0);
        c.globalCompositeOperation = 'source-over';
        c.clearRect(0, 0, W, H);
        c.setTransform(dpr, 0, 0, dpr, 0, 0);
        const u = Math.min(w / VW, h / VH);
        const vx = (w - VW * u) / 2;
        const vy = (h - VH * u) / 2;
        c.save();
        c.translate(vx, vy);
        c.scale(u, u);
        // 그림을 조금 크게 그려 천천히 흐르게 (화면 전체를 덮을 만큼)
        const cov = Math.max(w / (VW * u), h / (VH * u));
        c.translate(VW / 2, VH / 2);
        c.scale(cov * 1.05, cov * 1.05);
        c.translate(-VW / 2, -VH / 2);
        paint(c, tt);
        c.restore();
        // 글자 마스크
        const zin = seg(p, 4.0, 5.0);
        const zout = seg(p, 6.0, 7.0);
        const z = p < 5.5 ? Math.pow(40, inOut(zin)) : Math.pow(40, 1 - inOut(zout));
        const reveal = p < 5.5 ? seg(zin, 0.35, 1) : 1 - seg(zout, 0, 0.5);
        c.globalCompositeOperation = 'destination-in';
        c.save();
        c.translate(vx, vy);
        c.scale(u, u);
        c.translate(VW / 2 - 10 * (z - 1) * 0.0, VH / 2 + 4);
        c.scale(z, z);
        c.font = `400 92px ${TF}`;
        c.textAlign = 'center';
        c.textBaseline = 'middle';
        c.fillStyle = '#000';
        c.fillText('수학', 0, 0);
        c.restore();
        if (reveal > 0) {
          c.globalAlpha = reveal;
          c.fillRect(0, 0, w, h);
          c.globalAlpha = 1;
        }
        c.globalCompositeOperation = 'source-over';
        // 바탕
        bg(g, w, h, '#1b1f4a', '#0b0d24');
        view(g, w, h);
        g.save();
        g.translate(VW / 2, VH / 2 + 4);
        g.scale(z, z);
        g.font = `400 92px ${TF}`;
        g.textAlign = 'center';
        g.textBaseline = 'middle';
        g.fillStyle = 'rgba(0,0,0,.45)';
        g.fillText('수학', 3, 5);
        g.restore();
        g.restore();
        g.save();
        g.setTransform(1, 0, 0, 1, 0, 0);
        g.drawImage(oc, 0, 0);
        g.restore();
        view(g, w, h);
        if (z < 1.5) {
          g.save();
          g.translate(VW / 2, VH / 2 + 4);
          g.scale(z, z);
          g.font = `400 92px ${TF}`;
          g.textAlign = 'center';
          g.textBaseline = 'middle';
          g.strokeStyle = 'rgba(255,255,255,.75)';
          g.lineWidth = 1.5;
          g.strokeText('수학', 0, 0);
          g.restore();
        }
        g.restore();
      },
    };
  },
};
```

## 관련 기술
- 이 효과를 만든 기술: [글자 테 · 그러데이션 글자](https://ai-techstudio.web.app/ai/t/u64.md) `u64`
