# AI 꾸러미 — 마우스 따라 3D 카드 기울기
> 카드가 손끝 쪽으로 기울고 빛이 따라옴  
> 견본: https://ai-techstudio.web.app/#t/i219

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

## 주문서

### 만들어 줘: 마우스 따라 3D 카드 기울기

#### 1. 목표
메뉴 카드에 「마우스 따라 3D 카드 기울기」 기술을 깔끔한 기본 느낌으로 넣어 줘. — 카드가 손끝 쪽으로 기울고 빛이 따라옴

#### 2. 핵심 기술 용어
- 마우스 따라 3D 카드 기울기 — 카드가 손끝 쪽으로 기울고 빛이 따라옴

#### 3. 환경
- 플랫폼: TypeScript (브라우저), 라이브러리 없이 — 화면과 떨어진 순수 함수로
- 화면: 브라우저 — PC · 폰 모두

#### 4. 조건
- 화면 코드와 계산 코드를 나눠서 (계산은 순수 함수)
- 한 프레임 16ms 안 — 오래 걸리는 계산은 나눠서

#### 5. 완성 기준 (이게 보이면 성공)
- 견본처럼 「카드가 손끝 쪽으로 기울고 빛이 따라옴」 이(가) 첫 화면에서 바로 보인다
- 켬/끔(또는 전/후) 비교 단추로 이 기술이 만드는 차이를 확인할 수 있다
- 중요한 값 1~3개를 슬라이더로 바꿔 볼 수 있다
- 콘솔 오류 0, 폰 폭(390px)에서도 동작한다

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

## 원리
- 카드가 손끝 쪽으로 기울고 빛이 따라옴


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

## 완성 기준 체크리스트
- [ ] 견본처럼 「카드가 손끝 쪽으로 기울고 빛이 따라옴」 이(가) 첫 화면에서 바로 보인다
- [ ] 켬/끔(또는 전/후) 비교 단추로 이 기술이 만드는 차이를 확인할 수 있다
- [ ] 중요한 값 1~3개를 슬라이더로 바꿔 볼 수 있다
- [ ] 콘솔 오류 0, 폰 폭(390px)에서도 동작한다

## 이 기술 정보
- id: `i219` · 분류: 이펙트 · 연출 › 손맛 · 주스 · 공통 · 난이도 쉬움 · 폰 부담 보통 (폰 주의)
- 라이브 견본 (브라우저에서 직접 조작): https://ai-techstudio.web.app/#t/i219
- 쓰면 좋을 때: 쓰면 좋을 곳 — 메뉴 카드 · 단계 고르기

## 견본 실제 코드 (라이브 견본이 돌리는 코드 — three.js · TypeScript)
### D219 — `src/demos/demosJuice.ts:2555`
```ts
const D219: Demo = {
  kind: 'dom',
  caption: '단계 고르기 카드가 손끝 쪽으로 기울고 반사광이 따라온다 — 그림 · 글씨는 층마다 다른 깊이 (마우스를 올려 봐도 됨)',
  make(box) {
    const root = domRoot(
      box,
      'jx-tilt',
      `.jx-tilt{background:radial-gradient(110% 90% at 50% 10%,#33508f,#0d1633);perspective:110cqmin}
       .jx-tilt .stage{position:absolute;left:50%;top:50%;width:62cqmin;height:84cqmin;transform:translate(-50%,-50%);transform-style:preserve-3d}
       .jx-tilt .shadow{position:absolute;left:8%;right:8%;bottom:-6%;height:14%;border-radius:50%;background:rgba(0,0,0,.55);filter:blur(3cqmin)}
       .jx-tilt .card{position:absolute;inset:0;border-radius:6cqmin;transform-style:preserve-3d;background:linear-gradient(160deg,#ffffff,#e8eeff);box-shadow:0 0 0 1cqmin #ffd43b inset,0 2cqmin 0 #b9c3ea}
       .jx-tilt .art{position:absolute;left:6%;right:6%;top:5%;height:56%;border-radius:4cqmin;background:linear-gradient(#7ec8ff,#c8ecff)}
       .jx-tilt .art svg{position:absolute;inset:0;width:100%;height:100%}
       .jx-tilt .pop{position:absolute;left:6%;right:6%;top:5%;height:56%;pointer-events:none}
       .jx-tilt .pop svg{width:100%;height:100%;overflow:visible}
       .jx-tilt .ttl{position:absolute;left:0;right:0;top:64%;text-align:center;font-size:8.4cqmin;font-weight:900;color:#2b2f7a}
       .jx-tilt .sub{position:absolute;left:0;right:0;top:76%;text-align:center;font-size:4.6cqmin;font-weight:700;color:#7a80b8}
       .jx-tilt .st{position:absolute;left:0;right:0;top:85%;text-align:center;font-size:7cqmin;letter-spacing:1cqmin;color:#ffc107;text-shadow:0 .5cqmin 0 #c77c00}
       .jx-tilt .st b{color:#d3d8ee;text-shadow:0 .5cqmin 0 #aab2d2;font-weight:400}
       .jx-tilt .glare{position:absolute;inset:0;border-radius:6cqmin;pointer-events:none;mix-blend-mode:soft-light}`,
      `<div class="stage"><div class="shadow"></div><div class="card">
         <div class="art"><svg viewBox="0 0 100 70" preserveAspectRatio="xMidYMid slice">
           <circle cx="80" cy="14" r="7" fill="#fff6c2"/><ellipse cx="22" cy="18" rx="12" ry="4" fill="#fff" opacity=".8"/>
           <ellipse cx="50" cy="52" rx="36" ry="9" fill="#5fae4f"/><path d="M16 52 Q50 82 84 52 Z" fill="#a0703f"/>
           <ellipse cx="50" cy="50" rx="34" ry="7" fill="#79c868"/></svg></div>
         <div class="pop"><svg viewBox="0 0 100 70">
           <g transform="translate(40 26)"><rect x="0" y="4" width="20" height="20" rx="4" fill="#c2255c"/><rect x="0" y="0" width="20" height="20" rx="4" fill="#ff6b8a"/><text x="10" y="15.5" font-size="14" font-weight="900" text-anchor="middle" fill="#fff" font-family="${FCSS}">7</text></g>
           <path transform="translate(22 30) scale(.9)" d="M0-9 2.6-3 9-2.8 4-1.4 5.6 7 0 3 -5.6 7 -4 1.4 -9-2.8 -2.6-3Z" fill="#ffd43b" stroke="#c77c00" stroke-width="1.5" stroke-linejoin="round"/>
           <path transform="translate(80 34) scale(.7)" d="M0-9 2.6-3 9-2.8 4-1.4 5.6 7 0 3 -5.6 7 -4 1.4 -9-2.8 -2.6-3Z" fill="#ffd43b" stroke="#c77c00" stroke-width="1.5" stroke-linejoin="round"/>
         </svg></div>
         <div class="ttl">월드 2</div><div class="sub">별 둘 · 14단계</div><div class="st">★★<b>★</b></div>
         <div class="glare"></div></div></div>`,
    );
    const stage = q(root, '.stage');
    const card = q(root, '.card');
    const art = q(root, '.art');
    const pop = q(root, '.pop');
    const ttl = q(root, '.ttl');
    const glare = q(root, '.glare');
    const shadow = q(root, '.shadow');
    const rig = tiltRig(root);
    let tilt = true;
    let light = true;
    let depth = true;
    let maxDeg = 16;
    return {
      update(t, dt) {
        rig.step(t, dt);
        const nx = tilt ? rig.nx : 0;
        const ny = tilt ? rig.ny : 0;
        stage.style.transform = `translate(-50%,-50%) rotateX(${(-ny * maxDeg * 0.8).toFixed(2)}deg) rotateY(${(nx * maxDeg).toFixed(2)}deg)`;
        const z = depth ? 1 : 0;
        card.style.transform = 'translateZ(0)';
        art.style.transform = `translateZ(${2 * z}cqmin)`;
        pop.style.transform = `translateZ(${9 * z}cqmin) translate(${(nx * 1.5 * z).toFixed(2)}cqmin,${(ny * 1.5 * z).toFixed(2)}cqmin)`;
        ttl.style.transform = `translateZ(${6 * z}cqmin)`;
        shadow.style.transform = `translate(${(-nx * 5).toFixed(2)}cqmin,${(-ny * 2).toFixed(2)}cqmin) translateZ(-10cqmin)`;
        glare.style.background = light
          ? `radial-gradient(circle at ${(50 + rig.nx * 55).toFixed(1)}% ${(50 + rig.ny * 55).toFixed(1)}%, rgba(255,255,255,.95), rgba(255,255,255,.15) 38%, rgba(0,0,40,.35) 85%)`
          : 'none';
      },
      controls: [
        tgl('기울기', tilt, (v) => (tilt = v)),
        tgl('반사광 따라오기', light, (v) => (light = v)),
        tgl('층마다 깊이 (시차)', depth, (v) => (depth = v)),
        rng('최대 각도', 4, 30, 1, maxDeg, (v) => (maxDeg = v)),
      ],
      dispose() {
        rig.dispose();
      },
    };
  },
};
```

## 관련 기술
- 이 효과를 만든 기술: [홀로 포일 카드](https://ai-techstudio.web.app/ai/t/i220.md) `i220`
