# 커뮤니티 스킨과 팩

누구나 FolderSkin을 쓰는 모든 사람과 스킨을 무료로 공유할 수 있어요. 공유하는 스킨 묶음을 **팩**이라고 하고, 스킨 하나만 있어도 하나의 팩이에요. 팩은 별도의 저장소인 [folderskin-community](https://github.com/prajwal-svm/folderskin-community)의 `packs/` 아래에 있고, 팩을 추가할 때 계정은 필요 없어요. FolderSkin에는 기본으로 들어 있는 스킨이 없어요. 스킨은 모두 팩이나 내 이미지, AI 생성 결과에서 나와요.

## 팩 추가하기

FolderSkin을 처음 열면 라이브러리를 채울 첫 팩을 추천해 줘요. 그다음부터는 앱에서 **커뮤니티**를 열면 돼요. 위쪽의 필터는 팩에 붙은 태그예요. **추가**를 누르면 팩의 스킨이 팩의 태그와 함께 라이브러리에 들어가요. 각 스킨의 ⋯ 메뉴에서 어느 팩에서 왔는지, 누가 공유했는지 볼 수 있어요. **제거**를 누르면 팩 전체를 다시 빼요. 이미 그 팩의 스킨을 쓰고 있는 폴더는 아이콘이 그대로 남아요. 아이콘은 폴더 안에 저장되기 때문이에요.

**폴더에서 추가**는 내 컴퓨터에 있는 팩 폴더로 같은 일을 해요. 팩을 공유하기 전에 미리 써 볼 때도 이 방법을 써요.

[folderskin.app](https://folderskin.app/ko/community/)의 갤러리에는 모든 팩에 **설치** 버튼이 있어요. 누르면 FolderSkin이 열리면서 커뮤니티에서 그 팩을 보여 주고 추가해요. 팩의 **추가** 버튼을 누른 것과 똑같아요(아래 [설치 링크](https://folderskin.app/ko/docs/packs/#설치-링크) 참고). **공식** 표시가 붙은 팩은 메인테이너가 보증하는 팩이에요.

팩은 통째로 추가되거나, 아예 추가되지 않거나 둘 중 하나예요. 먼저 모든 이미지를 내려받아 검사한 다음 한 번에 저장하기 때문에, 연결이 끊기거나 디스크가 가득 차도 팩이 반만 라이브러리에 남는 일은 없어요. 스킨은 팩에서 정한 순서대로 나타나요.

## 내 스킨 공유하기

공유는 앱에서 해요. FolderSkin이 팩을 커뮤니티 서비스(`community.folderskin.app`)로 보내면, 메인테이너가 거기서 검토해요. GitHub 계정은 필요 없어요. FolderSkin 0.1.6 이하에서는 GitHub에 풀 리퀘스트를 대신 열어 주는 방법도 있었지만, 0.1.7부터 이 방법은 없어졌어요.

1. 공유하고 싶은 스킨에 태그를 붙여요(⋯ → 태그). 스킨 하나만 공유하려면 ⋯ → **커뮤니티에 공유**를 쓰세요. 여러 개를 공유하려면 **커뮤니티 → 스킨 공유**를 열고 태그를 고르세요.
2. 팩 이름, 태그, 라이선스를 입력하고, 이미지의 출처를 적은 다음, 내가 공유할 권리가 있는 이미지라는 항목에 체크해요.
3. 처음 한 번은 FolderSkin이 브라우저에서 이 컴퓨터를 인증해요. 이때 쓰는 이름이 팩의 제작자로 표시돼요. 인증은 컴퓨터마다 한 번만 해요.
4. 팩을 보내요. FolderSkin은 무엇이든 컴퓨터 밖으로 나가기 전에 팩이 아래 규격에 맞는지 검사해요. **제출 내역**에서 보낸 팩을 모두 볼 수 있고, 거절된 팩은 그 이유도 볼 수 있어요.

모든 이미지는 무손실로 공유되기 때문에, 완성된 폴더의 투명한 가장자리까지 만든 그대로 보여요. FolderSkin은 보내기 전에 이미지를 하나씩 무손실 WebP로 바꿔요. 이미지 한 장에 몇 초씩 걸리고, 준비된 이미지 수를 세면서 진행해요. 1024px에서 1.5MB에 들어가지 않을 만큼 정밀한 이미지는 무손실 그대로 896px로, 그래도 안 되면 768px로 줄이고, 어떤 이미지를 줄였는지 알려 줘요. 팩 하나의 이미지는 모두 합쳐 64MB까지예요. 그보다 크면 거절되고, 팩을 두 개로 나누라는 제안이 함께 나와요.

모든 팩은 다른 사람에게 보이기 전에 사람이 직접 살펴봐요. 승인되면 15분 안팎으로 자동 공개돼요(아래 [승인하면 공개되는 과정](https://folderskin.app/ko/docs/packs/#승인하면-공개되는-과정) 참고). 이름이 같은 팩이 여러 개 있어도 괜찮아요. 내가 고른 이름이 그대로 모두에게 보이고, 팩에는 따로 고유한 ID가 붙어요([팩 ID](https://folderskin.app/ko/docs/packs/#팩-id)).

팩은 직접 추가할 수도 있어요. [folderskin-community](https://github.com/prajwal-svm/folderskin-community)에 `packs/` 아래 폴더 하나를 추가하는 풀 리퀘스트를 보내면 돼요. 폴더는 `packs make`로 만들어요([이미지로 팩 만들기](https://folderskin.app/ko/docs/packs/#이미지로-팩-만들기)). 그러면 생성된 ID가 붙고, 풀 리퀘스트에서는 앱과 똑같은 검사가 실행돼요. 앱의 **폴더로 저장**을 써도 아래 규칙을 모두 지키는 팩 폴더를 만들 수 있어요.

## 팩 ID

모든 팩에는 ID가 있어요. ID는 팩 폴더의 이름이고, 팩으로 가는 모든 링크에 들어가요. ID는 팩을 만들 때 한 번, 이름과 무작위 문자 6개로 만들어요. 예를 들어 Classic Art라는 팩은 `classic-art-k7q2mx` 같은 ID를 받아요. 이름은 얼마든지 겹쳐도 되기 때문에 Classic Art라는 팩이 100개 있어도 괜찮고, 고유해야 하는 건 ID뿐이에요. 직접 만드는 팩의 ID는 `packs make`가, 앱에서 공유한 팩의 ID는 커뮤니티 서비스가 만들어요. 한번 정해진 ID는 팩 이름이 바뀌어도 바뀌지 않아요.

무작위 부분은 `a`부터 `z`까지, `2`부터 `7`까지의 문자 중 6개로, 시스템의 안전한 난수 소스에서 뽑아요. 새 ID는 `packs/`에 있는 폴더 이름이나 `moved.json`에 있는 옛 ID와 절대 겹치지 않아요.

### moved.json

ID를 생성하기 전에 만든 팩은 `classic-art`처럼 이름만으로 만든 ID를 갖고 있었어요. 이런 팩은 `packs rename`으로 생성된 ID를 새로 받았고, `packs/` 옆에 있는 `moved.json`에 각 옛 ID와 그 팩의 현재 ID가 기록돼 있어요.

```json
{ "version": 1, "moved": { "classic-art": "classic-art-k7q2mx" } }
```

- 옛 ID는 모두 `packs/`의 어떤 폴더도 쓰지 않는 팩 ID이고, 새 팩에 주어지는 일도 없어요.
- 새 ID는 모두 `packs/`에 있는 팩을 가리켜요. 다른 옛 ID로 이어지는 일은 없어요. 팩 ID를 다시 바꾸면, 그 팩으로 이어지던 것이 모두 가장 새로운 ID를 가리키도록 바뀌어요.
- `packs index`와 `packs catalog`는 이 대응표를 `index.json`과 `head.json`에 `"moved"`로 복사해요. 그래서 앱, 웹사이트, 커뮤니티 서비스가 옛 ID로 팩을 찾아갈 수 있어요. 0.1.7부터 앱은 옛 ID로 추가한 팩을 새 ID로 옮기고, 옛 ID가 든 설치 링크로도 팩을 찾을 수 있어요.
- ID를 바꾼 팩도 처음 공개된 날짜를 그대로 유지해요. `packs index`는 그 팩의 ID 중 하나라도 추가한 가장 이른 커밋을 기준으로 날짜를 매겨요.
- `pack.json`에는 아무것도 넣지 않아요. `pack.json`은 규격에 없는 필드를 받지 않고, 넣으면 앱 0.1.4부터 0.1.6까지는 그 팩을 거절해요.

### packs rename

```sh
cargo run -p folderskin-tools -- packs rename --dir ../folderskin-community --all
cargo run -p folderskin-tools -- packs rename --dir ../folderskin-community classic-art
cargo run -p folderskin-tools -- packs rename --dir ../folderskin-community classic-art --to classic-art-k7q2mx
```

folderskin-community의 git 체크아웃 안에서 팩에 생성된 ID를 붙여요. 폴더는 각각 `git mv`로 옮기기 때문에 히스토리도 함께 따라가요. `featured.json`과 `official.json`은 순서를 그대로 둔 채 새 ID로 다시 쓰이고, 모든 이동은 `moved.json`에 기록돼요(파일이 없으면 새로 만들어요). 모든 변경은 스테이징되어 바로 커밋할 수 있는 상태가 돼요.

`--all`은 ID가 생성된 형식이 아닌 팩을 모두 바꾸고, 나머지는 그대로 둬요. 판단은 ID의 모양만 보고 해요. 그래서 `data-structures-in-bricks`처럼 마지막 단어가 우연히 여섯 글자인 옛 ID는 생성된 ID처럼 보이니, 그런 팩은 ID를 직접 지정해서 바꾸세요. ID를 지정한 팩은 지금 ID가 무엇이든 새로 생성된 ID로 옮겨지고, `--to`를 쓰면 그 ID로 옮겨져요. 이때 `--to`의 값은 생성된 형식의 ID이면서, 지금도 예전에도 어떤 팩도 쓴 적이 없는 ID여야 해요. 다시 실행해도 아무것도 바뀌지 않아요. 이미 옮긴 팩은 `moved.json`에서 찾아서, 이미 옮겼다고 알려 줘요.

## 승인하면 공개되는 과정

팩을 승인하면 그대로 공개돼요. 누가 손으로 파일을 복사하는 일은 없어요.

1. 메인테이너가 팩을 승인하면 커뮤니티 서비스가 팩에 ID를 붙여요. 서비스에 GitHub 토큰(`GITHUB_DISPATCH_TOKEN`)이 있으면, `pack-approved` repository dispatch로 folderskin-community의 Packs 워크플로도 곧바로 시작해요.
2. 토큰이 없으면 워크플로가 직접 팩을 찾아요. 15분마다 `https://community.folderskin.app/v1/exports/pending`에 승인된 채 기다리는 팩이 몇 개인지 물어보고(몇 초면 끝나요), 기다리는 팩이 있을 때만 나머지 작업을 해요. 기다리는 팩이 있든 없든 매일, 매주 실행되고, 메인테이너가 직접 시작할 때도 실행돼요.
3. `community pull --no-done`이 승인된 팩을 하나씩 `packs/`에 쓰고, 모든 파일의 크기와 SHA-256을 업로드할 때 서비스가 기록한 값과 대조해요. 서비스가 준 ID는 생성된 형식이어야 하고, 이미 있는 폴더를 덮어쓰거나 번호를 붙여 다른 이름으로 만드는 일은 없어요. 팩의 완성된 폴더는 검사 전에 한 가지 모양으로 맞춰요([팩의 폴더를 한 가지 모양으로](https://folderskin.app/ko/docs/packs/#팩의-폴더를-한-가지-모양으로)). 그 모양에서 너무 벗어난 폴더는 그대로 두고, 실행 로그에 경고로 남겨요.
4. `packs check`가 풀 리퀘스트 때와 똑같이 모든 팩을 검사해요.
5. 각 팩은 github-actions[bot]이 `Add the <name> pack`이라는 메시지로 커밋하고 `main`에 푸시해요.
6. 그다음에야 `community done`이 서비스에 팩이 공개됐다고 알려요. 검사나 푸시에 실패한 팩은 서비스에서 계속 기다리고, 다음 실행 때 다시 시도해요.
7. 같은 실행에서 `index.json`, 미리 보기, `v2/`를 다시 만들고, `v2/`를 미러로 복사한 다음([아래](https://folderskin.app/ko/docs/packs/#미러) 참고) 커밋해요. 워크플로 자체 토큰으로 한 푸시는 다른 워크플로를 시작하지 않기 때문에, 이 모든 걸 한 번의 실행에서 처리해요.

그래서 팩은 승인 후 15분 안팎이면 공개되고, 서비스가 워크플로를 직접 시작하면 몇 분 안에 공개돼요. 실패한 실행은 아무것도 공개하지 않고, GitHub이 워크플로가 실패했다는 이메일을 메인테이너에게 보내요. GitHub은 바쁠 때 예약 실행을 늦게 시작할 수 있고, 60일 동안 활동이 없는 저장소에서는 예약을 꺼 버려요. repository dispatch는 이 두 가지 모두와 상관없어요.

워크플로에는 메인테이너의 서명 키, 즉 `community keygen`이 만든 파일 전체가 저장소 시크릿 `FOLDERSKIN_ADMIN_KEY`로 있어야 해요. 없으면 승인된 팩은 서비스에서 계속 기다리고, 실행 로그에도 그렇게 표시돼요. 저장소 변수 `REQUIRE_GENERATED_IDS`를 `true`로 설정하면, 모든 검사에서 생성된 ID가 없는 팩을 거절해요.

직접 가져오는 방법도 여전히 쓸 수 있어요. `community pull`은 팩을 쓰고 나서 곧바로 서비스에 알려요. 실행한 사람이 쓴 내용을 커밋한다고 보기 때문이에요. `--no-done --pulled pulled.json`을 붙이면 `community done --from pulled.json`을 실행할 때까지 알림을 미뤄요. 워크플로는 이 방식으로 실행해요. 서비스에 두 번 알려도 문제는 없어요.

### 미러

앱은 전체 트리를 `https://packs.folderskin.app`에서 읽을 수 있어요. 이곳은 저장소와 같은 `v2/`를 담고 있는 Cloudflare R2 버킷이에요. 버킷에 쓰는 건 커뮤니티 서비스라서, 워크플로에는 따로 Cloudflare 토큰이 필요 없어요. `community mirror`가 각 파일을 메인테이너의 키로 서명해서 서비스를 통해 보내요.

```sh
cargo run -p folderskin-tools -- community mirror --tree ../folderskin-community \
  --public https://packs.folderskin.app --api https://community.folderskin.app --key ~/folderskin-admin.key
```

모든 파일에 대해 미러에 HEAD 요청을 보내고, 이미 같은 길이로 제공되고 있는 파일은 그대로 둬요. `head.json`을 제외한 모든 파일 이름은 내용의 해시이기 때문이에요. 나머지 파일은 `PUT /v1/admin/tree/<path>`로 올리고, 버킷이 확인할 수 있도록 각 파일의 SHA-256을 `X-Content-SHA256`에 담아 보내요. `head.json`은 다른 파일이 모두 올라간 뒤 마지막에 보내기 때문에, 미러가 갖고 있지 않은 카탈로그를 가리키는 일은 없어요. 시간이 지나면 통과할 수도 있는 요청(응답 없음, 5xx, 429)은 대기 시간을 1초부터 두 배씩 늘리며 모두 다섯 번까지 시도하고, `Retry-After`는 30초까지 따라요. 워크플로는 커밋하기 전에 이 명령을 실행하므로, GitHub의 `head.json`은 거기 적힌 모든 것이 미러에 올라간 다음에야 바뀌어요. 미러는 저장소 변수 `COMMUNITY_MIRROR_URL`로 켜고, `head.json`에는 미러가 켜져 있는 동안에만 미러가 들어가요.

## 팩 규격

팩은 폴더 하나예요.

```
packs/night-prints-h4x2qe/
  pack.json
  koi.webp
  fox-in-the-rain.webp
```

`pack.json`은 이렇게 생겼어요.

```json
{
  "version": 1,
  "name": "Night prints",
  "author": "your-github-name",
  "license": "CC0-1.0",
  "tags": ["woodblock", "night"],
  "skins": [
    { "file": "koi.webp", "name": "Koi over the wave", "tags": ["animals"] },
    { "file": "fox-in-the-rain.webp", "name": "Fox in the rain" }
  ]
}
```

| 필드 | 규칙 |
|---|---|
| `version` | `1` |
| `name` | 1~40자 |
| `author` | 내 GitHub 사용자 이름 |
| `license` | `CC0-1.0`, `CC-BY-4.0`, `MIT` 중 하나 |
| `tags` | 태그 1~5개. 팩의 모든 스킨에 붙고, 첫 번째 태그가 모두의 필터에서 이 팩을 나타내요 |
| `skins` | 항목 1~50개 |
| `skins[].file` | 폴더 안의 이미지 |
| `skins[].name` | 1~60자 |
| `skins[].tags` | 선택 사항. 그 스킨에만 최대 3개까지 더 붙일 수 있어요 |

다른 필드는 쓸 수 없어요. 그래서 `"tag"` 같은 오타는 무시되지 않고 검사에서 걸려요.

### 제한

| | 제한 |
|---|---|
| 팩의 스킨 수 | 1~50개 |
| 이미지 한 장 | 무손실(PNG 또는 무손실 WebP). 최대 1.5MB |
| 팩의 이미지 합계 | 최대 64MB |
| 이미지 변의 길이 | 256~1024px |
| 파일 이름 | 영문자, 숫자, `.`, `-`, `_`만 쓰고 `.png`나 `.webp`로 끝나야 해요 |
| ID(폴더 이름) | 이름과 무작위 문자 6개. 예: `night-prints-h4x2qe`([팩 ID](https://folderskin.app/ko/docs/packs/#팩-id)). 소문자 영문자와 숫자로 된 단어를 하이픈 하나로 이어 붙이고, 최대 40자 |
| 태그 | 소문자 영문자, 숫자, 공백, 하이픈으로 최대 24자 |
| `pack.json` | 최대 64KB |

50개와 64MB인 이유는, 팩이 주제가 있는 세트이고 팩을 추가하는 사람은 전부를 내려받기 때문이에요. 스킨 50개면 검토도 금방 끝나고, 64MB면 한 장에 1.3MB인 이미지 50장, 가장 큰 크기라면 42장이 들어가요.

FolderSkin 0.1.7 이전에 공개된 팩은 PNG, JPEG 또는 모든 종류의 WebP로 한 장에 2MB까지라는 기준으로 만들어졌고, 앱은 지금도 이 팩들을 읽을 수 있어요. `packs make`로 다시 만들면 위 규칙을 따르게 돼요. `packs check`에 `--require-lossless`를 붙이면 팩을 이 규칙으로 검사하고([팩 직접 검사하기](https://folderskin.app/ko/docs/packs/#팩-직접-검사하기)), 커뮤니티 서비스는 이 규칙에 맞는 팩만 받아요.

### 이미지

이미지는 두 종류 중 하나이고, 창에 끌어다 놓은 이미지와 같은 방법으로 구분해요.

- **완성된 폴더**: 투명한 배경이나 단색 마젠타 `#FF00FF` 배경 위에 그린 폴더예요. 마젠타는 FolderSkin이 잘라 내요. 그린 그대로 아이콘이 돼요.
- **그 밖의 모든 이미지**: FolderSkin의 폴더에 씌워져요. 폴더가 이미지의 어디를 잘라 내는지는 [SKINS.md](https://folderskin.app/ko/docs/skins/)에 나와 있으니, 주제가 잘리지 않게 할 수 있어요.

세 운영체제 중 어디서든 가장 큰 아이콘은 1024px이라서, 그보다 큰 이미지는 아무 의미가 없어요.

모든 이미지는 무손실이라서 팩은 만든 그대로 보여요. 그라데이션에 블록 노이즈가 생기지 않고, 글자 주변에 링잉이 생기지 않으며, 완성된 폴더의 가장자리도 그린 그대로 깔끔해요. 무손실 WebP는 같은 이미지의 PNG보다 3분의 1 정도 작아서, 앱과 `packs make`는 WebP로 저장해요. 정밀한 1024px 이미지는 0.6MB에서 1.5MB 사이이고, 대부분은 800KB 정도예요. 1.5MB에 들어가지 않는 이미지는 흐리게 만들어 욱여넣지 않고, 무손실 그대로 896px, 그다음 768px로 줄여요.

### 팩의 폴더를 한 가지 모양으로

FolderSkin은 완성된 폴더 이미지 전체를 아이콘에 맞춰 넣어요. 하나씩 만든 폴더는 저마다 자기 윤곽에 딱 맞게 잘려 나오고, 가로세로 비율이 똑같은 렌더링은 하나도 없어요. 어떤 팩은 폭이 높이의 1.03배에서 1.30배까지 제각각이었어요. Finder에서 나란히 놓으면 납작한 폴더가 길쭉한 폴더보다 작아 보였죠. 그래서 팩 안의 완성된 폴더는 한 가지 모양으로 맞춰요.

- **팩의 모양**은 각 폴더 모양의 중앙값이에요. 폴더는 이미지에서 불투명도가 절반을 넘는 부분의 폭 ÷ 높이로 재기 때문에, 부드러운 가장자리나 희미한 그림자는 치지 않아요.
- **각 폴더를 정확히 그 모양으로 다시 그려요**. 폴더 부분만 잘라 낸 다음, 폭은 962px(1024px 템플릿에서 FolderSkin 자체 폴더의 폭으로, `folderskin-tools template`으로 볼 수 있어요), 높이는 그 모양에 맞는 값으로 크기를 바꿔요. 그런 다음 투명한 1024 × 1024 이미지 안에서 템플릿의 폴더와 같은 자리, 즉 같은 왼쪽 가장자리와 같은 기준선 위에 놓고 무손실 WebP로 저장해요. PNG는 이름이 같은 `.webp`가 되고, `pack.json`도 그에 맞게 바뀌어요.
- **모양은 최대 8%까지만 바꿔요**. 그 정도는 아무도 알아채지 못해요. 그보다 많이 바꿔야 하는 폴더는 *이상치*예요. 그만큼 늘리면 글자와 얼굴이 찌그러져 보이기 때문에 모양을 바꾸지 않아요. 이상치를 어떻게 처리할지는 명령에 따라 다르고, 결정은 사람이 해요.
- **아트워크는 건드리지 않아요**. FolderSkin이 자체 폴더에 씌우기 때문에 맞출 모양이 애초에 없어요. 완성된 폴더가 하나뿐인 팩도 마찬가지예요.

`packs make`는 만드는 모든 팩에, `community pull`은 가져오는 모든 팩에, `packs normalize`는 이미 `packs/`에 있는 팩에 이 작업을 해요. 두 번 해도 달라지는 건 없어요. 이미 팩의 모양으로 제자리에 있는 폴더는 다시 그리지 않고, 쓰려는 내용이 이미 들어 있는 파일은 다시 쓰지 않아요.

| 명령 | 이상치 처리 |
|---|---|
| `packs make` | 팩에서 빼고 목록에 표시해요. `--keep-outliers`를 붙이면 그대로 남겨요 |
| `packs normalize` | 목록에 표시하고 그대로 둬요. `--drop-outliers`를 붙이면 `pack.json`에서 빼고 이미지도 지워요 |
| `community pull` | 그대로 두고 경고를 출력해요(Packs 워크플로 로그에 표시돼요). 누군가 공유한 스킨이 사람의 결정 없이 빠지는 일은 없어요 |

```sh
cargo run -p folderskin-tools -- packs normalize --dir ../folderskin-community
cargo run -p folderskin-tools -- packs normalize --dir ../folderskin-community classic-art-5rxas2 --tolerance 0.3
cargo run -p folderskin-tools -- packs normalize --dir ../folderskin-community dreamscapes-ppfia6 --drop-outliers
```

`packs normalize`는 모든 팩, 또는 지정한 팩을 차례로 처리해요. 팩마다 모양과 다시 그린 폴더를 보여 주고, 이상치는 얼마나 벗어났는지와 함께 모두 알려 줘요. `packs check`를 통과하지 못하는 팩은 통과할 때까지 손대지 않아요. `--tolerance`는 8%라는 값을 바꿔요. Classic Art의 모나리자처럼, 이상치라도 팩의 모양에 맞추고 싶을 때 써요. `--drop-outliers`는 메인테이너용이에요. 이것 말고는 스킨을 지우는 건 아무것도 없어요. `packs check --require-one-shape`는 폴더끼리 1% 넘게 차이 나는 팩을 거절해요([팩 직접 검사하기](https://folderskin.app/ko/docs/packs/#팩-직접-검사하기)). 한 가지 모양으로 다시 그린 폴더는 거절되는 일이 없어요.

### 라이선스

공유하는 스킨에는 크리에이티브 커먼즈나 MIT 라이선스를 써요.

- `CC0-1.0`: 누구나 어떤 목적으로든 쓸 수 있어요. 대부분의 스킨은 AI로 만들고, CC0는 권리를 가장 적게 주장하기 때문에 이게 기본값이에요.
- `CC-BY-4.0`: 누구나 쓸 수 있고, 쓰는 사람은 나를 만든 사람으로 표시해요.
- `MIT`: 누구나 쓸 수 있고, 내 이름이 함께 남아요.

직접 만들었거나 공유해도 되는 이미지만 공유하세요.

## 이미지로 팩 만들기

`packs make`는 이미지 모델에서 저장한 렌더링 같은 이미지 폴더를, folderskin-community 체크아웃의 `packs/` 아래에 검사를 바로 통과하는 팩으로 만들어요. 아래 명령은 이 저장소에서, 옆에 folderskin-community를 체크아웃한 상태로 실행해요.

```sh
cargo run -p folderskin-tools -- packs make ~/Downloads/3d-renders --dir ../folderskin-community \
  --name "3D" --tags 3d,glossy --author your-github-name --preview /tmp/3d.png
```

팩에는 이름과 무작위 문자 6개로 된 `3d-k7q2mx` 같은 고유 ID가 붙고, 이게 폴더 이름이 돼요. ID는 보고서에 나와요. `packs/`에 있는 폴더 이름이나 `moved.json`의 옛 ID와 겹치는 일은 없어요.

이미지는 앱에서 이미지를 추가할 때와 똑같이 나뉘어요. [PROMPTS.md](https://folderskin.app/ko/docs/prompts/)의 채팅 프롬프트대로 마젠타 위에 그렸거나 실제로 투명한 배경 위에 그린 완성된 폴더는 잘라 내서 그대로 아이콘으로 써요. 그 밖의 이미지는 FolderSkin 폴더에 쓰일 아트워크가 돼요. 모든 이미지는 1024px로 줄여 무손실 WebP로 저장하는데, 앱이 팩을 공유할 때 쓰는 인코더가 내장되어 있어서 따로 설치할 건 없어요. 그래도 1.5MB가 넘는 이미지는 896px, 그다음 768px로 줄이고, 보고서에 그렇게 표시해요. `--max-kb`를 쓰면 이미지를 1.5MB보다 더 작게 제한할 수 있어요. 이미지를 모두 합쳐 64MB가 넘으면 거절하고, 팩을 두 개로 나누라고 제안해요. libwebp의 가장 꼼꼼한 설정은 이미지 한 장에 몇 초가 걸리기 때문에, 모든 코어에서 한꺼번에 처리해요. 형식에 대한 자세한 내용은 [SKINS.md](https://folderskin.app/ko/docs/skins/#팩에-넣을-이미지)에 있어요. 각 이미지가 어느 쪽으로 처리됐는지는 보고서에 나와요. 스킨 이름은 파일 이름에서 따오니, 파일 이름을 먼저 정리하거나 나중에 `pack.json`에서 이름을 고치세요. `--preview`를 쓰면 모든 스킨을 폴더 모양으로 PNG 한 장에 그려 주니 한눈에 확인할 수 있어요.

완성된 폴더가 둘 이상이면 한 가지 모양으로 맞추고([팩의 폴더를 한 가지 모양으로](https://folderskin.app/ko/docs/packs/#팩의-폴더를-한-가지-모양으로)), 어떤 폴더를 다시 그렸는지 보고서에 표시해요. 다른 폴더의 모양에서 8% 넘게 벗어난 폴더는 팩에서 빼고, 얼마나 벗어났는지 보고서에 표시해요. `--keep-outliers`를 붙이면 빼지 않고 그대로 둬요. 이상치를 남기지 않고 만든 팩은 `packs check --require-one-shape`를 통과해요.

`--id`를 쓰면 이미 있는 팩을 새 이미지로 다시 만들 수 있어요. `--id 3d-k7q2mx`는 `packs/3d-k7q2mx/`의 내용을 모두 바꾸고, 팩의 ID는 그대로 유지되기 때문에 이 팩을 추가한 모든 사람이 새 버전을 업데이트로 받아요. 새 폴더는 먼저 다른 곳에서 만들어 검사하고, 통과할 때만 옛 폴더를 대신해요. `--id` 없이 실행하면 `packs make`는 항상 새 팩을 만들어요.

이미지 모델에 `#FF00FF`를 요청해도 대신 라즈베리색이나 핫핑크로 고르게 칠하는 경우가 많아요(Classic Art 팩을 만들 때 Grok이 그랬어요). `--flat-backdrop`은 어떤 색이든 단색 배경이면 잘라 내요. 이미지마다 배경색을 직접 재고, 가장자리까지 이어진 부분만 지우고(그래서 폴더 안의 빨간 망토는 남아요), 부드러운 그림자도 함께 지운 다음, 가장자리에는 분홍 테두리 대신 그림의 색을 입혀요. 무늬 없는 회색이나 검은 배경에서는 그 배경 자체의 노이즈 범위 안에서만 지우고 위쪽으로 번지지 않기 때문에, 폴더 가장자리에 닿은 어두운 코트나 잉크 선을 배경으로 착각하지 않아요. 나중에 `--preview` 시트를 확인하세요(밝은 회색 위에 그리기 때문에 구멍이 있으면 보여요). 단색 배경이 없는 이미지는 여전히 아트워크로 처리돼요.

이미지 한 장이 앱에서 어떻게 보일지 보려면, `render`로 폴더 모양으로 그려 보세요.

```sh
cargo run -p folderskin-tools -- render ../folderskin-community/packs/3d-k7q2mx/glass.webp --out /tmp/glass.png --size 512
```

## 팩 직접 검사하기

이 저장소에서, 옆에 folderskin-community를 체크아웃한 상태로 실행해요.

```
cargo run -p folderskin-tools -- packs check --dir ../folderskin-community
```

`packs/`의 모든 폴더를 앱과 같은 규칙으로 검사하고, 문제가 있으면 하나하나 문장으로 알려 줘요. folderskin-community 체크아웃 안에서 실행하면 `--dir`은 생략해도 돼요. 도구가 기본으로 현재 폴더를 보기 때문이에요. 이미지는 한 장에 2MB(0.1.7 이전에 공개된 팩의 기준)까지인지, 팩 하나의 이미지는 합쳐서 64MB까지인지 검사해요. `--require-lossless`를 붙이면 새 팩이 따르는 규칙으로 모든 이미지를 검사해요. PNG 또는 무손실 WebP에 최대 1.5MB라는 규칙으로, 커뮤니티 서비스가 받고 `packs make`가 만드는 형식이에요. 예전 팩을 모두 다시 만들 때까지는 따로 요청하지 않으면 꺼져 있어요. `--max-kb`를 쓰면 이미지를 더 작게 제한할 수 있어요.

ID끼리의 관계도 검사해요. 대소문자만 다른 두 폴더 이름은 macOS와 Windows에서 같은 폴더가 돼 버려요. 또 팩은 `moved.json`에 있는 옛 ID를 쓸 수 없고, `moved.json` 자체도 정해진 규칙을 따라야 해요([moved.json](https://folderskin.app/ko/docs/packs/#movedjson)). 이름은 겹쳐도 되기 때문에 비교하지 않아요. `--require-generated-ids`는 ID가 생성된 형식이 아닌 팩을 모두 거절해요. 따로 요청하지 않으면 꺼져 있고, folderskin-community의 워크플로는 변수 `REQUIRE_GENERATED_IDS`가 `true`일 때 이 옵션을 켜요.

`--require-one-shape`는 완성된 폴더의 모양이 하나로 맞지 않는 팩, 즉 폭 ÷ 높이가 1% 넘게 차이 나는 폴더가 두 개 있는 팩을 거절해요. 한 가지 모양으로 다시 그린 폴더는 항상 이 범위 안에 있으므로, 모양을 한 번도 맞추지 않은 팩이나 누군가 남겨 둔 이상치를 잡아낼 수 있어요. 오류에는 가장 많이 차이 나는 두 폴더의 이름과 실행할 명령이 나와요([팩의 폴더를 한 가지 모양으로](https://folderskin.app/ko/docs/packs/#팩의-폴더를-한-가지-모양으로)). 따로 요청하지 않으면 꺼져 있고, folderskin-community의 워크플로를 위한 옵션이에요.

## 앱이 팩을 읽는 방식

커뮤니티에는 **목록** 보기와 **갤러리** 보기가 있고, 팩에서 **보기**를 누르면 그 팩이 열려요. 아무것도 추가하기 전에 모든 스킨이 이름과 함께 폴더에 입혀진 모습으로 보여요. **새로 고침**을 누르면 목록을 다시 읽어요. 추가한 뒤에 바뀐 팩에는 **업데이트**가 표시되고, 누르면 스킨이 새 버전으로 바뀌어요. 폴더의 아이콘은 그대로 남고, 두 버전에 모두 있는 이미지를 즐겨찾기해 두었다면 즐겨찾기도 그대로 남아요.

- folderskin-community의 `index.json`에는 모든 팩이 나열돼 있어요. ID, 이름, 제작자, 라이선스, 태그, 스킨 수, 그리고 정확한 내용(`pack.json`과 모든 이미지)의 해시예요. 앱은 추가한 스킨과 함께 이 해시를 저장해 두고, 이걸로 팩에 업데이트가 있는지 알아요. 각 항목에는 팩이 처음 공개된 시각(`"added"`. Unix 초 단위로, 첫 ID로 `pack.json`을 추가한 커밋의 시각)과, `official.json`에 있는 팩이면 `"official": true`도 들어 있어요. 팩 목록 옆의 `"moved"`는 [moved.json](https://folderskin.app/ko/docs/packs/#movedjson)이에요. `index.json`은 `folderskin-tools packs index`가 만들고, 이때 팩의 처음 스킨 4개를 폴더 모양으로 나란히 그린 `previews/<id>.png`도 함께 만들어요. 둘 다 folderskin-community의 `main`에서 생성되니 절대 직접 고치지 마세요.
- `packs catalog`가 만드는 `v2/`는 같은 팩들을 앱이 내 컴퓨터에서 검색하는 카탈로그 형태로 담은 거예요. 여기 있는 `head.json`은 현재 카탈로그를 가리키고, `featured`와 `official` 팩을 나열하고, `moved`를 담고, `https://packs.folderskin.app`처럼 같은 트리를 제공하는 미러를 나열해요([미러](https://folderskin.app/ko/docs/packs/#미러)). 앱은 각 파일을 먼저 미러에서 가져오고, 실패하면 GitHub에서 가져와요. 어느 쪽이든 모든 파일을 해시로 확인해요. 0.1.7부터는 `head.json` 자체도 먼저 `https://packs.folderskin.app`에서 읽어요.
- 앱은 팩을 추가할 때만 팩의 이미지를 내려받아요. 한 번에 네 장씩 받으면서 몇 장이 도착했는지 보여 줘요. 모든 이미지를 위의 제한에 비추어 검사하고, 하나라도 통과하지 못하면 아무것도 저장하지 않아요. 모두 통과하면 한꺼번에 저장하기 때문에 팩이 반만 추가되는 일은 없어요.
- `FOLDERSKIN_COMMUNITY_URL`을 설정하면 앱이 folderskin-community의 다른 사본을 보게 돼요. 예를 들어 체크아웃의 루트에서 `python3 -m http.server`로 서버를 띄우고 이 변수를 `http://localhost:8000`으로 설정하면, 팩을 처음부터 끝까지 시험해 볼 수 있어요.

`index.json`과 `head.json`에는 시간이 지나면서 필드가 늘어나지만, 어떤 버전의 앱이든 아는 필드만 읽고 나머지는 건너뛰어요. `pack.json`은 그 반대예요. 규격에 없는 필드를 받지 않기 때문에, 앞으로도 아무것도 추가할 수 없어요. 팩에 관한 새로운 정보는 인덱스에 넣어요.

## 추천 팩과 공식 팩

folderskin-community의 루트에는 `packs/` 옆에 목록 두 개가 있고, 메인테이너만 이 목록을 고쳐요. 둘 다 `["classic-art-k7q2mx", "colours-a2b3c4"]` 같은 팩 ID의 JSON 목록이에요.

| 파일 | 역할 |
|---|---|
| `featured.json` | 첫 실행 때 추천하고 커뮤니티에서 맨 먼저 보여 주는 팩(이 순서대로) |
| `official.json` | 커뮤니티, 팩 뷰어, 웹사이트에서 **공식** 표시가 붙는 팩 |

두 파일 모두 없어도 돼요. 모든 ID는 `packs/`에 있는 팩이어야 하고, 두 번 나온 ID는 한 번으로 쳐요. 둘 중 하나라도 없는 팩을 가리키면 `packs index`와 `packs catalog`는 멈추고 아무것도 쓰지 않아요. 그래서 ID를 바꾸거나 지운 팩이 빈자리로 남는 일은 없어요. 풀 리퀘스트 검사에서 `packs catalog`를 실행하기 때문에 병합 전에 잡아낼 수 있어요. `packs rename`은 두 목록을 알아서 고쳐 줘요. folderskin-community의 Packs 워크플로는 `paths`에 있는 파일이 바뀌면 인덱스를 다시 만들어요. 그래서 두 목록은 `packs/**`와 함께 `paths`에 들어 있어야 하고, `moved.json`도 마찬가지예요.

## 설치 링크

`folderskin://install?pack=<id>`를 열면 FolderSkin이 커뮤니티에서 `<id>` 팩을 보여 주고 추가해요. 팩의 **추가** 버튼과 똑같이 동작하고, 진행 표시와 마지막 메시지도 같아요. 먼저 창이 맨 앞으로 나와요. GitHub에 다시 물어봐도 커뮤니티에 그 ID의 팩이 없으면, FolderSkin이 그렇다고 알려 주고 검색해 보라고 제안해요. 이미 라이브러리에 있는 팩이면 팩을 열고 이미 있다고 알려 줘요. 0.1.7부터는 옛 ID가 든 링크를 열면 옮겨 간 팩이 열려요([moved.json](https://folderskin.app/ko/docs/packs/#movedjson)).

FolderSkin은 정확히 다음 형식인 링크만 받아요. 스킴은 `folderskin`이고, `install`이 호스트(`folderskin://install?…`)이거나 경로 전체(`folderskin:install?…`)여야 해요. 사용자, 비밀번호, 포트는 없어야 하고, `pack`이 정확히 하나 있어야 하며, 그 값은 팩 ID(소문자 영문자와 숫자로 된 단어를 하이픈 하나로 이어 붙인 최대 40자)여야 해요. 다른 매개변수는 건너뛰고, 그 밖의 링크는 무시해요.

스킴은 설치 프로그램이 등록해요. macOS는 앱의 `Info.plist`, Windows는 설치 프로그램, Linux는 `.deb`와 `.rpm`의 데스크톱 항목이에요. AppImage는 설치하는 과정이 없어서 실행할 때 직접 등록해요. Windows와 Linux에서는 링크를 열면 두 번째 FolderSkin이 실행되어 이미 실행 중인 FolderSkin에 링크를 넘기고 종료하기 때문에, 항상 하나만 실행돼요.

시험해 보려면:

| | |
|---|---|
| macOS | 앱을 빌드하고(`pnpm tauri build --bundles app`) `target/release/bundle/macos/FolderSkin.app`을 한 번 열어 macOS에 스킴을 등록해요(`/Applications`에 복사한 사본이 가장 확실해요). 그런 다음 `open 'folderskin://install?pack=classic-art'`를 실행해요. macOS는 번들로 만든 앱에만 링크를 보내기 때문에 `pnpm tauri dev`는 링크를 받지 못해요 |
| Windows | 빌드를 설치하거나 `pnpm tauri dev`를 실행해요(개발 빌드는 스스로 스킴을 등록해요). 그런 다음 명령 프롬프트에서 `start "" "folderskin://install?pack=classic-art"`를 실행하거나, 실행 창(Windows+R)에 같은 링크를 입력해요 |
| Linux | `.deb`나 `.rpm`을 설치하거나, AppImage를 한 번 실행하거나, `pnpm tauri dev`를 실행해요. 그런 다음 `xdg-open 'folderskin://install?pack=classic-art'`를 실행해요 |
| 브라우저 미리 보기 | `pnpm dev`를 실행하고 `http://localhost:14200/?install=classic-art`를 열어요 |

Windows나 Linux에서 `pnpm tauri dev`를 실행하기 전에 설치된 FolderSkin을 종료하세요. 이미 실행 중인 FolderSkin이 있으면 새로 실행한 쪽이 링크를 넘기고 종료해 버려요. 스킴을 등록한 개발 빌드는 설치 프로그램이나 다른 빌드가 다시 등록할 때까지 그 등록을 유지해요.

## 설치 수

커뮤니티의 팩이 추가되면 FolderSkin은 커뮤니티 서비스에 그 팩의 ID를 알려요. 본문 없이 `POST https://community.folderskin.app/v1/packs/<id>/installs`를 보내는 게 전부예요. 계정도, 기기 ID도, 라이브러리나 폴더에 관한 정보도 보내지 않아요(FolderSkin의 다른 모든 요청처럼 User-Agent에는 앱 버전이 들어가요). 이 요청은 팩을 저장한 다음에 보내고, 5초가 지나면 포기해요. 이 요청을 기다리는 것도 없고, 실패해도 알리지 않아요. 개발 빌드와, 다른 사본에서 팩을 읽는 빌드(`FOLDERSKIN_COMMUNITY_URL`)는 `FOLDERSKIN_COMMUNITY_API`로 보낼 서비스를 지정하지 않는 한 아무것도 보내지 않아요.

서비스는 네트워크와 팩마다 하루에 한 번만 추가를 세고, 공개된 `index.json`에 있는 팩만 대상으로 해요. 옛 ID로 추가한 것은 옮겨 간 팩으로 세기 때문에, 0.1.7 이전 앱에서 추가한 것도 계속 세어져요. 서비스는 팩별 횟수와, 그날(UTC)이 끝날 때까지 요청이 온 네트워크의 솔트 처리된 해시를 보관해요. 그래서 같은 날 같은 팩을 다시 추가해도 두 번 세지 않아요. 이 해시는 매일 하는 정리 작업에서 지워져요. 주소는 저장하지 않아요.

folderskin.app은 `GET https://community.folderskin.app/v1/packs/installs`에서 횟수를 읽어요. 응답은 `{"version": 1, "installs": {"classic-art-k7q2mx": 42}}` 형태이고, 5분 동안 캐시돼요. 자세한 내용은 [services/community/README.md](https://github.com/prajwal-svm/folderskin/blob/main/services/community/README.md)에 있어요.
