# AI 꾸러미 — 별점 터짐
> 별이 하나씩 쾅 · 찌그러짐 · 빛 입자, 셋째 별은 더 크게  
> 견본: https://ai-techstudio.web.app/#t/i211

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

## 주문서

### 만들어 줘: 별점 터짐

#### 1. 목표
결과 창에 「별점 터짐」 기술을 깔끔한 기본 느낌으로 넣어 줘. — 별이 하나씩 쾅 · 찌그러짐 · 빛 입자, 셋째 별은 더 크게

#### 2. 핵심 기술 용어
- 별점 터짐 — 별이 하나씩 쾅 · 찌그러짐 · 빛 입자, 셋째 별은 더 크게

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

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

#### 5. 완성 기준 (이게 보이면 성공)
- 견본처럼 「별이 하나씩 쾅 · 찌그러짐 · 빛 입자, 셋째 별은 더 크게」 이(가) 첫 화면에서 바로 보인다
- 켬/끔(또는 전/후) 비교 단추로 이 기술이 만드는 차이를 확인할 수 있다
- 중요한 값 1~3개를 슬라이더로 바꿔 볼 수 있다
- 콘솔 오류 0, 폰 폭(390px)에서도 동작한다

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

## 원리
- 별이 하나씩 쾅 · 찌그러짐 · 빛 입자, 셋째 별은 더 크게


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

## 완성 기준 체크리스트
- [ ] 견본처럼 「별이 하나씩 쾅 · 찌그러짐 · 빛 입자, 셋째 별은 더 크게」 이(가) 첫 화면에서 바로 보인다
- [ ] 켬/끔(또는 전/후) 비교 단추로 이 기술이 만드는 차이를 확인할 수 있다
- [ ] 중요한 값 1~3개를 슬라이더로 바꿔 볼 수 있다
- [ ] 콘솔 오류 0, 폰 폭(390px)에서도 동작한다

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

## 견본 실제 코드 (라이브 견본이 돌리는 코드 — three.js · TypeScript)
### D211 — `src/demos/demosJuice.ts:1363`
```ts
const D211: Demo = {
  kind: '2d',
  caption: '결과 창 별 셋이 하나씩 쾅 — 찌그러졌다 펴지고 빛 입자, 셋째 별은 더 크게 + 화면 흔들림',
  make() {
    let juice = true;
    let tOff = 0;
    let T = 0;
    const P = 3.8;
    const AT = [0.55, 0.95, 1.45];
    return {
      draw(g, w, h, t) {
        reset(g);
        T = t;
        const u = Math.min(w / 280, h / 175);
        const ph = (T - tOff) % P;
        bg(g, w, h, '#3b2a7a', '#120c2e');
        const cx = w / 2;
        const cy = h / 2;
        // 셋째 별 흔들림
        const a3 = ph - AT[2]!;
        g.save();
        if (juice && a3 >= 0 && a3 < 0.4) {
          const k = Math.exp(-a3 * 10) * 5 * u;
          g.translate(noise1(a3 * 70, 1) * k, noise1(a3 * 70, 2) * k);
        }
        // 빛살 (셋째 별 뒤)
        if (juice && a3 >= 0) {
          g.save();
          g.translate(cx, cy - 4 * u);
          g.rotate(T * 0.5);
          g.globalAlpha = 0.16 * smooth(0, 0.3, a3) * (1 - smooth(1.8, 2.3, ph));
          g.globalCompositeOperation = 'lighter';
          g.fillStyle = '#ffd43b';
          for (let i = 0; i < 12; i++) {
            g.rotate(TAU / 12);
            g.beginPath();
            g.moveTo(0, 0);
            g.lineTo(-14 * u, -150 * u);
            g.lineTo(14 * u, -150 * u);
            g.fill();
          }
          g.restore();
        }
        // 창
        const pw = 200 * u;
        const phh = 132 * u;
        rr(g, cx - pw / 2, cy - phh / 2 + 4 * u, pw, phh, 18 * u);
        g.fillStyle = '#1a0f45';
        g.fill();
        const pg = g.createLinearGradient(0, cy - phh / 2, 0, cy + phh / 2);
        pg.addColorStop(0, '#7b61ff');
        pg.addColorStop(1, '#4c3bc9');
        rr(g, cx - pw / 2, cy - phh / 2, pw, phh, 18 * u);
        g.fillStyle = pg;
        g.fill();
        rr(g, cx - pw / 2 + 6 * u, cy - phh / 2 + 6 * u, pw - 12 * u, phh - 12 * u, 13 * u);
        g.strokeStyle = 'rgba(255,255,255,.25)';
        g.lineWidth = 1.5 * u;
        g.stroke();
        otext(g, '단계 통과!', cx, cy - phh / 2 + 20 * u, 15 * u, '#fff', '#2b1a7a', 3.4 * u);
        const xs = [cx - 54 * u, cx, cx + 54 * u];
        const ys = [cy + 6 * u, cy - 4 * u, cy + 6 * u];
        const rs = [20 * u, 26 * u, 20 * u];
        // 빈 자리
        for (let i = 0; i < 3; i++) {
          starPath(g, xs[i]!, ys[i]!, rs[i]!, 0, 0.52);
          g.fillStyle = '#2a1d6e';
          g.fill();
          g.strokeStyle = '#1a1050';
          g.lineWidth = 3 * u;
          g.lineJoin = 'round';
          g.stroke();
        }
        // 별
        const order = [0, 2, 1];
        for (let k = 0; k < 3; k++) {
          const i = order[k]!;
          const a = ph - AT[k]!;
          if (a < 0) continue;
          const third = k === 2;
          if (!juice) {
            g.globalAlpha = clamp01(a / 0.3);
            star(g, xs[i]!, ys[i]!, rs[i]!);
            g.globalAlpha = 1;
            continue;
          }
          const fall = clamp01(a / 0.16);
          const sc = lerp(third ? 3.4 : 2.6, 1, fall * fall);
          const sq = a > 0.16 ? Math.exp(-(a - 0.16) * 9) * Math.cos((a - 0.16) * 34) : 0;
          const rot = lerp(-0.6, 0, fall * fall);
          g.globalAlpha = clamp01(a / 0.08);
          if (a > 0.16) {
            g.save();
            g.globalCompositeOperation = 'lighter';
            glow(g, xs[i]!, ys[i]!, rs[i]! * 2.6, '#ffd43b', 0.55 * Math.exp(-(a - 0.16) * 3) + 0.12);
            g.restore();
          }
          star(g, xs[i]!, ys[i]!, rs[i]! * sc, rot, '#ffd43b', 1 + 0.28 * sq, 1 - 0.28 * sq);
          g.globalAlpha = 1;
          const ia = a - 0.16;
          ringFx(g, xs[i]!, ys[i]!, ia, 0.45, rs[i]!, rs[i]! * (third ? 3.4 : 2.4), '#fff3a0', 3 * u);
          sparks(g, xs[i]!, ys[i]!, ia, third ? 22 : 12, 50 + k, (third ? 80 : 50) * u, ['#ffffff', '#ffe066', '#ffa94d'], 0.6, 20 * u, 2.6 * u);
        }
        // 점수
        const sc = Math.round(1240 * smooth(0.3, 1.9, ph));
        txt(g, `${sc.toLocaleString()}점`, cx, cy + phh / 2 - 18 * u, 13 * u, '#fff', 'center', 900);
        g.restore();
        reset(g);
      },
      controls: [
        tgl('주스 켜기', juice, (v) => (juice = v)),
        { type: 'button', label: '다시 보기', on: () => (tOff = T) },
      ],
    };
  },
};
```

## 관련 기술
- 이 효과를 만든 기술: [층 쌓기 (핵 · 빛무리 · 불티 · 연기)](https://ai-techstudio.web.app/ai/t/i171.md) `i171`
