kotsa-spot-game-widget
v1.0.8
Published
한국교통안전공단 교통안전 틀린그림찾기 — <script> 한 줄로 임베드하는 게임 위젯
Readme
한국교통안전공단 교통안전 틀린그림찾기 — 임베드 위젯
외부 사이트에 <script> 한 줄로 삽입하는 교통안전 틀린그림찾기 게임 위젯입니다.
Vite 라이브러리 모드로 단일 JS 파일 하나(IIFE)로 빌드되며, Shadow DOM 으로
스타일이 완전히 격리되어 host 사이트와 서로 영향을 주지 않습니다.
기술 스택
- Vite 8 (라이브러리 모드 · IIFE 단일 번들)
- React 19 · TypeScript
- Tailwind CSS v4 · shadcn/ui (radix-nova)
- Shadow DOM 스타일 격리 — 별도 CSS 파일 없이 JS 한 개로 자체 완결
빌드
pnpm install
pnpm build # → dist/kotsa-spot-the-difference.js (단일 파일)개발 미리보기:
pnpm dev # 외부 사이트를 흉내 낸 index.html 로 위젯 확인npm 배포 · CDN(unpkg) 임베드
pnpm build # dist/ 생성
npm publish # prepublishOnly 가 build 를 다시 실행해 dist 를 최신화package.json의files: ["dist"]로 배포 패키지에는dist/만 포함됩니다. (.npmignore가 있어.gitignore의dist무시 규칙은 적용되지 않습니다.)- 배포 후에는 별도 호스팅 없이 unpkg 로 바로 임베드할 수 있습니다:
<script src="https://unpkg.com/[email protected]"></script>- 버전 고정(
@1.0.0)을 권장합니다. 버전을 생략하면(.../kotsa-spot-game-widget) 항상 최신 버전을 받습니다.package.json의unpkg필드가 번들 파일을 가리키므로 경로를 적지 않아도 됩니다. (jsdelivr도 동일) - npm 에 동일한 패키지 이름이 이미 있으면 게시할 수 없습니다 — 그럴 땐
name을 스코프(@조직/kotsa-spot-game-widget)로 바꾸세요.
사용 방법 (외부 사이트에서 임베드)
1) 자동 삽입 — <main> 안에 append
스크립트만 넣으면 host 페이지의 <main> 태그 안에 게임이 자동으로 삽입됩니다.
<main></main>
<script src="https://unpkg.com/[email protected]"></script>2) 특정 위치에 삽입
원하는 자리에 data-kotsa-game 요소를 두면 그 자리에 마운트됩니다.
(이 속성을 가진 요소가 하나라도 있으면 <main> 자동 삽입은 건너뜁니다.)
<div data-kotsa-game></div>
<script src="https://unpkg.com/[email protected]"></script>3) 수동 제어 (JavaScript API)
<div id="game-here"></div>
<script src="https://unpkg.com/[email protected]"></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 참고.)
본 프로젝트는 교통안전 교육용 데모입니다.
