# 이프F 바닥 콘텐츠 제작 가이드 (AI용)

> 이 문서는 **사람이 아니라 AI가 읽는 지침**이다. 사용자가 "이프F용 콘텐츠를 만들어 줘"라고 하면
> 아래 규칙을 모두 지킨 **폴더 하나**를 만들어 준다. 규칙과 사용자 요청이 부딪히면 이유를 한 줄로 말하고 규칙을 따른다.
> 기준: if-shell 런처 · 2026-11-06

## 1. 기기

| 항목 | 값 |
|---|---|
| 정체 | 바닥에 투사하고 **발로 밟아** 조작하는 Windows PC. 콘텐츠는 Chrome 전체 화면으로 돈다 |
| 화면 | **1920×1080 가로** 고정. 관람객은 서서 내려다본다 |
| 입력 | 바닥 센서가 발을 **마우스 클릭**으로 넣는다. 동시에 1명(포인터 1개)만 인식 |
| 서 있는 발 | 발을 딛고 서 있으면 **같은 자리 클릭이 약 0.1초마다 반복**된다 |
| 인터넷 | **없는 현장이 많다.** 있어도 콘텐츠의 외부 통신은 막혀 있다(§6) |
| 운영자 | 발로 콘텐츠를 고르고, 관리자가 USB로 콘텐츠를 넣는다 |

## 2. 결과물 = 폴더 하나

```
내콘텐츠/                ← 폴더 하나가 콘텐츠 하나
├─ index.html            ← 반드시 맨 위에. 런처가 이것을 연다
├─ if-content.json       ← 이름·설명 (권장)
├─ cover.jpg             ← 목록에 보이는 표지, 16:9 가로 그림 (권장)
├─ js/ css/ img/ sound/ font/   ← 필요한 것은 전부 이 안에
```

- 모든 참조는 **상대 경로**(`img/bg.png`, `./js/game.js`). `/`로 시작하는 경로, `file://`, `C:\` 금지.
- 라이브러리(Phaser, three.js 등)·글꼴도 **파일로 폴더에 넣는다.** CDN·Google Fonts 금지.
- 파일·폴더 이름은 영문 소문자·숫자·`-` 권장. 한글을 쓰면 HTML 안의 글자와 **완전히 같게**.
- 폴더 안에 `.exe`·`.bat`·`.ps1` 같은 실행 파일을 넣지 않는다. 웹 콘텐츠에는 필요 없다.
- 런처는 웹 서버(http)로 폴더를 연다. 같은 폴더 안의 파일은 `fetch`·XHR로 읽어도 된다.

### if-content.json

```json
{
  "name": "밟아서 터뜨리기",
  "sub": "공을 밟으면 터진다",
  "tag": "1인 · 반응",
  "icon": "🎈",
  "color": "#FF8A3D"
}
```

| 키 | 뜻 |
|---|---|
| `name` | 목록에 보이는 이름. **고쳐서 다시 넣을 때 같은 이름을 유지**해야 같은 콘텐츠로 덮어쓴다 |
| `sub` | 한 줄 설명 |
| `tag` | 인원·장르 (예: "2인 · 대전") |
| `icon` | 표지가 없을 때 쓰는 이모지 |
| `color` | 카드 색 `#RRGGBB` |
| `cover` | 표지 파일 경로. 없으면 맨 위의 `cover`·`thumb`·`thumbnail`·`poster`·`표지` + `.jpg/.jpeg/.png/.webp/.gif` 를 찾는다 |
| `entry` | 진입 파일이 `index.html`이 아닐 때만 (예: `"web/start.html"`) |

## 3. 입력 규칙 — 가장 중요

1. **`pointerdown` 하나로 받는다.** `click`도 들어오지만 `pointerdown`이 빠르다.
2. **같은 대상은 0.7초 동안 다시 반응하지 않게 한다.** 서 있는 발이 클릭을 반복하므로, 이것이 없으면 한 번 밟았는데 점수가 여러 번 오른다.
3. 쓰지 않는 것: 마우스 올리기(hover)·끌기(drag)·더블클릭·우클릭·키보드·스크롤·핀치. 발은 커서를 움직이지 않고 그 자리를 누르기만 한다.
4. 밟을 대상은 **지름 200px 이상**, 판정은 그림보다 넉넉하게(반지름 +30px). 대상끼리는 300px 이상 띄운다.
5. 동시에 1명만 인식된다. 여러 명이 동시에 밟으면 위치가 튄다 → "번갈아 밟기"로 설계한다. 동시 대전을 요청받으면 이 한계를 사용자에게 알린다.
6. 마우스 클릭 한 번으로 끝까지 진행할 수 있어야 한다. 이것이 PC 테스트의 기준이다.

## 4. 화면 규칙

- `#stage`를 **1920×1080px**로 설계하고 CSS `transform: scale()`로 창에 맞춰 가운데 둔다(남는 곳은 검정). 좌표는 무대 기준으로 환산한다(§9 뼈대).
- **비워 둘 자리** — 여기에 밟을 대상을 두지 않는다:
  - 왼쪽 위 140×140px: 학교용 런처의 "길게 눌러 나가기" 자리
  - 오른쪽 위 440×120px: 런처의 [다시하기] [홈으로] 버튼 자리
- 글자는 서서 내려다봐도 읽히게: 안내 48px 이상, 점수·제목 64px 이상. 진한 배경 + 밝은 글자, 큰 대비.
- `contextmenu`·`dragstart`·`selectstart`를 막고 `user-select: none`, `overflow: hidden`.

## 5. 시작과 끝

- 런처는 콘텐츠를 **자동으로 닫지 않는다.** 그래서 콘텐츠가 스스로:
  - **60초 동안 입력이 없으면 처음 화면으로** 돌아간다.
  - 게임이 끝나면 결과를 몇 초 보여 주고 처음 화면으로 돌아간다.
- `alert`·`confirm`·`prompt`, `window.open`, `window.close`, `history.back` 을 쓰지 않는다. 관람객이 닫을 방법이 없다.
- 시작 화면은 "밟아서 시작" 하나로 충분하다. 설정·메뉴를 여러 겹 두지 않는다.

## 6. 인터넷·보안

- 바닥의 콘텐츠 서버는 다음 보안 헤더를 붙인다. **인터넷이 있어도** 아래 통신은 막힌다.
  ```
  connect-src 'self' data:; form-action 'none'; frame-src 'none'; base-uri 'none'; object-src 'none'
  ```
  - 외부 주소로 가는 `fetch`·XHR·WebSocket → 막힘. 외부 API·AI API·순위표 서버를 쓸 수 없다.
  - `<iframe>`·`<form>` 제출 → 막힘.
  - 같은 폴더 파일 읽기, `data:` 주소 → 된다.
- 외부 `<script src="https://…">`·글꼴·그림은 보안 헤더가 막지는 않지만 **인터넷 없는 현장에서 비어 보인다.** 전부 폴더에 넣는다.
- 저장이 필요하면 `localStorage`만 쓴다(기기 안에만 남는다).

## 7. 소리·움직임·안전

- 소리 파일(`.mp3`·`.ogg`·`.wav`)은 폴더에 넣어 쓴다. Web Audio 합성도 된다.
- 소리는 **첫 `pointerdown`에서 켠다**(`AudioContext.resume()`). 바닥에서는 자동 재생이 허용되지만 PC 미리보기 브라우저는 막는다.
- 배경음은 작게, 효과음은 짧게. 소리 없이도 진행을 알 수 있어야 한다.
- **0.2초보다 빠른 반복 깜빡임 금지**(초당 5번 넘게 번쩍이는 것). 화면 전체가 흰색으로 번쩍이는 효과 금지. 관람객 안전 문제다.
- `requestAnimationFrame`으로 그린다. 내장 그래픽에서 60fps가 나오도록 큰 그림은 미리 줄인다. 콘솔 오류 0.
- 관람객 대상이다. 공포·폭력·부적절한 표현을 넣지 않는다.

## 8. 다 만든 뒤 확인 (AI가 사용자에게 시킬 것)

1. **반드시 http로 연다. 파일 더블클릭(`file://`)으로 열지 않는다.** `file://`에서는 되는 것과 안 되는 것이 바닥과 달라서, PC에서 된 것이 바닥에서 안 되거나 그 반대가 된다.
   - 시작 예제에 든 `serve.cmd`(Windows)·`serve.command`(맥)를 더블클릭하고 콘텐츠 폴더를 고르면, 바닥과 같은 보안 헤더로 `http://localhost:8080/?sim` 이 열린다. 없는 파일은 창에 `MISSING`으로 찍힌다.
   - AI가 직접 셸을 쓸 수 있으면 그 폴더에서 아무 정적 서버를 띄워도 된다.
2. 주소 끝 `?sim` = 발 흉내. 마우스를 **누르고 있으면** 서 있는 발처럼 클릭이 0.1초마다 반복된다. 한 번 밟았는데 두 번 이상 반응하면 §3-2를 고친다.
3. 마우스 클릭만으로 처음부터 끝까지 해 본다. 끝난 뒤 처음 화면으로 돌아오는지 본다.
4. 60초 동안 손을 떼고 처음 화면으로 돌아오는지 본다.
5. **인터넷을 끊고** 새로고침해서 빠진 그림·글꼴·소리가 없는지 본다.
6. 브라우저 개발자 도구(F12) 콘솔에 빨간 오류가 없는지 본다.

USB로 넣으면 런처가 폴더를 한 번 더 검사해 결과 카드에 알려 준다. 막지는 않는다. **검사는 보조다.** 위 확인을 대신하지 않는다.

| 결과 카드 문구 | 원인 |
|---|---|
| 실행 파일이 함께 들어 있습니다 | 폴더에 `.exe`·`.bat`·`.cmd`·`.ps1` 등 |
| 인터넷에서 불러오는 파일이 있습니다 | CDN·Google Fonts·외부 `fetch` |
| 없는 파일을 부릅니다 | `index.html`이 부르는 파일이 폴더에 없음(대소문자·한글 확인) |
| 빠르게 깜빡이는 효과가 있습니다 | 0.2초 미만 반복 애니메이션 |
| 밟기(클릭)를 받는 코드가 보이지 않습니다 | `pointerdown`·`click` 처리 없음 |
| 마우스 올리기·끌기에 기대는 것으로 보입니다 | hover·drag만 있음 |
| 표지 그림이 없습니다 (참고) | `cover.jpg` 없음 |

## 9. 뼈대

시작 예제(`hello-web` 폴더)를 받았다면 그 `index.html`의 **[게임] 블록만 고친다.** 받지 못했다면 아래 세 조각을 반드시 넣는다.

```js
// ① 무대 맞춤 + 무대 좌표
const W = 1920, H = 1080, stage = document.getElementById('stage');
let scale = 1;
function fit() { scale = Math.min(innerWidth / W, innerHeight / H);
  stage.style.transform = `translate(-50%, -50%) scale(${scale})`; }   // #stage: position:absolute; left:50%; top:50%; width:1920px; height:1080px
addEventListener('resize', fit); fit();
function toStage(e) { const r = stage.getBoundingClientRect();
  return { x: (e.clientX - r.left) / scale, y: (e.clientY - r.top) / scale }; }

// ② 발 입력 — 같은 대상 0.7초 재반응 막기
const lastHit = new WeakMap();
function canHit(t) { const n = performance.now();
  if (n - (lastHit.get(t) || 0) < 700) return false; lastHit.set(t, n); return true; }
let lastInputAt = performance.now();
addEventListener('pointerdown', (e) => { lastInputAt = performance.now(); const p = toStage(e); onStep(p.x, p.y); });

// ③ 60초 무입력 → 처음 화면
setInterval(() => { if (performance.now() - lastInputAt > 60000) { lastInputAt = performance.now(); reset(); } }, 1000);
```

## 10. 바닥에 넣기 (사용자에게 안내할 것)

**if-shell 런처(관리 화면에 [USB 가져오기] 버튼이 있는 기기)**
1. USB 맨 위에 `IF-CONTENT` 폴더를 만들고 그 안에 콘텐츠 폴더를 넣는다.
2. 기기에 USB를 꽂고 관리 화면에서 **[USB 가져오기]** → 콘텐츠 줄의 **[가져오기]**.
3. 결과 카드에서 확인할 것을 보고 **[바로 실행]**. 부팅 때 자동으로 틀려면 **[★ 부팅 시 자동 시작]**.
4. 고친 판을 다시 넣으면 "이미 있음"이 뜬다. **[덮어쓰기]** 하면 같은 자리에 판이 올라가고 직전 판은 보관된다. 그래서 `if-content.json`의 `name`을 바꾸지 않는다.

**학교용 IF-Launcher(바탕화면 IF-Launcher 아이콘 기기)**: 런처를 끄고 콘텐츠 폴더를 `C:\IFContent` 안에 통째로 복사한다. 자세한 것은 기기와 함께 받은 사용 매뉴얼을 따른다.

**실행 파일(.exe) 콘텐츠**(Unity 등)는 바로 넣지 말고 페인트팸에 먼저 확인받는다.

**원격(Claude 커넥터)으로 보낼 때**는 형식이 다르다: `index.html` 파일 **하나**, 512KB 이하, 맨 위에 `<!-- if-f-content v1 -->`, 그림·소리도 파일 안에(data: 주소·Web Audio 합성). 여러 파일 콘텐츠는 USB로만 넣는다.

## 11. 사용자가 AI에게 붙여 넣을 문장

```
이 가이드(이프F 바닥 콘텐츠 제작 가이드)를 지켜서 이프F용 콘텐츠 폴더를 만들어 줘.
주제: (여기에 주제)
인원: 1명씩 번갈아 / 대상: (예: 초등 저학년)
다 만들면 폴더 구조와, PC에서 확인하는 순서를 알려 줘.
```
