초기 로딩 스켈레톤 정리

This commit is contained in:
2026-04-07 16:29:54 +09:00
parent 76de4b940a
commit 4fbd4a2845
7 changed files with 356 additions and 5 deletions

View File

@@ -51,6 +51,7 @@ const backendState = ref('online')
const backendMessage = ref('')
const isFullscreenActive = ref(false)
const unreadCommentCount = ref(0)
const isBootReady = ref(false)
provide('rightRailOpen', rightRailOpen)
provide('localRightRailTarget', '#local-right-rail-root')
@@ -378,7 +379,11 @@ onMounted(async () => {
if (savedTheme === 'light' || savedTheme === 'dark') applyTheme(savedTheme)
else applyTheme('dark')
}
await auth.refresh()
await Promise.all([
auth.refresh(),
new Promise((resolve) => setTimeout(resolve, 140)),
])
isBootReady.value = true
if (typeof window !== 'undefined') {
syncViewportWidth()
syncFullscreenState()
@@ -691,6 +696,24 @@ function reloadApp() {
</section>
</main>
</template>
<template v-else-if="!isBootReady">
<main class="bootGate">
<section class="bootGate__shell">
<div class="bootGate__brand">
<div class="bootGate__logo"></div>
<div class="bootGate__copy">
<div class="bootGate__title">Tier Maker</div>
<div class="bootGate__desc">계정 상태와 화면 구성을 준비하고 있어요.</div>
</div>
</div>
<div class="bootGate__panels">
<div class="bootGate__panel bootGate__panel--nav"></div>
<div class="bootGate__panel bootGate__panel--main"></div>
<div class="bootGate__panel bootGate__panel--side"></div>
</div>
</section>
</main>
</template>
<template v-else>
<button v-if="isMobileLayout && mobileLeftNavOpen" class="leftRailBackdrop" type="button" aria-label="왼쪽 패널 닫기" @click="toggleLeftRail"></button>
@@ -1004,6 +1027,95 @@ function reloadApp() {
cursor: pointer;
}
.bootGate {
min-width: 100dvw;
min-height: 100dvh;
display: grid;
place-items: center;
padding: 24px;
background: var(--theme-shell-bg);
}
.bootGate__shell {
width: min(100%, 1180px);
display: grid;
gap: 20px;
}
.bootGate__brand {
display: flex;
align-items: center;
gap: 16px;
padding: 18px 20px;
border-radius: 24px;
border: 1px solid var(--theme-border);
background: var(--theme-rail-bg);
}
.bootGate__logo,
.bootGate__panel {
position: relative;
overflow: hidden;
}
.bootGate__logo::after,
.bootGate__panel::after {
content: '';
position: absolute;
inset: 0;
transform: translateX(-100%);
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.08), transparent);
animation: bootGateShimmer 1.4s ease-in-out infinite;
}
.bootGate__logo {
width: 52px;
height: 52px;
border-radius: 18px;
border: 1px solid var(--theme-border);
background: var(--theme-surface-soft);
}
.bootGate__copy {
display: grid;
gap: 6px;
}
.bootGate__title {
font-size: 20px;
font-weight: 800;
letter-spacing: -0.03em;
color: var(--theme-text-strong);
}
.bootGate__desc {
color: var(--theme-text-muted);
line-height: 1.6;
}
.bootGate__panels {
display: grid;
grid-template-columns: 248px minmax(0, 1fr) 320px;
gap: 20px;
}
.bootGate__panel {
border-radius: 24px;
border: 1px solid var(--theme-border);
background: var(--theme-rail-bg);
min-height: 72dvh;
}
.bootGate__panel--main {
background: var(--theme-shell-bg);
}
@keyframes bootGateShimmer {
100% {
transform: translateX(100%);
}
}
.leftRail,
.rightRail {
min-height: 100dvh;
@@ -2179,6 +2291,32 @@ function reloadApp() {
}
@media (max-width: 860px) {
.bootGate {
padding: 16px;
}
.bootGate__shell {
gap: 16px;
}
.bootGate__brand {
padding: 16px;
border-radius: 20px;
}
.bootGate__panels {
grid-template-columns: 1fr;
}
.bootGate__panel--nav,
.bootGate__panel--side {
display: none;
}
.bootGate__panel--main {
min-height: 72dvh;
}
.guideModal {
padding: 20px 12px;
}