# AI 꾸러미 — 마스크 · 섞기 · 잘라내기 — CSS mask + mask-composite (exclude / xor)
> CSS 마스크 xor 로 테두리만 남기고, mix-blend-mode 로 색을 곱해 섞고, clip-path 다각형으로 별 ↔ 육각형을 바꾸고, backdrop-filter 로 뒤를 흐린 유리를 만든다.  
> 견본: https://ai-techstudio.web.app/#t/u63

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

## 주문서

### 만들어 줘: 마스크 · 섞기 · 잘라내기 — CSS mask + mask-composite (exclude / xor)

#### 1. 목표
카드 · 고르기 창 테두리에 CSS 마스크 · 섞기 · 잘라내기 · 뒤 흐림을 써 줘 — 도는 무지개 테두리, 겹치면 진해지는 색, 모양이 바뀌는 잘라내기, 유리 판. 분위기는 밤하늘 · 무지개 빛.

#### 2. 핵심 기술 용어
- **CSS mask + mask-composite (exclude / xor)** — 마스크 두 겹을 빼서 테두리만 남기기
- **mix-blend-mode: multiply** — 색 곱하기 섞기 — 겹친 곳이 진해짐
- **clip-path: polygon()** — 다각형으로 잘라내기 — 꼭짓점 수가 같으면 모양끼리 섞기
- **backdrop-filter: blur() saturate()** — 뒤에 비친 것을 흐리게 — 유리 판

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

#### 4. 조건
- 마스크는 표준(mask + exclude)과 웹킷(-webkit-mask + -webkit-mask-composite: xor)을 함께 쓴다
- backdrop-filter 도 -webkit- 접두어를 함께 (사파리)
- clip-path 다각형은 두 모양의 꼭짓점 수를 같게
- backdrop-filter 판은 화면에 두 장 이하

#### 5. 완성 기준 (이게 보이면 성공)
- 카드 둘레에 무지개 테두리만 돌고 가운데 카드는 그대로 보인다
- 세 색 원이 겹친 곳이 더 진한 색으로 섞인다
- 별이 육각형으로 부드럽게 바뀌었다 돌아온다
- 유리 판 뒤로 색 덩어리가 흐리게 비치며 지나간다 (사파리 · 크롬 둘 다)

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

## 원리
- 테두리만 남기기: 마스크를 두 겹(내용 상자 · 전체 상자) 깔고 mask-composite: exclude(웹킷은 -webkit-mask-composite: xor)로 빼면 padding 띠만 남는다 — 그 아래 도는 conic-gradient 를 깔면 무지개 테.
- 섞기: 원 셋에 mix-blend-mode: multiply 를 주면 겹친 곳이 물감처럼 진해진다 (하양 바탕 필요).
- 잘라내기: clip-path: polygon() 의 꼭짓점 수를 같게(12개) 두고 반지름만 별(0.5 · 0.22 번갈아)과 육각형 사이로 섞으면 모양이 부드럽게 바뀐다.
- 유리: 반투명 바탕 + backdrop-filter: blur(2.4cqmin) saturate(1.6) — 뒤에 지나가는 색 덩어리가 흐리게 비친다.

## 핵심 코드 — 테두리 마스크 · 유리 판 · 별 ↔ 육각형 clip-path
(발췌: demos/demosDraw2d.ts u63 의 CSS 와 update() 를 정리)
```ts
const css =
  // 테두리만 남기기: 내용 상자 마스크를 전체 마스크에서 빼기
  '.ring{border-radius:4cqmin;padding:1.6cqmin;' +
  '-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;' +
  'mask:linear-gradient(#000 0 0) content-box exclude,linear-gradient(#000 0 0)}' +
  // 유리 판
  '.glass{background:#ffffff26;border:.4cqmin solid #ffffff80;' +
  'backdrop-filter:blur(2.4cqmin) saturate(1.6);-webkit-backdrop-filter:blur(2.4cqmin) saturate(1.6)}' +
  '.blend i{mix-blend-mode:multiply;border-radius:50%}';

const lerp = (a: number, b: number, k: number) => a + (b - a) * k;
function update(t: number, ring: HTMLElement, shape: HTMLElement, k: number /* 0 별 → 1 육각형 */) {
  ring.style.background = 'conic-gradient(from ' + ((t * 120) % 360) + 'deg,#ff6fa8,#ffd23f,#3ee0ff,#8b6bff,#ff6fa8)';
  const pts: string[] = [];
  for (let i = 0; i < 12; i++) {                       // 꼭짓점 12개로 같게
    const a = -Math.PI / 2 + (i * Math.PI) / 6 + t * 0.4;
    const star = i % 2 ? 0.22 : 0.5;
    const hex = 0.5 / Math.cos(((((a + Math.PI / 2 - t * 0.4) % (Math.PI / 3)) + Math.PI / 3) % (Math.PI / 3)) - Math.PI / 6) * 0.866;
    const r = lerp(star, Math.min(0.5, hex), k);
    pts.push((50 + Math.cos(a) * r * 100).toFixed(1) + '% ' + (50 + Math.sin(a) * r * 100).toFixed(1) + '%');
  }
  shape.style.clipPath = 'polygon(' + pts.join(',') + ')';
}
```

## 흔한 실수 · 확인 목록
- [ ] **표준 mask 만 쓰면 사파리 · 옛 크롬에서 테두리가 꽉 찬 판으로 보인다** — -webkit-mask 와 -webkit-mask-composite: xor 를 함께 쓴다.
- [ ] **mix-blend-mode 를 어두운 바탕에 쓰면 곱하기가 안 보인다** — multiply 는 밝은 바탕에서 쓴다. 어두운 바탕엔 screen 이나 lighten.
- [ ] **backdrop-filter 판을 여러 장 겹치면 폰에서 버벅인다** — 유리 판은 한두 장만, 큰 배경 흐림은 미리 흐린 그림으로 대신한다.
- [ ] **꼭짓점 수가 다른 clip-path 로 transition 하면 뚝 바뀐다** — 두 모양을 같은 꼭짓점 수로 만들어 반지름만 섞는다.

## 완성 기준 체크리스트
- [ ] 카드 둘레에 무지개 테두리만 돌고 가운데 카드는 그대로 보인다
- [ ] 세 색 원이 겹친 곳이 더 진한 색으로 섞인다
- [ ] 별이 육각형으로 부드럽게 바뀌었다 돌아온다
- [ ] 유리 판 뒤로 색 덩어리가 흐리게 비치며 지나간다 (사파리 · 크롬 둘 다)

## 이 기술 정보
- id: `u63` · 분류: 2D · 화면 › CSS · 화면 틀 · 공통 · 난이도 보통 · 폰 부담 보통 (폰 주의) — backdrop-filter 는 뒤 화면을 매번 흐리므로 큰 판 · 여러 장이면 폰에서 프레임이 떨어진다. 마스크 · 섞기 · clip-path 는 가볍다.
- 라이브 견본 (브라우저에서 직접 조작): https://ai-techstudio.web.app/#t/u63
- 쓰면 좋을 때: 그림 없이 특별한 테두리 · 모양을 낼 때 / 게임 화면 위에 메뉴를 띄우되 뒤가 은은히 보이게 할 때
- 쓰지 말 때: backdrop-filter 를 큰 판 여러 장에 — 폰에서 무겁다. 한두 장만 / clip-path 로 꼭짓점 수가 다른 두 모양을 섞기 — 부드럽게 안 넘어간다. 꼭짓점 수를 맞춘다

## 견본 실제 코드 (라이브 견본이 돌리는 코드 — three.js · TypeScript)
### u63 견본 항목 — `src/demos/demosDraw2d.ts:1348`
```ts
  u63: {
    kind: 'dom',
    caption: '마스크(xor 로 테만 남기기) · 섞기(mix-blend-mode) · 잘라내기(clip-path 별 ↔ 육각) · 뒤 흐림(backdrop-filter 유리)',
    make(box) {
      const root = domRoot(
        box,
        'd2-mask',
        `.d2-mask{background:#151a3a;display:grid;grid-template-columns:1fr 1fr;grid-template-rows:1fr 1fr;gap:2cqmin;padding:2.5cqmin;box-sizing:border-box}
         .d2-mask .t{position:relative;border-radius:3cqmin;overflow:hidden;background:#232a5a}
         .d2-mask .t>span{position:absolute;left:3cqmin;top:2.5cqmin;font-size:4.6cqmin;font-weight:800;padding:.2em .7em;border-radius:99px;background:#000a;z-index:5}
         .d2-mask .ring{position:absolute;left:50%;top:56%;width:52%;height:58%;transform:translate(-50%,-50%);border-radius:4cqmin;padding:1.6cqmin;
            -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask:linear-gradient(#000 0 0) content-box exclude,linear-gradient(#000 0 0)}
         .d2-mask .inner{position:absolute;left:50%;top:56%;width:46%;height:46%;transform:translate(-50%,-50%);border-radius:3cqmin;background:#2f3a7a;display:grid;place-items:center;font-size:6cqmin;font-weight:900;color:#ffe7a6}
         .d2-mask .blend{background:#fff}
         .d2-mask .blend i{position:absolute;width:34%;aspect-ratio:1;border-radius:50%;mix-blend-mode:multiply;top:30%;left:33%}
         .d2-mask .clip{background:radial-gradient(circle at 50% 60%,#2b2f6e,#141838)}
         .d2-mask .shape{position:absolute;left:50%;top:58%;width:44%;aspect-ratio:1;transform:translate(-50%,-50%);background:linear-gradient(135deg,#ffd23f,#ff6fa8 60%,#8b6bff)}
         .d2-mask .glassbg{position:absolute;inset:0;background:#1d1f4a}
         .d2-mask .glassbg i{position:absolute;border-radius:50%;width:28%;aspect-ratio:1}
         .d2-mask .glass{position:absolute;top:30%;width:44%;height:58%;border-radius:3cqmin;background:#ffffff26;border:.4cqmin solid #ffffff80;
            backdrop-filter:blur(2.4cqmin) saturate(1.6);-webkit-backdrop-filter:blur(2.4cqmin) saturate(1.6);display:grid;place-items:center;font-size:5cqmin;font-weight:800}`,
        `<div class="t"><span>mask xor 테</span><div class="inner">카드</div><div class="ring"></div></div>
         <div class="t blend"><span>mix-blend-mode</span><i style="background:#00c8ff"></i><i style="background:#ff3fb4"></i><i style="background:#ffe600"></i></div>
         <div class="t clip"><span>clip-path</span><div class="shape"></div></div>
         <div class="t"><span>backdrop-filter</span><div class="glassbg"><i style="background:#ff6fa8;left:8%;top:30%"></i><i style="background:#ffd23f;left:38%;top:50%"></i><i style="background:#3ee0ff;left:66%;top:26%"></i></div><div class="glass">유리</div></div>`,
      );
      const ring = q(root, '.ring');
      const blobs = qa(root, '.blend i');
      const shape = q(root, '.shape');
      const glass = q(root, '.glass');
      const gb = qa(root, '.glassbg i');
      return {
        update(t) {
          ring.style.background = `conic-gradient(from ${(t * 120) % 360}deg,#ff6fa8,#ffd23f,#3ee0ff,#8b6bff,#ff6fa8)`;
          blobs.forEach((b, i) => {
            const a = t * 1.1 + (i * Math.PI * 2) / 3;
            b.style.transform = `translate(${Math.cos(a) * 32}%, ${Math.sin(a) * 26}%)`;
          });
          // 별(10점) ↔ 육각형 (같은 점 수로 섞기)
          const k = swing(t, 1.1, 1.4);
          const pts: string[] = [];
          for (let i = 0; i < 12; i++) {
            const a = -Math.PI / 2 + (i * Math.PI) / 6 + t * 0.4;
            const star = i % 2 ? 0.22 : 0.5;
            const hex = 0.5 / Math.cos(((((a + Math.PI / 2 - t * 0.4) % (Math.PI / 3)) + Math.PI / 3) % (Math.PI / 3)) - Math.PI / 6) * 0.866;
            const r = lerp(star, Math.min(0.5, hex), k);
            pts.push(`${(50 + Math.cos(a) * r * 100).toFixed(1)}% ${(50 + Math.sin(a) * r * 100).toFixed(1)}%`);
          }
          shape.style.clipPath = `polygon(${pts.join(',')})`;
          glass.style.left = `${28 + Math.sin(t * 0.9) * 24}%`;
          gb.forEach((b, i) => (b.style.transform = `translateY(${Math.sin(t * 1.3 + i * 2) * 25}%)`));
        },
      };
    },
  }
```

## 관련 기술
- 먼저 알면 좋은 기술: [CSS 무늬 (그러데이션 · 데이터 SVG)](https://ai-techstudio.web.app/ai/t/u62.md) `u62`
- 다음에 해 볼 기술: [글자 테 · 그러데이션 글자](https://ai-techstudio.web.app/ai/t/u64.md) `u64`
- 참고 문서: [MDN — mask-composite](https://developer.mozilla.org/en-US/docs/Web/CSS/mask-composite) · [MDN — backdrop-filter](https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter) · [MDN — clip-path](https://developer.mozilla.org/en-US/docs/Web/CSS/clip-path)
