init: create Play Uniare project
This commit is contained in:
9
.dockerignore
Normal file
9
.dockerignore
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.vscode
|
||||||
|
dist
|
||||||
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
docs
|
||||||
2
.env.example
Normal file
2
.env.example
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
VITE_CARD_ASSET_BASE_URL=/card-assets
|
||||||
|
VITE_SOCKET_SERVER_URL=http://localhost:3000
|
||||||
27
.gitignore
vendored
Normal file
27
.gitignore
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["Vue.volar"]
|
||||||
|
}
|
||||||
236
AGENTS.md
Normal file
236
AGENTS.md
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
# Play Uniare AI 작업 지침
|
||||||
|
|
||||||
|
## 1. 문서 목적
|
||||||
|
|
||||||
|
이 파일은 사람과 AI 작업자가 같은 기준으로 프로젝트를 이해하고 변경하기
|
||||||
|
위한 최상위 작업 규칙이다. 작업 시작 전에 이 파일과 `docs/`의 관련 문서를
|
||||||
|
반드시 읽는다.
|
||||||
|
|
||||||
|
## 2. 프로젝트 목표
|
||||||
|
|
||||||
|
- 기존 UA Deck Builder가 전달한 덱 데이터로 카드 시뮬레이터를 제공한다.
|
||||||
|
- 게임 규칙을 자동 판정하지 않는다.
|
||||||
|
- 사용자가 카드 이동, 뒤집기, 탭, 덱·손패·무덤 이동을 직접 수행한다.
|
||||||
|
- 혼자 연습하는 로컬 모드와 방 코드 기반 2인 세션을 제공한다.
|
||||||
|
- 각 플레이어는 자신이 소유한 카드만 조작한다.
|
||||||
|
- 상대 효과로 내 카드가 제거되는 경우에도 내 카드 소유자가 직접 처리한다.
|
||||||
|
|
||||||
|
## 3. 기술 기준
|
||||||
|
|
||||||
|
- 프런트엔드: Vue 3 Composition API와 `<script setup>`
|
||||||
|
- 빌드 도구: Vite
|
||||||
|
- 스타일: Tailwind CSS 우선
|
||||||
|
- 상태 관리: Pinia 도입 예정
|
||||||
|
- 화면 이동: Vue Router 도입 예정
|
||||||
|
- 실시간 통신: Socket.IO 도입 예정
|
||||||
|
- 배포: Docker Compose와 Nginx
|
||||||
|
- 운영 환경: UGREEN NAS
|
||||||
|
|
||||||
|
새 라이브러리를 추가하기 전에는 기존 기술로 해결 가능한지 확인하고, 추가
|
||||||
|
이유와 영향을 `docs/history.md`에 기록한다.
|
||||||
|
|
||||||
|
## 4. 작업 시작 절차
|
||||||
|
|
||||||
|
1. `AGENTS.md`를 읽는다.
|
||||||
|
2. `docs/spec.md`, `docs/map.md`, `docs/todo.md`를 읽는다.
|
||||||
|
3. `git status`로 기존 변경 사항을 확인한다.
|
||||||
|
4. 관련 코드와 기존 패턴을 확인한다.
|
||||||
|
5. 작업 범위를 작게 정하고 구현한다.
|
||||||
|
6. 빌드와 관련 검증을 수행한다.
|
||||||
|
7. 변경 성격에 맞는 문서를 업데이트한다.
|
||||||
|
8. 작업 결과 형식에 맞춰 보고한다.
|
||||||
|
|
||||||
|
## 5. 코드 작성 규칙
|
||||||
|
|
||||||
|
### 5.1 공통
|
||||||
|
|
||||||
|
- 기존 패턴을 우선하며 최소 범위만 수정한다.
|
||||||
|
- 하나의 컴포넌트는 하나의 분명한 역할을 갖는다.
|
||||||
|
- 반복되는 값은 상수, 설정 또는 데이터 모델로 분리한다.
|
||||||
|
- 서버 주소, 이미지 원본 주소, 포트, 비밀값을 코드에 하드코딩하지 않는다.
|
||||||
|
- 임시 이름과 의미 없는 자리표시자 이름을 사용하지 않는다.
|
||||||
|
- 검증 없이 작업을 완료 처리하지 않는다.
|
||||||
|
|
||||||
|
### 5.2 네이밍
|
||||||
|
|
||||||
|
- 의미가 드러나는 영문 이름을 사용한다.
|
||||||
|
- 축약어를 남용하지 않는다.
|
||||||
|
- `temp`, `tmp`, `test1`, `data1`, `foo`, `bar`, `aaa` 같은 임시 이름을
|
||||||
|
금지한다.
|
||||||
|
- Vue 컴포넌트 파일은 PascalCase를 사용한다.
|
||||||
|
- 함수와 변수는 camelCase를 사용한다.
|
||||||
|
- 상수는 의미가 분명한 경우 UPPER_SNAKE_CASE를 사용한다.
|
||||||
|
- 이벤트 이름은 발생한 사실을 나타내는 kebab-case를 사용한다.
|
||||||
|
- 불리언 값은 `is`, `has`, `can`, `should` 접두어를 우선한다.
|
||||||
|
|
||||||
|
### 5.3 Vue
|
||||||
|
|
||||||
|
- Composition API와 `<script setup>`을 사용한다.
|
||||||
|
- 화면 컴포넌트는 `src/views/`, 재사용 컴포넌트는 `src/components/`에 둔다.
|
||||||
|
- 비즈니스 상태를 컴포넌트 내부에 중복 저장하지 않는다.
|
||||||
|
- props를 직접 변경하지 않는다.
|
||||||
|
- 외부 효과와 구독은 컴포넌트 해제 시 반드시 정리한다.
|
||||||
|
|
||||||
|
### 5.4 스타일
|
||||||
|
|
||||||
|
- Tailwind CSS 유틸리티를 기본으로 사용한다.
|
||||||
|
- 반복되는 디자인 패턴은 컴포넌트로 추출한다.
|
||||||
|
- 임의 픽셀값은 게임 보드 좌표처럼 필요한 경우에만 사용하고 이유를
|
||||||
|
문서화한다.
|
||||||
|
- 카드 위치는 화면 픽셀이 아니라 보드 기준 정규화 좌표 또는 영역 기반으로
|
||||||
|
저장한다.
|
||||||
|
- 인라인 `style`은 드래그 좌표, 회전처럼 동적 계산이 필요한 경우에만
|
||||||
|
허용한다.
|
||||||
|
|
||||||
|
### 5.5 주석
|
||||||
|
|
||||||
|
- 함수와 클래스 앞에는 한국어 JSDoc 문서화 주석을 작성한다.
|
||||||
|
- 주석은 코드가 무엇을 하는지 반복하기보다 이유, 제약, 입력·출력을 설명한다.
|
||||||
|
- 복잡한 타입은 `@typedef`, 함수 입력은 `@param`, 반환은 `@returns`를 쓴다.
|
||||||
|
|
||||||
|
```js
|
||||||
|
/**
|
||||||
|
* 덱 빌더에서 전달된 카드 목록을 실제 카드 인스턴스로 확장한다.
|
||||||
|
*
|
||||||
|
* @param {ImportedDeckCard[]} importedCards 수량이 포함된 덱 카드 목록
|
||||||
|
* @returns {GameCard[]} 고유 식별자가 부여된 카드 인스턴스 목록
|
||||||
|
*/
|
||||||
|
function createGameCardInstances(importedCards) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. 상태 관리 원칙
|
||||||
|
|
||||||
|
### 6.1 상태 구분
|
||||||
|
|
||||||
|
- 서버 권위 상태: 방, 플레이어, 카드 소유권, 카드 영역, 덱 순서
|
||||||
|
- 공유 게임 상태: 필드 카드 좌표, 앞뒷면, 탭 상태, 무덤, 공개 영역
|
||||||
|
- 개인 비공개 상태: 내 손패 카드 식별 정보
|
||||||
|
- 로컬 UI 상태: 선택 카드, 드래그 중 여부, 메뉴 열림, 확대 상태
|
||||||
|
|
||||||
|
서버 권위 상태를 UI 컴포넌트의 로컬 상태만으로 확정하지 않는다.
|
||||||
|
|
||||||
|
### 6.2 카드 모델
|
||||||
|
|
||||||
|
카드는 덱 원본 정보와 플레이 중 인스턴스를 구분한다.
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
instanceId: 'player-1-card-17',
|
||||||
|
ownerId: 'player-1',
|
||||||
|
cardNumber: 'EX01BT/HTR-2-001',
|
||||||
|
imageFileName: 'EX01BT_HTR-2-001.png',
|
||||||
|
zone: 'deck',
|
||||||
|
position: { x: 0.5, y: 0.5 },
|
||||||
|
isFaceUp: false,
|
||||||
|
isTapped: false,
|
||||||
|
stackOrder: 17
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `instanceId`는 같은 카드 여러 장을 구분하는 고유값이다.
|
||||||
|
- 소유권은 게임 시작 후 임의로 변경하지 않는다.
|
||||||
|
- 카드 조작 요청은 요청자와 `ownerId`가 같은지 서버에서 확인한다.
|
||||||
|
- 상대 손패에는 카드 번호와 이미지 정보를 전송하지 않는 방향을 우선한다.
|
||||||
|
|
||||||
|
### 6.3 실시간 이벤트
|
||||||
|
|
||||||
|
- 상태 전체를 매번 전송하기보다 명시적인 이벤트와 필요한 변경값을 전송한다.
|
||||||
|
- 이벤트 이름은 `card-moved`, `card-flipped`, `deck-shuffled`처럼 작성한다.
|
||||||
|
- 재접속 시에는 서버의 최신 전체 스냅샷으로 복구한다.
|
||||||
|
- 같은 이벤트가 중복 수신되어도 상태가 망가지지 않도록 설계한다.
|
||||||
|
|
||||||
|
## 7. 데이터 처리 원칙
|
||||||
|
|
||||||
|
- 기존 덱 빌더 쿼리는 초기 덱 불러오기 용도로만 사용한다.
|
||||||
|
- 외부 입력은 신뢰하지 않고 스키마 검사 후 사용한다.
|
||||||
|
- 카드 이미지 URL은 환경변수의 원본 주소와 파일 경로를 조합한다.
|
||||||
|
- NAS 운영 환경에서는
|
||||||
|
`/volume1/docker/projects/apps/uniare/config/www/images`를 컨테이너에 읽기
|
||||||
|
전용으로 연결하고 `/card-assets` 경로로 제공한다.
|
||||||
|
- 기존 Uniare의 `config/www/images`에는 쓰기 작업을 수행하지 않는다.
|
||||||
|
- URL 길이 문제를 고려해 장기적으로 덱 코드 또는 `deckId` 방식을 도입한다.
|
||||||
|
- URL, 로그, Git에 사용자 토큰이나 비밀값을 남기지 않는다.
|
||||||
|
- 오류가 있는 카드 한 장 때문에 전체 덱 불러오기가 중단되지 않도록 오류
|
||||||
|
목록을 사용자에게 명확히 보여준다.
|
||||||
|
|
||||||
|
## 8. 문서 업데이트 기준
|
||||||
|
|
||||||
|
- 기능 변경 → `docs/spec.md`
|
||||||
|
- 버그 수정 → `docs/update.md`
|
||||||
|
- 이슈 발견 → `docs/todo.md`
|
||||||
|
- 구조·기술·의사결정 변경 → `docs/history.md`
|
||||||
|
- 화면 변경 → `docs/map.md`
|
||||||
|
- 배포 변경 → `docs/deploy.md`
|
||||||
|
- 개발 명령 변경 → `docs/development.md`
|
||||||
|
|
||||||
|
문서 업데이트 누락을 금지한다.
|
||||||
|
|
||||||
|
## 9. 금지 사항
|
||||||
|
|
||||||
|
- 기존 스타일과 구조를 확인하지 않은 변경
|
||||||
|
- 요청 범위를 벗어난 대규모 리팩터링
|
||||||
|
- 서버 주소, 포트, API 키와 비밀번호 하드코딩
|
||||||
|
- `.env`, 인증서, 개인키, NAS 계정 정보 커밋
|
||||||
|
- 조작 권한 검증을 클라이언트에만 의존
|
||||||
|
- 상대의 비공개 손패 정보를 클라이언트로 전송
|
||||||
|
- 테스트 또는 빌드 검증 없이 완료 처리
|
||||||
|
- 관련 문서 업데이트 누락
|
||||||
|
|
||||||
|
## 10. 의사결정 원칙
|
||||||
|
|
||||||
|
1. 기존 패턴 우선
|
||||||
|
2. 최소 수정
|
||||||
|
3. 유지보수 용이성
|
||||||
|
4. 구조 파악 용이성
|
||||||
|
5. 사용자가 직접 운영할 수 있는 단순성
|
||||||
|
6. 비공개 정보와 카드 소유권의 안전성
|
||||||
|
|
||||||
|
## 11. 단계별 개발 계획
|
||||||
|
|
||||||
|
### 1단계: 로컬 싱글 플레이
|
||||||
|
|
||||||
|
- 덱 쿼리 파싱과 검증
|
||||||
|
- 카드 인스턴스 생성
|
||||||
|
- 덱 셔플과 드로우
|
||||||
|
- 카드 이동, 뒤집기, 탭
|
||||||
|
- 손패, 필드, 무덤, 공개 영역
|
||||||
|
- 덱 위·아래 이동과 위에서 N장 공개
|
||||||
|
|
||||||
|
### 2단계: 2인 세션
|
||||||
|
|
||||||
|
- 로비와 방 생성·참가
|
||||||
|
- 플레이어 좌석과 소유권
|
||||||
|
- Socket.IO 서버
|
||||||
|
- 실시간 카드 이벤트 동기화
|
||||||
|
- 상대 손패 비공개 처리
|
||||||
|
- 연결 종료와 재접속 복구
|
||||||
|
|
||||||
|
### 3단계: 운영 안정화
|
||||||
|
|
||||||
|
- NAS Docker 배포
|
||||||
|
- 상태 저장소 도입 검토
|
||||||
|
- 모바일·태블릿 조작 개선
|
||||||
|
- 오류 로그와 상태 복구
|
||||||
|
- 관전자와 게임 기록은 별도 검토
|
||||||
|
|
||||||
|
구체적인 미완료 항목은 `docs/todo.md`에서 관리한다.
|
||||||
|
|
||||||
|
## 12. 작업 결과 보고 형식
|
||||||
|
|
||||||
|
- 수정 내용
|
||||||
|
- 변경 파일
|
||||||
|
- 검증 내용
|
||||||
|
- 문서 반영 여부
|
||||||
|
- 남은 이슈
|
||||||
|
|
||||||
|
## 13. 최종 체크리스트
|
||||||
|
|
||||||
|
- [ ] `AGENTS.md`와 관련 `docs/` 확인
|
||||||
|
- [ ] 코드 수정 완료
|
||||||
|
- [ ] 네이밍과 JSDoc 규칙 확인
|
||||||
|
- [ ] 빌드 또는 관련 검증 수행
|
||||||
|
- [ ] 변경 성격에 맞는 문서 반영
|
||||||
|
- [ ] 민감 정보 포함 여부 확인
|
||||||
|
- [ ] 남은 이슈를 `docs/todo.md`에 기록
|
||||||
19
Dockerfile
Normal file
19
Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
FROM node:22-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM nginx:1.27-alpine AS runtime
|
||||||
|
|
||||||
|
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
||||||
|
CMD wget --quiet --tries=1 --spider http://127.0.0.1/ || exit 1
|
||||||
27
README.md
Normal file
27
README.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Play Uniare
|
||||||
|
|
||||||
|
Union Arena 덱을 불러와 혼자 연습하거나 2인 세션으로 플레이하는 웹 카드
|
||||||
|
시뮬레이터입니다.
|
||||||
|
|
||||||
|
## 기술 구성
|
||||||
|
|
||||||
|
- Vue 3
|
||||||
|
- Vite
|
||||||
|
- Tailwind CSS
|
||||||
|
- 향후 Pinia, Vue Router, Socket.IO 도입 예정
|
||||||
|
|
||||||
|
## 빠른 실행
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
브라우저에서 터미널에 표시된 로컬 주소로 접속합니다.
|
||||||
|
|
||||||
|
상세한 개발 및 배포 방법은 다음 문서를 참고합니다.
|
||||||
|
|
||||||
|
- 개발: `docs/development.md`
|
||||||
|
- NAS 배포: `docs/deploy.md`
|
||||||
|
- 프로젝트 명세: `docs/spec.md`
|
||||||
|
- AI 작업 규칙: `AGENTS.md`
|
||||||
7
compose.nas.yaml
Normal file
7
compose.nas.yaml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
services:
|
||||||
|
playuniare-web:
|
||||||
|
volumes:
|
||||||
|
- type: bind
|
||||||
|
source: /volume1/docker/projects/apps/uniare/config/www/images
|
||||||
|
target: /srv/uniare-card-images
|
||||||
|
read_only: true
|
||||||
7
compose.test.nas.yaml
Normal file
7
compose.test.nas.yaml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
services:
|
||||||
|
playuniare-test-web:
|
||||||
|
volumes:
|
||||||
|
- type: bind
|
||||||
|
source: /volume1/docker/projects/apps/uniare/config/www/images
|
||||||
|
target: /srv/uniare-card-images
|
||||||
|
read_only: true
|
||||||
11
compose.test.yaml
Normal file
11
compose.test.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
name: playuniare-test
|
||||||
|
|
||||||
|
services:
|
||||||
|
playuniare-test-web:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: playuniare-test-web
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- '18080:80'
|
||||||
11
compose.yaml
Normal file
11
compose.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
name: playuniare-production
|
||||||
|
|
||||||
|
services:
|
||||||
|
playuniare-web:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: playuniare-web
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- '18081:80'
|
||||||
31
docker/nginx.conf
Normal file
31
docker/nginx.conf
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /health {
|
||||||
|
access_log off;
|
||||||
|
add_header Content-Type text/plain;
|
||||||
|
return 200 'ok';
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /card-assets/ {
|
||||||
|
alias /srv/uniare-card-images/;
|
||||||
|
autoindex off;
|
||||||
|
expires 1h;
|
||||||
|
add_header Cache-Control "public, max-age=3600";
|
||||||
|
add_header X-Content-Type-Options "nosniff";
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~* \.(?:css|js|jpg|jpeg|gif|png|svg|ico|webp|woff2?)$ {
|
||||||
|
expires 7d;
|
||||||
|
add_header Cache-Control "public, max-age=604800, immutable";
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
}
|
||||||
342
docs/deploy.md
Normal file
342
docs/deploy.md
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
# UGREEN NAS Docker 배포 안내
|
||||||
|
|
||||||
|
## 문서 목적
|
||||||
|
|
||||||
|
이 문서는 터미널과 Docker에 익숙하지 않은 사용자가 Play Uniare의 테스트
|
||||||
|
버전과 운영 버전을 UGREEN NAS에서 실행하고 업데이트하는 절차를 설명한다.
|
||||||
|
|
||||||
|
현재 프로젝트는 Vue 정적 프런트엔드만 포함한다. 실시간 서버가 추가되면
|
||||||
|
`compose.yaml`에 백엔드 서비스와 상태 저장소를 함께 추가한다.
|
||||||
|
|
||||||
|
## 배포 방식
|
||||||
|
|
||||||
|
```text
|
||||||
|
사용자 브라우저
|
||||||
|
↓
|
||||||
|
NAS 포트 또는 리버스 프록시
|
||||||
|
↓
|
||||||
|
Play Uniare 컨테이너의 Nginx
|
||||||
|
├── / → Vue 빌드 결과
|
||||||
|
└── /card-assets/ → 기존 Uniare 카드 이미지
|
||||||
|
```
|
||||||
|
|
||||||
|
Docker 이미지를 만들 때 Node.js로 Vue를 빌드하고, 실행할 때는 가벼운
|
||||||
|
Nginx가 빌드 결과를 제공한다.
|
||||||
|
|
||||||
|
카드 이미지는 다음 기존 폴더를 복사하지 않고 읽기 전용으로 연결한다.
|
||||||
|
|
||||||
|
```text
|
||||||
|
/volume1/docker/projects/apps/uniare/config/www/images
|
||||||
|
```
|
||||||
|
|
||||||
|
## NAS에서 처음 한 번 준비하기
|
||||||
|
|
||||||
|
### 1. Docker 앱 설치
|
||||||
|
|
||||||
|
UGREEN NAS의 앱 센터에서 Docker 또는 Container Manager에 해당하는 앱을
|
||||||
|
설치한다. 모델과 UGOS 버전에 따라 앱 이름이 다를 수 있다.
|
||||||
|
|
||||||
|
### 2. 프로젝트 저장 폴더 준비
|
||||||
|
|
||||||
|
NAS 공유 폴더에 다음과 같은 폴더를 만든다.
|
||||||
|
|
||||||
|
```text
|
||||||
|
/volume1/docker/projects/apps/
|
||||||
|
├── uniare/
|
||||||
|
│ └── config/
|
||||||
|
│ └── www/
|
||||||
|
│ ├── index.html
|
||||||
|
│ └── images/
|
||||||
|
└── playuniare/
|
||||||
|
```
|
||||||
|
|
||||||
|
이 프로젝트의 NAS 경로는 다음으로 고정한다.
|
||||||
|
|
||||||
|
```text
|
||||||
|
/volume1/docker/projects/apps/playuniare
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. SSH 사용 여부 결정
|
||||||
|
|
||||||
|
UGREEN 관리 화면에서 Docker Compose 프로젝트를 직접 등록할 수 있다면 GUI를
|
||||||
|
우선 사용한다. GUI에서 Git 저장소를 직접 받을 수 없거나 업데이트가 어려운
|
||||||
|
경우에만 SSH 터미널을 사용한다.
|
||||||
|
|
||||||
|
SSH를 활성화할 때는 관리자 화면에서 포트와 계정을 확인한다. NAS 비밀번호를
|
||||||
|
문서, `.env`, Git 저장소에 기록하지 않는다.
|
||||||
|
|
||||||
|
## 테스트 버전 실행
|
||||||
|
|
||||||
|
테스트 버전은 외부 도메인에 연결하기 전에 NAS 내부에서 확인하는 용도다.
|
||||||
|
|
||||||
|
### 1. NAS에 프로젝트 받기
|
||||||
|
|
||||||
|
NAS 터미널에서 프로젝트 저장 폴더로 이동한다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /volume1/docker/projects/apps
|
||||||
|
```
|
||||||
|
|
||||||
|
처음 한 번만 저장소를 복제한다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.sori.studio/zenn/playuniare.sori.studio.git playuniare
|
||||||
|
cd playuniare
|
||||||
|
```
|
||||||
|
|
||||||
|
비공개 저장소가 인증을 요구하면 비밀번호 대신 Git 서버에서 발급한 접근
|
||||||
|
토큰을 사용하는 것을 권장한다. 토큰을 명령문이나 문서에 저장하지 않는다.
|
||||||
|
|
||||||
|
### 2. 테스트 컨테이너 빌드 및 실행
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose \
|
||||||
|
-f compose.test.yaml \
|
||||||
|
-f compose.test.nas.yaml \
|
||||||
|
up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
이 명령은 다음 작업을 한 번에 수행한다.
|
||||||
|
|
||||||
|
- 최신 소스 코드로 Docker 이미지 생성
|
||||||
|
- 테스트 컨테이너 실행
|
||||||
|
- NAS의 18080 포트와 컨테이너 연결
|
||||||
|
|
||||||
|
### 3. 브라우저에서 확인
|
||||||
|
|
||||||
|
NAS의 내부 IP가 `192.168.0.20`이라면 다음 주소로 접속한다.
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://192.168.0.20:18080
|
||||||
|
```
|
||||||
|
|
||||||
|
실제 NAS IP는 공유기 또는 UGREEN 관리 화면에서 확인한다.
|
||||||
|
|
||||||
|
### 4. 테스트 로그 확인
|
||||||
|
|
||||||
|
화면이 열리지 않으면 다음을 실행한다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose \
|
||||||
|
-f compose.test.yaml \
|
||||||
|
-f compose.test.nas.yaml \
|
||||||
|
ps
|
||||||
|
|
||||||
|
docker compose \
|
||||||
|
-f compose.test.yaml \
|
||||||
|
-f compose.test.nas.yaml \
|
||||||
|
logs --tail=100
|
||||||
|
```
|
||||||
|
|
||||||
|
`Up` 또는 `running` 상태인지 확인한다.
|
||||||
|
|
||||||
|
### 5. 테스트 버전 중지
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose \
|
||||||
|
-f compose.test.yaml \
|
||||||
|
-f compose.test.nas.yaml \
|
||||||
|
down
|
||||||
|
```
|
||||||
|
|
||||||
|
컨테이너는 제거되지만 소스 코드와 Docker 이미지는 남아 있다.
|
||||||
|
|
||||||
|
## 운영 버전 최초 배포
|
||||||
|
|
||||||
|
### 1. 배포 전 로컬 검증
|
||||||
|
|
||||||
|
개발 컴퓨터에서 다음 명령이 성공해야 한다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
그 후 변경 사항을 커밋하고 원격 저장소에 푸시한다.
|
||||||
|
|
||||||
|
### 2. NAS에서 운영 컨테이너 실행
|
||||||
|
|
||||||
|
NAS 프로젝트 폴더에서 실행한다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /volume1/docker/projects/apps/playuniare
|
||||||
|
git pull
|
||||||
|
docker compose \
|
||||||
|
-f compose.yaml \
|
||||||
|
-f compose.nas.yaml \
|
||||||
|
up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
운영 컨테이너는 NAS의 18081 포트를 사용한다.
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://NAS_IP:18081
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 도메인 연결
|
||||||
|
|
||||||
|
UGREEN NAS 또는 사용 중인 리버스 프록시에서 다음과 같이 연결한다.
|
||||||
|
|
||||||
|
```text
|
||||||
|
외부 도메인: https://playuniare.sori.studio
|
||||||
|
내부 대상: http://127.0.0.1:18081
|
||||||
|
```
|
||||||
|
|
||||||
|
- 외부 접속은 HTTPS를 사용한다.
|
||||||
|
- 공유기에서 컨테이너 포트를 그대로 외부에 노출하기보다 리버스 프록시를
|
||||||
|
사용한다.
|
||||||
|
- 인증서 자동 갱신이 설정되어 있는지 확인한다.
|
||||||
|
|
||||||
|
### 4. 카드 이미지 연결 확인
|
||||||
|
|
||||||
|
브라우저에서 실제 카드 파일 하나를 직접 연다.
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://NAS_IP:18081/card-assets/ua03/EX01BT_HTR-2-001.png
|
||||||
|
```
|
||||||
|
|
||||||
|
이미지가 보이면 읽기 전용 마운트와 Nginx 경로가 정상이다. 파일명이 실제로
|
||||||
|
존재하는 카드인지 먼저 확인한다.
|
||||||
|
|
||||||
|
이미지가 보이지 않으면 NAS 터미널에서 원본 경로를 확인한다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls /volume1/docker/projects/apps/uniare/config/www/images
|
||||||
|
```
|
||||||
|
|
||||||
|
시리즈 폴더가 보여야 한다. 다음 명령으로 컨테이너 내부 연결도 확인한다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec playuniare-web ls /srv/uniare-card-images
|
||||||
|
```
|
||||||
|
|
||||||
|
이 경로 역시 같은 시리즈 폴더를 보여야 한다.
|
||||||
|
|
||||||
|
## 새 버전 업데이트
|
||||||
|
|
||||||
|
### 안전한 권장 순서
|
||||||
|
|
||||||
|
1. 개발 컴퓨터에서 `npm run build`를 실행한다.
|
||||||
|
2. 변경 내용을 Git에 커밋하고 푸시한다.
|
||||||
|
3. NAS에서 테스트 버전을 먼저 업데이트한다.
|
||||||
|
4. 테스트 주소에서 주요 기능을 확인한다.
|
||||||
|
5. 문제가 없으면 운영 버전을 업데이트한다.
|
||||||
|
|
||||||
|
### 테스트 버전 업데이트
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /volume1/docker/projects/apps/playuniare
|
||||||
|
git pull
|
||||||
|
docker compose \
|
||||||
|
-f compose.test.yaml \
|
||||||
|
-f compose.test.nas.yaml \
|
||||||
|
up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
브라우저에서 `http://NAS_IP:18080`을 확인한다.
|
||||||
|
|
||||||
|
### 운영 버전 업데이트
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /volume1/docker/projects/apps/playuniare
|
||||||
|
git pull
|
||||||
|
docker compose \
|
||||||
|
-f compose.yaml \
|
||||||
|
-f compose.nas.yaml \
|
||||||
|
up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
브라우저에서 실제 도메인을 확인한다.
|
||||||
|
|
||||||
|
### 사용하지 않는 Docker 이미지 정리
|
||||||
|
|
||||||
|
업데이트를 여러 번 하면 이전 이미지가 NAS 용량을 차지할 수 있다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker image prune
|
||||||
|
```
|
||||||
|
|
||||||
|
삭제 대상 확인 질문이 나오면 내용을 읽고 `y`를 입력한다. `-a` 옵션이나
|
||||||
|
볼륨 삭제 명령은 데이터 유실 가능성이 있으므로 이 문서에서는 사용하지 않는다.
|
||||||
|
|
||||||
|
## 이전 버전으로 되돌리기
|
||||||
|
|
||||||
|
문제가 생기면 먼저 현재 커밋을 확인한다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git log --oneline -10
|
||||||
|
```
|
||||||
|
|
||||||
|
정상 동작했던 커밋 식별자를 확인한 뒤 다음처럼 전환한다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout 정상커밋식별자
|
||||||
|
docker compose \
|
||||||
|
-f compose.yaml \
|
||||||
|
-f compose.nas.yaml \
|
||||||
|
up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
복구가 끝난 뒤 개발 컴퓨터에서 문제를 수정하고 정상 브랜치에 새 커밋을
|
||||||
|
만든다. NAS에서 임의로 소스 파일을 직접 수정하지 않는다.
|
||||||
|
|
||||||
|
다시 최신 `main`으로 돌아갈 때:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout main
|
||||||
|
git pull
|
||||||
|
docker compose \
|
||||||
|
-f compose.yaml \
|
||||||
|
-f compose.nas.yaml \
|
||||||
|
up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
## 운영 점검 명령
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f compose.yaml -f compose.nas.yaml ps
|
||||||
|
docker compose -f compose.yaml -f compose.nas.yaml logs --tail=100
|
||||||
|
docker compose -f compose.yaml -f compose.nas.yaml restart
|
||||||
|
```
|
||||||
|
|
||||||
|
- `ps`: 실행 상태 확인
|
||||||
|
- `logs`: 최근 오류 확인
|
||||||
|
- `restart`: 같은 이미지로 컨테이너 재시작
|
||||||
|
|
||||||
|
## 배포 체크리스트
|
||||||
|
|
||||||
|
- [ ] 로컬 `npm run build` 성공
|
||||||
|
- [ ] 관련 문서 업데이트
|
||||||
|
- [ ] Git 커밋과 푸시 완료
|
||||||
|
- [ ] 테스트 컨테이너 재빌드
|
||||||
|
- [ ] 테스트 URL 주요 기능 확인
|
||||||
|
- [ ] 운영 컨테이너 재빌드
|
||||||
|
- [ ] 실제 도메인 HTTPS 접속 확인
|
||||||
|
- [ ] `/card-assets/<series>/<fileName>` 이미지 접속 확인
|
||||||
|
- [ ] 카드 이미지 마운트가 읽기 전용인지 확인
|
||||||
|
- [ ] 브라우저 새로고침과 직접 URL 접근 확인
|
||||||
|
- [ ] 오류 로그 확인
|
||||||
|
- [ ] `.env`, 토큰, 비밀번호가 Git에 없는지 확인
|
||||||
|
|
||||||
|
## 향후 실시간 서버 추가 시
|
||||||
|
|
||||||
|
프런트엔드만 있을 때는 Nginx 컨테이너 하나면 충분하다. Socket.IO 서버가
|
||||||
|
추가되면 다음 항목이 필요하다.
|
||||||
|
|
||||||
|
- Node.js 서버 컨테이너
|
||||||
|
- WebSocket을 지원하는 리버스 프록시 설정
|
||||||
|
- 프런트엔드와 서버의 환경변수
|
||||||
|
- 방 상태 백업 또는 Redis 도입 여부
|
||||||
|
- 컨테이너 재시작 시 방이 종료된다는 안내
|
||||||
|
|
||||||
|
이 구조가 변경되면 반드시 이 문서와 `docs/history.md`를 함께 수정한다.
|
||||||
|
|
||||||
|
## NAS 전용 Compose 파일을 분리한 이유
|
||||||
|
|
||||||
|
- `compose.yaml`: 어느 컴퓨터에서도 검사하고 빌드할 수 있는 공통 운영 설정
|
||||||
|
- `compose.nas.yaml`: NAS에만 존재하는 카드 이미지 절대 경로 연결
|
||||||
|
- `compose.test.yaml`: 공통 테스트 설정
|
||||||
|
- `compose.test.nas.yaml`: 테스트 컨테이너의 NAS 카드 이미지 연결
|
||||||
|
|
||||||
|
개발 Mac에는 `/volume1/...` 경로가 없으므로 NAS 전용 파일을 사용하지 않는다.
|
||||||
|
NAS에서는 반드시 공통 파일과 NAS 전용 파일을 함께 지정한다.
|
||||||
132
docs/development.md
Normal file
132
docs/development.md
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
# 로컬 개발과 테스트 실행 안내
|
||||||
|
|
||||||
|
## 처음 한 번만 준비하기
|
||||||
|
|
||||||
|
### 1. Node.js 확인
|
||||||
|
|
||||||
|
터미널에서 실행한다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node --version
|
||||||
|
npm --version
|
||||||
|
```
|
||||||
|
|
||||||
|
두 명령 모두 버전 번호가 출력되어야 한다. 명령을 찾을 수 없다는 메시지가
|
||||||
|
나오면 Node.js LTS 버전을 먼저 설치한다.
|
||||||
|
|
||||||
|
### 2. 프로젝트 폴더로 이동
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /Users/bicute/Desktop/dxp6800pro/uniare-simulator
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 패키지 설치
|
||||||
|
|
||||||
|
프로젝트를 처음 받았거나 `package.json`이 변경되었을 때 실행한다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
`node_modules`는 자동 생성되며 Git에 올리지 않는다.
|
||||||
|
|
||||||
|
## 개발 서버 실행
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
터미널에 다음과 비슷한 주소가 표시된다.
|
||||||
|
|
||||||
|
```text
|
||||||
|
Local: http://localhost:5173/
|
||||||
|
```
|
||||||
|
|
||||||
|
해당 주소를 브라우저에서 연다. 개발 중 코드를 저장하면 화면이 자동으로
|
||||||
|
갱신된다.
|
||||||
|
|
||||||
|
개발 서버를 종료하려면 실행 중인 터미널에서 `Control + C`를 누른다.
|
||||||
|
|
||||||
|
## 같은 네트워크의 다른 기기에서 테스트
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev -- --host
|
||||||
|
```
|
||||||
|
|
||||||
|
터미널의 `Network` 주소를 같은 공유기에 연결된 휴대전화나 태블릿에서 연다.
|
||||||
|
macOS 방화벽이 연결 허용 여부를 물으면 개발 중에만 허용한다.
|
||||||
|
|
||||||
|
## 배포용 빌드 확인
|
||||||
|
|
||||||
|
코드 변경을 완료하기 전에 반드시 실행한다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
성공하면 `dist/` 폴더가 생성된다. 오류가 발생하면 배포하지 말고 오류를 먼저
|
||||||
|
수정한다.
|
||||||
|
|
||||||
|
빌드 결과를 로컬에서 확인하려면 다음을 실행한다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
개발 서버와 마찬가지로 터미널에 표시된 주소로 접속한다.
|
||||||
|
|
||||||
|
## 환경변수 사용
|
||||||
|
|
||||||
|
로컬 전용 값은 프로젝트 루트의 `.env.local`에 작성한다.
|
||||||
|
|
||||||
|
```text
|
||||||
|
VITE_CARD_ASSET_BASE_URL=http://127.0.0.1:5500/images
|
||||||
|
VITE_SOCKET_SERVER_URL=http://localhost:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
- `VITE_`로 시작하는 값은 브라우저에 노출될 수 있다.
|
||||||
|
- 비밀번호, 개인키, NAS 관리자 계정은 넣지 않는다.
|
||||||
|
- `.env.local`은 Git에 커밋하지 않는다.
|
||||||
|
|
||||||
|
## 일반적인 작업 순서
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git pull
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
작업이 끝난 뒤:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
git status
|
||||||
|
```
|
||||||
|
|
||||||
|
변경 문서를 확인한 후 커밋하고 푸시한다.
|
||||||
|
|
||||||
|
## 자주 발생하는 문제
|
||||||
|
|
||||||
|
### 포트가 이미 사용 중이라고 나오는 경우
|
||||||
|
|
||||||
|
Vite가 다른 포트를 자동으로 제시할 수 있다. 터미널에 새로 표시된 주소로
|
||||||
|
접속한다. 반드시 5173 포트를 고정할 필요는 없다.
|
||||||
|
|
||||||
|
### 패키지 오류가 발생하는 경우
|
||||||
|
|
||||||
|
먼저 다음을 다시 실행한다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
문제가 지속되면 오류 전문과 Node.js 버전을 기록하고 임의로 대규모 패키지
|
||||||
|
업데이트를 하지 않는다.
|
||||||
|
|
||||||
|
### 카드 이미지가 보이지 않는 경우
|
||||||
|
|
||||||
|
- 덱의 `series` 값이 올바른지 확인한다.
|
||||||
|
- 이미지 파일명이 실제 파일과 같은지 확인한다.
|
||||||
|
- `VITE_CARD_ASSET_BASE_URL` 값 끝에 불필요한 경로가 없는지 확인한다.
|
||||||
|
- 브라우저 개발자 도구의 Network 탭에서 404 또는 CORS 오류를 확인한다.
|
||||||
38
docs/history.md
Normal file
38
docs/history.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# 구조 및 의사결정 이력
|
||||||
|
|
||||||
|
## 2026-06-19: 독립 Vue 프로젝트로 시작
|
||||||
|
|
||||||
|
- 기존 UA Deck Builder와 별도 Git 저장소로 분리했다.
|
||||||
|
- 덱 빌더는 카드 선택과 덱 데이터 제공을 담당한다.
|
||||||
|
- Play Uniare는 카드 조작과 실시간 세션을 담당한다.
|
||||||
|
- 카드 이미지의 대규모 복제를 피하기 위해 기존 카드 자산 도메인을 참조한다.
|
||||||
|
|
||||||
|
## 2026-06-19: Vue 3, Vite, Tailwind CSS 채택
|
||||||
|
|
||||||
|
- 사용자가 직접 유지보수할 수 있는 Vue 3를 선택했다.
|
||||||
|
- 정적 빌드와 빠른 개발 서버를 위해 Vite를 사용한다.
|
||||||
|
- 일관된 UI 작성을 위해 Tailwind CSS를 기본 스타일 방식으로 정했다.
|
||||||
|
|
||||||
|
## 2026-06-19: 카드 소유자만 조작하는 권한 모델
|
||||||
|
|
||||||
|
- 플레이어는 자신이 소유한 카드만 조작한다.
|
||||||
|
- 전투나 효과로 내 카드가 제거되어도 내 카드 소유자가 직접 정리한다.
|
||||||
|
- 이 원칙으로 동시에 같은 카드를 수정하는 충돌을 줄인다.
|
||||||
|
- 온라인 모드에서는 서버가 소유권을 최종 검증한다.
|
||||||
|
|
||||||
|
## 2026-06-19: Docker 기반 NAS 배포
|
||||||
|
|
||||||
|
- UGREEN NAS에서 재현 가능한 배포를 위해 Docker Compose를 사용한다.
|
||||||
|
- Vue 빌드에는 Node.js를 사용하고 실행 이미지는 Nginx를 사용한다.
|
||||||
|
- 테스트와 운영 컨테이너를 서로 다른 포트로 분리한다.
|
||||||
|
|
||||||
|
## 2026-06-19: 기존 카드 이미지 읽기 전용 공유
|
||||||
|
|
||||||
|
- 기존 Uniare의 실제 웹 루트는
|
||||||
|
`/volume1/docker/projects/apps/uniare/config/www`로 확인했다.
|
||||||
|
- 카드 원본은 해당 웹 루트의 `images` 폴더를 사용한다.
|
||||||
|
- 새 프로젝트의 NAS 위치는
|
||||||
|
`/volume1/docker/projects/apps/playuniare`로 정했다.
|
||||||
|
- 이미지 중복 저장을 막기 위해 Docker bind mount를 사용한다.
|
||||||
|
- 원본 보호를 위해 컨테이너에는 읽기 전용으로 연결한다.
|
||||||
|
- 브라우저에는 같은 출처의 `/card-assets` 경로로 제공한다.
|
||||||
53
docs/map.md
Normal file
53
docs/map.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# 화면 및 파일 구조
|
||||||
|
|
||||||
|
## 현재 화면
|
||||||
|
|
||||||
|
### `/`
|
||||||
|
|
||||||
|
- 파일: `src/App.vue`
|
||||||
|
- 역할: 프로젝트 준비 상태를 보여주는 초기 랜딩 화면
|
||||||
|
- 현재 기능: 제품 목표 안내
|
||||||
|
- 다음 변경: 로비 화면과 게임 화면을 Vue Router로 분리
|
||||||
|
- 문서 제목과 기본 언어: `index.html`의 `Play Uniare`, 한국어
|
||||||
|
|
||||||
|
## 예정 화면
|
||||||
|
|
||||||
|
### `/`
|
||||||
|
|
||||||
|
- 로컬 연습 시작
|
||||||
|
- 방 만들기
|
||||||
|
- 방 참가
|
||||||
|
- 덱 쿼리 유효성 안내
|
||||||
|
|
||||||
|
### `/room/:roomCode`
|
||||||
|
|
||||||
|
- 상대 필드
|
||||||
|
- 공용 게임 보드
|
||||||
|
- 내 필드와 손패
|
||||||
|
- 덱·무덤·공개 영역
|
||||||
|
- 카드 조작 메뉴
|
||||||
|
- 연결 상태와 재접속 안내
|
||||||
|
|
||||||
|
## 예정 디렉터리 구조
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/
|
||||||
|
├── components/
|
||||||
|
│ ├── cards/
|
||||||
|
│ ├── game-board/
|
||||||
|
│ └── room/
|
||||||
|
├── composables/
|
||||||
|
├── services/
|
||||||
|
├── stores/
|
||||||
|
├── types/
|
||||||
|
├── utils/
|
||||||
|
└── views/
|
||||||
|
```
|
||||||
|
|
||||||
|
- `components`: 화면에서 재사용하는 UI
|
||||||
|
- `composables`: Vue 반응형 조합 로직
|
||||||
|
- `services`: 쿼리 파싱, API, Socket.IO 연결
|
||||||
|
- `stores`: Pinia 기반 게임·방·사용자 상태
|
||||||
|
- `types`: JSDoc 타입 정의
|
||||||
|
- `utils`: Vue에 의존하지 않는 순수 함수
|
||||||
|
- `views`: Router 단위 화면
|
||||||
106
docs/spec.md
Normal file
106
docs/spec.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
# 프로젝트 기술 명세
|
||||||
|
|
||||||
|
## 프로젝트 개요
|
||||||
|
|
||||||
|
- 프로젝트명: Play Uniare
|
||||||
|
- 목적: Union Arena 덱을 불러와 카드 조작 중심의 로컬·온라인 플레이 제공
|
||||||
|
- 현재 단계: 프로젝트 기반 구성
|
||||||
|
- 프런트엔드: Vue 3, Vite, Tailwind CSS
|
||||||
|
- 배포 대상: UGREEN NAS의 Docker 환경
|
||||||
|
|
||||||
|
## 제품 범위
|
||||||
|
|
||||||
|
### 포함
|
||||||
|
|
||||||
|
- 기존 덱 빌더 쿼리에서 덱 불러오기
|
||||||
|
- 카드 수량만큼 독립 인스턴스 생성
|
||||||
|
- 덱 셔플과 드로우
|
||||||
|
- 카드 드래그 이동
|
||||||
|
- 카드 앞면·뒷면 전환
|
||||||
|
- 카드 탭·언탭
|
||||||
|
- 손패, 필드, 덱, 무덤, 공개 영역
|
||||||
|
- 덱 위·아래 또는 무덤으로 카드 이동
|
||||||
|
- 덱 위에서 여러 장을 공개한 후 목적지 지정
|
||||||
|
- 혼자 연습하는 로컬 모드
|
||||||
|
- 방 코드 기반 2인 세션
|
||||||
|
- 각 플레이어가 자신의 카드만 조작하는 권한 모델
|
||||||
|
|
||||||
|
### 제외
|
||||||
|
|
||||||
|
- 카드 효과와 게임 규칙 자동 판정
|
||||||
|
- 음성·텍스트 채팅
|
||||||
|
- 랭킹, 매칭, 대회 운영
|
||||||
|
- 초기 단계의 회원가입
|
||||||
|
|
||||||
|
## 덱 입력
|
||||||
|
|
||||||
|
초기에는 기존 덱 빌더 형식을 호환한다.
|
||||||
|
|
||||||
|
```text
|
||||||
|
?series=ua03&seriesName=HUNTER%C3%97HUNTER&cards=<encoded-json>
|
||||||
|
```
|
||||||
|
|
||||||
|
수신 후 다음 절차를 적용한다.
|
||||||
|
|
||||||
|
1. 쿼리 길이와 필수 필드를 검사한다.
|
||||||
|
2. JSON 디코딩 오류를 처리한다.
|
||||||
|
3. `series`, `imgSrc`, `Number`, `count`를 검증한다.
|
||||||
|
4. 카드 수량만큼 고유한 게임 카드 인스턴스를 만든다.
|
||||||
|
5. 이미지 주소는 환경변수 기반 카드 자산 원본과 조합한다.
|
||||||
|
|
||||||
|
장기적으로는 축약된 덱 코드 또는 서버의 `deckId` 방식으로 전환한다.
|
||||||
|
|
||||||
|
## 권한 모델
|
||||||
|
|
||||||
|
- 플레이어는 자기 카드만 이동·뒤집기·탭·정리할 수 있다.
|
||||||
|
- 상대 효과로 내 카드가 제거될 때에도 내가 직접 내 카드를 이동한다.
|
||||||
|
- 서버는 모든 카드 조작에서 요청 사용자와 카드 소유자를 비교한다.
|
||||||
|
- 상대 손패에는 장수만 공개하고 카드 식별 정보는 공개하지 않는다.
|
||||||
|
|
||||||
|
## 화면 좌표
|
||||||
|
|
||||||
|
- 배경 이미지는 카드 배치 가이드로 사용한다.
|
||||||
|
- 실제 영역은 배경 위의 투명한 드롭 영역으로 정의한다.
|
||||||
|
- 자유 배치 좌표는 보드 너비·높이 기준 0부터 1 사이 값으로 저장한다.
|
||||||
|
- 창 크기가 변해도 같은 상대 위치를 유지해야 한다.
|
||||||
|
|
||||||
|
## 상태 저장
|
||||||
|
|
||||||
|
- 로컬 모드: 브라우저 메모리, 추후 복구가 필요하면 localStorage 검토
|
||||||
|
- 온라인 모드: 서버가 방 상태의 최종 권위를 가진다.
|
||||||
|
- 초기 서버: Node.js와 Socket.IO, 메모리 기반 방 상태
|
||||||
|
- 운영 안정화 단계: Redis 또는 데이터베이스 저장 검토
|
||||||
|
|
||||||
|
## 환경변수 예정
|
||||||
|
|
||||||
|
```text
|
||||||
|
VITE_CARD_ASSET_BASE_URL=https://uniare.sori.studio/images
|
||||||
|
VITE_SOCKET_SERVER_URL=https://playuniare.sori.studio
|
||||||
|
```
|
||||||
|
|
||||||
|
실제 운영 값은 `.env`에 두고 Git에 커밋하지 않는다.
|
||||||
|
|
||||||
|
## NAS 카드 이미지 공유
|
||||||
|
|
||||||
|
운영 NAS에서는 카드 이미지를 새 프로젝트에 복사하지 않는다. 기존 Uniare
|
||||||
|
컨테이너가 사용하는 웹 루트의 이미지 폴더를 읽기 전용으로 공유한다.
|
||||||
|
|
||||||
|
```text
|
||||||
|
기존 Uniare 웹 루트:
|
||||||
|
/volume1/docker/projects/apps/uniare/config/www
|
||||||
|
|
||||||
|
카드 이미지 원본:
|
||||||
|
/volume1/docker/projects/apps/uniare/config/www/images
|
||||||
|
|
||||||
|
Play Uniare 컨테이너 내부:
|
||||||
|
/srv/uniare-card-images
|
||||||
|
|
||||||
|
브라우저 공개 경로:
|
||||||
|
/card-assets
|
||||||
|
```
|
||||||
|
|
||||||
|
- `config/www`는 기존 `index.html`이 존재하는 실제 웹 루트다.
|
||||||
|
- Docker bind mount는 `images` 폴더만 읽기 전용으로 연결한다.
|
||||||
|
- Play Uniare는 기존 카드 이미지를 읽을 수 있지만 수정하거나 삭제할 수 없다.
|
||||||
|
- 카드 이미지 URL은 `/card-assets/<series>/<fileName>` 형식을 사용한다.
|
||||||
|
- 같은 Play Uniare 도메인으로 제공되므로 별도의 CORS 설정이 필요하지 않다.
|
||||||
58
docs/todo.md
Normal file
58
docs/todo.md
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# 개발 할 일
|
||||||
|
|
||||||
|
## 기반 작업
|
||||||
|
|
||||||
|
- [x] Vue 3와 Vite 프로젝트 생성
|
||||||
|
- [x] Tailwind CSS 설정
|
||||||
|
- [x] Docker 정적 배포 기반 작성
|
||||||
|
- [x] 개발·배포·AI 작업 문서 작성
|
||||||
|
- [ ] ESLint와 Prettier 규칙 확정
|
||||||
|
- [ ] Vue Router 설치와 화면 구조 생성
|
||||||
|
- [ ] Pinia 설치와 상태 구조 생성
|
||||||
|
- [ ] 테스트 도구 도입 범위 결정
|
||||||
|
|
||||||
|
## 1단계: 덱 불러오기와 로컬 플레이
|
||||||
|
|
||||||
|
- [ ] 기존 덱 쿼리 파서 작성
|
||||||
|
- [ ] 외부 입력 검증 스키마 작성
|
||||||
|
- [x] NAS 카드 이미지 읽기 전용 공유 경로 구성
|
||||||
|
- [ ] 카드 이미지 원본 환경변수를 프런트엔드 코드에 연결
|
||||||
|
- [ ] 카드 인스턴스 생성
|
||||||
|
- [ ] 게임 보드 배경과 영역 정의
|
||||||
|
- [ ] 덱 셔플과 드로우
|
||||||
|
- [ ] 카드 드래그 이동
|
||||||
|
- [ ] 앞면·뒷면 전환
|
||||||
|
- [ ] 탭·언탭
|
||||||
|
- [ ] 덱·손패·필드·무덤 이동
|
||||||
|
- [ ] 덱 위 N장 공개와 정리
|
||||||
|
- [ ] 로컬 상태 초기화
|
||||||
|
|
||||||
|
## 2단계: 온라인 2인 세션
|
||||||
|
|
||||||
|
- [ ] Node.js 실시간 서버 구성
|
||||||
|
- [ ] Socket.IO 연결
|
||||||
|
- [ ] 방 만들기와 방 코드 참가
|
||||||
|
- [ ] 좌석과 플레이어 소유권
|
||||||
|
- [ ] 카드 조작 권한 서버 검증
|
||||||
|
- [ ] 상대 손패 비공개 전송
|
||||||
|
- [ ] 상태 스냅샷과 재접속
|
||||||
|
- [ ] 연결 종료 처리
|
||||||
|
- [ ] NAS WebSocket 프록시 배포
|
||||||
|
|
||||||
|
## 3단계: 안정화
|
||||||
|
|
||||||
|
- [ ] 모바일·태블릿 Pointer Events 검증
|
||||||
|
- [ ] 카드 겹침과 z-index 조작 UX
|
||||||
|
- [ ] 잘못된 덱 입력 오류 화면
|
||||||
|
- [ ] 이미지 로딩 실패 폴백
|
||||||
|
- [ ] 접근성 키보드 조작 범위 검토
|
||||||
|
- [ ] 서버 재시작 시 방 종료 안내
|
||||||
|
- [ ] Redis 또는 상태 저장 필요성 검토
|
||||||
|
|
||||||
|
## 확인이 필요한 정책
|
||||||
|
|
||||||
|
- [ ] 카드 뒷면 이미지와 플레이매트 자산 확정
|
||||||
|
- [ ] 한 방의 최대 플레이어와 관전자 정책
|
||||||
|
- [ ] 방 유지 시간
|
||||||
|
- [ ] 사용하지 않는 방 자동 삭제 시간
|
||||||
|
- [ ] 덱 빌더와 시뮬레이터 사이의 최종 전달 형식
|
||||||
13
docs/update.md
Normal file
13
docs/update.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# 버그 수정 및 작업 기록
|
||||||
|
|
||||||
|
## 2026-06-19
|
||||||
|
|
||||||
|
- Vue 3와 Vite 기본 프로젝트를 생성했다.
|
||||||
|
- Tailwind CSS Vite 플러그인을 적용했다.
|
||||||
|
- 기본 샘플 화면을 프로젝트 안내 화면으로 교체했다.
|
||||||
|
- UGREEN NAS Docker 테스트·운영 배포 문서를 작성했다.
|
||||||
|
- AI 작업 지침과 초기 명세, 화면 구조, 할 일, 의사결정 문서를 추가했다.
|
||||||
|
- Vue 프로덕션 빌드, Docker Compose 설정, Docker 이미지 빌드와 Nginx
|
||||||
|
헬스체크를 검증했다.
|
||||||
|
- NAS의 기존 `uniare/config/www/images`를 읽기 전용으로 공유하는 Compose
|
||||||
|
설정과 `/card-assets` Nginx 경로를 추가했다.
|
||||||
17
index.html
Normal file
17
index.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="ko">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Union Arena 덱을 불러와 연습하고 함께 플레이하는 웹 카드 시뮬레이터"
|
||||||
|
/>
|
||||||
|
<title>Play Uniare</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
1464
package-lock.json
generated
Normal file
1464
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
20
package.json
Normal file
20
package.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "uniare-simulator",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"vue": "^3.5.34"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tailwindcss/vite": "^4.3.1",
|
||||||
|
"@vitejs/plugin-vue": "^6.0.6",
|
||||||
|
"tailwindcss": "^4.3.1",
|
||||||
|
"vite": "^8.0.12"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
public/favicon.svg
Normal file
1
public/favicon.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 9.3 KiB |
24
public/icons.svg
Normal file
24
public/icons.svg
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<symbol id="bluesky-icon" viewBox="0 0 16 17">
|
||||||
|
<g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
|
||||||
|
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="discord-icon" viewBox="0 0 20 19">
|
||||||
|
<path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="documentation-icon" viewBox="0 0 21 20">
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="github-icon" viewBox="0 0 19 19">
|
||||||
|
<path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="social-icon" viewBox="0 0 20 20">
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="x-icon" viewBox="0 0 19 19">
|
||||||
|
<path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
|
||||||
|
</symbol>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.9 KiB |
40
src/App.vue
Normal file
40
src/App.vue
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<template>
|
||||||
|
<main class="min-h-screen bg-slate-950 px-6 py-16 text-slate-100">
|
||||||
|
<section class="mx-auto max-w-4xl">
|
||||||
|
<p class="mb-4 text-sm font-semibold tracking-[0.24em] text-cyan-400">
|
||||||
|
PLAY UNIARE
|
||||||
|
</p>
|
||||||
|
<h1 class="text-4xl font-black tracking-tight sm:text-6xl">
|
||||||
|
Union Arena Simulator
|
||||||
|
</h1>
|
||||||
|
<p class="mt-6 max-w-2xl text-base leading-7 text-slate-400 sm:text-lg">
|
||||||
|
덱 빌더에서 전달받은 카드로 혼자 연습하거나, 방을 만들어 상대와
|
||||||
|
실시간으로 플레이하는 시뮬레이터를 준비하고 있습니다.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="mt-10 grid gap-4 sm:grid-cols-3">
|
||||||
|
<article class="rounded-2xl border border-slate-800 bg-slate-900 p-5">
|
||||||
|
<p class="text-sm font-bold text-cyan-300">01</p>
|
||||||
|
<h2 class="mt-3 text-lg font-bold">덱 불러오기</h2>
|
||||||
|
<p class="mt-2 text-sm leading-6 text-slate-400">
|
||||||
|
기존 덱 빌더의 쿼리 데이터를 안전하게 변환합니다.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
<article class="rounded-2xl border border-slate-800 bg-slate-900 p-5">
|
||||||
|
<p class="text-sm font-bold text-cyan-300">02</p>
|
||||||
|
<h2 class="mt-3 text-lg font-bold">카드 조작</h2>
|
||||||
|
<p class="mt-2 text-sm leading-6 text-slate-400">
|
||||||
|
이동, 뒤집기, 탭과 덱·손패·무덤 이동을 제공합니다.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
<article class="rounded-2xl border border-slate-800 bg-slate-900 p-5">
|
||||||
|
<p class="text-sm font-bold text-cyan-300">03</p>
|
||||||
|
<h2 class="mt-3 text-lg font-bold">2인 세션</h2>
|
||||||
|
<p class="mt-2 text-sm leading-6 text-slate-400">
|
||||||
|
각 플레이어가 자신의 카드만 조작하도록 동기화합니다.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
5
src/main.js
Normal file
5
src/main.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import './style.css'
|
||||||
|
import App from './App.vue'
|
||||||
|
|
||||||
|
createApp(App).mount('#app')
|
||||||
34
src/style.css
Normal file
34
src/style.css
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
@import 'tailwindcss';
|
||||||
|
|
||||||
|
:root {
|
||||||
|
font-family:
|
||||||
|
Inter, Pretendard, 'Noto Sans KR', system-ui, -apple-system, BlinkMacSystemFont,
|
||||||
|
'Segoe UI', sans-serif;
|
||||||
|
color: #e2e8f0;
|
||||||
|
background: #020617;
|
||||||
|
font-synthesis: none;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
#app {
|
||||||
|
min-width: 320px;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
background: #020617;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
7
vite.config.js
Normal file
7
vite.config.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue(), tailwindcss()],
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user