v4.0.0: UNIARE 리브랜딩·레이아웃·덱 UX 개선
제품명·헤더 로고 이미지 통일, 덱 이름 변경·조회 수 제거, 썸네일 홀드 미리보기를 누르는 동안만 표시하도록 수정하고 Tailwind CSS를 재빌드했다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
# UADECK API 환경변수 — 이 파일을 .env로 복사한 뒤 값을 채우세요
|
# UNIARE API 환경변수 — 이 파일을 .env로 복사한 뒤 값을 채우세요
|
||||||
|
|
||||||
# --- API ---
|
# --- API ---
|
||||||
API_PORT=3000
|
API_PORT=3000
|
||||||
|
|||||||
@@ -6,12 +6,15 @@
|
|||||||
<link rel="stylesheet" href="./style/output.css" />
|
<link rel="stylesheet" href="./style/output.css" />
|
||||||
<link rel="stylesheet" href="./style/auth-header.css" />
|
<link rel="stylesheet" href="./style/auth-header.css" />
|
||||||
<link rel="stylesheet" href="./style/admin.css" />
|
<link rel="stylesheet" href="./style/admin.css" />
|
||||||
<title>관리자 - UADECK</title>
|
<title>관리자 - UNIARE</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="admin-page">
|
<body class="admin-page">
|
||||||
<nav class="admin-nav">
|
<nav class="admin-nav">
|
||||||
<div class="admin-nav-inner">
|
<div class="admin-nav-inner">
|
||||||
<a href="/" class="admin-brand">UADECK <span>v4.0.0</span> 관리자</a>
|
<a href="/" class="admin-brand">
|
||||||
|
<img src="/images/assets/uniare-h48.png" alt="UNIARE" class="app-brand-logo" />
|
||||||
|
<span class="admin-brand-label">관리자</span>
|
||||||
|
</a>
|
||||||
<div id="auth-header-slot"></div>
|
<div id="auth-header-slot"></div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ app.use((err, _req, res, _next) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
console.log(`UADECK API v4.0.0 listening on :${PORT}`);
|
console.log(`UNIARE API v4.0.0 listening on :${PORT}`);
|
||||||
console.log(`DB: ${DB_PATH}`);
|
console.log(`DB: ${DB_PATH}`);
|
||||||
console.log(`DATAS: ${process.env.DATAS_PATH || '(default ../datas)'}`);
|
console.log(`DATAS: ${process.env.DATAS_PATH || '(default ../datas)'}`);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -82,7 +82,8 @@
|
|||||||
<div class="animate-spin rounded-full h-12 w-12 border-4 border-blue-500 border-t-transparent"></div>
|
<div class="animate-spin rounded-full h-12 w-12 border-4 border-blue-500 border-t-transparent"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sticky top-0 z-50 h-16 bg-white/80 backdrop-blur-md border-b px-4 py-3 flex justify-between items-center">
|
<div class="sticky top-0 z-50 bg-white/80 backdrop-blur-md border-b">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 h-16 flex justify-between items-center">
|
||||||
<button type="button" id="i18n-home" onclick="goToHome()" class="font-bold text-slate-600 hover:text-blue-600 cursor-pointer">← 홈</button>
|
<button type="button" id="i18n-home" onclick="goToHome()" class="font-bold text-slate-600 hover:text-blue-600 cursor-pointer">← 홈</button>
|
||||||
<div class="font-bold text-slate-800">
|
<div class="font-bold text-slate-800">
|
||||||
<span id="i18n-searchResults">검색결과</span>
|
<span id="i18n-searchResults">검색결과</span>
|
||||||
@@ -90,9 +91,10 @@
|
|||||||
<span id="SearchResultsCounter" class="text-blue-600">0</span>
|
<span id="SearchResultsCounter" class="text-blue-600">0</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="auth-header-slot"></div>
|
<div id="auth-header-slot"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="max-w-[1400px] mx-auto p-4 flex flex-col lg:flex-row gap-6">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 py-4 flex flex-col lg:flex-row gap-6">
|
||||||
<aside class="w-full lg:w-64 space-y-4 lg:sticky lg:top-20 lg:h-fit">
|
<aside class="w-full lg:w-64 space-y-4 lg:sticky lg:top-20 lg:h-fit">
|
||||||
<div class="bg-white p-5 rounded-2xl border border-slate-200 shadow-sm">
|
<div class="bg-white p-5 rounded-2xl border border-slate-200 shadow-sm">
|
||||||
<button id="resetSelection" class="w-full mb-4 flex items-center justify-center gap-2 py-2 bg-red-50 hover:bg-red-100 text-red-600 rounded-xl font-bold text-xs transition-all border border-red-100">
|
<button id="resetSelection" class="w-full mb-4 flex items-center justify-center gap-2 py-2 bg-red-50 hover:bg-red-100 text-red-600 rounded-xl font-bold text-xs transition-all border border-red-100">
|
||||||
|
|||||||
@@ -8,12 +8,14 @@
|
|||||||
<link rel="stylesheet" href="./style/app-shell.css" />
|
<link rel="stylesheet" href="./style/app-shell.css" />
|
||||||
<link rel="stylesheet" href="./style/auth-header.css" />
|
<link rel="stylesheet" href="./style/auth-header.css" />
|
||||||
<link rel="stylesheet" href="./style/deck-history.css" />
|
<link rel="stylesheet" href="./style/deck-history.css" />
|
||||||
<title>내 덱 관리 - UADECK</title>
|
<title>내 덱 관리 - UNIARE</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="app-page deck-history-page">
|
<body class="app-page deck-history-page">
|
||||||
<nav class="app-nav">
|
<nav class="app-nav">
|
||||||
<div class="app-nav-inner">
|
<div class="app-nav-inner">
|
||||||
<a href="/" class="app-brand">UADECK <span>v4.0.0</span></a>
|
<a href="/" class="app-brand">
|
||||||
|
<img src="/images/assets/uniare-h48.png" alt="UNIARE" class="app-brand-logo" />
|
||||||
|
</a>
|
||||||
<div id="auth-header-slot"></div>
|
<div id="auth-header-slot"></div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -278,7 +280,7 @@
|
|||||||
${visibilityBadge}
|
${visibilityBadge}
|
||||||
</div>
|
</div>
|
||||||
<h2 class="deck-card-title">${title}</h2>
|
<h2 class="deck-card-title">${title}</h2>
|
||||||
<div class="deck-card-meta">${escapeHtml(t('deckViews', '조회'))} ${d.view_count} · ${date}</div>
|
<div class="deck-card-meta">${date}</div>
|
||||||
<div class="deck-card-actions deck-card-actions--primary">
|
<div class="deck-card-actions deck-card-actions--primary">
|
||||||
${favoriteButtonHtml(d.share_code)}
|
${favoriteButtonHtml(d.share_code)}
|
||||||
<button type="button" class="deck-card-btn deck-card-btn-load" data-load="${escapeHtml(d.share_code)}" data-series="${escapeHtml(d.series)}" data-name="${escapeHtml(d.series_name || '')}">${escapeHtml(t('deckLoad', '불러오기'))}</button>
|
<button type="button" class="deck-card-btn deck-card-btn-load" data-load="${escapeHtml(d.share_code)}" data-series="${escapeHtml(d.series)}" data-name="${escapeHtml(d.series_name || '')}">${escapeHtml(t('deckLoad', '불러오기'))}</button>
|
||||||
@@ -288,6 +290,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<div class="deck-card-more-menu hidden" role="menu">
|
<div class="deck-card-more-menu hidden" role="menu">
|
||||||
<button type="button" class="deck-card-more-item" role="menuitem" data-copy="${escapeHtml(d.share_code)}">${escapeHtml(t('deckCopyLink', '링크 복사'))}</button>
|
<button type="button" class="deck-card-more-item" role="menuitem" data-copy="${escapeHtml(d.share_code)}">${escapeHtml(t('deckCopyLink', '링크 복사'))}</button>
|
||||||
|
<button type="button" class="deck-card-more-item" role="menuitem" data-rename="${escapeHtml(d.share_code)}" data-current-name="${escapeHtml(getDeckTitle(d))}">${escapeHtml(t('deckRename', '이름 변경'))}</button>
|
||||||
<button type="button" class="deck-card-more-item" role="menuitem" data-toggle="${escapeHtml(d.share_code)}" data-public="${isPublic ? '1' : '0'}">${escapeHtml(toggleLabel)}</button>
|
<button type="button" class="deck-card-more-item" role="menuitem" data-toggle="${escapeHtml(d.share_code)}" data-public="${isPublic ? '1' : '0'}">${escapeHtml(toggleLabel)}</button>
|
||||||
<button type="button" class="deck-card-more-item deck-card-more-item--danger" role="menuitem" data-delete="${escapeHtml(d.share_code)}">${escapeHtml(t('deckDelete', '삭제'))}</button>
|
<button type="button" class="deck-card-more-item deck-card-more-item--danger" role="menuitem" data-delete="${escapeHtml(d.share_code)}">${escapeHtml(t('deckDelete', '삭제'))}</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -441,6 +444,33 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const renameBtn = e.target.closest('[data-rename]');
|
||||||
|
if (renameBtn) {
|
||||||
|
const code = renameBtn.dataset.rename;
|
||||||
|
const current = renameBtn.dataset.currentName || '';
|
||||||
|
const newName = prompt(t('deckRenamePrompt', '새 덱 이름을 입력하세요'), current);
|
||||||
|
if (newName === null) return;
|
||||||
|
const trimmed = newName.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
alert(t('deckRenameEmpty', '이름을 입력해 주세요.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (trimmed === current) {
|
||||||
|
closeAllDeckMenus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await UaDeckApi.updateDeck(code, { name: trimmed });
|
||||||
|
const deck = allDecks.find((d) => d.share_code === code);
|
||||||
|
if (deck) deck.name = trimmed;
|
||||||
|
closeAllDeckMenus();
|
||||||
|
renderDeckList();
|
||||||
|
} catch (err) {
|
||||||
|
alert(err.message || t('deckRenameFailed', '이름 변경에 실패했습니다.'));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const toggleBtn = e.target.closest('[data-toggle]');
|
const toggleBtn = e.target.closest('[data-toggle]');
|
||||||
if (toggleBtn) {
|
if (toggleBtn) {
|
||||||
const code = toggleBtn.dataset.toggle;
|
const code = toggleBtn.dataset.toggle;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# UADECK v4 프로덕션 스택 (NAS Docker)
|
# UNIARE v4 프로덕션 스택 (NAS Docker)
|
||||||
# 사용: docker compose -f docker-compose.prod.yml up -d --build
|
# 사용: docker compose -f docker-compose.prod.yml up -d --build
|
||||||
#
|
#
|
||||||
# 정적 파일(HTML, images, datas)은 필요한 경로만 read-only 마운트 — 카드/이미지 추가 시 재빌드 불필요
|
# 정적 파일(HTML, images, datas)은 필요한 경로만 read-only 마운트 — 카드/이미지 추가 시 재빌드 불필요
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# 옆 프로젝트 API 연동 가이드
|
# 옆 프로젝트 API 연동 가이드
|
||||||
|
|
||||||
UADECK v4부터 덱 데이터는 **공유 코드 + REST API**로 제공합니다.
|
UNIARE v4부터 덱 데이터는 **공유 코드 + REST API**로 제공합니다.
|
||||||
URL 쿼리(`?cards=`) 방식은 사용하지 않습니다.
|
URL 쿼리(`?cards=`) 방식은 사용하지 않습니다.
|
||||||
|
|
||||||
## 공식 엔드포인트
|
## 공식 엔드포인트
|
||||||
@@ -48,7 +48,7 @@ GET https://uniare.sori.studio/api/v1/decks/:shareCode
|
|||||||
|
|
||||||
| 방법 | 예시 |
|
| 방법 | 예시 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| UADECK 공유 버튼 | `https://uniare.sori.studio/d/x7Kp9m2Q` |
|
| UNIARE 공유 버튼 | `https://uniare.sori.studio/d/x7Kp9m2Q` |
|
||||||
| URL 파라미터 | `cardList.html?d=x7Kp9m2Q` |
|
| URL 파라미터 | `cardList.html?d=x7Kp9m2Q` |
|
||||||
| 코드만 | `x7Kp9m2Q` |
|
| 코드만 | `x7Kp9m2Q` |
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ https://uniare.sori.studio/images{imgSrc}
|
|||||||
|
|
||||||
## CORS
|
## CORS
|
||||||
|
|
||||||
다른 도메인에서 브라우저 `fetch`를 쓰려면 UADECK 서버 `.env`의 `CORS_ORIGINS`에 해당 origin을 추가하세요.
|
다른 도메인에서 브라우저 `fetch`를 쓰려면 UNIARE 서버 `.env`의 `CORS_ORIGINS`에 해당 origin을 추가하세요.
|
||||||
|
|
||||||
```
|
```
|
||||||
CORS_ORIGINS=https://your-app.sori.studio,https://uniare.sori.studio
|
CORS_ORIGINS=https://your-app.sori.studio,https://uniare.sori.studio
|
||||||
|
|||||||
@@ -24,7 +24,8 @@
|
|||||||
|
|
||||||
## CSS 규칙
|
## CSS 규칙
|
||||||
- 공통 UI는 가능한 한 Tailwind 유틸리티 클래스를 우선 사용한다.
|
- 공통 UI는 가능한 한 Tailwind 유틸리티 클래스를 우선 사용한다.
|
||||||
- `style/output.css`에 포함되지 않은 유틸리티 클래스는 적용되지 않으므로, 페이지 특화 스타일은 인라인 `<style>` 또는 전용 CSS 파일(`style/auth-pages.css` 등)에 명시한다.
|
- `style/output.css`는 **빌드 산출물**이다. HTML 등에 새 Tailwind 클래스를 추가한 뒤 `npm run build:css`로 재생성한다.
|
||||||
|
- `style/output.css`에 포함되지 않은 유틸리티 클래스는 적용되지 않으므로, 빌드 전까지는 페이지 특화 스타일을 인라인 `<style>` 또는 전용 CSS 파일(`style/auth-pages.css` 등)에 명시한다.
|
||||||
- 페이지 특화 보정 스타일만 인라인 `<style>` 또는 전용 CSS 파일에 둔다.
|
- 페이지 특화 보정 스타일만 인라인 `<style>` 또는 전용 CSS 파일에 둔다.
|
||||||
- 캡처 안정성처럼 이유가 분명한 예외 스타일은 주석으로 목적을 남긴다.
|
- 캡처 안정성처럼 이유가 분명한 예외 스타일은 주석으로 목적을 남긴다.
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NAS 배포 가이드 (UADECK v4)
|
# NAS 배포 가이드 (UNIARE v4)
|
||||||
|
|
||||||
## 1. 사전 준비
|
## 1. 사전 준비
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# 기술 명세
|
# 기술 명세
|
||||||
|
|
||||||
## 1. 프로젝트 개요
|
## 1. 프로젝트 개요
|
||||||
- 프로젝트명: `UADECK V4.0.0 - Union Arena Deck Builder`
|
- 프로젝트명: `UNIARE - Union Arena Deck Builder` (표시명 UNIARE, 내부 API 식별자 일부는 `uadeck` 레거시 유지)
|
||||||
- 형태: 정적 HTML/CSS/JavaScript 프론트 + Docker API 백엔드
|
- 형태: 정적 HTML/CSS/JavaScript 프론트 + Docker API 백엔드
|
||||||
- 패키지 의존성 (프론트): `tailwindcss`, `@tailwindcss/cli`
|
- 패키지 의존성 (프론트): `tailwindcss`, `@tailwindcss/cli`
|
||||||
- 패키지 의존성 (API): `express`, `better-sqlite3`, `bcryptjs`, `jsonwebtoken`, `cors`
|
- 패키지 의존성 (API): `express`, `better-sqlite3`, `bcryptjs`, `jsonwebtoken`, `cors`
|
||||||
|
|||||||
@@ -1,5 +1,33 @@
|
|||||||
# 업데이트 이력
|
# 업데이트 이력
|
||||||
|
|
||||||
|
## 2026-06-22 / v4.0.0 — 덱 카드 조회 수 제거
|
||||||
|
|
||||||
|
### 수행 작업
|
||||||
|
- `deckHistory.html`, `preset.html`: 카드 메타에서 조회 수 표시 제거 (저장일·작성자만 유지)
|
||||||
|
|
||||||
|
## 2026-06-22 / v4.0.0 — 제품명 UNIARE·로고 이미지
|
||||||
|
|
||||||
|
### 수행 작업
|
||||||
|
- 표시 제품명 `UADECK` → **UNIARE** (페이지 title, i18n, 관리자, 연동 문서)
|
||||||
|
- 상단 헤더 텍스트 로고 → `images/assets/uniare-h48.png` 이미지 로고
|
||||||
|
- 이미지 다운로드 파일명 접두사 `uadeck_` → `uniare_`
|
||||||
|
- 내부 호환 유지: `UaDeckApi`, `uadeck_token`, `uadeck.db`, `UADECK_API_BASE` 등
|
||||||
|
|
||||||
|
## 2026-06-22 / v4.0.0 — Tailwind CSS 재빌드
|
||||||
|
|
||||||
|
### 수행 작업
|
||||||
|
- `package.json`: `npm run build:css` 스크립트 추가
|
||||||
|
- `style/output.css` 재생성 — `sm:px-6` 등 HTML에 추가됐으나 빌드에 없던 클래스 반영
|
||||||
|
- `cardList.html`, `result.html`: `px-4 sm:px-6` 유지 (클래스 우회 제거)
|
||||||
|
|
||||||
|
## 2026-06-22 / v4.0.0 — 레이아웃·덱 이름 변경
|
||||||
|
|
||||||
|
### 수행 작업
|
||||||
|
- 헤더 `v4.0.0` 표시 제거 (로그인 페이지 하단 푸터에만 작게 표시)
|
||||||
|
- `deckHistory.html`: 더보기 메뉴에 **이름 변경** 추가 (`PUT /decks/:code`)
|
||||||
|
- `cardList.html`, `result.html`: 콘텐츠·헤더 최대 너비 `1400px` → **1280px** (`max-w-7xl`) 통일
|
||||||
|
- `profile.css`: 프로필 본문 `max-width: 42rem` 제거 → 내 덱·공개 덱과 동일한 `app-main` 너비
|
||||||
|
|
||||||
## 2026-06-22 / v4.0.0 — Docker 서비스명 `uniare-*` 통일
|
## 2026-06-22 / v4.0.0 — Docker 서비스명 `uniare-*` 통일
|
||||||
|
|
||||||
### 수행 작업
|
### 수행 작업
|
||||||
@@ -45,7 +73,7 @@ npm stop && npm start
|
|||||||
## 2026-06-19 / v4.0.0 — 공개 덱 미리보기 수정
|
## 2026-06-19 / v4.0.0 — 공개 덱 미리보기 수정
|
||||||
|
|
||||||
### 수행 작업
|
### 수행 작업
|
||||||
- `deck-preview-hold.js`: mouseleave 즉시 닫힘 버그 수정, 클릭 고정·길게 누르기 동시 지원
|
- `deck-preview-hold.js`: mouseleave 즉시 닫힘 버그 수정, 길게 누르는 동안만 확대 (짧은 클릭 고정 제거)
|
||||||
- ~~안내 문구:「클릭·길게 눌러 크게 보기」~~ → 이후 제거됨
|
- ~~안내 문구:「클릭·길게 눌러 크게 보기」~~ → 이후 제거됨
|
||||||
|
|
||||||
## 2026-06-19 / v4.0.0 — 프로필 덱 저장 설정 UI
|
## 2026-06-19 / v4.0.0 — 프로필 덱 저장 설정 UI
|
||||||
|
|||||||
@@ -77,7 +77,7 @@
|
|||||||
"loginPageTitle": "Log in",
|
"loginPageTitle": "Log in",
|
||||||
"loginPageDesc": "Sign in to save and manage<br />your decks.",
|
"loginPageDesc": "Sign in to save and manage<br />your decks.",
|
||||||
"signupPageTitle": "Sign up",
|
"signupPageTitle": "Sign up",
|
||||||
"signupPageDesc": "Create a UADECK account to<br />save and share decks.",
|
"signupPageDesc": "Create a UNIARE account to<br />save and share decks.",
|
||||||
"emailLabel": "Email",
|
"emailLabel": "Email",
|
||||||
"passwordLabel": "Password",
|
"passwordLabel": "Password",
|
||||||
"usernameLabel": "Username",
|
"usernameLabel": "Username",
|
||||||
@@ -127,6 +127,10 @@
|
|||||||
"deckMakePrivate": "Make private",
|
"deckMakePrivate": "Make private",
|
||||||
"deckLoad": "Load",
|
"deckLoad": "Load",
|
||||||
"deckCopyLink": "Copy link",
|
"deckCopyLink": "Copy link",
|
||||||
|
"deckRename": "Rename",
|
||||||
|
"deckRenamePrompt": "Enter a new deck name",
|
||||||
|
"deckRenameEmpty": "Please enter a name.",
|
||||||
|
"deckRenameFailed": "Failed to rename deck.",
|
||||||
"deckDelete": "Delete",
|
"deckDelete": "Delete",
|
||||||
"deckViews": "Views",
|
"deckViews": "Views",
|
||||||
"deckCountFormat": "{filtered} / {total}",
|
"deckCountFormat": "{filtered} / {total}",
|
||||||
|
|||||||
@@ -77,7 +77,7 @@
|
|||||||
"loginPageTitle": "ログイン",
|
"loginPageTitle": "ログイン",
|
||||||
"loginPageDesc": "デッキを保存・管理するには<br />アカウントでログインしてください。",
|
"loginPageDesc": "デッキを保存・管理するには<br />アカウントでログインしてください。",
|
||||||
"signupPageTitle": "会員登録",
|
"signupPageTitle": "会員登録",
|
||||||
"signupPageDesc": "UADECKアカウントを作成して<br />デッキを保存・共有しましょう。",
|
"signupPageDesc": "UNIAREアカウントを作成して<br />デッキを保存・共有しましょう。",
|
||||||
"emailLabel": "メール",
|
"emailLabel": "メール",
|
||||||
"passwordLabel": "パスワード",
|
"passwordLabel": "パスワード",
|
||||||
"usernameLabel": "ユーザー名",
|
"usernameLabel": "ユーザー名",
|
||||||
@@ -127,6 +127,10 @@
|
|||||||
"deckMakePrivate": "非公開にする",
|
"deckMakePrivate": "非公開にする",
|
||||||
"deckLoad": "読み込み",
|
"deckLoad": "読み込み",
|
||||||
"deckCopyLink": "リンクをコピー",
|
"deckCopyLink": "リンクをコピー",
|
||||||
|
"deckRename": "名前を変更",
|
||||||
|
"deckRenamePrompt": "新しいデッキ名を入力してください",
|
||||||
|
"deckRenameEmpty": "名前を入力してください。",
|
||||||
|
"deckRenameFailed": "名前の変更に失敗しました。",
|
||||||
"deckDelete": "削除",
|
"deckDelete": "削除",
|
||||||
"deckViews": "閲覧",
|
"deckViews": "閲覧",
|
||||||
"deckCountFormat": "{filtered} / {total}件",
|
"deckCountFormat": "{filtered} / {total}件",
|
||||||
|
|||||||
@@ -77,7 +77,7 @@
|
|||||||
"loginPageTitle": "로그인",
|
"loginPageTitle": "로그인",
|
||||||
"loginPageDesc": "덱을 저장하고 관리하려면<br />계정으로 로그인하세요.",
|
"loginPageDesc": "덱을 저장하고 관리하려면<br />계정으로 로그인하세요.",
|
||||||
"signupPageTitle": "회원가입",
|
"signupPageTitle": "회원가입",
|
||||||
"signupPageDesc": "UADECK 계정을 만들고<br />덱을 저장·공유해 보세요.",
|
"signupPageDesc": "UNIARE 계정을 만들고<br />덱을 저장·공유해 보세요.",
|
||||||
"emailLabel": "이메일",
|
"emailLabel": "이메일",
|
||||||
"passwordLabel": "비밀번호",
|
"passwordLabel": "비밀번호",
|
||||||
"usernameLabel": "사용자명",
|
"usernameLabel": "사용자명",
|
||||||
@@ -127,6 +127,10 @@
|
|||||||
"deckMakePrivate": "비공개 전환",
|
"deckMakePrivate": "비공개 전환",
|
||||||
"deckLoad": "불러오기",
|
"deckLoad": "불러오기",
|
||||||
"deckCopyLink": "링크 복사",
|
"deckCopyLink": "링크 복사",
|
||||||
|
"deckRename": "이름 변경",
|
||||||
|
"deckRenamePrompt": "새 덱 이름을 입력하세요",
|
||||||
|
"deckRenameEmpty": "이름을 입력해 주세요.",
|
||||||
|
"deckRenameFailed": "이름 변경에 실패했습니다.",
|
||||||
"deckDelete": "삭제",
|
"deckDelete": "삭제",
|
||||||
"deckViews": "조회",
|
"deckViews": "조회",
|
||||||
"deckCountFormat": "{filtered} / {total}개",
|
"deckCountFormat": "{filtered} / {total}개",
|
||||||
|
|||||||
BIN
images/assets/uniare-h48.png
LFS
Normal file
BIN
images/assets/uniare-h48.png
LFS
Normal file
Binary file not shown.
14
index.html
14
index.html
@@ -10,7 +10,7 @@
|
|||||||
<script src="/script/navigation.js"></script>
|
<script src="/script/navigation.js"></script>
|
||||||
<script src="/script/api.js"></script>
|
<script src="/script/api.js"></script>
|
||||||
<link rel="stylesheet" href="./style/auth-header.css" />
|
<link rel="stylesheet" href="./style/auth-header.css" />
|
||||||
<title>UADECK V4.0.0 - Union Arena Deck Builder</title>
|
<title>UNIARE - Union Arena Deck Builder</title>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
@@ -58,15 +58,9 @@
|
|||||||
<body class="text-slate-900">
|
<body class="text-slate-900">
|
||||||
<nav class="sticky top-0 z-50 bg-white/70 backdrop-blur-lg border-b border-slate-200">
|
<nav class="sticky top-0 z-50 bg-white/70 backdrop-blur-lg border-b border-slate-200">
|
||||||
<div class="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between">
|
<div class="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between">
|
||||||
<div class="flex items-center">
|
<a href="/" class="inline-flex items-center">
|
||||||
<div class="w-8 h-8 rounded-lg flex items-center justify-center overflow-hidden">
|
<img src="/images/assets/uniare-h48.png" alt="UNIARE" class="h-8 w-auto" />
|
||||||
<img src="/images/assets/apple-touch-icon.png" alt="UA Logo" class="w-6 h-6 object-contain" />
|
</a>
|
||||||
</div>
|
|
||||||
<span class="text-xl font-extrabold tracking-tight text-slate-800">
|
|
||||||
UADECK
|
|
||||||
<span class="text-blue-600">v4.0.0</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<div class="language-selector flex gap-1 p-1 bg-slate-100 rounded-xl">
|
<div class="language-selector flex gap-1 p-1 bg-slate-100 rounded-xl">
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const client = new UaDeckDeckClient({
|
|||||||
imageBase: 'https://uniare.sori.studio',
|
imageBase: 'https://uniare.sori.studio',
|
||||||
});
|
});
|
||||||
|
|
||||||
// UADECK 공유 링크에서 덱 로드
|
// UNIARE 공유 링크에서 덱 로드
|
||||||
const deck = await client.fetchFromUrl('https://uniare.sori.studio/d/SHARE_CODE');
|
const deck = await client.fetchFromUrl('https://uniare.sori.studio/d/SHARE_CODE');
|
||||||
|
|
||||||
// 카드 사용
|
// 카드 사용
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>UADECK API 연동 예제</title>
|
<title>UNIARE API 연동 예제</title>
|
||||||
<style>
|
<style>
|
||||||
body { font-family: system-ui, sans-serif; max-width: 640px; margin: 2rem auto; padding: 0 1rem; }
|
body { font-family: system-ui, sans-serif; max-width: 640px; margin: 2rem auto; padding: 0 1rem; }
|
||||||
input { width: 100%; padding: 0.5rem; margin: 0.5rem 0 1rem; box-sizing: border-box; }
|
input { width: 100%; padding: 0.5rem; margin: 0.5rem 0 1rem; box-sizing: border-box; }
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>UADECK API 연동 예제</h1>
|
<h1>UNIARE API 연동 예제</h1>
|
||||||
<p>공유 링크 또는 shareCode로 덱을 불러옵니다.</p>
|
<p>공유 링크 또는 shareCode로 덱을 불러옵니다.</p>
|
||||||
|
|
||||||
<label>API Base URL</label>
|
<label>API Base URL</label>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* UADECK 덱 API 클라이언트 (옆 프로젝트 연동용)
|
* UNIARE 덱 API 클라이언트 (옆 프로젝트 연동용)
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* const client = new UaDeckDeckClient({ baseUrl: 'https://uniare.sori.studio/api/v1' });
|
* const client = new UaDeckDeckClient({ baseUrl: 'https://uniare.sori.studio/api/v1' });
|
||||||
|
|||||||
@@ -6,14 +6,13 @@
|
|||||||
<link rel="stylesheet" href="./style/output.css" />
|
<link rel="stylesheet" href="./style/output.css" />
|
||||||
<link rel="stylesheet" href="./style/auth-pages.css" />
|
<link rel="stylesheet" href="./style/auth-pages.css" />
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=Noto+Sans+KR:wght@400;700&display=swap" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=Noto+Sans+KR:wght@400;700&display=swap" rel="stylesheet" />
|
||||||
<title>로그인 - UADECK</title>
|
<title>로그인 - UNIARE</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="auth-page">
|
<body class="auth-page">
|
||||||
<nav class="auth-nav">
|
<nav class="auth-nav">
|
||||||
<div class="auth-nav-inner">
|
<div class="auth-nav-inner">
|
||||||
<a href="/" class="auth-brand">
|
<a href="/" class="auth-brand">
|
||||||
<img src="/images/assets/apple-touch-icon.png" alt="UA Logo" />
|
<img src="/images/assets/uniare-h48.png" alt="UNIARE" class="app-brand-logo" />
|
||||||
<span class="auth-brand-text">UADECK <span>v4.0.0</span></span>
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -43,6 +42,8 @@
|
|||||||
</p>
|
</p>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<footer class="auth-version-footer" aria-hidden="true">v4.0.0</footer>
|
||||||
|
|
||||||
<script src="/script/page-i18n.js"></script>
|
<script src="/script/page-i18n.js"></script>
|
||||||
<script src="/script/api.js"></script>
|
<script src="/script/api.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
"tailwindcss": "^4.1.12"
|
"tailwindcss": "^4.1.12"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "docker compose -f docker-compose.prod.yml up -d --build && node -e \"console.log('\\nUADECK running: http://localhost:' + (process.env.WEB_PORT || '44082') + '\\nStop: npm stop\\nLogs: npm run logs\\n')\"",
|
"build:css": "npx @tailwindcss/cli -i ./style/input.css -o ./style/output.css --minify",
|
||||||
|
"start": "docker compose -f docker-compose.prod.yml up -d --build && node -e \"console.log('\\nUNIARE running: http://localhost:' + (process.env.WEB_PORT || '44082') + '\\nStop: npm stop\\nLogs: npm run logs\\n')\"",
|
||||||
"stop": "docker compose -f docker-compose.prod.yml down",
|
"stop": "docker compose -f docker-compose.prod.yml down",
|
||||||
"logs": "docker compose -f docker-compose.prod.yml logs -f",
|
"logs": "docker compose -f docker-compose.prod.yml logs -f",
|
||||||
"dev": "docker compose -f docker-compose.prod.yml up --build",
|
"dev": "docker compose -f docker-compose.prod.yml up --build",
|
||||||
|
|||||||
@@ -8,12 +8,14 @@
|
|||||||
<link rel="stylesheet" href="./style/app-shell.css" />
|
<link rel="stylesheet" href="./style/app-shell.css" />
|
||||||
<link rel="stylesheet" href="./style/auth-header.css" />
|
<link rel="stylesheet" href="./style/auth-header.css" />
|
||||||
<link rel="stylesheet" href="./style/deck-history.css" />
|
<link rel="stylesheet" href="./style/deck-history.css" />
|
||||||
<title>공개 덱 - UADECK</title>
|
<title>공개 덱 - UNIARE</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="app-page deck-history-page">
|
<body class="app-page deck-history-page">
|
||||||
<nav class="app-nav">
|
<nav class="app-nav">
|
||||||
<div class="app-nav-inner">
|
<div class="app-nav-inner">
|
||||||
<a href="/" class="app-brand">UADECK <span>v4.0.0</span></a>
|
<a href="/" class="app-brand">
|
||||||
|
<img src="/images/assets/uniare-h48.png" alt="UNIARE" class="app-brand-logo" />
|
||||||
|
</a>
|
||||||
<div id="auth-header-slot"></div>
|
<div id="auth-header-slot"></div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -189,7 +191,7 @@
|
|||||||
<div class="deck-card-body">
|
<div class="deck-card-body">
|
||||||
<span class="deck-card-series">${seriesLabel}</span>
|
<span class="deck-card-series">${seriesLabel}</span>
|
||||||
<h2 class="deck-card-title">${title}</h2>
|
<h2 class="deck-card-title">${title}</h2>
|
||||||
<div class="deck-card-meta">${escapeHtml(d.author)} · ${escapeHtml(t('deckViews', '조회'))} ${d.view_count} · ${date}</div>
|
<div class="deck-card-meta">${escapeHtml(d.author)} · ${date}</div>
|
||||||
<div class="deck-card-actions">
|
<div class="deck-card-actions">
|
||||||
${favoriteButtonHtml(d.share_code)}
|
${favoriteButtonHtml(d.share_code)}
|
||||||
<button type="button" class="deck-card-btn deck-card-btn-load" data-load="${escapeHtml(d.share_code)}" data-series="${escapeHtml(d.series)}" data-name="${escapeHtml(d.series_name || '')}">${escapeHtml(t('deckLoad', '불러오기'))}</button>
|
<button type="button" class="deck-card-btn deck-card-btn-load" data-load="${escapeHtml(d.share_code)}" data-series="${escapeHtml(d.series)}" data-name="${escapeHtml(d.series_name || '')}">${escapeHtml(t('deckLoad', '불러오기'))}</button>
|
||||||
|
|||||||
@@ -8,12 +8,14 @@
|
|||||||
<link rel="stylesheet" href="./style/app-shell.css" />
|
<link rel="stylesheet" href="./style/app-shell.css" />
|
||||||
<link rel="stylesheet" href="./style/auth-header.css" />
|
<link rel="stylesheet" href="./style/auth-header.css" />
|
||||||
<link rel="stylesheet" href="./style/profile.css" />
|
<link rel="stylesheet" href="./style/profile.css" />
|
||||||
<title>내 프로필 - UADECK</title>
|
<title>내 프로필 - UNIARE</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="app-page profile-page">
|
<body class="app-page profile-page">
|
||||||
<nav class="app-nav">
|
<nav class="app-nav">
|
||||||
<div class="app-nav-inner">
|
<div class="app-nav-inner">
|
||||||
<a href="/" class="app-brand">UADECK <span>v4.0.0</span></a>
|
<a href="/" class="app-brand">
|
||||||
|
<img src="/images/assets/uniare-h48.png" alt="UNIARE" class="app-brand-logo" />
|
||||||
|
</a>
|
||||||
<div id="auth-header-slot"></div>
|
<div id="auth-header-slot"></div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -424,13 +424,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sticky top-0 z-50 h-16 bg-white/80 backdrop-blur-md border-b px-4 py-3 flex justify-between items-center">
|
<div class="sticky top-0 z-50 bg-white/80 backdrop-blur-md border-b">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 h-16 flex justify-between items-center">
|
||||||
<button id="i18n-editDeck" onclick="goBackToCardList()" class="font-bold text-slate-600 hover:text-blue-600 transition-colors">← EDIT DECK</button>
|
<button id="i18n-editDeck" onclick="goBackToCardList()" class="font-bold text-slate-600 hover:text-blue-600 transition-colors">← EDIT DECK</button>
|
||||||
<div id="i18n-previewTitle" class="font-bold text-slate-800 uppercase tracking-wider">Deck Preview</div>
|
<div id="i18n-previewTitle" class="font-bold text-slate-800 uppercase tracking-wider">Deck Preview</div>
|
||||||
<div id="auth-header-slot"></div>
|
<div id="auth-header-slot"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="max-w-[1400px] mx-auto p-4 flex flex-col lg:flex-row gap-6">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 py-4 flex flex-col lg:flex-row gap-6">
|
||||||
<aside class="w-full lg:w-80 space-y-4 lg:sticky lg:top-20 lg:h-fit">
|
<aside class="w-full lg:w-80 space-y-4 lg:sticky lg:top-20 lg:h-fit">
|
||||||
<div class="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm space-y-6">
|
<div class="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm space-y-6">
|
||||||
<div>
|
<div>
|
||||||
@@ -1177,7 +1179,7 @@
|
|||||||
loader.setProgress(92, t('generatingImage', '이미지 생성 중...'));
|
loader.setProgress(92, t('generatingImage', '이미지 생성 중...'));
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const timestamp = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}_${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(2, '0')}${String(now.getSeconds()).padStart(2, '0')}`;
|
const timestamp = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}_${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(2, '0')}${String(now.getSeconds()).padStart(2, '0')}`;
|
||||||
const fileNameStr = `uadeck_${deckName}_${timestamp}`;
|
const fileNameStr = `uniare_${deckName}_${timestamp}`;
|
||||||
|
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.download = `${fileNameStr}.png`;
|
link.download = `${fileNameStr}.png`;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* UADECK API 클라이언트
|
* UNIARE API 클라이언트
|
||||||
*/
|
*/
|
||||||
const UaDeckApi = (() => {
|
const UaDeckApi = (() => {
|
||||||
const TOKEN_KEY = 'uadeck_token';
|
const TOKEN_KEY = 'uadeck_token';
|
||||||
@@ -10,6 +10,7 @@ const UaDeckApi = (() => {
|
|||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
function getBaseUrl() {
|
function getBaseUrl() {
|
||||||
|
if (window.UNIARE_API_BASE) return window.UNIARE_API_BASE;
|
||||||
if (window.UADECK_API_BASE) return window.UADECK_API_BASE;
|
if (window.UADECK_API_BASE) return window.UADECK_API_BASE;
|
||||||
|
|
||||||
const { hostname, port, protocol } = window.location;
|
const { hostname, port, protocol } = window.location;
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
/**
|
/**
|
||||||
* 썸네일 미리보기 확대 (클릭 고정 / 길게 누르기)
|
* 썸네일 미리보기 확대 (길게 누르는 동안만 표시)
|
||||||
*/
|
*/
|
||||||
(function initDeckPreviewHold() {
|
(function initDeckPreviewHold() {
|
||||||
/** @type {HTMLElement|null} */
|
/** @type {HTMLElement|null} */
|
||||||
let overlay = null;
|
let overlay = null;
|
||||||
|
|
||||||
/** @type {number} */
|
/** @type {number|null} */
|
||||||
let pressStartAt = 0;
|
let holdTimer = null;
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
let suppressClick = false;
|
|
||||||
|
|
||||||
/** @type {number} */
|
/** @type {number} */
|
||||||
const HOLD_THRESHOLD_MS = 280;
|
const HOLD_THRESHOLD_MS = 280;
|
||||||
@@ -25,21 +22,11 @@
|
|||||||
overlay.setAttribute('aria-modal', 'true');
|
overlay.setAttribute('aria-modal', 'true');
|
||||||
overlay.innerHTML =
|
overlay.innerHTML =
|
||||||
'<div class="deck-preview-hold-box"><img alt="" draggable="false" /></div>';
|
'<div class="deck-preview-hold-box"><img alt="" draggable="false" /></div>';
|
||||||
overlay.addEventListener('click', (e) => {
|
|
||||||
if (e.target === overlay) hide();
|
|
||||||
});
|
|
||||||
document.body.appendChild(overlay);
|
document.body.appendChild(overlay);
|
||||||
}
|
}
|
||||||
return overlay;
|
return overlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
function isVisible() {
|
|
||||||
return Boolean(overlay && !overlay.classList.contains('hidden'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} src
|
* @param {string} src
|
||||||
*/
|
*/
|
||||||
@@ -51,10 +38,16 @@
|
|||||||
el.classList.remove('hidden');
|
el.classList.remove('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearHoldTimer() {
|
||||||
|
if (holdTimer !== null) {
|
||||||
|
window.clearTimeout(holdTimer);
|
||||||
|
holdTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function hide() {
|
function hide() {
|
||||||
|
clearHoldTimer();
|
||||||
if (overlay) overlay.classList.add('hidden');
|
if (overlay) overlay.classList.add('hidden');
|
||||||
pressStartAt = 0;
|
|
||||||
suppressClick = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,6 +68,20 @@
|
|||||||
return hold.dataset.previewSrc || '';
|
return hold.dataset.previewSrc || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {PointerEvent} e
|
||||||
|
* @param {HTMLElement|null} hold
|
||||||
|
*/
|
||||||
|
function releaseCapture(e, hold) {
|
||||||
|
if (hold?.releasePointerCapture && e.pointerId !== undefined) {
|
||||||
|
try {
|
||||||
|
hold.releasePointerCapture(e.pointerId);
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function initGlobal() {
|
function initGlobal() {
|
||||||
if (document.documentElement.dataset.deckHoldInit === '1') return;
|
if (document.documentElement.dataset.deckHoldInit === '1') return;
|
||||||
document.documentElement.dataset.deckHoldInit = '1';
|
document.documentElement.dataset.deckHoldInit = '1';
|
||||||
@@ -87,8 +94,11 @@
|
|||||||
const src = getPreviewSrc(hold);
|
const src = getPreviewSrc(hold);
|
||||||
if (!src) return;
|
if (!src) return;
|
||||||
|
|
||||||
pressStartAt = Date.now();
|
clearHoldTimer();
|
||||||
show(src);
|
holdTimer = window.setTimeout(() => {
|
||||||
|
holdTimer = null;
|
||||||
|
show(src);
|
||||||
|
}, HOLD_THRESHOLD_MS);
|
||||||
|
|
||||||
if (hold.setPointerCapture && e.pointerId !== undefined) {
|
if (hold.setPointerCapture && e.pointerId !== undefined) {
|
||||||
try {
|
try {
|
||||||
@@ -104,32 +114,9 @@
|
|||||||
document.addEventListener(
|
document.addEventListener(
|
||||||
'pointerup',
|
'pointerup',
|
||||||
(e) => {
|
(e) => {
|
||||||
if (!pressStartAt) return;
|
|
||||||
|
|
||||||
const hold = findHoldTarget(e);
|
const hold = findHoldTarget(e);
|
||||||
if (hold?.releasePointerCapture && e.pointerId !== undefined) {
|
releaseCapture(e, hold);
|
||||||
try {
|
hide();
|
||||||
hold.releasePointerCapture(e.pointerId);
|
|
||||||
} catch {
|
|
||||||
/* ignore */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const heldMs = Date.now() - pressStartAt;
|
|
||||||
pressStartAt = 0;
|
|
||||||
|
|
||||||
if (heldMs >= HOLD_THRESHOLD_MS) {
|
|
||||||
hide();
|
|
||||||
suppressClick = true;
|
|
||||||
window.setTimeout(() => {
|
|
||||||
suppressClick = false;
|
|
||||||
}, 0);
|
|
||||||
} else {
|
|
||||||
suppressClick = true;
|
|
||||||
window.setTimeout(() => {
|
|
||||||
suppressClick = false;
|
|
||||||
}, 400);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
@@ -142,25 +129,6 @@
|
|||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
document.addEventListener('click', (e) => {
|
|
||||||
if (suppressClick) return;
|
|
||||||
|
|
||||||
const hold = findHoldTarget(e);
|
|
||||||
if (hold) {
|
|
||||||
const src = getPreviewSrc(hold);
|
|
||||||
if (!src) return;
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
if (isVisible()) hide();
|
|
||||||
else show(src);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isVisible() && e.target === overlay) {
|
|
||||||
hide();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
if (e.key === 'Escape') hide();
|
if (e.key === 'Escape') hide();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,14 +6,13 @@
|
|||||||
<link rel="stylesheet" href="./style/output.css" />
|
<link rel="stylesheet" href="./style/output.css" />
|
||||||
<link rel="stylesheet" href="./style/auth-pages.css" />
|
<link rel="stylesheet" href="./style/auth-pages.css" />
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=Noto+Sans+KR:wght@400;700&display=swap" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=Noto+Sans+KR:wght@400;700&display=swap" rel="stylesheet" />
|
||||||
<title>회원가입 - UADECK</title>
|
<title>회원가입 - UNIARE</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="auth-page">
|
<body class="auth-page">
|
||||||
<nav class="auth-nav">
|
<nav class="auth-nav">
|
||||||
<div class="auth-nav-inner">
|
<div class="auth-nav-inner">
|
||||||
<a href="/" class="auth-brand">
|
<a href="/" class="auth-brand">
|
||||||
<img src="/images/assets/apple-touch-icon.png" alt="UA Logo" />
|
<img src="/images/assets/uniare-h48.png" alt="UNIARE" class="app-brand-logo" />
|
||||||
<span class="auth-brand-text">UADECK <span>v4.0.0</span></span>
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -21,7 +20,7 @@
|
|||||||
<main class="auth-main">
|
<main class="auth-main">
|
||||||
<header class="auth-header">
|
<header class="auth-header">
|
||||||
<h1 data-i18n="signupPageTitle">회원가입</h1>
|
<h1 data-i18n="signupPageTitle">회원가입</h1>
|
||||||
<p data-i18n-html="signupPageDesc">UADECK 계정을 만들고<br />덱을 저장·공유해 보세요.</p>
|
<p data-i18n-html="signupPageDesc">UNIARE 계정을 만들고<br />덱을 저장·공유해 보세요.</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<form id="signup-form" class="auth-card">
|
<form id="signup-form" class="auth-card">
|
||||||
|
|||||||
@@ -29,15 +29,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.admin-brand {
|
.admin-brand {
|
||||||
font-size: 1.25rem;
|
display: inline-flex;
|
||||||
font-weight: 800;
|
align-items: center;
|
||||||
color: #1e293b;
|
gap: 0.625rem;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
letter-spacing: -0.025em;
|
letter-spacing: -0.025em;
|
||||||
|
line-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-brand span {
|
.admin-brand .app-brand-logo {
|
||||||
color: #2563eb;
|
height: 2rem;
|
||||||
|
width: auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-brand-label {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #1e293b;
|
||||||
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-main {
|
.admin-main {
|
||||||
|
|||||||
@@ -38,16 +38,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.app-brand {
|
.app-brand {
|
||||||
font-size: 1.25rem;
|
display: inline-flex;
|
||||||
font-weight: 800;
|
align-items: center;
|
||||||
color: #1e293b;
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
letter-spacing: -0.025em;
|
line-height: 0;
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-brand span {
|
.app-brand-logo {
|
||||||
color: #2563eb;
|
height: 2rem;
|
||||||
|
width: auto;
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-ad-top {
|
.app-ad-top {
|
||||||
|
|||||||
@@ -30,27 +30,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.auth-brand {
|
.auth-brand {
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #1e293b;
|
color: #1e293b;
|
||||||
|
line-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-brand img {
|
.auth-brand .app-brand-logo {
|
||||||
width: 2rem;
|
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
object-fit: contain;
|
width: auto;
|
||||||
}
|
display: block;
|
||||||
|
|
||||||
.auth-brand-text {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 800;
|
|
||||||
letter-spacing: -0.025em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-brand-text span {
|
|
||||||
color: #2563eb;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-main {
|
.auth-main {
|
||||||
@@ -185,3 +175,12 @@
|
|||||||
.auth-footer a:hover {
|
.auth-footer a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.auth-version-footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 0 1.5rem 1.25rem;
|
||||||
|
font-size: 0.625rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #cbd5e1;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
|||||||
1768
style/output.css
1768
style/output.css
File diff suppressed because one or more lines are too long
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
.profile-content {
|
.profile-content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 42rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-header {
|
.profile-header {
|
||||||
|
|||||||
Reference in New Issue
Block a user