Compare commits

...

3 Commits

15 changed files with 136 additions and 111 deletions

View File

@@ -1,5 +1,15 @@
# 의사결정 이력
## 2026-04-02 v1.4.1
- 좌측 메뉴와 화면 타이틀의 명칭이 서로 다르면 사용자가 현재 위치를 직관적으로 매칭하기 어렵기 때문에, 메뉴 이름과 진입 타이틀을 같은 문구로 맞추는 편이 맞다고 판단했다.
## 2026-04-02 v1.4.0
- 서비스가 게임 외 주제 전반을 다룰 수 있는 단계에 온 만큼, 내부 모델명은 유지하더라도 사용자에게 보이는 주요 용어는 `주제 / 템플릿` 기준으로 먼저 정리하는 편이 맞다고 판단했다.
- 대규모 내부 리네이밍은 API와 DB까지 손대야 하므로, 이번 단계에서는 사용자 화면 문구만 우선 바꾸고 내부 `game` 모델은 그대로 두는 점진적 전환이 더 안전하다고 정리했다.
## 2026-04-02 v1.3.93
- 목록 카드 썸네일은 드래그 대상이 아니라 클릭 대상에 가깝기 때문에, 브라우저 기본 이미지 드래그 프리뷰는 전부 막아 두는 편이 UX 측면에서 맞다고 판단했다.
## 2026-04-02 v1.3.92
- 왼쪽 레일 활성 메뉴도 로그인 토글과 같은 이동형 배경 문법을 쓰는 편이 앱 전체 인터랙션 언어를 더 일관되게 만든다고 판단했다.

View File

@@ -1,6 +1,10 @@
# 할 일 및 이슈
## 단기 확인
- 왼쪽 사이드 메뉴와 각 화면 타이틀을 한글 기준으로 맞췄으므로, 홈/나의 티어표/즐겨찾기/설정 진입 시 실제 체감이 자연스럽고 중복 표현이 어색하지 않은지 한 번 더 QA한다.
- 용어 정리 1차는 사용자 노출 문구만 `주제 / 템플릿`으로 바꿨으므로, 홈/주제 화면/관리자 템플릿 관리에서 어색하게 남은 `게임` 문구가 없는지 한 번 더 QA한다.
- 내부 모델명은 아직 `game`을 유지하므로, 다음 단계에서는 문서와 보조 화면 문구를 더 정리할지, 아니면 내부 리네이밍 계획을 따로 잡을지 결정한다.
- 게임 목록과 티어표 카드 썸네일은 기본 이미지 드래그를 막았으므로, 데스크톱 브라우저에서 클릭/드래그 시 원본 이미지 프리뷰가 더 이상 뜨지 않는지 한 번 더 QA한다.
- 왼쪽 레일 활성 배경은 공용 인디케이터가 이동하는 방식으로 바뀌었으므로, 홈/내 티어표/즐겨찾기/설정 전환과 레일 접힘 상태 양쪽에서 위치 보정이 자연스러운지 한 번 더 QA한다.
- 로그인 화면 상단 토글은 이동형 인디케이터로 바뀌었으므로, 데스크톱과 모바일에서 `로그인 / 회원가입` 전환 애니메이션이 어색하지 않고 포커스/클릭 상태도 자연스러운지 한 번 더 QA한다.
- 관리자 카드 설명 줄임은 `line-clamp` 표준 속성까지 함께 선언했으므로, 실제 브라우저별 표시 차이가 없는지 한 번 더 QA한다.

View File

@@ -1,5 +1,15 @@
# 업데이트 로그
## 2026-04-02 v1.4.1
- 왼쪽 사이드 메뉴를 `주제 선택 / 나의 티어표 / 즐겨찾기 / 설정` 한글 문구로 통일하고, 해당 화면 진입 시 헤더 타이틀도 같은 이름 기준으로 맞췄다.
## 2026-04-02 v1.4.0
- 사용자 노출 용어 1차 정리를 시작해 홈/좌측 레일/가이드/주제 화면에서는 `게임` 대신 `주제`, 관리자 핵심 화면에서는 `게임 관리` 대신 `템플릿 관리` 중심 표현으로 바꿨다.
- 내부 데이터 모델과 API의 `gameId`, `/games` 구조는 아직 유지하고, 이번 단계는 화면 문구와 안내 텍스트를 먼저 정리하는 안전한 1차 리네이밍 범위로 제한했다.
## 2026-04-02 v1.3.93
- 게임 목록, 티어표 리스트, 사용자 아바타 버튼 등 목록성 썸네일 이미지에 `draggable=\"false\"`를 적용해 브라우저 기본 이미지 드래그 프리뷰가 뜨지 않도록 정리함.
## 2026-04-02 v1.3.92
- 왼쪽 네비게이션의 활성 메뉴 배경은 개별 항목에 즉시 붙는 방식에서, 공용 인디케이터가 현재 메뉴 위치로 미끄러져 이동하는 토글형 인터랙션으로 정리함.

View File

@@ -26,7 +26,7 @@ const RIGHT_RAIL_COPYRIGHT_URL = 'https://zenn.town/@murabito'
const leftRailCollapsed = ref(false)
const rightRailOpen = ref(true)
const searchQuery = ref('')
const leftRailSearchPlaceholder = '게임 템플릿 검색'
const leftRailSearchPlaceholder = '주제 템플릿 검색'
const isCollapsedSearchOpen = ref(false)
const isGuideModalOpen = ref(false)
const themeMode = ref('dark')
@@ -60,10 +60,10 @@ const shellStyle = computed(() => ({
}))
const leftNavItems = computed(() => {
const items = [
{ key: 'home', label: 'Games', path: '/', iconSrc: iconGridView },
{ key: 'me', label: 'My Lists', path: '/me', iconSrc: iconLists, requiresAuth: true },
{ key: 'favorites', label: 'Favorites', path: '/favorites', iconSrc: iconFavorite, requiresAuth: true },
{ key: 'profile', label: 'Settings', path: '/profile', iconSrc: iconSettings, requiresAuth: true },
{ key: 'home', label: '주제 선택', path: '/', iconSrc: iconGridView },
{ key: 'me', label: '나의 티어표', path: '/me', iconSrc: iconLists, requiresAuth: true },
{ key: 'favorites', label: '즐겨찾기', path: '/favorites', iconSrc: iconFavorite, requiresAuth: true },
{ key: 'profile', label: '설정', path: '/profile', iconSrc: iconSettings, requiresAuth: true },
]
return items.filter((item) => !item.requiresAuth || (authReady.value && auth.user))
})
@@ -73,10 +73,10 @@ const showSettingsGuideButton = computed(() => route.name === 'profile')
const guideSteps = [
{
id: 'select-game',
title: '게임 또는 양식 선택',
summary: '게임 템플릿을 고르거나 커스텀 티어표 만들기로 바로 시작합니다.',
title: '주제 또는 양식 선택',
summary: '주제 템플릿을 고르거나 커스텀 티어표 만들기로 바로 시작합니다.',
description:
'홈 화면에서는 게임 템플릿을 선택하거나 커스텀 티어표 만들기로 바로 새 보드를 열 수 있어요. 게임을 먼저 고르면 해당 게임의 공개 티어표도 같이 살펴볼 수 있어서, 완전히 처음 만드는지 기존 흐름을 참고할지 결정하기 쉽습니다.',
'홈 화면에서는 주제 템플릿을 선택하거나 커스텀 티어표 만들기로 바로 새 보드를 열 수 있어요. 원하는 주제를 먼저 고르면 해당 주제의 공개 티어표도 같이 살펴볼 수 있어서, 완전히 처음 만드는지 기존 흐름을 참고할지 결정하기 쉽습니다.',
},
{
id: 'arrange-board',
@@ -90,7 +90,7 @@ const guideSteps = [
title: '아이템 배치와 커스텀 추가',
summary: '프리셋 아이템과 직접 올린 이미지를 드래그로 배치합니다.',
description:
'오른쪽 아이템 영역의 이미지를 원하는 칸으로 끌어다 놓으면 바로 배치됩니다. 게임 템플릿에 없는 이미지는 커스텀 이미지로 추가해 같이 쓸 수 있고, 이름 표시 옵션을 켜면 결과 이미지를 더 설명적으로 정리할 수 있어요.',
'오른쪽 아이템 영역의 이미지를 원하는 칸으로 끌어다 놓으면 바로 배치됩니다. 주제 템플릿에 없는 이미지는 커스텀 이미지로 추가해 같이 쓸 수 있고, 이름 표시 옵션을 켜면 결과 이미지를 더 설명적으로 정리할 수 있어요.',
},
{
id: 'save-share',
@@ -109,23 +109,23 @@ const guideSteps = [
{
id: 'request-template-update',
title: '템플릿 업그레이드 요청',
summary: '현재 게임 템플릿에 공통 아이템을 추가해 달라고 관리자에게 요청합니다.',
summary: '현재 템플릿에 공통 아이템을 추가해 달라고 관리자에게 요청합니다.',
description:
'직접 추가한 아이템 중 여러 사람이 함께 써도 좋을 것 같은 항목이 있다면 템플릿 업데이트 요청을 보낼 수 있어요. 요청 모달에서는 현재 티어표 제목과 설명을 기본값으로 가져오고, 필요하면 요청 제목과 설명을 더 다듬어 공통 템플릿에 왜 필요한지 설명할 수 있습니다.',
},
{
id: 'request-new-template',
title: '새 템플릿 추가 요청',
summary: '아직 없는 게임이나 새로운 양식을 관리자에게 제안합니다.',
summary: '아직 없는 주제나 새로운 양식을 관리자에게 제안합니다.',
description:
'원하는 게임 템플릿이 아직 없다면 새 템플릿 추가 요청으로 관리자에게 직접 제안할 수 있어요. 이때는 제목과 설명에 어떤 게임인지, 어떤 캐릭터나 항목이 기본으로 필요할지 적어두면 검토 속도가 훨씬 빨라집니다.',
'원하는 주제 템플릿이 아직 없다면 새 템플릿 추가 요청으로 관리자에게 직접 제안할 수 있어요. 이때는 제목과 설명에 어떤 주제인지, 어떤 캐릭터나 항목이 기본으로 필요할지 적어두면 검토 속도가 훨씬 빨라집니다.',
},
{
id: 'manage-library',
title: '즐겨찾기와 내 티어표 관리',
summary: '마음에 드는 템플릿과 저장한 결과물을 나중에 다시 쉽게 찾습니다.',
description:
'게임 템플릿은 즐겨찾기로 상단에 고정해둘 수 있고, 저장한 보드는 내 티어표에서 다시 열어 이어서 수정할 수 있어요. 자주 보는 템플릿, 공개 티어표, 내가 만든 결과물을 각각 다른 화면에서 정리해두면 이후 작업이 훨씬 빨라집니다.',
'주제 템플릿은 즐겨찾기로 상단에 고정해둘 수 있고, 저장한 보드는 내 티어표에서 다시 열어 이어서 수정할 수 있어요. 자주 보는 템플릿, 공개 티어표, 내가 만든 결과물을 각각 다른 화면에서 정리해두면 이후 작업이 훨씬 빨라집니다.',
},
]
const currentGuideStep = computed(() => guideSteps[guideStepIndex.value] || guideSteps[0])
@@ -151,10 +151,10 @@ const leftBottomPrimaryAction = computed(() => {
const routeMeta = computed(() => {
if (route.name === 'home') {
return {
title: 'Tier Maker',
subtitle: '게임 템플릿 선택과 커스텀 보드 시작',
title: '주제 선택',
subtitle: '주제 템플릿 선택과 커스텀 보드 시작',
contextTitle: '빠른 시작',
contextText: auth.user ? '커스텀 티어표를 만들거나 원하는 게임을 바로 선택할 수 있어요.' : '로그인하면 커스텀 티어표 생성과 개인 목록 관리가 열립니다.',
contextText: auth.user ? '커스텀 티어표를 만들거나 원하는 주제를 바로 선택할 수 있어요.' : '로그인하면 커스텀 티어표 생성과 개인 목록 관리가 열립니다.',
actionLabel: auth.user ? '커스텀 티어표 만들기' : '로그인하러 가기',
action: () => {
router.push(auth.user ? '/editor/freeform/new' : '/login')
@@ -163,10 +163,10 @@ const routeMeta = computed(() => {
}
if (route.name === 'gameHub') {
return {
title: 'Game Boards',
subtitle: '게임별 공개 티어표 탐색',
title: '주제 티어표',
subtitle: '주제별 공개 티어표 탐색',
contextTitle: '작성 작업',
contextText: auth.user ? '이 게임의 새 티어표를 만들거나 기존 공개 티어표를 확인할 수 있어요.' : '로그인 후 새 티어표를 만들 수 있어요.',
contextText: auth.user ? '이 주제의 새 티어표를 만들거나 기존 공개 티어표를 확인할 수 있어요.' : '로그인 후 새 티어표를 만들 수 있어요.',
actionLabel: auth.user ? '새 티어표 만들기' : '로그인하러 가기',
action: () => {
const target = `/editor/${route.params.gameId}/new`
@@ -180,24 +180,24 @@ const routeMeta = computed(() => {
subtitle: '티어표 편집 및 공유',
contextTitle: '편집 패널',
contextText: '현재 편집 옵션은 중앙 화면 안에 유지되어 있습니다. 다음 단계에서 우측 패널로 정리해갈게요.',
actionLabel: '게임 목록으로',
actionLabel: '주제 목록으로',
action: () => router.push('/'),
}
}
if (isAdminRoute.value) {
return {
title: 'Admin Workspace',
subtitle: '게임·아이템·회원 관리',
subtitle: '템플릿·아이템·회원 관리',
contextTitle: '운영 노트',
contextText: '관리자 화면은 기능이 많아 우선 공통 셸 톤을 맞췄고, 세부 패널은 다음 단계에서 시안 방식으로 더 세밀하게 나눌 예정입니다.',
actionLabel: '게임 목록으로',
actionLabel: '주제 목록으로',
action: () => router.push('/'),
}
}
if (route.name === 'me') {
return {
title: 'My Lists',
subtitle: '내가 저장한 티어표',
title: '나의 티어표',
subtitle: '저장한 티어표 모아보기',
contextTitle: '작성 이력',
contextText: '최근 저장한 티어표를 열람하고 정리할 수 있어요.',
actionLabel: '즐겨찾기 보기',
@@ -206,27 +206,27 @@ const routeMeta = computed(() => {
}
if (route.name === 'favorites') {
return {
title: 'Favorites',
title: '즐겨찾기',
subtitle: '마음에 드는 티어표 모음',
contextTitle: '정리 도구',
contextText: '정렬과 검색으로 원하는 티어표를 빠르게 다시 찾을 수 있어요.',
actionLabel: ' 티어표 보기',
actionLabel: '나의 티어표 보기',
action: () => router.push('/me'),
}
}
if (route.name === 'profile') {
return {
title: 'Profile',
title: '설정',
subtitle: '프로필 및 계정 설정',
contextTitle: '계정 관리',
contextText: '아바타와 닉네임을 관리하고 표시 정보를 확인할 수 있어요.',
actionLabel: ' 티어표 보기',
actionLabel: '나의 티어표 보기',
action: () => router.push('/me'),
}
}
if (route.name === 'search') {
return {
title: 'Search',
title: '검색',
subtitle: '전체 공개 티어표 검색 결과',
contextTitle: '검색',
contextText: '제목, 작성자 기준으로 공개 티어표를 전체 검색할 수 있어요.',
@@ -236,7 +236,7 @@ const routeMeta = computed(() => {
}
return {
title: 'Tier Maker',
subtitle: '게임 템플릿으로 만드는 티어표',
subtitle: '주제 템플릿으로 만드는 티어표',
contextTitle: 'Workspace',
contextText: '현재 화면에 맞는 도구와 안내를 여기에 배치할 수 있습니다.',
actionLabel: '홈으로',
@@ -429,7 +429,7 @@ function submitGlobalSearch() {
<div class="leftRail__content">
<div v-if="authReady && auth.user" class="appUserCard">
<div class="appUserCard__button">
<img v-if="avatarUrl" :src="avatarUrl" class="appUserCard__avatar" alt="avatar" />
<img v-if="avatarUrl" :src="avatarUrl" class="appUserCard__avatar" alt="avatar" draggable="false" />
<div v-else class="appUserCard__avatar appUserCard__avatar--fallback">{{ accountName[0]?.toUpperCase() || 'U' }}</div>
<div class="appUserCard__meta">
<div class="appUserCard__name">{{ accountName }}</div>

View File

@@ -16,7 +16,7 @@ const props = defineProps({
<div class="sectionHeader">
<div>
<div class="panel__title"> 화면 상단 고정 순서</div>
<div class="hint hint--tight">여기에 넣은 게임 지정한 순서대로 먼저 노출되고, 나머지 게임 최근 생성순으로 뒤에 이어집니다. 최대 50개까지 설정할 있어요.</div>
<div class="hint hint--tight">여기에 넣은 템플릿 지정한 순서대로 먼저 노출되고, 나머지 템플릿 최근 생성순으로 뒤에 이어집니다. 최대 50개까지 설정할 있어요.</div>
</div>
<button class="btn btn--primary" @click="props.saveFeaturedOrder">순서 저장</button>
</div>
@@ -24,7 +24,7 @@ const props = defineProps({
<div class="featuredOrderPanel">
<div class="featuredOrderPanel__list">
<div class="section__title">상단 고정 목록</div>
<div v-if="!props.featuredGames.length" class="hint">아직 상단 고정 게임 없어요.</div>
<div v-if="!props.featuredGames.length" class="hint">아직 상단 고정 템플릿 없어요.</div>
<div v-else :ref="props.featuredListRef" class="featuredList">
<article v-for="(game, index) in props.featuredGames" :key="game.id" class="featuredCard" :data-featured-id="game.id">
<div class="featuredCard__meta">
@@ -45,7 +45,7 @@ const props = defineProps({
</div>
<div class="featuredOrderPanel__picker">
<div class="section__title">게임 추가</div>
<div class="section__title">템플릿 추가</div>
<div class="featuredPickerList">
<button
v-for="game in props.availableGamesForFeatured"

View File

@@ -67,17 +67,17 @@ function setThumbFileElement(el) {
props.activeTemplateRequest.type === 'create'
? (props.activeTemplateRequest.targetGameId
? '이미 연결된 신규 템플릿이 있어요. 필요한 아이템만 골라 저장하세요.'
: '새 게임을 한 번 만든 뒤 필요한 아이템만 골라 저장하세요.')
: '새 템플릿을 한 번 만든 뒤 필요한 아이템만 골라 저장하세요.')
: '필요한 아이템만 남긴 뒤 기본 아이템 추가 버튼으로 반영하세요.'
}}
</div>
</div>
<div class="requestWorkspace__stats">
<span class="pill pill--accent">{{ props.activeTemplateRequest.type === 'create' ? '신규 게임 요청' : '기존 게임 업데이트' }}</span>
<span class="pill pill--accent">{{ props.activeTemplateRequest.type === 'create' ? '신규 템플릿 요청' : '기존 템플릿 업데이트' }}</span>
<span class="pill">요청 아이템 {{ props.stagedRequestDraftCount }}</span>
<span v-if="props.appliedRequestItemCount" class="pill pill--soft">이미 반영 {{ props.appliedRequestItemCount }}</span>
<span v-if="props.activeTemplateRequest.type === 'create' && (props.activeTemplateRequest.targetGameName || props.activeTemplateRequest.targetGameId)" class="pill pill--soft">
연결된 게임 · {{ props.activeTemplateRequest.targetGameName || props.activeTemplateRequest.targetGameId }}
연결된 템플릿 · {{ props.activeTemplateRequest.targetGameName || props.activeTemplateRequest.targetGameId }}
</span>
</div>
</div>
@@ -97,15 +97,15 @@ function setThumbFileElement(el) {
type="button"
@click="props.openGameCreateModal"
>
게임 만들기
템플릿 만들기
</button>
</div>
</div>
<div v-if="props.isGameLoading" class="panel panel--empty">
<div class="emptyState">
<div class="emptyState__title">게임 정보를 불러오는 중이에요.</div>
<div class="emptyState__desc">선택한 게임 썸네일과 기본 아이템을 표시합니다.</div>
<div class="emptyState__title">템플릿 정보를 불러오는 중이에요.</div>
<div class="emptyState__desc">선택한 템플릿 썸네일과 기본 아이템을 표시합니다.</div>
</div>
</div>
<div v-else-if="props.hasSelectedGame" class="panel">
@@ -133,7 +133,7 @@ function setThumbFileElement(el) {
</button>
</div>
<div class="gameSettingsCard__body">
<div class="panel__title">게임 설정</div>
<div class="panel__title">템플릿 설정</div>
<div class="gameSettingsCard__meta">{{ props.selectedGame.game.name }} · {{ props.selectedGame.game.id }}</div>
<label class="toggleSwitch" :class="{ 'toggleSwitch--disabled': props.gameVisibilitySaving }">
<input :checked="!!props.selectedGame.game.isPublic" type="checkbox" @change="props.toggleSelectedGameVisibility($event.target.checked)" />
@@ -142,7 +142,7 @@ function setThumbFileElement(el) {
</label>
<div class="gameSettingsCard__actions">
<button class="btn" :disabled="!props.canApplyThumbnail" @click="props.uploadThumbnail">썸네일 적용</button>
<button class="btn btn--danger" @click="props.removeGame">게임 삭제</button>
<button class="btn btn--danger" @click="props.removeGame">템플릿 삭제</button>
</div>
</div>
</section>
@@ -236,9 +236,9 @@ function setThumbFileElement(el) {
</div>
<div v-else class="panel panel--empty">
<div class="emptyState">
<div class="emptyState__title">게임 선택해 주세요.</div>
<div v-if="props.activeTemplateRequest?.type === 'create'" class="hint hint--tight">진행 중인 신규 게임 요청이 있어요. 위의 ` 게임 만들기` 게임 만든 아이템을 추가할 있습니다.</div>
<div v-if="props.selectedGameId" class="hint hint--tight">선택한 게임 찾지 못했거나 로딩 오류가 발생했어요. 다시 선택해보세요.</div>
<div class="emptyState__title">템플릿 선택해 주세요.</div>
<div v-if="props.activeTemplateRequest?.type === 'create'" class="hint hint--tight">진행 중인 신규 템플릿 요청이 있어요. 위의 ` 템플릿 만들기` 템플릿 만든 아이템을 추가할 있습니다.</div>
<div v-if="props.selectedGameId" class="hint hint--tight">선택한 템플릿 찾지 못했거나 로딩 오류가 발생했어요. 다시 선택해보세요.</div>
</div>
</div>
</template>

View File

@@ -53,18 +53,18 @@ const props = defineProps({
<div class="templateRequestCard__thumbMeta">
<template v-if="request.type === 'create'">
<label class="templateRequestField">
<span class="templateRequestField__label">게임 이름</span>
<input v-model="request.draftGameName" class="input" placeholder="새 게임 이름" />
<span class="templateRequestField__label">템플릿 이름</span>
<input v-model="request.draftGameName" class="input" placeholder="새 템플릿 이름" />
</label>
<label class="templateRequestField">
<span class="templateRequestField__label">게임 ID</span>
<input v-model="request.draftGameId" class="input" placeholder="임시 게임 ID" />
<span class="templateRequestField__label">템플릿 ID</span>
<input v-model="request.draftGameId" class="input" placeholder="임시 템플릿 ID" />
</label>
</template>
<template v-else>
<div class="templateRequestCard__thumbLabel">게임 이름</div>
<div class="templateRequestCard__thumbLabel">템플릿 이름</div>
<div class="templateRequestCard__thumbValue">{{ request.draftGameName || request.sourceGameName || '-' }}</div>
<div class="templateRequestCard__thumbLabel">게임 ID</div>
<div class="templateRequestCard__thumbLabel">템플릿 ID</div>
<div class="templateRequestCard__thumbValue">{{ request.draftGameId || request.sourceGameId || '-' }}</div>
</template>
</div>
@@ -111,7 +111,7 @@ const props = defineProps({
request.isHandling
? '이동중...'
: request.type === 'create' && (request.targetGameName || request.targetGameId)
? '연결된 게임 열기'
? '연결된 템플릿 열기'
: '확인하기'
}}
</button>

View File

@@ -214,7 +214,7 @@ const customItemTargetGame = computed(() => games.value.find((game) => game.id =
const importModalItemCount = computed(() => importModalItems.value.length)
const activeTabTitle = computed(() => {
if (activeTab.value === 'featured') return '목록 관리'
if (activeTab.value === 'game-admin') return '게임 관리'
if (activeTab.value === 'game-admin') return '템플릿 관리'
if (activeTab.value === 'items') return '아이템 관리'
if (activeTab.value === 'tierlists') {
return tierlistsMode.value === 'requests' ? '템플릿 요청 관리' : '전체 티어표 관리'
@@ -223,17 +223,17 @@ const activeTabTitle = computed(() => {
})
const activeTabDescription = computed(() => {
if (activeTab.value === 'featured') {
return '홈 화면 상단에 고정 노출되는 게임 순서를 따로 관리합니다.'
return '홈 화면 상단에 고정 노출되는 템플릿 순서를 따로 관리합니다.'
}
if (activeTab.value === 'game-admin') {
return '게임 생성, 선택, 썸네일, 기본 아이템 관리를 전용 작업 화면으로 분리했습니다.'
return '템플릿 생성, 선택, 썸네일, 기본 아이템 관리를 전용 작업 화면으로 분리했습니다.'
}
if (activeTab.value === 'items') {
return '사용자 업로드와 관리자 템플릿 이미지를 함께 검수하고, 필요한 게임에 직접 연결할 수 있어요.'
return '사용자 업로드와 관리자 템플릿 이미지를 함께 검수하고, 필요한 템플릿에 직접 연결할 수 있어요.'
}
if (activeTab.value === 'tierlists') {
return tierlistsMode.value === 'requests'
? '사용자 요청을 확인하고, 게임 관리 화면에서 필요한 아이템만 선별 반영한 뒤 직접 완료 처리합니다.'
? '사용자 요청을 확인하고, 템플릿 관리 화면에서 필요한 아이템만 선별 반영한 뒤 직접 완료 처리합니다.'
: '공개/비공개 포함 전체 티어표를 확인하고, 추가 아이템을 템플릿으로 가져올 수 있어요.'
}
return '계정 정보, 권한, 비밀번호와 최근 활동을 더 가볍게 확인하고 수정합니다.'
@@ -245,14 +245,14 @@ const adminOverviewStats = computed(() => {
if (activeTab.value === 'featured') {
return [
{ label: '전체 게임', value: `${games.value.length}` },
{ label: '전체 템플릿', value: `${games.value.length}` },
{ label: '상단 고정', value: `${featuredGameIds.value.length}/50` },
{ label: '추가 가능', value: `${Math.max(0, 50 - featuredGameIds.value.length)}` },
]
}
if (activeTab.value === 'game-admin') {
return [
{ label: '전체 게임', value: `${games.value.length}` },
{ label: '전체 템플릿', value: `${games.value.length}` },
{ label: '티어표 전체', value: `${selectedGameTierListStats.value.total || 0}` },
{ label: '공개', value: `${selectedGameTierListStats.value.publicCount || 0}` },
{ label: '비공개', value: `${selectedGameTierListStats.value.privateCount || 0}` },
@@ -794,7 +794,7 @@ async function refreshGames() {
.map((game) => game.id)
await syncFeaturedSortable()
} catch (e) {
error.value = '게임 목록을 불러오지 못했어요.'
error.value = '템플릿 목록을 불러오지 못했어요.'
}
}
@@ -1275,7 +1275,7 @@ async function removeGame() {
await refreshGames()
success.value = `${deletedName} 게임을 삭제했어요.`
} catch (e) {
error.value = '게임 삭제에 실패했어요.'
error.value = '템플릿 삭제에 실패했어요.'
}
}
@@ -1570,7 +1570,7 @@ async function confirmTierListImport() {
try {
if (importModalMode.value === 'existing') {
if (!importModalTargetGameId.value) {
error.value = '아이템을 추가할 기존 게임을 선택해주세요.'
error.value = '아이템을 추가할 기존 템플릿을 선택해주세요.'
return
}
@@ -1584,7 +1584,7 @@ async function confirmTierListImport() {
const nextGameId = (importModalNewGameId.value || '').trim()
const nextGameName = (importModalNewGameName.value || '').trim()
if (!nextGameId || !nextGameName) {
error.value = '새 게임 ID와 이름을 모두 입력해주세요.'
error.value = '새 템플릿 ID와 이름을 모두 입력해주세요.'
return
}
@@ -1610,9 +1610,9 @@ function templateRequestTypeLabel(request) {
function templateRequestTargetLabel(request) {
if (request.type === 'create') {
if (request.targetGameName || request.targetGameId) {
return `연결된 게임 · ${request.targetGameName || request.targetGameId}`
return `연결된 템플릿 · ${request.targetGameName || request.targetGameId}`
}
return '연결된 게임 없음'
return '연결된 템플릿 없음'
}
return request.targetGameName || request.targetGameId || request.sourceGameName
}
@@ -1787,16 +1787,16 @@ function userAvatarFallback(user) {
<div v-if="gameCreateModalOpen" class="modalOverlay" @click.self="closeGameCreateModal">
<div class="modalCard" role="dialog" aria-modal="true">
<div class="modalCard__title"> 게임 만들기</div>
<div class="modalCard__desc">게임 이름과 고유 ID를 입력한 생성하면 바로 아래 상세 관리 화면으로 이어집니다.</div>
<div class="modalCard__title"> 템플릿 만들기</div>
<div class="modalCard__desc">템플릿 이름과 고유 ID를 입력한 생성하면 바로 아래 상세 관리 화면으로 이어집니다.</div>
<div class="modalCard__form">
<label class="field">
<span class="field__label">게임 이름</span>
<input v-model="newGameName" class="field__input" maxlength="60" placeholder="게임 이름" />
<span class="field__label">템플릿 이름</span>
<input v-model="newGameName" class="field__input" maxlength="60" placeholder="템플릿 이름" />
<span class="field__hint">{{ newGameName.length }}/60</span>
</label>
<label class="field">
<span class="field__label">게임 ID</span>
<span class="field__label">템플릿 ID</span>
<input
v-model="newGameId"
class="field__input"
@@ -1814,7 +1814,7 @@ function userAvatarFallback(user) {
</div>
<div class="modalCard__actions">
<button class="btn btn--ghost" @click="closeGameCreateModal">취소</button>
<button class="btn btn--primary" :disabled="!newGameId.trim() || !newGameName.trim()" @click="createGame">게임 생성</button>
<button class="btn btn--primary" :disabled="!newGameId.trim() || !newGameName.trim()" @click="createGame">템플릿 생성</button>
</div>
</div>
</div>
@@ -1925,14 +1925,14 @@ function userAvatarFallback(user) {
<div v-if="importModalMode === 'existing'" class="modalCard__form">
<select v-model="importModalTargetGameId" class="select">
<option value="">기존 게임 선택</option>
<option value="">기존 템플릿 선택</option>
<option v-for="game in games" :key="game.id" :value="game.id">{{ game.name }}</option>
</select>
</div>
<div v-else class="modalCard__form">
<input v-model="importModalNewGameId" class="input" placeholder="새 게임 ID" />
<input v-model="importModalNewGameName" class="input" placeholder="새 게임 이름" />
<input v-model="importModalNewGameId" class="input" placeholder="새 템플릿 ID" />
<input v-model="importModalNewGameName" class="input" placeholder="새 템플릿 이름" />
</div>
<div class="modalCard__actions">
@@ -1953,12 +1953,12 @@ function userAvatarFallback(user) {
<div class="customItemModal__pickerTitle">템플릿으로 추가할 게임</div>
</div>
<div class="adminSelectionCard">
<div class="adminSelectionCard__label">선택한 게임</div>
<div class="adminSelectionCard__label">선택한 템플릿</div>
<div class="adminSelectionCard__title">{{ customItemTargetGame?.name || '아직 선택하지 않음' }}</div>
<div class="adminSelectionCard__meta">{{ customItemTargetGame?.id || '게임을 골라 주세요.' }}</div>
</div>
<div class="customItemModal__pickerActions">
<button class="btn btn--ghost" type="button" @click="openGamePickerModal('custom-item-target')">게임 선택</button>
<button class="btn btn--ghost" type="button" @click="openGamePickerModal('custom-item-target')">템플릿 선택</button>
<button class="btn btn--ghost btn--small customItemModal__createGameButton" type="button" @click="openGameCreateModal"> 템플릿 만들기</button>
</div>
</aside>
@@ -1991,7 +1991,7 @@ function userAvatarFallback(user) {
<div v-if="visibleLinkedGames.length" class="customItemModal__chips">
<button v-for="game in visibleLinkedGames" :key="game.id" type="button" class="pill pill--link" @click="jumpToGameAdmin(game.id)">{{ game.name }}</button>
</div>
<div v-else class="hint hint--tight">아직 템플릿에 연결된 게임 없어요.</div>
<div v-else class="hint hint--tight">아직 템플릿에 연결된 항목 없어요.</div>
</div>
<div class="customItemModal__actions">
<a class="btn btn--ghost customItemModal__action" :href="toApiUrl(modalTargetCustomItem.src)" :download="modalTargetCustomItem.label">이미지 다운로드</a>
@@ -2010,7 +2010,7 @@ function userAvatarFallback(user) {
<div class="modalCard" role="dialog" aria-modal="true">
<div class="modalCard__titleRow">
<div>
<div class="modalCard__title">게임 선택</div>
<div class="modalCard__title">템플릿 선택</div>
<div class="modalCard__desc">
{{ gamePickerMode === 'tierlists-filter' ? '특정 게임의 티어표만 보려면 게임을 선택하세요.' : '관리할 게임을 검색해서 바로 열 수 있어요.' }}
</div>
@@ -2018,7 +2018,7 @@ function userAvatarFallback(user) {
<button class="btn btn--ghost btn--small" @click="closeGamePickerModal">닫기</button>
</div>
<div class="modalCard__form">
<input v-model="gamePickerQuery" class="input" placeholder="게임 이름 또는 ID 검색" />
<input v-model="gamePickerQuery" class="input" placeholder="템플릿 이름 또는 ID 검색" />
<select v-model="gamePickerSort" class="select">
<option value="recent">최신순</option>
<option value="oldest">오래된순</option>
@@ -2201,7 +2201,7 @@ function userAvatarFallback(user) {
<div class="adminSidebar__label">Mode</div>
<div class="adminSidebar__tabs">
<button class="tab" :class="{ 'tab--active': activeTab === 'featured' }" @click="setTab('featured')">목록 관리</button>
<button class="tab" :class="{ 'tab--active': activeTab === 'game-admin' }" @click="setTab('game-admin')">게임 관리</button>
<button class="tab" :class="{ 'tab--active': activeTab === 'game-admin' }" @click="setTab('game-admin')">템플릿 관리</button>
<button class="tab" :class="{ 'tab--active': activeTab === 'items' }" @click="setTab('items')">아이템 관리</button>
<button class="tab" :class="{ 'tab--active': activeTab === 'tierlists' }" @click="setTab('tierlists')">티어표 관리</button>
<button class="tab" :class="{ 'tab--active': activeTab === 'users' }" @click="setTab('users')">회원 관리</button>
@@ -2209,16 +2209,16 @@ function userAvatarFallback(user) {
</section>
<section v-if="activeTab === 'game-admin'" class="adminSidebar__panel">
<div class="adminSidebar__label">Game</div>
<div class="adminSidebar__label">Template</div>
<div class="adminSidebar__group">
<button class="btn btn--primary" @click="openGameCreateModal"> 게임 생성</button>
<button class="btn btn--ghost" @click="openGamePickerModal('game-admin')">게임 선택</button>
<button class="btn btn--primary" @click="openGameCreateModal"> 템플릿 생성</button>
<button class="btn btn--ghost" @click="openGamePickerModal('game-admin')">템플릿 선택</button>
<div v-if="selectedGame?.game" class="adminSelectionCard">
<div class="adminSelectionCard__label">선택한 게임</div>
<div class="adminSelectionCard__label">선택한 템플릿</div>
<div class="adminSelectionCard__title">{{ selectedGame.game.name }}</div>
<div class="adminSelectionCard__meta">{{ selectedGame.game.id }}</div>
</div>
<div v-if="selectedGameId && !hasSelectedGame && !isGameLoading" class="hint hint--tight">선택된 게임 ID: {{ selectedGameId }}</div>
<div v-if="selectedGameId && !hasSelectedGame && !isGameLoading" class="hint hint--tight">선택된 템플릿 ID: {{ selectedGameId }}</div>
</div>
</section>
@@ -2277,14 +2277,14 @@ function userAvatarFallback(user) {
<input
v-model="adminTierListQuery"
class="input"
placeholder="제목, 작성자, 게임 이름 검색"
placeholder="제목, 작성자, 주제 이름 검색"
@keydown.enter.prevent="submitAdminTierListSearch"
/>
<button class="btn btn--ghost" @click="submitAdminTierListSearch">검색</button>
</div>
<button class="btn btn--ghost" @click="openGamePickerModal('tierlists-filter')">게임 선택</button>
<button class="btn btn--ghost" @click="openGamePickerModal('tierlists-filter')">주제 선택</button>
<div v-if="adminTierListGameId" class="adminSelectionCard">
<div class="adminSelectionCard__label">필터된 게임</div>
<div class="adminSelectionCard__label">필터된 주제</div>
<div class="adminSelectionCard__title">{{ games.find((game) => game.id === adminTierListGameId)?.name || adminTierListGameId }}</div>
<div class="adminSelectionCard__meta">{{ adminTierListGameId }}</div>
<button class="btn btn--ghost btn--small" @click="setAdminTierListGameId('')">필터 해제</button>

View File

@@ -58,11 +58,11 @@ onMounted(loadFavorites)
<div class="pageHead">
<div class="pageHead__main">
<div class="pageHead__eyebrow">Collection</div>
<h2 class="pageHead__title"> 즐겨찾기</h2>
<h2 class="pageHead__title">즐겨찾기</h2>
<div class="pageHead__desc">마음에 드는 티어표를 모아보고, 원하는 기준으로 정렬할 있어요.</div>
</div>
<div class="pageHead__aside toolbar">
<input v-model="query" class="input" placeholder="제목, 게임, 작성자 검색" @keydown.enter.prevent="loadFavorites" />
<input v-model="query" class="input" placeholder="제목, 주제, 작성자 검색" @keydown.enter.prevent="loadFavorites" />
<select v-model="sort" class="select" @change="loadFavorites">
<option value="favorited">즐겨찾기한 </option>
<option value="updated">최신 업데이트순</option>
@@ -77,7 +77,7 @@ onMounted(loadFavorites)
<article v-for="tierList in favorites" :key="tierList.id" class="boardCard">
<button class="boardCard__body" @click="openTierList(tierList)">
<div class="boardCard__thumbWrap">
<img v-if="tierListThumbnailUrl(tierList)" class="boardCard__thumb" :src="tierListThumbnailUrl(tierList)" :alt="tierList.title" />
<img v-if="tierListThumbnailUrl(tierList)" class="boardCard__thumb" :src="tierListThumbnailUrl(tierList)" :alt="tierList.title" draggable="false" />
<div v-else class="boardCard__thumbPlaceholder">대표 썸네일</div>
</div>
<div class="boardCard__head">
@@ -87,7 +87,7 @@ onMounted(loadFavorites)
</div>
<div class="boardCard__metaRow">
<div class="boardCard__author">
<img v-if="avatarSrcOf(tierList)" class="boardCard__avatar" :src="avatarSrcOf(tierList)" :alt="displayNameOf(tierList)" />
<img v-if="avatarSrcOf(tierList)" class="boardCard__avatar" :src="avatarSrcOf(tierList)" :alt="displayNameOf(tierList)" draggable="false" />
<div v-else class="boardCard__avatar boardCard__avatar--fallback">{{ avatarFallbackOf(tierList) }}</div>
<span class="boardCard__authorName">{{ displayNameOf(tierList) }}</span>
</div>

View File

@@ -62,7 +62,7 @@ async function loadTierLists() {
brokenThumbnailIds.value = {}
tierLists.value = listRes.tierLists || []
} catch (e) {
error.value = '게임 정보를 불러오지 못했어요.'
error.value = '주제 정보를 불러오지 못했어요.'
}
}
@@ -88,7 +88,7 @@ function submitSearch() {
<div class="dashboardHero__left">
<div class="dashboardHero__eyebrow">Collection</div>
<h2 class="dashboardHero__title">{{ gameName || gameId }}</h2>
<p class="dashboardHero__desc"> 게임 공개 티어표를 탐색하고, 바로 보드를 만들어 같은 흐름으로 이어갈 있어요.</p>
<p class="dashboardHero__desc"> 주제 공개 티어표를 탐색하고, 바로 보드를 만들어 같은 흐름으로 이어갈 있어요.</p>
</div>
</section>
@@ -109,7 +109,7 @@ function submitSearch() {
<article v-for="t in tierLists" :key="t.id" class="boardCard" :class="{ 'boardCard--list': isListView }">
<button class="boardCard__body" :class="{ 'boardCard__body--list': isListView }" @click="openTierList(t.id)">
<div class="boardCard__thumbWrap">
<img v-if="tierListThumbnailUrl(t)" class="boardCard__thumb" :src="tierListThumbnailUrl(t)" alt="" @error="handleThumbnailError(t.id)" />
<img v-if="tierListThumbnailUrl(t)" class="boardCard__thumb" :src="tierListThumbnailUrl(t)" alt="" draggable="false" @error="handleThumbnailError(t.id)" />
<div v-else class="boardCard__thumbPlaceholder">대표 썸네일</div>
</div>
<div class="boardCard__head">
@@ -121,7 +121,7 @@ function submitSearch() {
</div>
<div class="boardCard__metaRow">
<div class="boardCard__author">
<img v-if="avatarSrcOf(t)" class="boardCard__avatar" :src="avatarSrcOf(t)" :alt="displayNameOf(t)" />
<img v-if="avatarSrcOf(t)" class="boardCard__avatar" :src="avatarSrcOf(t)" :alt="displayNameOf(t)" draggable="false" />
<div v-else class="boardCard__avatar boardCard__avatar--fallback">{{ avatarFallbackOf(t) }}</div>
<span class="boardCard__authorName">{{ displayNameOf(t) }}</span>
</div>

View File

@@ -77,9 +77,9 @@ function thumbUrl(g) {
<section class="pageHead">
<div class="pageHead__main">
<div class="pageHead__eyebrow">Workspace</div>
<h1 class="pageHead__title">Game Library</h1>
<p class="pageHead__desc">자주 쓰는 게임 템플릿을 빠르게 고르고, 필요하면 바로 커스텀 티어표를 시작할 있어요.</p>
<p v-if="query" class="pageHead__searchState">"{{ query }}" 맞는 게임 템플릿만 보고 있어요.</p>
<h1 class="pageHead__title">Topic Library</h1>
<p class="pageHead__desc">자주 쓰는 주제 템플릿을 빠르게 고르고, 필요하면 바로 커스텀 티어표를 시작할 있어요.</p>
<p v-if="query" class="pageHead__searchState">"{{ query }}" 맞는 주제 템플릿만 보고 있어요.</p>
</div>
</section>
@@ -97,7 +97,7 @@ function thumbUrl(g) {
</button>
<button class="libraryCard__main" type="button" @click="goGame(g.id)">
<div class="libraryCard__thumbWrap">
<img v-if="thumbUrl(g)" class="libraryCard__thumb" :src="thumbUrl(g)" :alt="g.name" />
<img v-if="thumbUrl(g)" class="libraryCard__thumb" :src="thumbUrl(g)" :alt="g.name" draggable="false" />
<div v-else class="libraryCard__thumbFallback">대표 썸네일</div>
</div>
<div class="libraryCard__body">
@@ -107,7 +107,7 @@ function thumbUrl(g) {
</button>
</article>
</TransitionGroup>
<div v-else class="libraryEmpty">{{ query ? '검색어에 맞는 게임 템플릿이 없어요.' : '표시할 게임 템플릿이 없어요.' }}</div>
<div v-else class="libraryEmpty">{{ query ? '검색어에 맞는 주제 템플릿이 없어요.' : '표시할 주제 템플릿이 없어요.' }}</div>
</template>
<style scoped>

View File

@@ -69,7 +69,7 @@ function openList(t) {
<section class="pageHead">
<div class="pageHead__main">
<div class="pageHead__eyebrow">Library</div>
<h2 class="pageHead__title"> 티어표</h2>
<h2 class="pageHead__title">나의 티어표</h2>
<div class="pageHead__desc">직접 저장한 티어표를 같은 카드 레이아웃으로 다시 열고 정리할 있어요.</div>
</div>
</section>
@@ -85,6 +85,7 @@ function openList(t) {
class="boardCard__thumb"
:src="tierListThumbnailUrl(t)"
alt=""
draggable="false"
@error="handleThumbnailError(t.id)"
/>
<div v-else class="boardCard__thumbPlaceholder">대표 썸네일</div>
@@ -96,7 +97,7 @@ function openList(t) {
</div>
<div class="boardCard__metaRow">
<div class="boardCard__author">
<img v-if="avatarSrcOf(t)" class="boardCard__avatar" :src="avatarSrcOf(t)" :alt="displayNameOf(t)" />
<img v-if="avatarSrcOf(t)" class="boardCard__avatar" :src="avatarSrcOf(t)" :alt="displayNameOf(t)" draggable="false" />
<div v-else class="boardCard__avatar boardCard__avatar--fallback">{{ avatarFallbackOf(t) }}</div>
<span class="boardCard__authorName">{{ displayNameOf(t) }}</span>
</div>

View File

@@ -121,7 +121,7 @@ async function logout() {
<header class="pageHead">
<div class="pageHead__main">
<div class="pageHead__eyebrow">Account</div>
<h2 class="pageHead__title">Settings</h2>
<h2 class="pageHead__title">설정</h2>
<div class="pageHead__desc">계정 정보를 간결하게 정리하고, 프로필 이미지를 클릭해서 바로 변경할 있어요.</div>
</div>
</header>
@@ -134,7 +134,7 @@ async function logout() {
<div class="settingsIdentity">
<div class="avatarButtonWrap">
<button class="avatarButton" type="button" @click="openAvatarPicker">
<img v-if="avatarUrl" :src="avatarUrl" class="avatarButton__image" alt="avatar" />
<img v-if="avatarUrl" :src="avatarUrl" class="avatarButton__image" alt="avatar" draggable="false" />
<div v-else class="avatarButton__fallback">{{ displayInitial }}</div>
<div class="avatarButton__overlay">
<span>{{ avatarUrl ? '이미지 변경' : '이미지 추가' }}</span>

View File

@@ -67,7 +67,7 @@ watch(
<section class="wrap">
<div class="head">
<div>
<div class="head__eyebrow">Search</div>
<div class="head__eyebrow">검색</div>
<h2 class="title">전체 티어표 검색</h2>
<div class="desc">공개된 모든 티어표를 제목과 작성자 기준으로 찾아볼 있어요.</div>
</div>
@@ -80,7 +80,7 @@ watch(
<article v-for="tierList in tierLists" :key="tierList.id" class="boardCard">
<button class="boardCard__body" @click="openTierList(tierList)">
<div class="boardCard__thumbWrap">
<img v-if="tierListThumbnailUrl(tierList)" class="boardCard__thumb" :src="tierListThumbnailUrl(tierList)" :alt="tierList.title" />
<img v-if="tierListThumbnailUrl(tierList)" class="boardCard__thumb" :src="tierListThumbnailUrl(tierList)" :alt="tierList.title" draggable="false" />
<div v-else class="boardCard__thumbPlaceholder">대표 썸네일</div>
</div>
<div class="boardCard__head">
@@ -92,7 +92,7 @@ watch(
</div>
<div class="boardCard__metaRow">
<div class="boardCard__author">
<img v-if="avatarSrcOf(tierList)" class="boardCard__avatar" :src="avatarSrcOf(tierList)" :alt="displayNameOf(tierList)" />
<img v-if="avatarSrcOf(tierList)" class="boardCard__avatar" :src="avatarSrcOf(tierList)" :alt="displayNameOf(tierList)" draggable="false" />
<div v-else class="boardCard__avatar boardCard__avatar--fallback">{{ avatarFallbackOf(tierList) }}</div>
<span class="boardCard__authorName">{{ displayNameOf(tierList) }}</span>
</div>

View File

@@ -130,7 +130,7 @@ const canRequestTemplateUpdate = computed(
)
const canSubmitTemplateCreateRequest = computed(() => !!templateRequestDraftTitle.value.trim() && !!templateRequestDraftDescription.value.trim())
const canSubmitTemplateUpdateRequest = computed(() => !!templateRequestDraftTitle.value.trim() && !!templateRequestDraftDescription.value.trim())
const templateRequestTargetLabel = computed(() => (gameId.value === 'freeform' ? '새로운 템플릿' : (gameName.value || gameId.value || '선택한 게임')))
const templateRequestTargetLabel = computed(() => (gameId.value === 'freeform' ? '새로운 템플릿' : (gameName.value || gameId.value || '선택한 주제')))
const shareTierListUrl = computed(() => {
const savedTierListId = persistedTierListId.value || (tierListId.value && tierListId.value !== 'new' ? tierListId.value : '')
if (!savedTierListId) return ''
@@ -910,7 +910,7 @@ onMounted(() => {
itemsById.value = map
pool.value = base.map((it) => it.id)
} catch (e) {
error.value = '게임 기본 이미지를 불러오지 못했어요.'
error.value = '기본 템플릿 이미지를 불러오지 못했어요.'
}
if (tierListId.value && tierListId.value !== 'new') {
@@ -1021,7 +1021,7 @@ onUnmounted(() => {
</div>
<div class="requestChecklist__hint">
제목과 설명을 함께 적어두면 관리자가 어떤 신규 템플릿인지 훨씬 빠르게 파악할 있어요.
예시: 제목 `템플릿 등록 요청`, 설명 `여름 이벤트 한정 캐릭터 중심으로 새 게임 템플릿이 필요합니다.`
예시: 제목 `템플릿 등록 요청`, 설명 `여름 이벤트 한정 캐릭터 중심으로 새 주제 템플릿이 필요합니다.`
</div>
<div class="templateRequestDraft">
<label class="templateRequestDraft__field">