v-app.zen-app.overflow-hidden
v-navigation-drawer(
v-if="store.token || viewState === 'zooming'"
v-model="drawer"
temporary
location="right"
color="#1e1e24"
class="border-none"
width="280"
)
.d-flex.flex-column.h-100.pa-4
.d-flex.align-center.mb-6.px-2
img.mr-3(:src="logo" height="32" width="32" alt="Logo")
span.text-h6.font-weight-bold {{ $t('nav.menu') }}
v-list.pa-0(nav bg-color="transparent")
v-list-item.mb-2(
to="/"
rounded="lg"
:active="$route.path === '/'"
)
template(v-slot:prepend)
v-icon(color="#00cec9" icon="mdi-view-dashboard")
v-list-item-title.font-weight-bold {{ $t('nav.dashboard') }}
v-list-item.mb-2(
to="/collection"
rounded="lg"
:active="$route.path === '/collection'"
)
template(v-slot:prepend)
v-icon(color="#00cec9" icon="mdi-bookshelf")
v-list-item-title.font-weight-bold {{ $t('nav.collection') }}
v-divider.my-4.border-subtle
.d-flex.flex-column.gap-2
v-btn.justify-start.px-4(
variant="text"
block
color="grey-lighten-1"
@click="showSettings = true; drawer = false"
)
v-icon(start icon="mdi-cog")
| {{ $t('nav.settings') }}
v-btn.justify-start.px-4(
variant="text"
block
color="grey-lighten-1"
:loading="syncing"
@click="manualSync"
)
v-icon(start icon="mdi-sync")
| {{ $t('nav.sync') }}
v-btn.justify-start.px-4.text-red-lighten-2(
variant="text"
block
@click="handleLogout"
)
v-icon(start icon="mdi-logout")
| {{ $t('nav.logout') }}
.parallax-bg(
:style="parallaxStyle"
:class="{ 'zooming': viewState === 'zooming' }"
)
.bg-gradient
.bg-grid
.kanji-layer
span.bg-kanji(
v-for="(k, i) in backgroundKanjis"
:key="i"
:style="k.style"
) {{ k.char }}
.orb.orb-1(:class="{ 'zooming': viewState === 'zooming' }")
.orb.orb-2(:class="{ 'zooming': viewState === 'zooming' }")
.orb.orb-3(:class="{ 'zooming': viewState === 'zooming' }")
.app-content-wrapper(
v-if="store.token || viewState === 'zooming'"
:class="{ 'app-entering': viewState === 'zooming', 'app-visible': viewState === 'app' }"
)
v-app-bar.px-2.app-bar-blur.safe-area-header(
flat
color="rgba(30, 30, 36, 0.8)"
border="b"
)
v-app-bar-title.font-weight-bold.text-h6(style="min-width: fit-content;")
.d-flex.align-center.cursor-pointer.logo-hover(@click="$router.push('/')")
img(:src="logo" height="32" width="32" alt="Zen Kanji Logo")
span.ml-3.tracking-tight Zen Kanji
v-spacer
template(v-if="store.token")
.d-none.d-md-flex.align-center
v-btn.mx-1(
to="/"
variant="text"
:color="$route.path === '/' ? '#00cec9' : 'grey'"
) {{ $t('nav.dashboard') }}
v-btn.mx-1(
to="/collection"
variant="text"
:color="$route.path === '/collection' ? '#00cec9' : 'grey'"
) {{ $t('nav.collection') }}
v-divider.mx-2.my-auto(vertical length="20" color="grey-darken-2")
v-tooltip(:text="$t('nav.settings')" location="bottom")
template(v-slot:activator="{ props }")
v-btn(
v-bind="props"
icon="mdi-cog"
variant="text"
color="grey-lighten-1"
@click="showSettings = true"
)
v-tooltip(:text="$t('nav.sync')" location="bottom")
template(v-slot:activator="{ props }")
v-btn(
v-bind="props"
icon="mdi-sync"
variant="text"
:loading="syncing"
color="grey-lighten-1"
@click="manualSync"
)
v-tooltip(:text="$t('nav.logout')" location="bottom")
template(v-slot:activator="{ props }")
v-btn(
v-bind="props"
icon="mdi-logout"
variant="text"
color="grey-darken-1"
@click="handleLogout"
)
.d-flex.d-md-none
v-btn(
icon="mdi-menu"
variant="text"
color="grey-lighten-1"
@click="drawer = !drawer"
)
v-main.fill-height
router-view(v-slot="{ Component }")
transition(name="page" mode="out-in")
component(:is="Component")
.login-overlay(
v-if="!store.token || viewState === 'zooming' || viewState === 'authenticating'"
:class="{ 'zooming': viewState === 'zooming' }"
)
v-card.login-card.pa-6.rounded-lg.elevation-10.text-center.border-subtle(
color="#1e1e24"
width="100%"
max-width="400"
)
.login-content
img.mb-4(:src="logo" height="64" width="64")
h1.text-h5.font-weight-bold.mb-2 {{ $t('hero.welcome') }}
p.text-grey-lighten-1.text-body-2.mb-6 {{ $t('login.instruction') }}
v-text-field.mb-2(
v-model="inputKey"
:placeholder="$t('login.placeholder')"
variant="solo-filled"
bg-color="#2f3542"
color="white"
hide-details
density="comfortable"
@keyup.enter="triggerLogin"
:disabled="viewState === 'authenticating' || viewState === 'zooming'"
)
v-alert.mb-4.text-left.text-caption(
v-if="errorMsg"
type="error"
density="compact"
variant="tonal"
) {{ errorMsg }}
v-btn.text-black.font-weight-bold.mt-4.login-btn(
block
color="#00cec9"
height="44"
:loading="viewState === 'authenticating'"
@click="triggerLogin"
) {{ $t('login.button') }}
v-dialog(v-model="showSettings" max-width="340")
v-card.rounded-xl.pa-4.border-subtle(color="#1e1e24")
v-card-title.text-center.font-weight-bold {{ $t('settings.title') }}
v-card-text.pt-4
.text-caption.text-grey.mb-2 {{ $t('settings.batchSize') }}
v-slider(
v-model="tempBatchSize"
:min="5"
:max="100"
:step="5"
thumb-label
color="#00cec9"
track-color="grey-darken-3"
)
.text-center.text-h5.font-weight-bold.text-teal-accent-3.mb-6
| {{ tempBatchSize }} {{ $t('settings.items') }}
.text-caption.text-grey.mb-2 {{ $t('settings.drawingTolerance') }}
v-slider(
v-model="tempDrawingAccuracy"
:min="1"
:max="20"
:step="1"
thumb-label
color="#00cec9"
track-color="grey-darken-3"
)
.d-flex.justify-space-between.text-caption.text-grey-lighten-1.mb-6.px-1
span {{ $t('settings.strict') }} (5)
span.font-weight-bold.text-body-1(color="#00cec9") {{ tempDrawingAccuracy }}
span {{ $t('settings.loose') }} (20)
.text-caption.text-grey.mb-2 {{ $t('settings.language') }}
.lang-switch-wrapper.border-subtle.mb-2
.lang-glider(:style="{ transform: `translateX(${localeIndex * 100}%)` }")
button.lang-btn(
v-for="l in availableLocales"
:key="l"
:class="{ 'active': tempLocale === l }"
@click="tempLocale = l"
) {{ l.toUpperCase() }}
v-card-actions.mt-2
v-btn.text-white(
block
color="#2f3542"
@click="saveSettings"
) {{ $t('settings.save') }}
v-dialog(v-model="showLogoutDialog" max-width="320")
v-card.rounded-xl.pa-4.border-subtle(color="#1e1e24")
.d-flex.flex-column.align-center.text-center.pt-2
v-avatar.mb-3(color="red-darken-4" size="48")
v-icon(icon="mdi-logout" size="24" color="red-lighten-1")
h3.text-h6.font-weight-bold.mb-2 {{ $t('nav.logout') }}?
p.text-body-2.text-grey-lighten-1.px-2.mb-4
| {{ $t('alerts.logoutConfirm') }}
v-card-actions.d-flex.gap-2.pa-0.mt-2
v-btn.flex-grow-1.text-capitalize(
variant="tonal"
color="grey"
height="40"
@click="showLogoutDialog = false"
) {{ $t('common.cancel') }}
v-btn.flex-grow-1.text-capitalize(
color="red-accent-2"
variant="flat"
height="40"
@click="confirmLogout"
) {{ $t('nav.logout') }}
v-snackbar(v-model="snackbar.show" :color="snackbar.color" timeout="3000")
| {{ snackbar.text }}
template(v-slot:actions)
v-btn(variant="text" @click="snackbar.show = false") {{ $t('common.close') }}