Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ad985f7c5 | |||
| 3b5e744130 | |||
| 28cf4fdfa0 | |||
| cf96e931e9 |
@@ -1,6 +1,7 @@
|
||||
# 할 일 및 이슈
|
||||
|
||||
## 중기 개선
|
||||
- 라이트모드/다크모드 1차 전환은 붙였으므로, 관리자 화면과 티어 에디터까지 세부 색상 균형을 더 정교하게 맞추는 후속 테마 보정 작업을 이어간다.
|
||||
- 관리자용 티어표 승인/숨김 처리, 아이템 정렬 UI를 추가한다.
|
||||
- 회원 일괄 작업(다중 선택, 일괄 비밀번호 초기화, 활동 저조 계정 정리) 같은 관리 보조 기능을 추가한다.
|
||||
- 티어 행 프리셋 저장, 색상 관리, 행 복제 같은 고급 편집 기능을 추가한다.
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# 업데이트 로그
|
||||
|
||||
## 2026-04-01 v1.3.32
|
||||
- 전역 테마 변수와 로컬 저장 기반 테마 토글을 추가해, Settings 화면 오른쪽 사이드에서 라이트모드/다크모드를 전환하고 재방문 시 같은 테마를 유지할 수 있게 함.
|
||||
- 앱 셸, 홈, 게임 허브, 내 티어표, 즐겨찾기, 검색, 로그인, 설정 화면의 공통 카드·입력·텍스트 색을 테마 변수 기준으로 바꿔, 주요 사용자 화면은 라이트/다크 전환이 자연스럽게 이어지도록 1차 정리함.
|
||||
- 관리자 화면과 티어 에디터처럼 스타일 밀도가 높은 화면은 후속 단계에서 세부 톤을 더 정교하게 맞추도록 todo 기준도 갱신함.
|
||||
|
||||
## 2026-04-01 v1.3.31
|
||||
- 관리자 게임 관리의 오른쪽 사이드 게임 선택 리스트는 더 많은 항목을 한 번에 볼 수 있도록 최대 높이를 늘리고, 게임 카드 내부 간격도 사용자가 조정한 CSS 기준으로 반영해 목록 밀도를 다시 다듬음.
|
||||
|
||||
## 2026-04-01 v1.3.30
|
||||
- 헤더의 `Tier Maker` 로고는 레인보우 그라데이션 텍스트로 바꿔 서비스 첫인상이 더 또렷하게 보이도록 정리하고, `by zenn`은 새 창으로 프로필 페이지를 여는 외부 링크로 연결함.
|
||||
- 다음 단계 작업용으로 라이트모드/다크모드 전환 항목을 todo 문서에 추가해, 현재의 다크 톤 UI를 유지하면서도 이후 테마 확장 흐름을 공식 작업 목록에 올림.
|
||||
|
||||
## 2026-04-01 v1.3.29
|
||||
- 책 아이콘 사용법 모달 진입점은 항상 보이는 오른쪽 사이드 하단 버튼 대신, Settings 화면에서만 왼쪽 사이드 하단의 보조 액션 버튼으로 옮겨 더 필요할 때만 찾게 되는 문맥형 진입 방식으로 정리함.
|
||||
- 인증 스토어에 초기 세션 동기화 완료 상태를 추가하고, 앱 셸·로그인 화면·프로필 화면은 세션 확인 전까지 비로그인 UI를 먼저 그리지 않도록 보강해 첫 진입 시 화면이 갑자기 로그인 상태로 뒤집히는 플래시를 줄임.
|
||||
|
||||
## 2026-04-01 v1.3.28
|
||||
- 책 아이콘 기반 사용법 모달은 기존의 단순 제작 흐름 안내를 넘어, 다른 사람 티어표 복사, 템플릿 업그레이드 요청, 새 템플릿 추가 요청, 즐겨찾기/내 티어표 관리까지 포함한 전체 기능 안내 허브로 확장함.
|
||||
- 사용법 모달 제목과 단계 표기를 더 넓은 개념의 `기능 안내` 기준으로 정리하고, 실제 스크린샷이 없어도 설명만으로 핵심 기능을 순서대로 이해할 수 있게 단계 문구를 전면 보강함.
|
||||
|
||||
@@ -26,12 +26,14 @@ const searchQuery = ref('')
|
||||
const searchPlaceholder = computed(() => (route.name === 'home' ? '게임 템플릿 검색' : '전체 티어표 검색'))
|
||||
const isCollapsedSearchOpen = ref(false)
|
||||
const isGuideModalOpen = ref(false)
|
||||
const themeMode = ref('dark')
|
||||
const guideStepIndex = ref(0)
|
||||
const viewportWidth = ref(typeof window !== 'undefined' ? window.innerWidth : 1440)
|
||||
provide('rightRailOpen', rightRailOpen)
|
||||
provide('localRightRailTarget', '#local-right-rail-root')
|
||||
|
||||
const isAdmin = computed(() => !!auth.user?.isAdmin)
|
||||
const authReady = computed(() => auth.hydrated)
|
||||
const isAdmin = computed(() => authReady.value && !!auth.user?.isAdmin)
|
||||
const isPreviewMode = computed(() => route.query.preview === '1')
|
||||
const usesLocalRightRail = computed(() => ['editEditor', 'newEditor', 'admin'].includes(String(route.name || '')))
|
||||
const isRightRailOverlay = computed(() => viewportWidth.value <= 1200)
|
||||
@@ -44,7 +46,10 @@ const accountName = computed(() => {
|
||||
if (email) return email.split('@')[0] || email
|
||||
return 'Guest'
|
||||
})
|
||||
const accountEmail = computed(() => (auth.user?.email || '').trim() || '로그인 후 개인 메뉴를 사용할 수 있어요.')
|
||||
const accountEmail = computed(() => {
|
||||
if (!authReady.value) return '계정 상태를 확인하고 있어요.'
|
||||
return (auth.user?.email || '').trim() || '로그인 후 개인 메뉴를 사용할 수 있어요.'
|
||||
})
|
||||
const shellStyle = computed(() => ({
|
||||
'--left-rail-width': leftRailCollapsed.value ? '76px' : '248px',
|
||||
'--right-rail-width': !isRightRailOverlay.value && rightRailOpen.value ? '325px' : '0px',
|
||||
@@ -56,9 +61,10 @@ const leftNavItems = computed(() => {
|
||||
{ key: 'favorites', label: 'Favorites', path: '/favorites', iconSrc: iconFavorite, requiresAuth: true },
|
||||
{ key: 'profile', label: 'Settings', path: '/profile', iconSrc: iconSettings, requiresAuth: true },
|
||||
]
|
||||
return items.filter((item) => !item.requiresAuth || auth.user)
|
||||
return items.filter((item) => !item.requiresAuth || (authReady.value && auth.user))
|
||||
})
|
||||
const showRightRailAction = computed(() => false)
|
||||
const showSettingsGuideButton = computed(() => route.name === 'profile')
|
||||
const guideSteps = [
|
||||
{
|
||||
id: 'select-game',
|
||||
@@ -120,9 +126,13 @@ const guideSteps = [
|
||||
const currentGuideStep = computed(() => guideSteps[guideStepIndex.value] || guideSteps[0])
|
||||
const isGuidePrevDisabled = computed(() => guideStepIndex.value <= 0)
|
||||
const isGuideNextDisabled = computed(() => guideStepIndex.value >= guideSteps.length - 1)
|
||||
const isLightTheme = computed(() => themeMode.value === 'light')
|
||||
const themeToggleLabel = computed(() => (isLightTheme.value ? '다크 모드' : '라이트 모드'))
|
||||
const showSettingsThemePanel = computed(() => route.name === 'profile')
|
||||
const showGameHubViewToggle = computed(() => route.name === 'gameHub')
|
||||
const gameHubViewMode = computed(() => (route.query.view === 'list' ? 'list' : 'grid'))
|
||||
const leftBottomPrimaryAction = computed(() => {
|
||||
if (!authReady.value) return null
|
||||
if (route.name === 'home' && auth.user) {
|
||||
return { label: '커스텀 티어표 만들기', to: '/editor/freeform/new' }
|
||||
}
|
||||
@@ -234,7 +244,22 @@ function syncViewportWidth() {
|
||||
viewportWidth.value = window.innerWidth
|
||||
}
|
||||
|
||||
function applyTheme(mode) {
|
||||
themeMode.value = mode === 'light' ? 'light' : 'dark'
|
||||
if (typeof document !== 'undefined') document.documentElement.dataset.theme = themeMode.value
|
||||
if (typeof window !== 'undefined') window.localStorage.setItem('tier-maker:theme', themeMode.value)
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
applyTheme(isLightTheme.value ? 'dark' : 'light')
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const savedTheme = window.localStorage.getItem('tier-maker:theme')
|
||||
if (savedTheme === 'light' || savedTheme === 'dark') applyTheme(savedTheme)
|
||||
else applyTheme(window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark')
|
||||
}
|
||||
await auth.refresh()
|
||||
if (typeof window !== 'undefined') {
|
||||
syncViewportWidth()
|
||||
@@ -401,7 +426,7 @@ function submitGlobalSearch() {
|
||||
|
||||
<div class="leftRail__body">
|
||||
<div class="leftRail__content">
|
||||
<div v-if="auth.user" class="appUserCard">
|
||||
<div v-if="authReady && auth.user" class="appUserCard">
|
||||
<div class="appUserCard__button">
|
||||
<img v-if="avatarUrl" :src="avatarUrl" class="appUserCard__avatar" alt="avatar" />
|
||||
<div v-else class="appUserCard__avatar appUserCard__avatar--fallback">{{ accountName[0]?.toUpperCase() || 'U' }}</div>
|
||||
@@ -442,8 +467,12 @@ function submitGlobalSearch() {
|
||||
</div>
|
||||
<div class="leftRail__bottom">
|
||||
<RouterLink v-if="leftBottomPrimaryAction" :to="leftBottomPrimaryAction.to" class="adminButton">{{ leftBottomPrimaryAction.label }}</RouterLink>
|
||||
<RouterLink v-if="isAdmin" to="/admin" class="adminButton">관리자 메뉴</RouterLink>
|
||||
<RouterLink v-else-if="!auth.user" to="/login" class="adminButton">로그인</RouterLink>
|
||||
<button v-if="showSettingsGuideButton" class="adminButton adminButton--icon" type="button" @click="openGuideModal()">
|
||||
<SvgIcon :src="iconMenuBook" :size="18" class="adminButton__icon" />
|
||||
<span>가이드 보기</span>
|
||||
</button>
|
||||
<RouterLink v-if="authReady && isAdmin" to="/admin" class="adminButton">관리자 메뉴</RouterLink>
|
||||
<RouterLink v-else-if="authReady && !auth.user" to="/login" class="adminButton">로그인</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
@@ -453,7 +482,15 @@ function submitGlobalSearch() {
|
||||
<header class="workspaceHead railHeader">
|
||||
<div class="workspaceHead__brand" @click="$router.push('/')">
|
||||
<span class="workspaceHead__brandTitle">Tier Maker</span>
|
||||
<span class="workspaceHead__brandSub">by zenn</span>
|
||||
<a
|
||||
class="workspaceHead__brandSub"
|
||||
href="https://zenn.town/@murabito"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
@click.stop
|
||||
>
|
||||
by zenn
|
||||
</a>
|
||||
</div>
|
||||
<div class="workspaceHead__actions">
|
||||
<div v-if="showGameHubViewToggle" class="viewToggle" role="group" aria-label="티어표 보기 방식">
|
||||
@@ -556,7 +593,17 @@ function submitGlobalSearch() {
|
||||
<div class="rightRail__content">
|
||||
<div v-if="usesLocalRightRail" id="local-right-rail-root" class="localRightRailRoot"></div>
|
||||
<template v-else>
|
||||
<RightRailAd />
|
||||
<section v-if="showSettingsThemePanel" class="settingsThemePanel">
|
||||
<div class="settingsThemePanel__eyebrow">Appearance</div>
|
||||
<div class="settingsThemePanel__title">테마 설정</div>
|
||||
<div class="settingsThemePanel__desc">밝은 톤과 어두운 톤 중 원하는 작업 환경으로 전환할 수 있어요.</div>
|
||||
<label class="toggleSwitch settingsThemePanel__toggle">
|
||||
<input :checked="isLightTheme" type="checkbox" @change="toggleTheme" />
|
||||
<span class="toggleSwitch__label">{{ isLightTheme ? '라이트 모드' : '다크 모드' }}</span>
|
||||
<span class="toggleSwitch__track"><span class="toggleSwitch__thumb"></span></span>
|
||||
</label>
|
||||
</section>
|
||||
<RightRailAd v-else />
|
||||
</template>
|
||||
</div>
|
||||
<div class="rightRail__bottom">
|
||||
@@ -567,9 +614,6 @@ function submitGlobalSearch() {
|
||||
</button>
|
||||
</section>
|
||||
</template>
|
||||
<button class="guideDockButton" type="button" aria-label="사용법 열기" @click="openGuideModal()">
|
||||
<SvgIcon :src="iconMenuBook" :size="22" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
@@ -592,8 +636,8 @@ function submitGlobalSearch() {
|
||||
min-height: 100dvh;
|
||||
display: grid;
|
||||
grid-template-columns: var(--left-rail-width, 248px) minmax(0, 1fr) var(--right-rail-width, 325px);
|
||||
background: rgba(14, 14, 14, 0.96);
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
background: var(--theme-shell-bg);
|
||||
color: var(--theme-text);
|
||||
transition: grid-template-columns 220ms ease;
|
||||
}
|
||||
|
||||
@@ -604,8 +648,8 @@ function submitGlobalSearch() {
|
||||
.leftRail,
|
||||
.rightRail {
|
||||
min-height: 100dvh;
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(14, 14, 14, 0.92);
|
||||
border-right: 1px solid var(--theme-border);
|
||||
background: var(--theme-rail-bg);
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
@@ -615,7 +659,7 @@ function submitGlobalSearch() {
|
||||
|
||||
.rightRail {
|
||||
border-right: 0;
|
||||
border-left: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-left: 1px solid var(--theme-border);
|
||||
transition:
|
||||
opacity 220ms ease,
|
||||
transform 220ms ease,
|
||||
@@ -639,7 +683,7 @@ function submitGlobalSearch() {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-bottom: 1px solid var(--theme-border);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@@ -698,8 +742,8 @@ function submitGlobalSearch() {
|
||||
height: 28px;
|
||||
padding: 0 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid var(--theme-border);
|
||||
background: var(--theme-pill-bg);
|
||||
color: rgba(255, 255, 255, 0.72);
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
@@ -768,7 +812,7 @@ function submitGlobalSearch() {
|
||||
object-fit: cover;
|
||||
flex: 0 0 auto;
|
||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
background: var(--theme-surface-soft-3);
|
||||
}
|
||||
|
||||
.appUserCard__avatar--fallback {
|
||||
@@ -794,7 +838,7 @@ function submitGlobalSearch() {
|
||||
|
||||
.appUserCard__email {
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.56);
|
||||
color: var(--theme-text-muted);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -807,8 +851,8 @@ function submitGlobalSearch() {
|
||||
gap: 10px;
|
||||
padding: 11px 12px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid var(--theme-border);
|
||||
background: var(--theme-pill-bg);
|
||||
color: rgba(255, 255, 255, 0.62);
|
||||
margin-bottom: 14px;
|
||||
box-sizing: border-box;
|
||||
@@ -821,7 +865,7 @@ function submitGlobalSearch() {
|
||||
max-width: 100%;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
color: var(--theme-text);
|
||||
outline: none;
|
||||
font: inherit;
|
||||
overflow: hidden;
|
||||
@@ -829,7 +873,7 @@ function submitGlobalSearch() {
|
||||
}
|
||||
|
||||
.searchStub__input::placeholder {
|
||||
color: rgba(255, 255, 255, 0.42);
|
||||
color: var(--theme-text-soft);
|
||||
}
|
||||
|
||||
.searchStub__iconButton {
|
||||
@@ -960,23 +1004,32 @@ function submitGlobalSearch() {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
border: 1px solid var(--theme-border-strong);
|
||||
background: var(--theme-surface-soft);
|
||||
color: var(--theme-text);
|
||||
text-decoration: none;
|
||||
box-sizing: border-box;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.adminButton--icon {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.adminButton__icon {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.appMain {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
box-sizing: border-box;
|
||||
background: rgba(18, 18, 18, 0.98);
|
||||
border-left: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: var(--theme-main-bg);
|
||||
border-left: 1px solid var(--theme-border);
|
||||
border-right: 1px solid var(--theme-border);
|
||||
}
|
||||
|
||||
.appMain--preview {
|
||||
@@ -1012,12 +1065,22 @@ function submitGlobalSearch() {
|
||||
font-size: 28px;
|
||||
font-weight: 900;
|
||||
letter-spacing: -0.05em;
|
||||
background-image: linear-gradient(90deg, #ff75c3 0%, #ffa647 20%, #ffe83f 40%, #9fff5b 60%, #70e2ff 80%, #cd93ff 100%);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.workspaceHead__brandSub {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: rgba(255, 255, 255, 0.58);
|
||||
color: var(--theme-text-muted);
|
||||
text-decoration: none;
|
||||
transition: color 180ms ease, opacity 180ms ease;
|
||||
}
|
||||
|
||||
.workspaceHead__brandSub:hover {
|
||||
color: var(--theme-text);
|
||||
}
|
||||
|
||||
.workspaceHead__actions {
|
||||
@@ -1033,7 +1096,7 @@ function submitGlobalSearch() {
|
||||
gap: 6px;
|
||||
padding: 4px;
|
||||
border-radius: 14px;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
background: var(--theme-surface-soft);
|
||||
}
|
||||
|
||||
.viewToggle .ghostIcon--iconOnly {
|
||||
@@ -1044,7 +1107,7 @@ function submitGlobalSearch() {
|
||||
}
|
||||
|
||||
.ghostIcon--active {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
background: var(--theme-surface-soft-3);
|
||||
}
|
||||
|
||||
.workspaceBody {
|
||||
@@ -1052,7 +1115,7 @@ function submitGlobalSearch() {
|
||||
padding: 18px 18px 32px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: rgba(24, 24, 24, 0.92);
|
||||
background: var(--theme-workspace-bg);
|
||||
box-shadow: none;
|
||||
margin: 0;
|
||||
}
|
||||
@@ -1062,7 +1125,7 @@ function submitGlobalSearch() {
|
||||
padding: 18px 18px 32px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: rgba(24, 24, 24, 0.92);
|
||||
background: var(--theme-workspace-bg);
|
||||
box-shadow: none;
|
||||
margin: 0;
|
||||
}
|
||||
@@ -1084,23 +1147,96 @@ function submitGlobalSearch() {
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.guideDockButton {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
color: rgba(255, 255, 255, 0.78);
|
||||
.settingsThemePanel {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
padding: 18px;
|
||||
border-radius: 22px;
|
||||
border: 1px solid var(--theme-border);
|
||||
background: var(--theme-pill-bg);
|
||||
}
|
||||
|
||||
.settingsThemePanel__eyebrow {
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--theme-text-soft);
|
||||
}
|
||||
|
||||
.settingsThemePanel__title {
|
||||
font-size: 22px;
|
||||
font-weight: 800;
|
||||
color: var(--theme-text-strong);
|
||||
}
|
||||
|
||||
.settingsThemePanel__desc {
|
||||
color: var(--theme-text-muted);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.settingsThemePanel__toggle {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.toggleSwitch {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid var(--theme-border-strong);
|
||||
background: var(--theme-surface-soft);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.toggleSwitch input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.toggleSwitch__track {
|
||||
position: relative;
|
||||
width: 42px;
|
||||
height: 24px;
|
||||
border-radius: 999px;
|
||||
background: var(--theme-surface-soft-3);
|
||||
border: 1px solid var(--theme-border-strong);
|
||||
transition: background 180ms ease, border-color 180ms ease;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.guideDockButton:hover {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: rgba(255, 255, 255, 0.96);
|
||||
.toggleSwitch__thumb {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.24);
|
||||
transition: transform 180ms ease;
|
||||
}
|
||||
|
||||
:root[data-theme='light'] .toggleSwitch__thumb {
|
||||
background: #fff;
|
||||
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.16);
|
||||
}
|
||||
|
||||
.toggleSwitch__label {
|
||||
font-weight: 800;
|
||||
color: var(--theme-text);
|
||||
}
|
||||
|
||||
.toggleSwitch input:checked ~ .toggleSwitch__track {
|
||||
background: rgba(96, 165, 250, 0.34);
|
||||
border-color: rgba(96, 165, 250, 0.42);
|
||||
}
|
||||
|
||||
.toggleSwitch input:checked ~ .toggleSwitch__track .toggleSwitch__thumb {
|
||||
transform: translateX(18px);
|
||||
}
|
||||
|
||||
.rightRailAction__button {
|
||||
@@ -1108,8 +1244,8 @@ function submitGlobalSearch() {
|
||||
padding: 12px 14px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(77, 127, 233, 0.96);
|
||||
background: rgba(77, 127, 233, 0.88);
|
||||
color: #fff;
|
||||
background: var(--theme-accent-bg);
|
||||
color: var(--theme-accent-text);
|
||||
font-weight: 800;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -1147,8 +1283,8 @@ function submitGlobalSearch() {
|
||||
align-content: start;
|
||||
gap: 18px;
|
||||
padding: 28px 22px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: var(--theme-pill-bg);
|
||||
border-right: 1px solid var(--theme-border);
|
||||
}
|
||||
|
||||
.guideModal__eyebrow {
|
||||
@@ -1177,7 +1313,7 @@ function submitGlobalSearch() {
|
||||
align-items: center;
|
||||
padding: 12px 14px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border: 1px solid var(--theme-border);
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
cursor: pointer;
|
||||
@@ -1213,7 +1349,7 @@ function submitGlobalSearch() {
|
||||
justify-self: end;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: rgba(255, 255, 255, 0.56);
|
||||
color: var(--theme-text-muted);
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
}
|
||||
@@ -1241,7 +1377,7 @@ function submitGlobalSearch() {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
border-radius: 24px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border: 1px solid var(--theme-border);
|
||||
background: radial-gradient(circle at top, rgba(77, 127, 233, 0.18), rgba(255, 255, 255, 0.02) 52%), rgba(255, 255, 255, 0.03);
|
||||
display: grid;
|
||||
align-content: center;
|
||||
@@ -1276,7 +1412,7 @@ function submitGlobalSearch() {
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255, 255, 255, 0.42);
|
||||
color: var(--theme-text-soft);
|
||||
}
|
||||
|
||||
.guideModal__stepTitle {
|
||||
@@ -1329,8 +1465,8 @@ function submitGlobalSearch() {
|
||||
padding: 12px 18px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(77, 127, 233, 0.96);
|
||||
background: rgba(77, 127, 233, 0.88);
|
||||
color: #fff;
|
||||
background: var(--theme-accent-bg);
|
||||
color: var(--theme-accent-text);
|
||||
font-weight: 800;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -1339,9 +1475,9 @@ function submitGlobalSearch() {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
border: 1px solid var(--theme-border);
|
||||
background: var(--theme-surface-soft);
|
||||
color: var(--theme-text);
|
||||
font-size: 28px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
@@ -1397,7 +1533,7 @@ function submitGlobalSearch() {
|
||||
flex: 1;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
color: var(--theme-text);
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
outline: none;
|
||||
@@ -1435,7 +1571,7 @@ function submitGlobalSearch() {
|
||||
justify-content: space-between;
|
||||
padding: 12px 14px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border: 1px solid var(--theme-border-strong);
|
||||
background: rgba(11, 18, 32, 0.94);
|
||||
backdrop-filter: blur(12px);
|
||||
box-shadow: 0 14px 30px rgba(0, 0, 0, 0.28);
|
||||
@@ -1469,7 +1605,7 @@ function submitGlobalSearch() {
|
||||
.toast__count {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.56);
|
||||
color: var(--theme-text-muted);
|
||||
}
|
||||
|
||||
.toast__close {
|
||||
@@ -1488,7 +1624,7 @@ function submitGlobalSearch() {
|
||||
|
||||
.guideModal__sidebar {
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-bottom: 1px solid var(--theme-border);
|
||||
}
|
||||
|
||||
.guideModal__content {
|
||||
@@ -1515,8 +1651,8 @@ function submitGlobalSearch() {
|
||||
width: min(360px, calc(100vw - 20px));
|
||||
height: 100dvh;
|
||||
z-index: 30;
|
||||
border-left: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(14, 14, 14, 0.96);
|
||||
border-left: 1px solid var(--theme-border);
|
||||
background: var(--theme-shell-bg);
|
||||
box-shadow: -18px 0 36px rgba(0, 0, 0, 0.34);
|
||||
}
|
||||
|
||||
@@ -1577,7 +1713,7 @@ function submitGlobalSearch() {
|
||||
min-height: auto;
|
||||
height: auto;
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-bottom: 1px solid var(--theme-border);
|
||||
}
|
||||
|
||||
.leftRail__top {
|
||||
|
||||
@@ -5,30 +5,40 @@ export const useAuthStore = defineStore('auth', {
|
||||
state: () => ({
|
||||
user: null,
|
||||
status: 'idle',
|
||||
hydrated: false,
|
||||
}),
|
||||
actions: {
|
||||
async refresh() {
|
||||
if (this.status === 'loading') return this.user
|
||||
this.status = 'loading'
|
||||
try {
|
||||
const data = await api.me()
|
||||
this.user = data.user
|
||||
return this.user
|
||||
} catch (error) {
|
||||
this.user = null
|
||||
return null
|
||||
} finally {
|
||||
this.status = 'idle'
|
||||
this.hydrated = true
|
||||
}
|
||||
},
|
||||
async signup(email, password) {
|
||||
const user = await api.signup({ email, password })
|
||||
this.user = user
|
||||
this.hydrated = true
|
||||
return user
|
||||
},
|
||||
async login(email, password) {
|
||||
const user = await api.login({ email, password })
|
||||
this.user = user
|
||||
this.hydrated = true
|
||||
return user
|
||||
},
|
||||
async logout() {
|
||||
await api.logout()
|
||||
this.user = null
|
||||
this.hydrated = true
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -2,12 +2,67 @@
|
||||
font-family: 'Pretendard', 'Inter', 'Segoe UI', sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
background: #121212;
|
||||
color: var(--theme-text);
|
||||
background: var(--theme-body-bg);
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
--theme-body-bg: #121212;
|
||||
--theme-shell-bg: rgba(14, 14, 14, 0.96);
|
||||
--theme-rail-bg: rgba(14, 14, 14, 0.92);
|
||||
--theme-main-bg: rgba(18, 18, 18, 0.98);
|
||||
--theme-workspace-bg: rgba(24, 24, 24, 0.92);
|
||||
--theme-card-bg: rgba(62, 62, 62, 0.82);
|
||||
--theme-card-bg-hover: rgba(70, 70, 70, 0.96);
|
||||
--theme-card-border: rgba(255, 255, 255, 0.16);
|
||||
--theme-card-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
||||
--theme-surface-soft: rgba(255, 255, 255, 0.05);
|
||||
--theme-surface-soft-2: rgba(255, 255, 255, 0.06);
|
||||
--theme-surface-soft-3: rgba(255, 255, 255, 0.08);
|
||||
--theme-pill-bg: rgba(255, 255, 255, 0.03);
|
||||
--theme-border: rgba(255, 255, 255, 0.08);
|
||||
--theme-border-strong: rgba(255, 255, 255, 0.12);
|
||||
--theme-text: rgba(255, 255, 255, 0.92);
|
||||
--theme-text-strong: var(--theme-text-strong);
|
||||
--theme-text-muted: var(--theme-text-muted);
|
||||
--theme-text-soft: var(--theme-text-soft);
|
||||
--theme-text-faint: rgba(255, 255, 255, 0.4);
|
||||
--theme-thumb-fallback-bg: #555;
|
||||
--theme-select-arrow: var(--theme-select-arrow);
|
||||
--theme-danger-bg: rgba(239, 68, 68, 0.1);
|
||||
--theme-danger-border: rgba(239, 68, 68, 0.18);
|
||||
--theme-accent-bg: rgba(76, 133, 245, 0.92);
|
||||
--theme-accent-text: #fff;
|
||||
}
|
||||
|
||||
:root[data-theme='light'] {
|
||||
--theme-body-bg: #edf1f7;
|
||||
--theme-shell-bg: rgba(244, 247, 252, 0.98);
|
||||
--theme-rail-bg: rgba(248, 250, 253, 0.96);
|
||||
--theme-main-bg: rgba(241, 244, 249, 0.98);
|
||||
--theme-workspace-bg: rgba(250, 252, 255, 0.95);
|
||||
--theme-card-bg: var(--theme-text-strong);
|
||||
--theme-card-bg-hover: rgba(245, 248, 255, 0.98);
|
||||
--theme-card-border: rgba(26, 32, 44, 0.1);
|
||||
--theme-card-shadow: 0 18px 34px rgba(31, 41, 55, 0.08);
|
||||
--theme-surface-soft: rgba(15, 23, 42, 0.05);
|
||||
--theme-surface-soft-2: rgba(15, 23, 42, 0.07);
|
||||
--theme-surface-soft-3: rgba(15, 23, 42, 0.1);
|
||||
--theme-pill-bg: rgba(15, 23, 42, 0.04);
|
||||
--theme-border: rgba(15, 23, 42, 0.1);
|
||||
--theme-border-strong: rgba(15, 23, 42, 0.14);
|
||||
--theme-text: rgba(20, 27, 40, 0.9);
|
||||
--theme-text-strong: rgba(10, 15, 28, 0.98);
|
||||
--theme-text-muted: rgba(55, 65, 81, 0.74);
|
||||
--theme-text-soft: rgba(75, 85, 99, 0.64);
|
||||
--theme-text-faint: rgba(100, 116, 139, 0.82);
|
||||
--theme-thumb-fallback-bg: #d8dde8;
|
||||
--theme-select-arrow: rgba(55, 65, 81, 0.72);
|
||||
--theme-danger-bg: rgba(239, 68, 68, 0.1);
|
||||
--theme-danger-border: rgba(239, 68, 68, 0.22);
|
||||
--theme-accent-bg: rgba(64, 110, 226, 0.94);
|
||||
--theme-accent-text: #fff;
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -22,7 +77,9 @@ body,
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: #121212;
|
||||
background: var(--theme-body-bg);
|
||||
color: var(--theme-text);
|
||||
transition: background 220ms ease, color 220ms ease;
|
||||
}
|
||||
|
||||
button,
|
||||
@@ -43,7 +100,7 @@ a {
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
color: var(--theme-text);
|
||||
}
|
||||
|
||||
select {
|
||||
@@ -51,8 +108,8 @@ select {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
background-image:
|
||||
linear-gradient(45deg, transparent 50%, rgba(255, 255, 255, 0.72) 50%),
|
||||
linear-gradient(135deg, rgba(255, 255, 255, 0.72) 50%, transparent 50%);
|
||||
linear-gradient(45deg, transparent 50%, var(--theme-select-arrow) 50%),
|
||||
linear-gradient(135deg, var(--theme-select-arrow) 50%, transparent 50%);
|
||||
background-position:
|
||||
calc(100% - 20px) calc(50% - 2px),
|
||||
calc(100% - 14px) calc(50% - 2px);
|
||||
@@ -99,19 +156,19 @@ p {
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255, 255, 255, 0.42);
|
||||
color: var(--theme-text-soft);
|
||||
}
|
||||
|
||||
.pageHead__title {
|
||||
font-size: 32px;
|
||||
line-height: 1.05;
|
||||
letter-spacing: -0.04em;
|
||||
color: rgba(255, 255, 255, 0.96);
|
||||
color: var(--theme-text-strong);
|
||||
}
|
||||
|
||||
.pageHead__desc {
|
||||
max-width: 720px;
|
||||
color: rgba(255, 255, 255, 0.58);
|
||||
color: var(--theme-text-muted);
|
||||
}
|
||||
|
||||
.pageHead__aside {
|
||||
|
||||
@@ -2409,13 +2409,13 @@ async function saveFeaturedOrder() {
|
||||
.adminGamePicker {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
max-height: 320px;
|
||||
max-height: 640px;
|
||||
overflow: auto;
|
||||
padding-right: 4px;
|
||||
}
|
||||
.adminGamePicker__item {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
/* gap: 2px; */
|
||||
padding: 11px 12px;
|
||||
text-align: left;
|
||||
border-radius: 14px;
|
||||
|
||||
@@ -110,16 +110,16 @@ onMounted(loadFavorites)
|
||||
.select {
|
||||
padding: 11px 13px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
border: 1px solid var(--theme-border);
|
||||
background: var(--theme-surface-soft);
|
||||
color: var(--theme-text);
|
||||
}
|
||||
.btn {
|
||||
padding: 11px 13px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
border: 1px solid var(--theme-border);
|
||||
background: var(--theme-surface-soft-2);
|
||||
color: var(--theme-text);
|
||||
font-weight: 800;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -133,18 +133,18 @@ onMounted(loadFavorites)
|
||||
}
|
||||
.boardCard {
|
||||
border-radius: 22px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.16);
|
||||
background: rgba(62, 62, 62, 0.82);
|
||||
border: 1px solid var(--theme-card-border);
|
||||
background: var(--theme-card-bg);
|
||||
overflow: hidden;
|
||||
display: grid;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
||||
box-shadow: inset 0 1px 0 var(--theme-card-shadow);
|
||||
transition:
|
||||
transform 0.16s ease,
|
||||
background 0.16s ease;
|
||||
}
|
||||
.boardCard:hover {
|
||||
transform: translateY(-2px);
|
||||
background: rgba(70, 70, 70, 0.96);
|
||||
background: var(--theme-card-bg-hover);
|
||||
}
|
||||
.boardCard__body {
|
||||
border: 0;
|
||||
@@ -172,10 +172,10 @@ onMounted(loadFavorites)
|
||||
object-fit: cover;
|
||||
}
|
||||
.boardCard__thumbPlaceholder {
|
||||
background: #555;
|
||||
background: var(--theme-thumb-fallback-bg);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
color: var(--theme-text-faint);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
@@ -222,7 +222,7 @@ onMounted(loadFavorites)
|
||||
height: 22px;
|
||||
border-radius: 9999px;
|
||||
object-fit: cover;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
background: var(--theme-border);
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.boardCard__avatar--fallback {
|
||||
@@ -235,7 +235,7 @@ onMounted(loadFavorites)
|
||||
.favoriteStat {
|
||||
flex: 0 0 auto;
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.64);
|
||||
color: var(--theme-text-faint);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ function submitSearch() {
|
||||
}
|
||||
.dashboardHero__eyebrow {
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.42);
|
||||
color: var(--theme-text-soft);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
@@ -157,15 +157,15 @@ function submitSearch() {
|
||||
margin: 4px 0 6px;
|
||||
font-size: 32px;
|
||||
letter-spacing: -0.04em;
|
||||
color: rgba(255, 255, 255, 0.96);
|
||||
color: var(--theme-text-strong);
|
||||
}
|
||||
.dashboardHero__desc {
|
||||
margin: 0;
|
||||
color: rgba(255, 255, 255, 0.58);
|
||||
color: var(--theme-text-muted);
|
||||
max-width: 720px;
|
||||
}
|
||||
.panel {
|
||||
/* border: 1px solid rgba(255, 255, 255, 0.08); */
|
||||
/* border: 1px solid var(--theme-border); */
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
@@ -174,8 +174,8 @@ function submitSearch() {
|
||||
margin: 10px 0 14px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||
background: rgba(239, 68, 68, 0.12);
|
||||
border: 1px solid var(--theme-danger-border);
|
||||
background: var(--theme-danger-bg);
|
||||
}
|
||||
.panel__title {
|
||||
font-weight: 800;
|
||||
@@ -183,7 +183,7 @@ function submitSearch() {
|
||||
}
|
||||
.panel__sub {
|
||||
margin-top: 6px;
|
||||
color: rgba(255, 255, 255, 0.56);
|
||||
color: var(--theme-text-muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
.panel__head {
|
||||
@@ -204,16 +204,16 @@ function submitSearch() {
|
||||
min-width: 240px;
|
||||
padding: 11px 13px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
border: 1px solid var(--theme-border);
|
||||
background: var(--theme-surface-soft);
|
||||
color: var(--theme-text);
|
||||
}
|
||||
.searchBar__button {
|
||||
padding: 11px 14px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
border: 1px solid var(--theme-border);
|
||||
background: var(--theme-surface-soft-2);
|
||||
color: var(--theme-text);
|
||||
font-weight: 800;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -232,18 +232,18 @@ function submitSearch() {
|
||||
.boardCard {
|
||||
min-width: 0;
|
||||
border-radius: 22px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.16);
|
||||
background: rgba(62, 62, 62, 0.82);
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
border: 1px solid var(--theme-card-border);
|
||||
background: var(--theme-card-bg);
|
||||
color: var(--theme-text);
|
||||
display: grid;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
||||
box-shadow: inset 0 1px 0 var(--theme-card-shadow);
|
||||
transition:
|
||||
transform 0.16s ease,
|
||||
background 0.16s ease;
|
||||
}
|
||||
.boardCard:hover {
|
||||
background: rgba(70, 70, 70, 0.96);
|
||||
background: var(--theme-card-bg-hover);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.boardCard__body {
|
||||
@@ -293,10 +293,10 @@ function submitSearch() {
|
||||
.boardCard__thumbPlaceholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #555;
|
||||
background: var(--theme-thumb-fallback-bg);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
color: var(--theme-text-faint);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
border-radius: 18px;
|
||||
@@ -362,7 +362,7 @@ function submitSearch() {
|
||||
border-radius: 9999px;
|
||||
object-fit: cover;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
background: var(--theme-border);
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.boardCard__avatar--fallback {
|
||||
@@ -377,7 +377,7 @@ function submitSearch() {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.64);
|
||||
color: var(--theme-text-faint);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@@ -120,31 +120,31 @@ function thumbUrl(g) {
|
||||
margin: 0 0 16px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(239, 68, 68, 0.18);
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
border: 1px solid var(--theme-danger-border);
|
||||
background: var(--theme-danger-bg);
|
||||
color: var(--theme-text);
|
||||
}
|
||||
.pageHead__searchState {
|
||||
margin-top: 8px;
|
||||
color: rgba(255, 255, 255, 0.62);
|
||||
color: var(--theme-text-muted);
|
||||
}
|
||||
.libraryCard {
|
||||
position: relative;
|
||||
text-align: left;
|
||||
padding: 14px;
|
||||
border-radius: 22px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.16);
|
||||
background: rgba(62, 62, 62, 0.82);
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
border: 1px solid var(--theme-card-border);
|
||||
background: var(--theme-card-bg);
|
||||
color: var(--theme-text);
|
||||
cursor: pointer;
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
||||
box-shadow: inset 0 1px 0 var(--theme-card-shadow);
|
||||
transition: transform 0.16s ease, background 0.16s ease;
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
.libraryCard:hover {
|
||||
background: rgba(70, 70, 70, 0.96);
|
||||
background: var(--theme-card-bg-hover);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.libraryCard__main {
|
||||
@@ -191,8 +191,8 @@ function thumbUrl(g) {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
background: #555;
|
||||
border: 1px solid var(--theme-surface-soft-2);
|
||||
background: var(--theme-thumb-fallback-bg);
|
||||
overflow: hidden;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
@@ -204,7 +204,7 @@ function thumbUrl(g) {
|
||||
}
|
||||
.libraryCard__thumbFallback {
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
color: var(--theme-text-faint);
|
||||
}
|
||||
.libraryCard__body {
|
||||
display: grid;
|
||||
@@ -241,7 +241,7 @@ function thumbUrl(g) {
|
||||
|
||||
.libraryEmpty {
|
||||
padding: 20px 0;
|
||||
color: rgba(255, 255, 255, 0.62);
|
||||
color: var(--theme-text-muted);
|
||||
}
|
||||
@media (max-width: 1400px) {
|
||||
.libraryGrid {
|
||||
|
||||
@@ -30,8 +30,15 @@ const description = computed(() =>
|
||||
: '저장한 티어표와 즐겨찾기, 프로필 설정을 이어서 관리할 수 있어요.'
|
||||
)
|
||||
const submitLabel = computed(() => (mode.value === 'signup' ? '가입하기' : '로그인'))
|
||||
const authReady = computed(() => auth.hydrated)
|
||||
const checkingSession = computed(() => !authReady.value || auth.status === 'loading')
|
||||
|
||||
onMounted(async () => {
|
||||
if (!auth.hydrated) await auth.refresh()
|
||||
if (auth.user) {
|
||||
router.replace(typeof route.query.redirect === 'string' ? route.query.redirect : '/me')
|
||||
return
|
||||
}
|
||||
try {
|
||||
const meta = await api.authMeta()
|
||||
hasUsers.value = !!meta.hasUsers
|
||||
@@ -40,6 +47,15 @@ onMounted(async () => {
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => [auth.hydrated, auth.user],
|
||||
([hydrated, user]) => {
|
||||
if (!hydrated || !user) return
|
||||
router.replace(typeof route.query.redirect === 'string' ? route.query.redirect : '/me')
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
async function submit() {
|
||||
error.value = ''
|
||||
if (mode.value === 'signup' && password.value !== passwordConfirm.value) {
|
||||
@@ -66,7 +82,11 @@ async function submit() {
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="authScreen">
|
||||
<section v-if="checkingSession" class="authScreen authScreen--loading">
|
||||
<div class="authLoading">로그인 상태를 확인하고 있어요.</div>
|
||||
</section>
|
||||
|
||||
<section v-else class="authScreen">
|
||||
<div class="authTabs" role="tablist" aria-label="로그인 또는 회원가입">
|
||||
<button type="button" class="authTabs__button" :class="{ 'authTabs__button--active': mode === 'login' }" @click="mode = 'login'">
|
||||
로그인
|
||||
@@ -128,14 +148,24 @@ async function submit() {
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.authScreen--loading {
|
||||
min-height: 220px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.authLoading {
|
||||
color: var(--theme-text-muted);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.authTabs {
|
||||
display: inline-flex;
|
||||
gap: 8px;
|
||||
width: fit-content;
|
||||
padding: 6px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid var(--theme-border);
|
||||
background: var(--theme-pill-bg);
|
||||
}
|
||||
|
||||
.authTabs__button {
|
||||
@@ -144,14 +174,14 @@ async function submit() {
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
background: transparent;
|
||||
color: rgba(255, 255, 255, 0.62);
|
||||
color: var(--theme-text-muted);
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.authTabs__button--active {
|
||||
background: rgba(76, 133, 245, 0.22);
|
||||
color: rgba(255, 255, 255, 0.96);
|
||||
color: var(--theme-text-strong);
|
||||
}
|
||||
|
||||
.authFields {
|
||||
@@ -166,16 +196,16 @@ async function submit() {
|
||||
|
||||
.field__label {
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.62);
|
||||
color: var(--theme-text-muted);
|
||||
}
|
||||
|
||||
.field__input {
|
||||
width: 100%;
|
||||
padding: 14px 0;
|
||||
border: 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-bottom: 1px solid var(--theme-border-strong);
|
||||
background: transparent;
|
||||
color: rgba(255, 255, 255, 0.94);
|
||||
color: var(--theme-text);
|
||||
outline: none;
|
||||
font-size: 18px;
|
||||
letter-spacing: -0.02em;
|
||||
@@ -187,7 +217,7 @@ async function submit() {
|
||||
|
||||
.field__hint {
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.42);
|
||||
color: var(--theme-text-soft);
|
||||
}
|
||||
|
||||
.roleBadge {
|
||||
@@ -196,7 +226,7 @@ async function submit() {
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(96, 165, 250, 0.28);
|
||||
background: rgba(96, 165, 250, 0.1);
|
||||
color: rgba(191, 219, 254, 0.92);
|
||||
color: var(--theme-text);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
@@ -218,14 +248,14 @@ async function submit() {
|
||||
|
||||
.primaryAction {
|
||||
border: 1px solid rgba(76, 133, 245, 0.96);
|
||||
background: rgba(76, 133, 245, 0.92);
|
||||
color: #fff;
|
||||
background: var(--theme-accent-bg);
|
||||
color: var(--theme-accent-text);
|
||||
}
|
||||
|
||||
.secondaryAction {
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
color: rgba(255, 255, 255, 0.86);
|
||||
border: 1px solid var(--theme-border-strong);
|
||||
background: var(--theme-surface-soft);
|
||||
color: var(--theme-text);
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
|
||||
@@ -124,7 +124,7 @@ function openList(t) {
|
||||
}
|
||||
.dashboardHero__eyebrow {
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.42);
|
||||
color: var(--theme-text-soft);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
@@ -132,11 +132,11 @@ function openList(t) {
|
||||
margin: 4px 0 6px;
|
||||
font-size: 32px;
|
||||
letter-spacing: -0.04em;
|
||||
color: rgba(255, 255, 255, 0.96);
|
||||
color: var(--theme-text-strong);
|
||||
}
|
||||
.dashboardHero__desc {
|
||||
margin: 0;
|
||||
color: rgba(255, 255, 255, 0.58);
|
||||
color: var(--theme-text-muted);
|
||||
max-width: 720px;
|
||||
}
|
||||
.panel {
|
||||
@@ -155,18 +155,18 @@ function openList(t) {
|
||||
.boardCard {
|
||||
min-width: 0;
|
||||
border-radius: 22px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.16);
|
||||
background: rgba(62, 62, 62, 0.82);
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
border: 1px solid var(--theme-card-border);
|
||||
background: var(--theme-card-bg);
|
||||
color: var(--theme-text);
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
||||
box-shadow: inset 0 1px 0 var(--theme-card-shadow);
|
||||
transition:
|
||||
transform 0.16s ease,
|
||||
background 0.16s ease;
|
||||
}
|
||||
.boardCard:hover {
|
||||
transform: translateY(-2px);
|
||||
background: rgba(70, 70, 70, 0.96);
|
||||
background: var(--theme-card-bg-hover);
|
||||
}
|
||||
.boardCard__body {
|
||||
min-width: 0;
|
||||
@@ -197,10 +197,10 @@ function openList(t) {
|
||||
.boardCard__thumbPlaceholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #555;
|
||||
background: var(--theme-thumb-fallback-bg);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
color: var(--theme-text-faint);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
border-radius: 18px;
|
||||
@@ -259,7 +259,7 @@ function openList(t) {
|
||||
border-radius: 9999px;
|
||||
object-fit: cover;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
background: var(--theme-border);
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.boardCard__avatar--fallback {
|
||||
@@ -274,7 +274,7 @@ function openList(t) {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.64);
|
||||
color: var(--theme-text-faint);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@@ -30,14 +30,19 @@ const avatarUrl = computed(() => {
|
||||
return toApiUrl(auth.user.avatarSrc)
|
||||
})
|
||||
|
||||
const authReady = computed(() => auth.hydrated)
|
||||
|
||||
const displayInitial = computed(() => {
|
||||
const email = auth.user?.email || 'U'
|
||||
return email[0].toUpperCase()
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await auth.refresh()
|
||||
if (!auth.user) router.push('/login')
|
||||
if (!auth.hydrated) await auth.refresh()
|
||||
if (!auth.user) {
|
||||
router.replace('/login')
|
||||
return
|
||||
}
|
||||
nickname.value = auth.user?.nickname || ''
|
||||
removeAvatar.value = false
|
||||
})
|
||||
@@ -121,7 +126,11 @@ async function logout() {
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section v-if="auth.user" class="settingsScreen">
|
||||
<section v-if="!authReady" class="settingsScreen settingsScreen--loading">
|
||||
<div class="settingsLoading">계정 정보를 불러오고 있어요.</div>
|
||||
</section>
|
||||
|
||||
<section v-else-if="auth.user" class="settingsScreen">
|
||||
<div class="settingsIdentity">
|
||||
<div class="avatarButtonWrap">
|
||||
<button class="avatarButton" type="button" @click="openAvatarPicker">
|
||||
@@ -185,6 +194,16 @@ async function logout() {
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.settingsScreen--loading {
|
||||
min-height: 240px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.settingsLoading {
|
||||
color: var(--theme-text-muted);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.settingsIdentity {
|
||||
display: grid;
|
||||
grid-template-columns: 120px minmax(0, 1fr);
|
||||
@@ -202,15 +221,15 @@ async function logout() {
|
||||
position: relative;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid var(--theme-border-strong);
|
||||
border-radius: 9999px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
background: var(--theme-pill-bg);
|
||||
color: var(--theme-text);
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
||||
box-shadow: var(--theme-card-shadow);
|
||||
}
|
||||
|
||||
.avatarButton__image {
|
||||
@@ -222,7 +241,7 @@ async function logout() {
|
||||
.avatarButton__fallback {
|
||||
font-size: 34px;
|
||||
font-weight: 900;
|
||||
color: rgba(255, 255, 255, 0.86);
|
||||
color: var(--theme-text);
|
||||
}
|
||||
|
||||
.avatarButton__overlay {
|
||||
@@ -232,7 +251,7 @@ async function logout() {
|
||||
background: linear-gradient(180deg, transparent, rgba(0, 0, 0, 0.72));
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: rgba(255, 255, 255, 0.82);
|
||||
color: var(--theme-text);
|
||||
}
|
||||
|
||||
.avatarButton__remove {
|
||||
@@ -243,8 +262,8 @@ async function logout() {
|
||||
height: 30px;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
background: rgba(10, 10, 10, 0.72);
|
||||
color: rgba(255, 255, 255, 0.88);
|
||||
background: var(--theme-shell-bg);
|
||||
color: var(--theme-text);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
cursor: pointer;
|
||||
@@ -264,7 +283,7 @@ async function logout() {
|
||||
|
||||
.avatarButton__remove:hover {
|
||||
background: rgba(190, 24, 24, 0.88);
|
||||
color: #fff;
|
||||
color: var(--theme-accent-text);
|
||||
}
|
||||
|
||||
.identityMeta {
|
||||
@@ -276,7 +295,7 @@ async function logout() {
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255, 255, 255, 0.36);
|
||||
color: var(--theme-text-soft);
|
||||
}
|
||||
|
||||
.identityMeta__title {
|
||||
@@ -286,7 +305,7 @@ async function logout() {
|
||||
}
|
||||
|
||||
.identityMeta__desc {
|
||||
color: rgba(255, 255, 255, 0.58);
|
||||
color: var(--theme-text-muted);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
@@ -307,16 +326,16 @@ async function logout() {
|
||||
|
||||
.field__label {
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.62);
|
||||
color: var(--theme-text-muted);
|
||||
}
|
||||
|
||||
.field__input {
|
||||
width: 100%;
|
||||
padding: 14px 0;
|
||||
border: 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-bottom: 1px solid var(--theme-border-strong);
|
||||
background: transparent;
|
||||
color: rgba(255, 255, 255, 0.94);
|
||||
color: var(--theme-text);
|
||||
outline: none;
|
||||
font-size: 18px;
|
||||
letter-spacing: -0.02em;
|
||||
@@ -327,12 +346,12 @@ async function logout() {
|
||||
}
|
||||
|
||||
.field__input--readonly {
|
||||
color: rgba(255, 255, 255, 0.58);
|
||||
color: var(--theme-text-muted);
|
||||
}
|
||||
|
||||
.field__hint {
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.42);
|
||||
color: var(--theme-text-soft);
|
||||
}
|
||||
|
||||
.roleBadge {
|
||||
@@ -341,7 +360,7 @@ async function logout() {
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(96, 165, 250, 0.28);
|
||||
background: rgba(96, 165, 250, 0.1);
|
||||
color: rgba(191, 219, 254, 0.92);
|
||||
color: var(--theme-text);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
@@ -363,14 +382,14 @@ async function logout() {
|
||||
|
||||
.primaryAction {
|
||||
border: 1px solid rgba(76, 133, 245, 0.96);
|
||||
background: rgba(76, 133, 245, 0.92);
|
||||
color: #fff;
|
||||
background: var(--theme-accent-bg);
|
||||
color: var(--theme-accent-text);
|
||||
}
|
||||
|
||||
.secondaryAction {
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
color: rgba(255, 255, 255, 0.86);
|
||||
border: 1px solid var(--theme-border-strong);
|
||||
background: var(--theme-surface-soft);
|
||||
color: var(--theme-text);
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
|
||||
@@ -122,24 +122,24 @@ watch(
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255, 255, 255, 0.42);
|
||||
color: var(--theme-text-soft);
|
||||
}
|
||||
.title {
|
||||
margin: 4px 0 0;
|
||||
font-size: 32px;
|
||||
color: rgba(255, 255, 255, 0.96);
|
||||
color: var(--theme-text-strong);
|
||||
letter-spacing: -0.04em;
|
||||
}
|
||||
.desc {
|
||||
margin-top: 6px;
|
||||
color: rgba(255, 255, 255, 0.58);
|
||||
color: var(--theme-text-muted);
|
||||
}
|
||||
.error {
|
||||
margin: 0 0 8px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(239, 68, 68, 0.18);
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
border: 1px solid var(--theme-danger-border);
|
||||
background: var(--theme-danger-bg);
|
||||
}
|
||||
.empty {
|
||||
opacity: 0.76;
|
||||
@@ -151,16 +151,16 @@ watch(
|
||||
}
|
||||
.boardCard {
|
||||
border-radius: 22px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.16);
|
||||
background: rgba(62, 62, 62, 0.82);
|
||||
border: 1px solid var(--theme-card-border);
|
||||
background: var(--theme-card-bg);
|
||||
overflow: hidden;
|
||||
display: grid;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
||||
box-shadow: inset 0 1px 0 var(--theme-card-shadow);
|
||||
transition: transform 0.16s ease, background 0.16s ease;
|
||||
}
|
||||
.boardCard:hover {
|
||||
transform: translateY(-2px);
|
||||
background: rgba(70, 70, 70, 0.96);
|
||||
background: var(--theme-card-bg-hover);
|
||||
}
|
||||
.boardCard__body {
|
||||
border: 0;
|
||||
@@ -188,10 +188,10 @@ watch(
|
||||
object-fit: cover;
|
||||
}
|
||||
.boardCard__thumbPlaceholder {
|
||||
background: #555;
|
||||
background: var(--theme-thumb-fallback-bg);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
color: var(--theme-text-faint);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
@@ -238,7 +238,7 @@ watch(
|
||||
height: 22px;
|
||||
border-radius: 9999px;
|
||||
object-fit: cover;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
background: var(--theme-border);
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.boardCard__avatar--fallback {
|
||||
@@ -251,7 +251,7 @@ watch(
|
||||
.favoriteStat {
|
||||
flex: 0 0 auto;
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.64);
|
||||
color: var(--theme-text-faint);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user