# 한국교통안전공단 교통안전 틀린그림찾기 — 임베드 위젯

외부 사이트에 `<script>` 한 줄로 삽입하는 **교통안전 틀린그림찾기 게임 위젯**입니다.
Vite 라이브러리 모드로 단일 JS 파일 하나(IIFE)로 빌드되며, Shadow DOM 으로
스타일이 완전히 격리되어 host 사이트와 서로 영향을 주지 않습니다.

## 기술 스택

- **Vite 8** (라이브러리 모드 · IIFE 단일 번들)
- **React 19** · **TypeScript**
- **Tailwind CSS v4** · **shadcn/ui** (radix-nova)
- **Shadow DOM** 스타일 격리 — 별도 CSS 파일 없이 JS 한 개로 자체 완결

## 빌드

```bash
pnpm install
pnpm build      # → dist/kotsa-spot-the-difference.js (단일 파일)
```

개발 미리보기:

```bash
pnpm dev        # 외부 사이트를 흉내 낸 index.html 로 위젯 확인
```

## npm 배포 · CDN(unpkg) 임베드

```bash
pnpm build      # dist/ 생성
npm publish     # prepublishOnly 가 build 를 다시 실행해 dist 를 최신화
```

- `package.json` 의 `files: ["dist"]` 로 배포 패키지에는 `dist/` 만 포함됩니다.
  (`.npmignore` 가 있어 `.gitignore` 의 `dist` 무시 규칙은 적용되지 않습니다.)
- 배포 후에는 별도 호스팅 없이 **unpkg** 로 바로 임베드할 수 있습니다:

```html
<script src="https://unpkg.com/kotsa-spot-game-widget@1.0.0"></script>
```

- 버전 고정(`@1.0.0`)을 권장합니다. 버전을 생략하면(`.../kotsa-spot-game-widget`)
  항상 최신 버전을 받습니다. `package.json` 의 `unpkg` 필드가 번들 파일을
  가리키므로 경로를 적지 않아도 됩니다. (`jsdelivr` 도 동일)
- npm 에 동일한 패키지 이름이 이미 있으면 게시할 수 없습니다 — 그럴 땐
  `name` 을 스코프(`@조직/kotsa-spot-game-widget`)로 바꾸세요.

## 사용 방법 (외부 사이트에서 임베드)

### 1) 자동 삽입 — `<main>` 안에 append

스크립트만 넣으면 host 페이지의 `<main>` 태그 안에 게임이 자동으로 삽입됩니다.

```html
<main></main>

<script src="https://unpkg.com/kotsa-spot-game-widget@1.0.0"></script>
```

### 2) 특정 위치에 삽입

원하는 자리에 `data-kotsa-game` 요소를 두면 그 자리에 마운트됩니다.
(이 속성을 가진 요소가 하나라도 있으면 `<main>` 자동 삽입은 건너뜁니다.)

```html
<div data-kotsa-game></div>

<script src="https://unpkg.com/kotsa-spot-game-widget@1.0.0"></script>
```

### 3) 수동 제어 (JavaScript API)

```html
<div id="game-here"></div>

<script src="https://unpkg.com/kotsa-spot-game-widget@1.0.0"></script>
<script>
  // mount 는 위젯을 제거하는 unmount 함수를 반환합니다.
  const unmount = KotsaSpotTheDifference.mount("#game-here");
  // unmount();
</script>
```

> `demo.html` 을 빌드 후 브라우저로 열어 실제 임베드 동작을 확인할 수 있습니다.

## 게임 구성

- 총 **5단계**, 단계마다 숨은 차이 **5곳** (전체 25곳)
- 제한 시간 · 점수 · 콤보 보너스 · 힌트 3회 · 단계별 결과 · 최종 등급(S~D)
- 어린이 보호구역 / 전동킥보드 / 기차역 승강장 / 공항 보안검색 / 패러글라이딩
- 시작 인트로 3종: 시작 화면 · 개인정보 수집 동의 · 정보 입력(호스트 폼 제출)

## 프로젝트 구조

```
src/
├─ embed.tsx                  # 라이브러리 진입점 (IIFE) — 자동 마운트 + 전역 API
├─ mount.tsx                  # Shadow DOM 마운트 로직 (스타일 격리)
├─ dev.tsx                    # 개발 서버 전용 진입점
├─ index.css                  # Tailwind v4 + 테마 + 게임 애니메이션 (?inline 로 번들 내장)
├─ components/
│  ├─ game/                   # 게임 화면·로직 UI
│  │  ├─ game-app.tsx          # 전체 흐름 (타이머·상태·토스트)
│  │  ├─ start-screen.tsx      # 시작 인트로 3종 (시작·개인정보 동의·정보 입력)
│  │  ├─ game-hud.tsx          # 점수·시간·힌트 HUD
│  │  ├─ difference-board.tsx  # 두 그림 보드 + 클릭 판정
│  │  ├─ image-panel.tsx       # 한 장의 그림 + 마커
│  │  ├─ stage-transition-overlay.tsx  # 단계 결과(포털 없는 자체 오버레이)
│  │  ├─ result-screen.tsx     # 최종 결과·등급
│  │  └─ game-toast.tsx        # 위젯 내부 전용 토스트
│  └─ site/kotsa-mark.tsx     # 엠블럼
└─ lib/
   ├─ stages.ts               # 스테이지 정의
   ├─ game-types.ts           # 공통 타입
   ├─ game-config.ts          # 점수 규칙·등급
   ├─ game-reducer.ts         # 게임 상태 머신
   ├─ hit-test.ts             # 클릭 정답 판정
   ├─ host-form.ts            # 호스트 사이트 입력 폼 채우기·제출
   └─ assets.ts               # asset/ 이미지 인라인
```

## 동작 원리 — 스타일 격리

- 위젯은 대상 요소에 **Shadow DOM** 을 붙이고 그 안에 렌더링합니다. host 사이트의
  CSS 가 위젯에 스며들지 않고, 위젯 CSS 도 host 로 새어 나가지 않습니다.
- `index.css` 는 `?inline` 로 import 되어 **JS 번들 안에 문자열로 내장**됩니다.
  (배포 파일은 JS 하나뿐 — 별도 CSS 링크 불필요)
- Tailwind v4 의 `@property` 규칙은 문서 전역 등록이 필요하므로 `document.head`
  로 한 번만 옮겨 주입하고, 나머지 규칙은 Shadow DOM 안에 둡니다.
- 반응형은 뷰포트가 아닌 **위젯 컨테이너 너비**(CSS container query)를 기준으로
  동작하므로, 좁은 칼럼에 임베드해도 레이아웃이 자연스럽게 맞춰집니다.

## 그림·차이점 교체하기

장면은 `asset/stageN-a.png`(원본) · `asset/stageN-b.png`(다른그림) 이미지를
사용합니다. 그림을 바꾸려면 `asset/` 의 파일을 교체하고, `src/lib/stages.ts`
의 `differences` 좌표(`x`·`y`·`r`, 이미지 기준 %)를 새 이미지에 맞게 잡으면
됩니다. 개발 모드(`pnpm dev`)에서 그림을 클릭하면 콘솔에 클릭 좌표가
출력되므로 그 값으로 보정하면 됩니다. (이미지는 빌드 시 base64 로 번들에
인라인됩니다 — `asset/README.md` 참고.)

---

> 본 프로젝트는 교통안전 교육용 데모입니다.
