Revamp app layout, theming and navigation

Refactor Layout.vue to implement a redesigned, responsive shell: new branding (SVG icon, title & subtitle), computed sidebar/footer routes, route-aware page title & description, improved footer visibility, and a mobile-friendly navigation drawer. Theme handling now uses a data-theme attribute, persistent storage, accessible labels and toggle controls. Added new image assets (icon.svg, icon.png, 404NotFound.png), global.css and documentation files (style.md, codexInfo.md), updated related plugins/routes/components, and removed the old logo.png. Also updated the favicon.
This commit is contained in:
Jonas
2026-04-17 22:55:58 +02:00
parent b9101a4582
commit 3b910850cb
15 changed files with 2113 additions and 102 deletions
+344 -50
View File
@@ -1,19 +1,97 @@
<script setup lang="ts">
import { onMounted, ref, watch } from 'vue'
import { useTheme } from 'vuetify'
import { computed, onMounted, ref, watch } from 'vue'
import { useDisplay, useTheme } from 'vuetify'
import { useRoute, useRouter } from 'vue-router'
import iconImage from '@/assets/images/icon.svg'
import { Visibility, routes } from '@/plugins/routesLayout'
const display = useDisplay()
const theme = useTheme()
const route = useRoute()
const router = useRouter()
const showDrawer = ref(true)
const currentYear = new Date().getFullYear()
const themeStorageKey = 'theme'
function changeTheme() {
const nextTheme = theme.global.name.value === 'dark' ? 'light' : 'dark'
function normalizeRoutePath(path: string) {
if (!path || path === '/') {
return '/'
}
return path.endsWith('/') ? path.slice(0, -1) : path
}
function isWithinRoutePath(currentPath: string, targetPath: string) {
const normalizedCurrentPath = normalizeRoutePath(currentPath)
const normalizedTargetPath = normalizeRoutePath(targetPath)
if (normalizedTargetPath === '/') {
return normalizedCurrentPath === '/'
}
return (
normalizedCurrentPath === normalizedTargetPath ||
normalizedCurrentPath.startsWith(`${normalizedTargetPath}/`)
)
}
function resolveVisibilityRoutes(path: string, visibilityRoute?: string | string[]) {
if (Array.isArray(visibilityRoute)) {
return visibilityRoute.map((entry) => entry.trim()).filter((entry) => entry.length > 0)
}
const normalizedVisibilityRoute = visibilityRoute?.trim()
if (normalizedVisibilityRoute && normalizedVisibilityRoute.length > 0) {
return [normalizedVisibilityRoute]
}
return [path]
}
const sidebarRoutes = computed(() =>
routes.filter((item) => {
if (item.visible === Visibility.Public) {
return true
}
if (item.visible !== Visibility.Route) {
return false
}
return resolveVisibilityRoutes(item.path, item.visibilityRoute).some((targetPath) =>
isWithinRoutePath(route.path, targetPath),
)
}),
)
const footerRoutes = computed(() => routes.filter((x) => x.visible === Visibility.Footer))
const activeRoute = computed(() => {
const byName = routes.find((x) => x.meta?.name === route.name)
if (byName) {
return byName
}
return routes.find((x) => x.path === route.path)
})
const pageName = computed(() => activeRoute.value?.name ?? 'Hoard')
const pageDescription = computed(
() => activeRoute.value?.description ?? 'Self-hosted Dateiablage im Browser',
)
const shouldShowFooter = computed(() => activeRoute.value?.disableFooter !== true)
const isDarkTheme = computed(() => theme.global.name.value === 'dark')
const themeIcon = computed(() => (isDarkTheme.value ? 'mdi-white-balance-sunny' : 'mdi-weather-night'))
const themeLabel = computed(() =>
isDarkTheme.value ? 'Hellen Modus aktivieren' : 'Dunklen Modus aktivieren',
)
function applyTheme(nextTheme: 'light' | 'dark', persist = true) {
theme.global.name.value = nextTheme
localStorage.setItem('theme', nextTheme)
document.documentElement.setAttribute('data-theme', nextTheme)
if (persist) {
localStorage.setItem(themeStorageKey, nextTheme)
}
}
function toggleDrawer() {
@@ -21,10 +99,13 @@ function toggleDrawer() {
localStorage.setItem('drawer', showDrawer.value ? 'Y' : 'N')
}
function changeWebsiteTitle(path: string) {
const currentPageInfo = routes.find((x) => x.path === path)
if (currentPageInfo) {
document.title = `Hoard | ${currentPageInfo.name}`
function toggleTheme() {
applyTheme(isDarkTheme.value ? 'light' : 'dark')
}
function changeWebsiteTitle() {
if (activeRoute.value) {
document.title = `Hoard | ${activeRoute.value.name}`
return
}
@@ -32,96 +113,309 @@ function changeWebsiteTitle(path: string) {
}
onMounted(() => {
const storedTheme = localStorage.getItem('theme')
const fallbackTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
theme.global.name.value = storedTheme === 'dark' || storedTheme === 'light' ? storedTheme : fallbackTheme
const storedTheme = localStorage.getItem(themeStorageKey)
const validTheme = storedTheme === 'dark' || storedTheme === 'light' ? storedTheme : 'light'
applyTheme(validTheme, false)
const storedDrawer = localStorage.getItem('drawer')
showDrawer.value = storedDrawer ? storedDrawer.startsWith('Y') : true
showDrawer.value = storedDrawer ? storedDrawer.startsWith('Y') : !display.mobile.value
})
watch(
() => route.path,
(newPath) => {
changeWebsiteTitle(newPath)
() => route.fullPath,
() => {
changeWebsiteTitle()
if (display.mobile.value) {
showDrawer.value = false
}
},
{ immediate: true },
)
</script>
<template>
<v-app>
<v-app-bar elevation="1">
<v-app class="hoard-shell">
<v-app-bar class="hoard-app-bar" elevation="0" height="64">
<template #prepend>
<v-app-bar-nav-icon
v-tooltip="!showDrawer ? 'Menue oeffnen' : 'Menue schliessen'"
v-tooltip="!showDrawer ? 'Menü öffnen' : 'Menü schließen'"
@click="toggleDrawer()"
/>
</template>
<v-app-bar-title class="title" @click="router.push({ name: 'Home' })">
<span class="pointer">Hoard</span>
</v-app-bar-title>
<button type="button" class="brand-button" @click="router.push({ name: 'Home' })">
<span class="brand-mark">
<img :src="iconImage" alt="Hoard Icon" class="brand-logo" />
</span>
<span class="brand-text">
<span class="brand-title">Hoard</span>
<span class="brand-subtitle">Dateien zuerst</span>
</span>
</button>
<v-tooltip v-if="!$vuetify.display.mobile">
<v-divider vertical class="mx-4 d-none d-md-flex" />
<div class="page-context d-none d-md-flex">
<p class="page-name">{{ pageName }}</p>
<p class="page-description">{{ pageDescription }}</p>
</div>
<v-spacer />
<v-tooltip location="bottom">
<template #activator="{ props }">
<v-btn icon v-bind="props" to="/login">
<v-icon>mdi-account</v-icon>
<v-btn
icon
:aria-label="themeLabel"
v-bind="props"
@click="toggleTheme"
>
<v-icon>{{ themeIcon }}</v-icon>
</v-btn>
</template>
Account
{{ themeLabel }}
</v-tooltip>
<v-tooltip>
<v-tooltip v-if="!display.smAndDown.value" location="bottom">
<template #activator="{ props }">
<v-btn icon @click="changeTheme()" v-bind="props">
<v-icon>mdi-brightness-6</v-icon>
<v-btn variant="outlined" prepend-icon="mdi-account-circle-outline" to="/login" v-bind="props">
Konto
</v-btn>
</template>
{{ theme.global.name.value === 'dark' ? 'Hellen Modus aktivieren' : 'Dunklen Modus aktivieren' }}
Zum Login
</v-tooltip>
<v-tooltip v-else location="bottom">
<template #activator="{ props }">
<v-btn icon to="/login" v-bind="props" aria-label="Zum Login">
<v-icon>mdi-account-circle-outline</v-icon>
</v-btn>
</template>
Zum Login
</v-tooltip>
</v-app-bar>
<v-navigation-drawer
v-model="showDrawer"
:location="$vuetify.display.mobile ? 'bottom' : undefined"
:permanent="!$vuetify.display.mobile"
class="hoard-drawer"
:location="display.mobile.value ? 'bottom' : 'left'"
:temporary="display.mobile.value"
:permanent="!display.mobile.value"
:width="268"
:elevation="display.mobile.value ? 6 : 0"
>
<v-list>
<div class="drawer-top">
<p class="drawer-kicker">Navigation</p>
<h2 class="drawer-title">Dateiverwaltung</h2>
<p class="drawer-text">Ordner, Dateien und Ansichten schnell erreichen.</p>
</div>
<v-list nav density="comfortable" class="px-1">
<v-list-item
v-for="item in routes.filter((x) => x.visible === Visibility.Public)"
v-for="item in sidebarRoutes"
:key="item.path"
:to="item.path"
:active="route.path === item.path"
link
:prepend-icon="item.icon"
:title="item.name"
class="rounded-lg mr-1 ml-1"
class="hoard-nav-item"
rounded="lg"
link
/>
</v-list>
<template #append>
<div class="drawer-bottom">
<v-btn variant="text" prepend-icon="mdi-home" to="/">Zur Startseite</v-btn>
</div>
</template>
</v-navigation-drawer>
<v-main>
<router-view />
<v-footer class="d-flex align-center justify-center ga-2 flex-wrap flex-grow-1 py-3">
<v-btn
v-for="link in routes.filter((x) => x.visible === Visibility.Footer)"
:key="link.path"
:to="link.path"
:text="link.name"
variant="text"
rounded
/>
<div class="flex-1-0-100 text-center mt-2">
{{ new Date().getFullYear() }} - <strong>Hoard</strong>
<v-main class="hoard-main">
<div class="main-shell">
<router-view />
</div>
<v-footer
v-if="shouldShowFooter"
class="hoard-footer d-flex align-center justify-space-between ga-2 flex-wrap py-3"
>
<div class="footer-links">
<v-btn
v-for="link in footerRoutes"
:key="link.path"
:to="link.path"
:text="link.name"
variant="text"
rounded
/>
</div>
<p class="footer-copy">{{ currentYear }} - <strong>Hoard</strong></p>
</v-footer>
</v-main>
</v-app>
</template>
<style scoped>
.pointer {
.hoard-shell {
min-height: 100vh;
}
.hoard-app-bar {
padding-inline: var(--space-2);
}
.brand-button {
display: inline-flex;
align-items: center;
gap: var(--space-3);
padding: 0;
border: none;
color: inherit;
background: transparent;
cursor: pointer;
}
.brand-mark {
display: inline-flex;
align-items: center;
justify-content: center;
}
.brand-logo {
width: 46px;
height: 46px;
object-fit: contain;
}
.brand-text {
display: flex;
flex-direction: column;
align-items: flex-start;
line-height: 1.1;
}
.brand-title {
color: var(--color-text);
font-size: 16px;
font-weight: 700;
}
.brand-subtitle {
color: var(--color-text-muted);
font-size: var(--font-size-xs);
font-weight: 500;
}
.page-context {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
}
.page-name,
.page-description,
.drawer-kicker,
.drawer-text,
.footer-copy {
margin: 0;
}
.page-name {
color: var(--color-text);
font-size: var(--font-size-md);
font-weight: 600;
}
.page-description {
max-width: min(360px, 32vw);
color: var(--color-text-muted);
font-size: var(--font-size-sm);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.hoard-drawer {
padding-top: var(--space-2);
}
.drawer-top {
padding: var(--space-2) var(--space-4) var(--space-4);
border-bottom: 1px solid color-mix(in srgb, var(--color-border) 85%, white 15%);
}
.drawer-kicker {
color: var(--color-primary-700);
font-size: var(--font-size-xs);
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.drawer-title {
margin: var(--space-2) 0 var(--space-1);
color: var(--color-text);
font-size: var(--font-size-lg);
}
.drawer-text {
color: var(--color-text-secondary);
font-size: var(--font-size-sm);
}
.drawer-bottom {
padding: var(--space-3) var(--space-2) var(--space-4);
border-top: 1px solid color-mix(in srgb, var(--color-border) 90%, white 10%);
}
:deep(.hoard-nav-item) {
min-height: 44px;
}
:deep(.hoard-nav-item .v-list-item-title) {
font-weight: 500;
}
.hoard-main {
display: flex;
flex-direction: column;
min-height: calc(100vh - 64px);
}
.main-shell {
flex: 1;
padding: var(--space-6);
}
.hoard-footer {
padding-inline: var(--space-6);
}
.footer-links {
display: flex;
flex-wrap: wrap;
gap: var(--space-1);
}
.footer-copy {
color: var(--color-text-secondary);
font-size: var(--font-size-sm);
}
@media (width <= 960px) {
.main-shell {
padding: var(--space-4);
}
.brand-subtitle {
display: none;
}
.hoard-footer {
justify-content: center !important;
padding-inline: var(--space-4);
}
}
</style>
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

+74
View File
@@ -0,0 +1,74 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" width="2048" height="2048">
<defs>
<linearGradient id="borderGrad" x1="560" y1="520" x2="1500" y2="1455" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#061117"/>
<stop offset="0.24" stop-color="#0a2530"/>
<stop offset="0.48" stop-color="#0a3946"/>
<stop offset="0.74" stop-color="#081f28"/>
<stop offset="1" stop-color="#050e13"/>
</linearGradient>
<radialGradient id="borderGlow" cx="1210" cy="930" r="720" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#1ea97e" stop-opacity="0.06"/>
<stop offset="0.45" stop-color="#0d4e4d" stop-opacity="0.05"/>
<stop offset="1" stop-color="#000000" stop-opacity="0"/>
</radialGradient>
<linearGradient id="topGrad" x1="590" y1="720" x2="1355" y2="540" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#9bdb61"/>
<stop offset="0.56" stop-color="#b9ee76"/>
<stop offset="1" stop-color="#a6df67"/>
</linearGradient>
<linearGradient id="leftGrad" x1="588" y1="670" x2="726" y2="1305" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#0a8b4d"/>
<stop offset="0.52" stop-color="#067841"/>
<stop offset="1" stop-color="#005a33"/>
</linearGradient>
<linearGradient id="frontGrad" x1="1120" y1="745" x2="1030" y2="1385" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#2dbf47"/>
<stop offset="0.42" stop-color="#24a847"/>
<stop offset="1" stop-color="#006b3b"/>
</linearGradient>
<radialGradient id="frontHighlight" cx="1110" cy="850" r="500" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#d6ff9c" stop-opacity="0.035"/>
<stop offset="0.6" stop-color="#7ad26f" stop-opacity="0.01"/>
<stop offset="1" stop-color="#000000" stop-opacity="0"/>
</radialGradient>
<linearGradient id="stripeGrad" x1="740" y1="560" x2="1335" y2="695" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#0b7d42"/>
<stop offset="1" stop-color="#165f37"/>
</linearGradient>
<linearGradient id="panelShadowGrad" x1="950" y1="1110" x2="1310" y2="1295" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#0a7a41" stop-opacity="0.45"/>
<stop offset="0.45" stop-color="#086a39" stop-opacity="0.58"/>
<stop offset="1" stop-color="#044f2c" stop-opacity="0.70"/>
</linearGradient>
<clipPath id="borderRingClip" clipPathUnits="userSpaceOnUse">
<path d="M 888.0 1477.5 L 867.0 1477.5 L 865.0 1475.5 L 854.0 1473.5 L 812.0 1451.5 L 811.0 1449.5 L 798.0 1443.5 L 720.0 1397.5 L 644.0 1347.5 L 576.0 1299.5 L 563.0 1289.5 L 545.5 1269.0 L 541.5 1257.0 L 540.5 1245.0 L 541.5 669.0 L 545.5 658.0 L 559.0 641.5 L 572.0 633.5 L 589.0 629.5 L 591.0 627.5 L 604.0 625.5 L 606.0 623.5 L 619.0 621.5 L 646.0 613.5 L 652.0 613.5 L 654.0 611.5 L 676.0 607.5 L 678.0 605.5 L 684.0 605.5 L 694.0 601.5 L 700.0 601.5 L 751.0 587.5 L 757.0 587.5 L 759.0 585.5 L 765.0 585.5 L 767.0 583.5 L 773.0 583.5 L 783.0 579.5 L 789.0 579.5 L 791.0 577.5 L 805.0 575.5 L 807.0 573.5 L 813.0 573.5 L 880.0 555.5 L 886.0 555.5 L 888.0 553.5 L 894.0 553.5 L 962.0 535.5 L 968.0 535.5 L 970.0 533.5 L 984.0 531.5 L 987.0 529.5 L 1000.0 527.5 L 1002.0 525.5 L 1008.0 525.5 L 1010.0 523.5 L 1032.0 519.5 L 1034.0 517.5 L 1040.0 517.5 L 1042.0 515.5 L 1064.0 511.5 L 1066.0 509.5 L 1072.0 509.5 L 1090.0 503.5 L 1096.0 503.5 L 1098.0 501.5 L 1104.0 501.5 L 1107.0 499.5 L 1120.0 497.5 L 1122.0 495.5 L 1136.0 493.5 L 1138.0 491.5 L 1144.0 491.5 L 1147.0 489.5 L 1166.0 489.5 L 1175.0 491.5 L 1222.0 513.5 L 1227.0 517.5 L 1245.0 525.5 L 1246.0 527.5 L 1253.0 529.5 L 1273.0 541.5 L 1287.0 547.5 L 1288.0 549.5 L 1291.0 549.5 L 1292.0 551.5 L 1329.0 569.5 L 1330.0 571.5 L 1441.0 629.5 L 1482.0 655.5 L 1497.5 675.0 L 1503.5 692.0 L 1503.5 1264.0 L 1495.5 1281.0 L 1486.0 1291.5 L 1475.0 1299.5 L 1455.0 1307.5 L 1437.0 1311.5 L 1435.0 1313.5 L 1405.0 1321.5 L 1403.0 1323.5 L 1398.0 1323.5 L 1396.0 1325.5 L 1365.0 1333.5 L 1363.0 1335.5 L 1351.0 1337.5 L 1330.0 1345.5 L 1318.0 1347.5 L 1303.0 1353.5 L 1278.0 1359.5 L 1263.0 1365.5 L 1258.0 1365.5 L 1243.0 1371.5 L 1225.0 1375.5 L 1203.0 1383.5 L 1198.0 1383.5 L 1196.0 1385.5 L 1172.0 1391.5 L 1170.0 1393.5 L 1165.0 1393.5 L 1143.0 1401.5 L 1138.0 1401.5 L 1130.0 1405.5 L 1118.0 1407.5 L 1116.0 1409.5 L 1046.0 1429.5 L 1031.0 1435.5 L 1006.0 1441.5 L 991.0 1447.5 L 986.0 1447.5 L 971.0 1453.5 L 966.0 1453.5 L 964.0 1455.5 L 939.0 1461.5 L 937.0 1463.5 L 932.0 1463.5 L 930.0 1465.5 Z M 892.5 1411.0 L 923.0 1403.5 L 925.0 1401.5 L 930.0 1401.5 L 932.0 1399.5 L 937.0 1399.5 L 939.0 1397.5 L 999.0 1381.5 L 1001.0 1379.5 L 1006.0 1379.5 L 1008.0 1377.5 L 1020.0 1375.5 L 1022.0 1373.5 L 1027.0 1373.5 L 1029.0 1371.5 L 1034.0 1371.5 L 1050.0 1365.5 L 1055.0 1365.5 L 1063.0 1361.5 L 1089.0 1355.5 L 1104.0 1349.5 L 1109.0 1349.5 L 1111.0 1347.5 L 1136.0 1341.5 L 1138.0 1339.5 L 1157.0 1335.5 L 1159.0 1333.5 L 1238.0 1311.5 L 1240.0 1309.5 L 1245.0 1309.5 L 1254.0 1305.5 L 1326.0 1285.5 L 1328.0 1283.5 L 1408.0 1261.5 L 1425.0 1255.5 L 1437.5 1245.0 L 1441.5 1239.0 L 1442.5 1232.0 L 1442.5 760.0 L 1441.5 706.0 L 1433.0 693.5 L 1422.0 689.5 L 1403.0 691.5 L 1388.0 695.5 L 1368.0 697.5 L 1365.0 699.5 L 1344.0 701.5 L 1342.0 703.5 L 1308.0 707.5 L 1294.0 711.5 L 1285.0 711.5 L 1282.0 713.5 L 1262.0 715.5 L 1248.0 719.5 L 1215.0 723.5 L 1212.0 725.5 L 1170.0 731.5 L 1156.0 735.5 L 1147.0 735.5 L 1144.0 737.5 L 1135.0 737.5 L 1132.0 739.5 L 1124.0 739.5 L 1098.0 745.5 L 1055.0 751.5 L 1041.0 755.5 L 1021.0 757.5 L 1007.0 761.5 L 1000.0 761.5 L 997.0 763.5 L 977.0 765.5 L 974.0 767.5 L 953.0 769.5 L 950.0 771.5 L 942.0 771.5 L 939.0 773.5 L 931.0 773.5 L 905.0 779.5 L 896.0 779.5 L 881.0 783.5 L 849.0 787.5 L 846.0 789.5 L 805.0 795.5 L 791.0 799.5 L 771.0 801.5 L 761.0 805.5 L 751.5 814.0 L 745.5 827.0 L 745.5 1333.0 L 786.0 1359.5 L 809.0 1371.5 L 861.0 1403.5 L 879.0 1411.5 Z" clip-rule="evenodd"/>
</clipPath>
<clipPath id="panelClip" clipPathUnits="userSpaceOnUse">
<path d="M 901.0 1371.5 L 893.0 1371.5 L 877.0 1365.5 L 808.0 1329.5 L 791.5 1318.0 L 791.5 847.0 L 793.5 842.0 L 803.0 833.5 L 818.0 829.5 L 827.0 829.5 L 841.0 825.5 L 850.0 825.5 L 853.0 823.5 L 874.0 821.5 L 877.0 819.5 L 909.0 815.5 L 912.0 813.5 L 932.0 811.5 L 946.0 807.5 L 955.0 807.5 L 969.0 803.5 L 977.0 803.5 L 980.0 801.5 L 1012.0 797.5 L 1015.0 795.5 L 1047.0 791.5 L 1050.0 789.5 L 1059.0 789.5 L 1073.0 785.5 L 1082.0 785.5 L 1085.0 783.5 L 1128.0 777.5 L 1131.0 775.5 L 1140.0 775.5 L 1143.0 773.5 L 1175.0 769.5 L 1189.0 765.5 L 1198.0 765.5 L 1201.0 763.5 L 1234.0 759.5 L 1237.0 757.5 L 1279.0 751.5 L 1282.0 749.5 L 1291.0 749.5 L 1294.0 747.5 L 1302.0 747.5 L 1352.0 737.5 L 1401.0 730.5 L 1401.5 1213.0 L 1397.5 1223.0 L 1389.0 1231.5 L 1373.0 1237.5 L 1368.0 1237.5 L 1366.0 1239.5 L 1361.0 1239.5 L 1359.0 1241.5 L 1333.0 1247.5 L 1324.0 1251.5 L 1319.0 1251.5 L 1317.0 1253.5 L 1312.0 1253.5 L 1310.0 1255.5 L 1285.0 1261.5 L 1283.0 1263.5 L 1277.0 1263.5 L 1275.0 1265.5 L 1256.0 1269.5 L 1241.0 1275.5 L 1236.0 1275.5 L 1187.0 1289.5 L 1185.0 1291.5 L 1165.0 1295.5 L 1163.0 1297.5 L 1158.0 1297.5 L 1149.0 1301.5 L 1144.0 1301.5 L 1142.0 1303.5 L 1109.0 1311.5 L 1107.0 1313.5 L 1088.0 1317.5 L 1086.0 1319.5 L 1067.0 1323.5 L 1058.0 1327.5 L 1052.0 1327.5 L 1050.0 1329.5 L 1015.0 1339.5 L 1010.0 1339.5 L 1008.0 1341.5 L 975.0 1349.5 L 973.0 1351.5 L 968.0 1351.5 L 959.0 1355.5 L 954.0 1355.5 L 952.0 1357.5 L 947.0 1357.5 Z M 906.5 1279.0 L 957.0 1265.5 L 979.0 1261.5 L 981.0 1259.5 L 986.0 1259.5 L 997.0 1255.5 L 1027.0 1249.5 L 1029.5 1247.0 L 1029.5 1100.0 L 1032.0 1097.5 L 1037.0 1097.5 L 1040.0 1095.5 L 1056.0 1093.5 L 1058.0 1091.5 L 1064.0 1091.5 L 1076.0 1087.5 L 1083.0 1087.5 L 1111.0 1079.5 L 1145.0 1073.5 L 1147.0 1071.5 L 1165.0 1068.5 L 1166.5 1074.0 L 1165.5 1226.0 L 1167.0 1227.5 L 1232.0 1211.5 L 1234.0 1209.5 L 1240.0 1209.5 L 1250.0 1205.5 L 1264.0 1203.5 L 1266.0 1201.5 L 1272.0 1201.5 L 1282.0 1197.5 L 1288.0 1197.5 L 1291.5 1195.0 L 1291.5 925.0 L 1290.5 812.0 L 1289.0 810.5 L 1279.0 813.5 L 1271.0 813.5 L 1269.0 815.5 L 1240.0 819.5 L 1237.0 821.5 L 1218.0 823.5 L 1205.0 827.5 L 1187.0 829.5 L 1184.0 831.5 L 1176.0 831.5 L 1165.5 835.0 L 1165.5 964.0 L 1164.0 965.5 L 1111.0 975.5 L 1108.0 977.5 L 1101.0 977.5 L 1098.0 979.5 L 1091.0 979.5 L 1088.0 981.5 L 1081.0 981.5 L 1078.0 983.5 L 1071.0 983.5 L 1068.0 985.5 L 1061.0 985.5 L 1058.0 987.5 L 1039.0 989.5 L 1036.0 991.5 L 1028.5 990.0 L 1029.5 857.0 L 1028.0 855.5 L 1001.0 859.5 L 998.0 861.5 L 990.0 861.5 L 987.0 863.5 L 978.0 863.5 L 965.0 867.5 L 956.0 867.5 L 943.0 871.5 L 934.0 871.5 L 921.0 875.5 L 900.0 877.5 L 897.5 880.0 L 899.5 1279.0 Z" clip-rule="evenodd"/>
</clipPath>
</defs>
<path d="M 888.0 1477.5 L 867.0 1477.5 L 865.0 1475.5 L 854.0 1473.5 L 812.0 1451.5 L 811.0 1449.5 L 798.0 1443.5 L 720.0 1397.5 L 644.0 1347.5 L 576.0 1299.5 L 563.0 1289.5 L 545.5 1269.0 L 541.5 1257.0 L 540.5 1245.0 L 541.5 669.0 L 545.5 658.0 L 559.0 641.5 L 572.0 633.5 L 589.0 629.5 L 591.0 627.5 L 604.0 625.5 L 606.0 623.5 L 619.0 621.5 L 646.0 613.5 L 652.0 613.5 L 654.0 611.5 L 676.0 607.5 L 678.0 605.5 L 684.0 605.5 L 694.0 601.5 L 700.0 601.5 L 751.0 587.5 L 757.0 587.5 L 759.0 585.5 L 765.0 585.5 L 767.0 583.5 L 773.0 583.5 L 783.0 579.5 L 789.0 579.5 L 791.0 577.5 L 805.0 575.5 L 807.0 573.5 L 813.0 573.5 L 880.0 555.5 L 886.0 555.5 L 888.0 553.5 L 894.0 553.5 L 962.0 535.5 L 968.0 535.5 L 970.0 533.5 L 984.0 531.5 L 987.0 529.5 L 1000.0 527.5 L 1002.0 525.5 L 1008.0 525.5 L 1010.0 523.5 L 1032.0 519.5 L 1034.0 517.5 L 1040.0 517.5 L 1042.0 515.5 L 1064.0 511.5 L 1066.0 509.5 L 1072.0 509.5 L 1090.0 503.5 L 1096.0 503.5 L 1098.0 501.5 L 1104.0 501.5 L 1107.0 499.5 L 1120.0 497.5 L 1122.0 495.5 L 1136.0 493.5 L 1138.0 491.5 L 1144.0 491.5 L 1147.0 489.5 L 1166.0 489.5 L 1175.0 491.5 L 1222.0 513.5 L 1227.0 517.5 L 1245.0 525.5 L 1246.0 527.5 L 1253.0 529.5 L 1273.0 541.5 L 1287.0 547.5 L 1288.0 549.5 L 1291.0 549.5 L 1292.0 551.5 L 1329.0 569.5 L 1330.0 571.5 L 1441.0 629.5 L 1482.0 655.5 L 1497.5 675.0 L 1503.5 692.0 L 1503.5 1264.0 L 1495.5 1281.0 L 1486.0 1291.5 L 1475.0 1299.5 L 1455.0 1307.5 L 1437.0 1311.5 L 1435.0 1313.5 L 1405.0 1321.5 L 1403.0 1323.5 L 1398.0 1323.5 L 1396.0 1325.5 L 1365.0 1333.5 L 1363.0 1335.5 L 1351.0 1337.5 L 1330.0 1345.5 L 1318.0 1347.5 L 1303.0 1353.5 L 1278.0 1359.5 L 1263.0 1365.5 L 1258.0 1365.5 L 1243.0 1371.5 L 1225.0 1375.5 L 1203.0 1383.5 L 1198.0 1383.5 L 1196.0 1385.5 L 1172.0 1391.5 L 1170.0 1393.5 L 1165.0 1393.5 L 1143.0 1401.5 L 1138.0 1401.5 L 1130.0 1405.5 L 1118.0 1407.5 L 1116.0 1409.5 L 1046.0 1429.5 L 1031.0 1435.5 L 1006.0 1441.5 L 991.0 1447.5 L 986.0 1447.5 L 971.0 1453.5 L 966.0 1453.5 L 964.0 1455.5 L 939.0 1461.5 L 937.0 1463.5 L 932.0 1463.5 L 930.0 1465.5 Z M 892.5 1411.0 L 923.0 1403.5 L 925.0 1401.5 L 930.0 1401.5 L 932.0 1399.5 L 937.0 1399.5 L 939.0 1397.5 L 999.0 1381.5 L 1001.0 1379.5 L 1006.0 1379.5 L 1008.0 1377.5 L 1020.0 1375.5 L 1022.0 1373.5 L 1027.0 1373.5 L 1029.0 1371.5 L 1034.0 1371.5 L 1050.0 1365.5 L 1055.0 1365.5 L 1063.0 1361.5 L 1089.0 1355.5 L 1104.0 1349.5 L 1109.0 1349.5 L 1111.0 1347.5 L 1136.0 1341.5 L 1138.0 1339.5 L 1157.0 1335.5 L 1159.0 1333.5 L 1238.0 1311.5 L 1240.0 1309.5 L 1245.0 1309.5 L 1254.0 1305.5 L 1326.0 1285.5 L 1328.0 1283.5 L 1408.0 1261.5 L 1425.0 1255.5 L 1437.5 1245.0 L 1441.5 1239.0 L 1442.5 1232.0 L 1442.5 760.0 L 1441.5 706.0 L 1433.0 693.5 L 1422.0 689.5 L 1403.0 691.5 L 1388.0 695.5 L 1368.0 697.5 L 1365.0 699.5 L 1344.0 701.5 L 1342.0 703.5 L 1308.0 707.5 L 1294.0 711.5 L 1285.0 711.5 L 1282.0 713.5 L 1262.0 715.5 L 1248.0 719.5 L 1215.0 723.5 L 1212.0 725.5 L 1170.0 731.5 L 1156.0 735.5 L 1147.0 735.5 L 1144.0 737.5 L 1135.0 737.5 L 1132.0 739.5 L 1124.0 739.5 L 1098.0 745.5 L 1055.0 751.5 L 1041.0 755.5 L 1021.0 757.5 L 1007.0 761.5 L 1000.0 761.5 L 997.0 763.5 L 977.0 765.5 L 974.0 767.5 L 953.0 769.5 L 950.0 771.5 L 942.0 771.5 L 939.0 773.5 L 931.0 773.5 L 905.0 779.5 L 896.0 779.5 L 881.0 783.5 L 849.0 787.5 L 846.0 789.5 L 805.0 795.5 L 791.0 799.5 L 771.0 801.5 L 761.0 805.5 L 751.5 814.0 L 745.5 827.0 L 745.5 1333.0 L 786.0 1359.5 L 809.0 1371.5 L 861.0 1403.5 L 879.0 1411.5 Z" fill="url(#borderGrad)" fill-rule="evenodd"/>
<rect width="2048" height="2048" fill="url(#borderGlow)" clip-path="url(#borderRingClip)"/>
<path d="M 740.0 749.5 L 729.0 749.5 L 717.0 743.5 L 596.0 675.5 L 591.0 672.5 L 589.5 669.0 L 688.0 643.5 L 694.0 643.5 L 697.0 641.5 L 785.0 621.5 L 796.0 617.5 L 802.0 617.5 L 812.0 613.5 L 818.0 613.5 L 877.0 597.5 L 883.0 597.5 L 910.0 589.5 L 916.0 589.5 L 975.0 573.5 L 981.0 573.5 L 1008.0 565.5 L 1014.0 565.5 L 1057.0 553.5 L 1063.0 553.5 L 1098.0 543.5 L 1145.0 533.5 L 1148.0 531.5 L 1156.0 531.5 L 1195.5 554.0 L 1119.0 573.5 L 1079.0 581.5 L 1052.0 589.5 L 1029.0 593.5 L 1026.0 595.5 L 995.0 601.5 L 950.0 613.5 L 944.0 613.5 L 941.0 615.5 L 909.0 621.5 L 906.0 623.5 L 773.0 653.5 L 751.0 659.5 L 743.5 666.0 L 746.0 669.5 L 757.0 673.5 L 777.0 673.5 L 1227.0 572.5 L 1233.0 573.5 L 1252.0 585.5 L 1269.0 593.5 L 1269.5 596.0 L 1052.0 639.5 L 1039.0 643.5 L 1022.0 645.5 L 1019.0 647.5 L 883.0 673.5 L 880.0 675.5 L 834.0 683.5 L 831.0 685.5 L 810.0 689.5 L 803.5 694.0 L 803.5 698.0 L 811.0 703.5 L 839.0 703.5 L 955.0 679.5 L 1302.0 614.5 L 1305.0 614.5 L 1353.5 640.0 L 1353.0 642.5 L 1340.0 643.5 L 1337.0 645.5 L 1329.0 645.5 L 1325.0 647.5 L 1282.0 653.5 L 1278.0 655.5 L 1269.0 655.5 L 1266.0 657.5 L 1235.0 661.5 L 1232.0 663.5 L 1224.0 663.5 L 1209.0 667.5 L 1177.0 671.5 L 1174.0 673.5 L 1166.0 673.5 L 1094.0 687.5 L 1063.0 691.5 L 1060.0 693.5 L 1052.0 693.5 L 1049.0 695.5 L 1041.0 695.5 L 1037.0 697.5 L 1029.0 697.5 L 1025.0 699.5 L 1017.0 699.5 L 1002.0 703.5 L 960.0 709.5 L 956.0 711.5 L 948.0 711.5 L 945.0 713.5 L 879.0 723.5 L 875.0 725.5 L 834.0 731.5 L 819.0 735.5 L 811.0 735.5 L 785.0 741.5 L 777.0 741.5 Z" fill="url(#topGrad)"/>
<path d="M 740.0 749.5 L 729.0 749.5 L 717.0 743.5 L 596.0 675.5 L 591.0 672.5 L 589.5 669.0 L 688.0 643.5 L 694.0 643.5 L 697.0 641.5 L 785.0 621.5 L 796.0 617.5 L 802.0 617.5 L 812.0 613.5 L 818.0 613.5 L 877.0 597.5 L 883.0 597.5 L 910.0 589.5 L 916.0 589.5 L 975.0 573.5 L 981.0 573.5 L 1008.0 565.5 L 1014.0 565.5 L 1057.0 553.5 L 1063.0 553.5 L 1098.0 543.5 L 1145.0 533.5 L 1148.0 531.5 L 1156.0 531.5 L 1195.5 554.0 L 1119.0 573.5 L 1079.0 581.5 L 1052.0 589.5 L 1029.0 593.5 L 1026.0 595.5 L 995.0 601.5 L 950.0 613.5 L 944.0 613.5 L 941.0 615.5 L 909.0 621.5 L 906.0 623.5 L 773.0 653.5 L 751.0 659.5 L 743.5 666.0 L 746.0 669.5 L 757.0 673.5 L 777.0 673.5 L 1227.0 572.5 L 1233.0 573.5 L 1252.0 585.5 L 1269.0 593.5 L 1269.5 596.0 L 1052.0 639.5 L 1039.0 643.5 L 1022.0 645.5 L 1019.0 647.5 L 883.0 673.5 L 880.0 675.5 L 834.0 683.5 L 831.0 685.5 L 810.0 689.5 L 803.5 694.0 L 803.5 698.0 L 811.0 703.5 L 839.0 703.5 L 955.0 679.5 L 1302.0 614.5 L 1305.0 614.5 L 1353.5 640.0 L 1353.0 642.5 L 1340.0 643.5 L 1337.0 645.5 L 1329.0 645.5 L 1325.0 647.5 L 1282.0 653.5 L 1278.0 655.5 L 1269.0 655.5 L 1266.0 657.5 L 1235.0 661.5 L 1232.0 663.5 L 1224.0 663.5 L 1209.0 667.5 L 1177.0 671.5 L 1174.0 673.5 L 1166.0 673.5 L 1094.0 687.5 L 1063.0 691.5 L 1060.0 693.5 L 1052.0 693.5 L 1049.0 695.5 L 1041.0 695.5 L 1037.0 697.5 L 1029.0 697.5 L 1025.0 699.5 L 1017.0 699.5 L 1002.0 703.5 L 960.0 709.5 L 956.0 711.5 L 948.0 711.5 L 945.0 713.5 L 879.0 723.5 L 875.0 725.5 L 834.0 731.5 L 819.0 735.5 L 811.0 735.5 L 785.0 741.5 L 777.0 741.5 Z" fill="none" stroke="#d6ff90" stroke-opacity="0.08" stroke-width="2" stroke-linejoin="round"/>
<path d="M 778.0 673.5 L 756.0 673.5 L 752.5 672.0 L 772.5 672.0 L 752.0 669.5 L 748.0 667.5 L 747.5 665.0 L 759.0 659.5 L 773.0 657.5 L 773.0 655.5 L 769.5 655.0 L 773.0 653.5 L 777.5 654.0 L 775.0 655.5 L 782.0 655.5 L 784.0 653.5 L 807.0 649.5 L 809.0 647.5 L 815.0 647.5 L 824.5 644.0 L 791.0 650.5 L 789.0 652.5 L 778.5 653.0 L 781.0 651.5 L 932.0 617.5 L 952.0 611.5 L 967.0 609.5 L 978.0 605.5 L 1168.0 561.5 L 1179.0 557.5 L 1184.5 558.0 L 1182.0 559.5 L 1188.0 559.5 L 1195.0 557.5 L 1196.0 553.5 L 1222.0 567.5 L 1223.5 571.0 L 1219.0 572.5 L 1230.5 572.0 Z" fill="url(#stripeGrad)" opacity="0.96"/>
<path d="M 833.0 704.5 L 811.0 703.5 L 808.5 702.0 L 811.5 700.0 L 807.5 699.0 L 806.5 695.0 L 811.0 691.5 L 834.0 687.5 L 836.0 685.5 L 853.0 683.5 L 854.0 681.5 L 807.5 691.0 L 824.0 685.5 L 1048.0 641.5 L 1051.0 639.5 L 1058.0 639.5 L 1071.0 635.5 L 1087.0 633.5 L 1090.0 631.5 L 1127.0 625.5 L 1130.0 623.5 L 1176.0 615.5 L 1258.0 597.5 L 1264.5 598.0 L 1250.5 601.0 L 1270.0 599.5 L 1269.0 597.5 L 1264.5 597.0 L 1271.0 594.5 L 1273.0 597.5 L 1299.5 612.0 L 1289.0 615.5 L 1269.5 618.0 L 1295.5 616.0 L 1292.0 617.5 L 1230.0 627.5 L 1162.0 641.5 L 1155.0 641.5 L 1141.0 645.5 L 1134.0 645.5 L 1068.0 659.5 L 976.0 675.5 L 973.0 677.5 L 966.0 677.5 Z" fill="url(#stripeGrad)" opacity="0.96"/>
<path d="M 680.0 1300.5 L 634.0 1265.5 L 599.0 1241.5 L 588.5 1232.0 L 587.5 1228.0 L 588.0 670.5 L 592.5 674.0 L 592.0 675.5 L 594.0 674.5 L 596.5 676.0 L 596.0 677.5 L 598.0 676.5 L 599.5 678.0 L 598.5 679.0 L 603.0 679.5 L 603.0 681.5 L 605.0 680.5 L 606.0 683.5 L 608.0 682.5 L 614.0 685.5 L 691.0 728.5 L 696.0 731.5 L 696.0 733.5 L 698.0 732.5 L 710.0 740.5 L 721.0 745.5 L 721.0 747.5 L 723.0 746.5 L 725.0 749.5 L 726.0 748.5 L 728.5 750.0 L 723.0 753.5 L 711.0 757.5 L 694.5 772.0 L 685.5 787.0 L 681.5 800.0 L 681.5 1299.0 Z" fill="url(#leftGrad)"/>
<path d="M 901.0 1371.5 L 893.0 1371.5 L 877.0 1365.5 L 808.0 1329.5 L 791.5 1318.0 L 791.5 847.0 L 793.5 842.0 L 803.0 833.5 L 818.0 829.5 L 827.0 829.5 L 841.0 825.5 L 850.0 825.5 L 853.0 823.5 L 874.0 821.5 L 877.0 819.5 L 909.0 815.5 L 912.0 813.5 L 932.0 811.5 L 946.0 807.5 L 955.0 807.5 L 969.0 803.5 L 977.0 803.5 L 980.0 801.5 L 1012.0 797.5 L 1015.0 795.5 L 1047.0 791.5 L 1050.0 789.5 L 1059.0 789.5 L 1073.0 785.5 L 1082.0 785.5 L 1085.0 783.5 L 1128.0 777.5 L 1131.0 775.5 L 1140.0 775.5 L 1143.0 773.5 L 1175.0 769.5 L 1189.0 765.5 L 1198.0 765.5 L 1201.0 763.5 L 1234.0 759.5 L 1237.0 757.5 L 1279.0 751.5 L 1282.0 749.5 L 1291.0 749.5 L 1294.0 747.5 L 1302.0 747.5 L 1352.0 737.5 L 1401.0 730.5 L 1401.5 1213.0 L 1397.5 1223.0 L 1389.0 1231.5 L 1373.0 1237.5 L 1368.0 1237.5 L 1366.0 1239.5 L 1361.0 1239.5 L 1359.0 1241.5 L 1333.0 1247.5 L 1324.0 1251.5 L 1319.0 1251.5 L 1317.0 1253.5 L 1312.0 1253.5 L 1310.0 1255.5 L 1285.0 1261.5 L 1283.0 1263.5 L 1277.0 1263.5 L 1275.0 1265.5 L 1256.0 1269.5 L 1241.0 1275.5 L 1236.0 1275.5 L 1187.0 1289.5 L 1185.0 1291.5 L 1165.0 1295.5 L 1163.0 1297.5 L 1158.0 1297.5 L 1149.0 1301.5 L 1144.0 1301.5 L 1142.0 1303.5 L 1109.0 1311.5 L 1107.0 1313.5 L 1088.0 1317.5 L 1086.0 1319.5 L 1067.0 1323.5 L 1058.0 1327.5 L 1052.0 1327.5 L 1050.0 1329.5 L 1015.0 1339.5 L 1010.0 1339.5 L 1008.0 1341.5 L 975.0 1349.5 L 973.0 1351.5 L 968.0 1351.5 L 959.0 1355.5 L 954.0 1355.5 L 952.0 1357.5 L 947.0 1357.5 Z M 906.5 1279.0 L 957.0 1265.5 L 979.0 1261.5 L 981.0 1259.5 L 986.0 1259.5 L 997.0 1255.5 L 1027.0 1249.5 L 1029.5 1247.0 L 1029.5 1100.0 L 1032.0 1097.5 L 1037.0 1097.5 L 1040.0 1095.5 L 1056.0 1093.5 L 1058.0 1091.5 L 1064.0 1091.5 L 1076.0 1087.5 L 1083.0 1087.5 L 1111.0 1079.5 L 1145.0 1073.5 L 1147.0 1071.5 L 1165.0 1068.5 L 1166.5 1074.0 L 1165.5 1226.0 L 1167.0 1227.5 L 1232.0 1211.5 L 1234.0 1209.5 L 1240.0 1209.5 L 1250.0 1205.5 L 1264.0 1203.5 L 1266.0 1201.5 L 1272.0 1201.5 L 1282.0 1197.5 L 1288.0 1197.5 L 1291.5 1195.0 L 1291.5 925.0 L 1290.5 812.0 L 1289.0 810.5 L 1279.0 813.5 L 1271.0 813.5 L 1269.0 815.5 L 1240.0 819.5 L 1237.0 821.5 L 1218.0 823.5 L 1205.0 827.5 L 1187.0 829.5 L 1184.0 831.5 L 1176.0 831.5 L 1165.5 835.0 L 1165.5 964.0 L 1164.0 965.5 L 1111.0 975.5 L 1108.0 977.5 L 1101.0 977.5 L 1098.0 979.5 L 1091.0 979.5 L 1088.0 981.5 L 1081.0 981.5 L 1078.0 983.5 L 1071.0 983.5 L 1068.0 985.5 L 1061.0 985.5 L 1058.0 987.5 L 1039.0 989.5 L 1036.0 991.5 L 1028.5 990.0 L 1029.5 857.0 L 1028.0 855.5 L 1001.0 859.5 L 998.0 861.5 L 990.0 861.5 L 987.0 863.5 L 978.0 863.5 L 965.0 867.5 L 956.0 867.5 L 943.0 871.5 L 934.0 871.5 L 921.0 875.5 L 900.0 877.5 L 897.5 880.0 L 899.5 1279.0 Z" fill="url(#frontGrad)" fill-rule="evenodd"/>
<rect width="2048" height="2048" fill="url(#frontHighlight)" clip-path="url(#panelClip)"/>
<path d="M 900.0 1278.0 L 1030.0 1247.0 L 1030.0 1100.0 L 1165.0 1071.0 L 1165.0 1227.0 L 1292.0 1195.0 L 1292.0 1250.0 L 980.0 1348.0 L 900.0 1316.0 Z" fill="url(#panelShadowGrad)"/>
<path d="M 901.0 1371.5 L 893.0 1371.5 L 877.0 1365.5 L 808.0 1329.5 L 791.5 1318.0 L 791.5 847.0 L 793.5 842.0 L 803.0 833.5 L 818.0 829.5 L 827.0 829.5 L 841.0 825.5 L 850.0 825.5 L 853.0 823.5 L 874.0 821.5 L 877.0 819.5 L 909.0 815.5 L 912.0 813.5 L 932.0 811.5 L 946.0 807.5 L 955.0 807.5 L 969.0 803.5 L 977.0 803.5 L 980.0 801.5 L 1012.0 797.5 L 1015.0 795.5 L 1047.0 791.5 L 1050.0 789.5 L 1059.0 789.5 L 1073.0 785.5 L 1082.0 785.5 L 1085.0 783.5 L 1128.0 777.5 L 1131.0 775.5 L 1140.0 775.5 L 1143.0 773.5 L 1175.0 769.5 L 1189.0 765.5 L 1198.0 765.5 L 1201.0 763.5 L 1234.0 759.5 L 1237.0 757.5 L 1279.0 751.5 L 1282.0 749.5 L 1291.0 749.5 L 1294.0 747.5 L 1302.0 747.5 L 1352.0 737.5 L 1401.0 730.5 L 1401.5 1213.0 L 1397.5 1223.0 L 1389.0 1231.5 L 1373.0 1237.5 L 1368.0 1237.5 L 1366.0 1239.5 L 1361.0 1239.5 L 1359.0 1241.5 L 1333.0 1247.5 L 1324.0 1251.5 L 1319.0 1251.5 L 1317.0 1253.5 L 1312.0 1253.5 L 1310.0 1255.5 L 1285.0 1261.5 L 1283.0 1263.5 L 1277.0 1263.5 L 1275.0 1265.5 L 1256.0 1269.5 L 1241.0 1275.5 L 1236.0 1275.5 L 1187.0 1289.5 L 1185.0 1291.5 L 1165.0 1295.5 L 1163.0 1297.5 L 1158.0 1297.5 L 1149.0 1301.5 L 1144.0 1301.5 L 1142.0 1303.5 L 1109.0 1311.5 L 1107.0 1313.5 L 1088.0 1317.5 L 1086.0 1319.5 L 1067.0 1323.5 L 1058.0 1327.5 L 1052.0 1327.5 L 1050.0 1329.5 L 1015.0 1339.5 L 1010.0 1339.5 L 1008.0 1341.5 L 975.0 1349.5 L 973.0 1351.5 L 968.0 1351.5 L 959.0 1355.5 L 954.0 1355.5 L 952.0 1357.5 L 947.0 1357.5 Z M 906.5 1279.0 L 957.0 1265.5 L 979.0 1261.5 L 981.0 1259.5 L 986.0 1259.5 L 997.0 1255.5 L 1027.0 1249.5 L 1029.5 1247.0 L 1029.5 1100.0 L 1032.0 1097.5 L 1037.0 1097.5 L 1040.0 1095.5 L 1056.0 1093.5 L 1058.0 1091.5 L 1064.0 1091.5 L 1076.0 1087.5 L 1083.0 1087.5 L 1111.0 1079.5 L 1145.0 1073.5 L 1147.0 1071.5 L 1165.0 1068.5 L 1166.5 1074.0 L 1165.5 1226.0 L 1167.0 1227.5 L 1232.0 1211.5 L 1234.0 1209.5 L 1240.0 1209.5 L 1250.0 1205.5 L 1264.0 1203.5 L 1266.0 1201.5 L 1272.0 1201.5 L 1282.0 1197.5 L 1288.0 1197.5 L 1291.5 1195.0 L 1291.5 925.0 L 1290.5 812.0 L 1289.0 810.5 L 1279.0 813.5 L 1271.0 813.5 L 1269.0 815.5 L 1240.0 819.5 L 1237.0 821.5 L 1218.0 823.5 L 1205.0 827.5 L 1187.0 829.5 L 1184.0 831.5 L 1176.0 831.5 L 1165.5 835.0 L 1165.5 964.0 L 1164.0 965.5 L 1111.0 975.5 L 1108.0 977.5 L 1101.0 977.5 L 1098.0 979.5 L 1091.0 979.5 L 1088.0 981.5 L 1081.0 981.5 L 1078.0 983.5 L 1071.0 983.5 L 1068.0 985.5 L 1061.0 985.5 L 1058.0 987.5 L 1039.0 989.5 L 1036.0 991.5 L 1028.5 990.0 L 1029.5 857.0 L 1028.0 855.5 L 1001.0 859.5 L 998.0 861.5 L 990.0 861.5 L 987.0 863.5 L 978.0 863.5 L 965.0 867.5 L 956.0 867.5 L 943.0 871.5 L 934.0 871.5 L 921.0 875.5 L 900.0 877.5 L 897.5 880.0 L 899.5 1279.0 Z" fill="none" stroke="#2fe56b" stroke-opacity="0.05" stroke-width="3" stroke-linejoin="round" fill-rule="evenodd"/>
<path d="M 888.0 1477.5 L 867.0 1477.5 L 865.0 1475.5 L 854.0 1473.5 L 812.0 1451.5 L 811.0 1449.5 L 798.0 1443.5 L 720.0 1397.5 L 644.0 1347.5 L 576.0 1299.5 L 563.0 1289.5 L 545.5 1269.0 L 541.5 1257.0 L 540.5 1245.0 L 541.5 669.0 L 545.5 658.0 L 559.0 641.5 L 572.0 633.5 L 589.0 629.5 L 591.0 627.5 L 604.0 625.5 L 606.0 623.5 L 619.0 621.5 L 646.0 613.5 L 652.0 613.5 L 654.0 611.5 L 676.0 607.5 L 678.0 605.5 L 684.0 605.5 L 694.0 601.5 L 700.0 601.5 L 751.0 587.5 L 757.0 587.5 L 759.0 585.5 L 765.0 585.5 L 767.0 583.5 L 773.0 583.5 L 783.0 579.5 L 789.0 579.5 L 791.0 577.5 L 805.0 575.5 L 807.0 573.5 L 813.0 573.5 L 880.0 555.5 L 886.0 555.5 L 888.0 553.5 L 894.0 553.5 L 962.0 535.5 L 968.0 535.5 L 970.0 533.5 L 984.0 531.5 L 987.0 529.5 L 1000.0 527.5 L 1002.0 525.5 L 1008.0 525.5 L 1010.0 523.5 L 1032.0 519.5 L 1034.0 517.5 L 1040.0 517.5 L 1042.0 515.5 L 1064.0 511.5 L 1066.0 509.5 L 1072.0 509.5 L 1090.0 503.5 L 1096.0 503.5 L 1098.0 501.5 L 1104.0 501.5 L 1107.0 499.5 L 1120.0 497.5 L 1122.0 495.5 L 1136.0 493.5 L 1138.0 491.5 L 1144.0 491.5 L 1147.0 489.5 L 1166.0 489.5 L 1175.0 491.5 L 1222.0 513.5 L 1227.0 517.5 L 1245.0 525.5 L 1246.0 527.5 L 1253.0 529.5 L 1273.0 541.5 L 1287.0 547.5 L 1288.0 549.5 L 1291.0 549.5 L 1292.0 551.5 L 1329.0 569.5 L 1330.0 571.5 L 1441.0 629.5 L 1482.0 655.5 L 1497.5 675.0 L 1503.5 692.0 L 1503.5 1264.0 L 1495.5 1281.0 L 1486.0 1291.5 L 1475.0 1299.5 L 1455.0 1307.5 L 1437.0 1311.5 L 1435.0 1313.5 L 1405.0 1321.5 L 1403.0 1323.5 L 1398.0 1323.5 L 1396.0 1325.5 L 1365.0 1333.5 L 1363.0 1335.5 L 1351.0 1337.5 L 1330.0 1345.5 L 1318.0 1347.5 L 1303.0 1353.5 L 1278.0 1359.5 L 1263.0 1365.5 L 1258.0 1365.5 L 1243.0 1371.5 L 1225.0 1375.5 L 1203.0 1383.5 L 1198.0 1383.5 L 1196.0 1385.5 L 1172.0 1391.5 L 1170.0 1393.5 L 1165.0 1393.5 L 1143.0 1401.5 L 1138.0 1401.5 L 1130.0 1405.5 L 1118.0 1407.5 L 1116.0 1409.5 L 1046.0 1429.5 L 1031.0 1435.5 L 1006.0 1441.5 L 991.0 1447.5 L 986.0 1447.5 L 971.0 1453.5 L 966.0 1453.5 L 964.0 1455.5 L 939.0 1461.5 L 937.0 1463.5 L 932.0 1463.5 L 930.0 1465.5 Z" fill="none" stroke="#00efc3" stroke-opacity="0.06" stroke-width="1.5" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 25 KiB

+410
View File
@@ -0,0 +1,410 @@
:root {
color-scheme: light;
--color-bg: #f6f8f5;
--color-surface: #ffffff;
--color-surface-alt: #f1f4ef;
--color-border: #dce4d8;
--color-border-strong: #c7d2c2;
--color-text: #1f2a21;
--color-text-secondary: #5f6e62;
--color-text-muted: #7d8a80;
--color-text-on-primary: #ffffff;
--color-primary-700: #1c652f;
--color-primary-600: #2e7d32;
--color-primary-500: #3c8f42;
--color-primary-300: #a8d5a2;
--color-primary-100: #eaf5e8;
--color-accent-lime: #b7e36b;
--color-success: #2e7d32;
--color-warning: #b7791f;
--color-danger: #c0392b;
--color-info: #2f6fb3;
--radius-sm: 8px;
--radius-md: 10px;
--radius-lg: 14px;
--shadow-sm: 0 1px 2px rgb(16 24 18 / 6%);
--shadow-md: 0 6px 18px rgb(16 24 18 / 8%);
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 20px;
--space-6: 24px;
--space-8: 32px;
--space-10: 40px;
--font-family-sans:
Inter, 'Segoe UI', Roboto, system-ui, -apple-system, BlinkMacSystemFont, 'Helvetica Neue',
Arial, sans-serif;
--font-size-xs: 12px;
--font-size-sm: 13px;
--font-size-md: 14px;
--font-size-lg: 18px;
--font-size-xl: 24px;
--line-height-tight: 1.3;
--line-height-normal: 1.5;
--line-height-loose: 1.65;
--transition-fast: 160ms ease;
--page-bg-glow: rgb(183 227 107 / 14%);
--scrollbar-thumb: #a6b3a2;
--scrollbar-track: #e8ede5;
}
:root[data-theme='dark'] {
color-scheme: dark;
--color-bg: #101215;
--color-surface: #171a1f;
--color-surface-alt: #1d2229;
--color-border: #2c333d;
--color-border-strong: #3a4350;
--color-text: #ebeff3;
--color-text-secondary: #c5ccd4;
--color-text-muted: #95a0ad;
--color-text-on-primary: #08120a;
--color-primary-700: #5fb968;
--color-primary-600: #4ea758;
--color-primary-500: #3f9148;
--color-primary-300: #2e6a37;
--color-primary-100: #202822;
--color-accent-lime: #b7e36b;
--color-success: #5fb968;
--color-warning: #d0a34e;
--color-danger: #e07a7a;
--color-info: #6aa8de;
--shadow-sm: 0 1px 2px rgb(0 0 0 / 35%);
--shadow-md: 0 6px 18px rgb(0 0 0 / 40%);
--page-bg-glow: rgb(79 145 72 / 7%);
--scrollbar-thumb: #4f5763;
--scrollbar-track: #171b22;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body,
#app {
min-height: 100%;
}
html {
font-size: 14px;
line-height: var(--line-height-normal);
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
font-family: var(--font-family-sans);
font-size: var(--font-size-md);
color: var(--color-text);
background: radial-gradient(circle at top right, var(--page-bg-glow), transparent 36%), var(--color-bg);
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
overflow-y: scroll;
}
::selection {
background-color: rgb(60 143 66 / 22%);
color: var(--color-text);
}
a {
color: var(--color-primary-700);
text-decoration: none;
transition: color var(--transition-fast);
}
a:hover {
color: var(--color-primary-600);
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 0 0 var(--space-4);
line-height: var(--line-height-tight);
color: var(--color-text);
}
p {
margin: 0 0 var(--space-4);
color: var(--color-text-secondary);
line-height: var(--line-height-loose);
}
:where(a, button, input, textarea, select, [tabindex]):focus-visible {
outline: 2px solid var(--color-primary-500);
outline-offset: 2px;
}
/* App shell */
.v-application {
font-family: var(--font-family-sans) !important;
color: var(--color-text) !important;
background-color: transparent !important;
}
.v-main {
background-color: transparent;
}
.v-app-bar {
background-color: var(--color-surface) !important;
border-bottom: 1px solid var(--color-border) !important;
box-shadow: var(--shadow-sm) !important;
}
.v-navigation-drawer {
background-color: var(--color-surface-alt) !important;
border-right: 1px solid var(--color-border) !important;
}
.v-navigation-drawer .v-list-item {
border-radius: var(--radius-md) !important;
margin: 2px var(--space-2);
}
.v-navigation-drawer .v-list-item:hover {
background-color: color-mix(in srgb, var(--color-primary-100) 45%, var(--color-surface-alt) 55%) !important;
}
.v-navigation-drawer .v-list-item--active {
color: var(--color-primary-700) !important;
background-color: var(--color-primary-100) !important;
font-weight: 600;
}
/* Surface components */
.v-card {
border: 1px solid var(--color-border) !important;
border-radius: var(--radius-lg) !important;
background-color: var(--color-surface) !important;
box-shadow: var(--shadow-sm) !important;
}
.v-card-title {
color: var(--color-text) !important;
font-size: var(--font-size-lg) !important;
font-weight: 600 !important;
}
.v-card-text {
color: var(--color-text-secondary) !important;
}
.v-footer {
border-top: 1px solid var(--color-border);
background-color: color-mix(in srgb, var(--color-surface) 90%, var(--color-bg) 10%) !important;
}
/* Buttons */
.v-btn {
letter-spacing: 0 !important;
text-transform: none !important;
border-radius: var(--radius-md) !important;
font-weight: 500 !important;
}
.v-btn--variant-elevated,
.v-btn--variant-flat {
box-shadow: none !important;
}
.v-btn--variant-elevated:not(.v-btn--disabled):hover,
.v-btn--variant-flat:not(.v-btn--disabled):hover {
box-shadow: var(--shadow-sm) !important;
}
.v-btn.v-btn--variant-elevated:not(.v-btn--disabled) {
color: var(--color-text-on-primary) !important;
background-color: var(--color-primary-700) !important;
}
.v-btn.v-btn--variant-elevated:not(.v-btn--disabled):hover {
background-color: var(--color-primary-600) !important;
}
.v-btn--variant-outlined {
border-color: var(--color-border-strong) !important;
color: var(--color-text) !important;
}
/* Inputs */
.v-input .v-field {
border-radius: var(--radius-md) !important;
background-color: var(--color-surface) !important;
}
.v-input .v-field__outline {
--v-field-border-opacity: 1;
color: var(--color-border-strong) !important;
}
.v-input.v-input--focused .v-field__outline {
color: var(--color-primary-600) !important;
}
.v-label {
color: var(--color-text-secondary) !important;
}
/* Tables and list-like content */
.v-table {
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background-color: var(--color-surface) !important;
}
.v-table thead th {
color: var(--color-text-secondary) !important;
font-weight: 600 !important;
background-color: var(--color-surface-alt) !important;
}
.v-table tbody tr:hover td {
background-color: color-mix(in srgb, var(--color-primary-100) 35%, var(--color-surface) 65%) !important;
}
/* Status helpers */
.hoard-status {
display: inline-flex;
align-items: center;
gap: var(--space-2);
padding: 2px var(--space-2);
border-radius: var(--radius-sm);
font-size: var(--font-size-sm);
font-weight: 500;
}
.hoard-status--success {
color: var(--color-success);
background-color: color-mix(in srgb, var(--color-success) 14%, var(--color-surface) 86%);
}
.hoard-status--warning {
color: var(--color-warning);
background-color: color-mix(in srgb, var(--color-warning) 15%, var(--color-surface) 85%);
}
.hoard-status--danger {
color: var(--color-danger);
background-color: color-mix(in srgb, var(--color-danger) 12%, var(--color-surface) 88%);
}
.hoard-status--info {
color: var(--color-info);
background-color: color-mix(in srgb, var(--color-info) 12%, var(--color-surface) 88%);
}
/* Reusable layout helpers for file/productivity pages */
.hoard-panel {
background-color: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
}
.hoard-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-3);
padding: var(--space-3) var(--space-4);
border-bottom: 1px solid var(--color-border);
background-color: var(--color-surface-alt);
}
.hoard-list-row {
display: grid;
grid-template-columns: minmax(220px, 2fr) minmax(120px, 1fr) minmax(100px, 1fr) minmax(120px, 1fr);
align-items: center;
gap: var(--space-3);
padding: var(--space-3) var(--space-4);
border-bottom: 1px solid color-mix(in srgb, var(--color-border) 85%, white 15%);
transition: background-color var(--transition-fast);
}
.hoard-list-row:hover {
background-color: color-mix(in srgb, var(--color-primary-100) 35%, var(--color-surface) 65%);
}
.hoard-list-row.is-selected {
color: var(--color-primary-700);
background-color: var(--color-primary-100);
}
.hoard-meta {
color: var(--color-text-muted);
font-size: var(--font-size-sm);
}
.hoard-empty-state {
padding: var(--space-8) var(--space-6);
text-align: center;
color: var(--color-text-secondary);
}
.hoard-empty-state h2 {
margin-bottom: var(--space-3);
font-size: 20px;
font-weight: 600;
}
/* Scrollbar refinement */
* {
scrollbar-width: thin;
scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
}
*::-webkit-scrollbar {
width: 10px;
height: 10px;
}
*::-webkit-scrollbar-track {
background: var(--scrollbar-track);
}
*::-webkit-scrollbar-thumb {
background: var(--scrollbar-thumb);
border-radius: 10px;
}
*::-webkit-scrollbar-thumb:hover {
background: color-mix(in srgb, var(--scrollbar-thumb) 85%, black 15%);
}
@media (width <= 960px) {
.hoard-list-row {
grid-template-columns: 1fr;
gap: var(--space-2);
align-items: start;
}
.v-navigation-drawer {
border-right: none !important;
border-top: 1px solid var(--color-border) !important;
}
}
+1
View File
@@ -1,5 +1,6 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import './global.css'
import App from './Layout.vue'
import router from './router'
+13 -1
View File
@@ -12,6 +12,7 @@ export enum Visibility {
Authorized,
Public,
Footer,
Route,
}
export interface LayoutRoute {
@@ -21,14 +22,25 @@ export interface LayoutRoute {
icon: string
disableFooter?: boolean
visible: Visibility
visibilityRoute?: string | string[]
meta?: RouteRecordRaw
}
/**
* Kurzanleitung fuer Sidebar-Sichtbarkeit:
* - `Visibility.Public`: Eintrag ist immer in der Sidebar sichtbar.
* - `Visibility.Route`: Eintrag ist nur sichtbar, wenn die aktuelle URL im angegebenen Bereich liegt.
* - Ohne `visibilityRoute` wird automatisch `path` als Bereich verwendet.
* - Szenario 1: `path: '/dash'` -> sichtbar bei `/dash` und `/dash/*`.
* - Szenario 2: `visibilityRoute: '/admin'` -> Eintrag wird nur im Admin-Bereich gezeigt.
* - Szenario 3: `visibilityRoute: ['/dash', '/projects']` -> sichtbar in beiden Bereichen.
* - `Visibility.Footer` und `Visibility.Hidden`: nicht in der Sidebar sichtbar.
*/
export const routes: LayoutRoute[] = [
{
path: '/',
name: 'Startseite',
description: 'Uebersicht der Anwendung',
description: 'Self-hosted Datei-Workspace für Hoard',
icon: 'mdi-home',
visible: Visibility.Public,
meta: {
+29 -1
View File
@@ -15,7 +15,35 @@ export default createVuetify({
components,
directives,
theme: {
defaultTheme: 'dark',
defaultTheme: 'light',
themes: {
light: {
dark: false,
colors: {
primary: '#1C652F',
secondary: '#5F6E62',
background: '#F6F8F5',
surface: '#FFFFFF',
success: '#2E7D32',
warning: '#B7791F',
error: '#C0392B',
info: '#2F6FB3',
},
},
dark: {
dark: true,
colors: {
primary: '#4EA758',
secondary: '#A7B0BC',
background: '#101215',
surface: '#171A1F',
success: '#5FB968',
warning: '#D0A34E',
error: '#E07A7A',
info: '#6AA8DE',
},
},
},
},
icons: {
defaultSet: 'mdi',
+131 -17
View File
@@ -1,30 +1,144 @@
<script setup lang="ts"></script>
<script setup lang="ts">
import { useRouter } from 'vue-router'
import notFoundImage from '@/assets/images/404NotFound.png'
const router = useRouter()
function navigateBack() {
if (window.history.length > 1) {
router.back()
return
}
router.push('/')
}
</script>
<template>
<v-container fluid class="fill-height d-flex flex-column justify-center align-center">
<h1 class="error-title text-h1 font-weight-bold">404</h1>
<p class="error-message text-h5">Seite nicht gefunden</p>
<v-container fluid class="not-found-page">
<section class="not-found-shell hoard-panel">
<div class="not-found-visual">
<div class="image-frame">
<img :src="notFoundImage" alt="Illustration für eine nicht gefundene Seite" class="not-found-image" />
</div>
</div>
<router-link to="/" class="text-primary text-decoration-none font-weight-medium mt-5">
Zurueck zur Startseite
</router-link>
<div class="not-found-content">
<p class="not-found-kicker">Fehler 404</p>
<h1>Seite nicht gefunden</h1>
<p class="not-found-text">
Der Link ist ungültig oder die Seite wurde verschoben. Du kannst direkt zur
Startseite zurück oder die vorherige Ansicht öffnen.
</p>
<div class="not-found-actions">
<v-btn color="primary" prepend-icon="mdi-home" to="/">Zur Startseite</v-btn>
<v-btn variant="outlined" prepend-icon="mdi-arrow-left" @click="navigateBack">Zurück</v-btn>
</div>
</div>
</section>
</v-container>
</template>
<style scoped>
.error-title {
font-size: 3rem;
font-weight: bold;
margin-bottom: 1rem;
letter-spacing: 2px;
.not-found-page {
display: flex;
align-items: center;
justify-content: center;
min-height: calc(100vh - 210px);
padding: var(--space-8) var(--space-4);
}
.error-message {
font-size: 1.25rem;
opacity: 0.85;
.not-found-shell {
display: grid;
grid-template-columns: minmax(260px, 1fr) minmax(320px, 1fr);
gap: var(--space-8);
width: min(100%, 980px);
padding: var(--space-8);
background:
linear-gradient(
180deg,
color-mix(in srgb, var(--color-surface) 94%, var(--color-primary-100) 6%) 0%,
color-mix(in srgb, var(--color-surface) 82%, var(--color-surface-alt) 18%) 100%
);
}
a:hover {
text-decoration: underline !important;
.not-found-visual {
display: flex;
align-items: center;
justify-content: center;
}
.image-frame {
width: min(100%, 360px);
aspect-ratio: 1 / 1;
padding: var(--space-4);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
background-color: color-mix(in srgb, var(--color-surface-alt) 84%, var(--color-surface) 16%);
box-shadow: var(--shadow-sm);
}
.not-found-image {
width: 100%;
height: 100%;
object-fit: contain;
}
.not-found-content {
display: flex;
flex-direction: column;
justify-content: center;
}
.not-found-kicker {
margin: 0 0 var(--space-2);
color: var(--color-primary-700);
font-size: var(--font-size-sm);
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
}
h1 {
margin-bottom: var(--space-3);
font-size: clamp(1.8rem, 2vw + 1rem, 2.4rem);
font-weight: 700;
}
.not-found-text {
margin-bottom: var(--space-6);
max-width: 44ch;
color: var(--color-text-secondary);
font-size: 15px;
}
.not-found-actions {
display: flex;
flex-wrap: wrap;
gap: var(--space-3);
}
@media (width <= 960px) {
.not-found-page {
min-height: calc(100vh - 180px);
padding: var(--space-5) var(--space-2);
}
.not-found-shell {
grid-template-columns: 1fr;
gap: var(--space-5);
padding: var(--space-5);
}
.not-found-content {
text-align: center;
align-items: center;
}
.not-found-actions {
justify-content: center;
}
}
</style>
+433 -14
View File
@@ -1,19 +1,438 @@
<script setup lang="ts"></script>
<script setup lang="ts">
const valueProps = [
{
icon: 'mdi-folder-multiple-outline',
title: 'Dateien zuerst',
text: 'Ordner, Dateiliste und Vorschau sind der Kern. Kein überladenes Dashboard-Gefühl.',
},
{
icon: 'mdi-file-document-edit-outline',
title: 'Markdown direkt im Browser',
text: 'Dokumente bearbeiten, lesen und strukturieren ohne Tool-Wechsel.',
},
{
icon: 'mdi-server-outline',
title: 'Self-hosted Kontrolle',
text: 'Dateimetadaten in PostgreSQL, Dateien in MinIO, alles auf deinem Server.',
},
]
const coreFeatures = [
{
icon: 'mdi-folder-open-outline',
title: 'Ordnernavigation wie gewohnt',
text: 'Schnell durch Verzeichnisse klicken, Inhalte erfassen und sauber organisieren.',
},
{
icon: 'mdi-image-outline',
title: 'PDF- und Bildvorschau',
text: 'Dateien öffnen und direkt einsehen, ohne externe Viewer oder Downloads.',
},
{
icon: 'mdi-account-lock-outline',
title: 'Klare Benutzerlogik',
text: 'Keine offene Registrierung. Accounts werden bewusst und kontrolliert verwaltet.',
},
{
icon: 'mdi-lightning-bolt-outline',
title: 'Schlankes MVP-Setup',
text: 'Fokus auf das Wesentliche: stabil, wartbar und realistisch für Solo-Entwicklung.',
},
]
const workflowSteps = [
{
number: '01',
title: 'Anmelden',
text: 'Melde dich mit einem vorhandenen Konto an und starte direkt in deiner Dateiablage.',
},
{
number: '02',
title: 'Dateien strukturieren',
text: 'Lege Ordner an, lade Dateien hoch und halte deine Arbeitsbereiche aufgeräumt.',
},
{
number: '03',
title: 'Inhalte bearbeiten',
text: 'Markdown, Bilder und PDFs direkt in der App ansehen und bearbeiten.',
},
]
const techStack = ['Vue 3', 'ASP.NET Core', 'PostgreSQL', 'MinIO', 'md-editor-v3', 'Cookie Auth']
</script>
<template>
<v-container class="py-10">
<v-row justify="center">
<v-col cols="12" md="10" lg="8">
<v-card rounded="lg" elevation="2">
<v-card-title class="text-h4">Willkommen bei Hoard</v-card-title>
<v-card-text class="text-body-1">
Dein Vuetify-Setup ist aktiv. Ueber das Menue links kannst du zu den weiteren Seiten
navigieren.
</v-card-text>
</v-card>
</v-col>
</v-row>
<v-container fluid class="landing-page">
<section class="hero hoard-panel">
<div class="hero-copy">
<p class="hero-kicker">Self-hosted Datei-Workspace</p>
<h1>Hoard ist deine ruhige Startseite für Dateien, Ordner und Markdown.</h1>
<p class="hero-lead">
Eine einfache, Google-Drive-inspirierte Web-App für Teams, die volle Kontrolle über
Daten, Struktur und Workflow behalten wollen.
</p>
<div class="hero-actions">
<v-btn color="primary" size="large" prepend-icon="mdi-login" to="/login">
Zum Login
</v-btn>
<v-btn variant="outlined" size="large" prepend-icon="mdi-file-document-outline" to="/impressum">
Mehr erfahren
</v-btn>
</div>
<div class="hero-tags">
<span class="hero-tag">Light-first UX</span>
<span class="hero-tag">Mehrbenutzerfähig</span>
<span class="hero-tag">Ohne SaaS-Abhängigkeit</span>
</div>
</div>
<div class="hero-preview hoard-panel">
<header class="preview-head">
<p class="preview-title">Beispielansicht</p>
<span class="preview-pill">Workspace</span>
</header>
<div class="preview-list">
<article class="preview-row">
<v-icon icon="mdi-folder-outline" size="18" />
<div>
<p class="row-title">Dokumentation</p>
<p class="row-meta">Ordner · vor 3 Tagen aktualisiert</p>
</div>
</article>
<article class="preview-row">
<v-icon icon="mdi-file-document-outline" size="18" />
<div>
<p class="row-title">roadmap.md</p>
<p class="row-meta">Markdown · 18 KB</p>
</div>
</article>
<article class="preview-row">
<v-icon icon="mdi-file-pdf-box" size="18" />
<div>
<p class="row-title">api-reference.pdf</p>
<p class="row-meta">PDF · 1.2 MB</p>
</div>
</article>
</div>
</div>
</section>
<section class="value-grid">
<article v-for="item in valueProps" :key="item.title" class="value-card hoard-panel">
<v-icon :icon="item.icon" size="22" />
<h2>{{ item.title }}</h2>
<p>{{ item.text }}</p>
</article>
</section>
<section class="feature-section hoard-panel">
<header class="section-head">
<p class="section-kicker">Für den Produktivalltag</p>
<h2>Weniger Tool-Chaos, mehr Fokus auf Inhalte</h2>
</header>
<div class="feature-grid">
<article v-for="feature in coreFeatures" :key="feature.title" class="feature-card">
<v-icon :icon="feature.icon" size="20" />
<h3>{{ feature.title }}</h3>
<p>{{ feature.text }}</p>
</article>
</div>
</section>
<section class="workflow-section">
<header class="section-head">
<p class="section-kicker">So funktioniert Hoard</p>
<h2>In drei klaren Schritten produktiv starten</h2>
</header>
<div class="workflow-grid">
<article v-for="step in workflowSteps" :key="step.number" class="workflow-card hoard-panel">
<p class="workflow-number">{{ step.number }}</p>
<h3>{{ step.title }}</h3>
<p>{{ step.text }}</p>
</article>
</div>
</section>
<section class="stack-section hoard-panel">
<div class="stack-copy">
<p class="section-kicker">Technische Basis</p>
<h2>Schlank gebaut für ein realistisches MVP</h2>
<p class="stack-text">
Hoard kombiniert einen modernen Frontend-Stack mit einem pragmatischen Backend-Setup,
damit Weiterentwicklung und Betrieb auch solo gut machbar bleiben.
</p>
</div>
<div class="stack-list">
<span v-for="item in techStack" :key="item" class="stack-pill">{{ item }}</span>
</div>
</section>
</v-container>
</template>
<style scoped></style>
<style scoped>
.landing-page {
display: flex;
flex-direction: column;
gap: var(--space-6);
margin-inline: auto;
width: min(100%, 1180px);
padding-block: var(--space-4) var(--space-8);
}
.hero {
display: grid;
grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
gap: var(--space-6);
padding: var(--space-8);
background:
linear-gradient(
120deg,
color-mix(in srgb, var(--color-primary-100) 34%, var(--color-surface) 66%) 0%,
var(--color-surface) 52%
);
}
.hero-kicker,
.section-kicker,
.workflow-number,
.preview-title,
.row-title,
.row-meta,
.stack-text {
margin: 0;
}
.hero-kicker,
.section-kicker {
margin-bottom: var(--space-2);
color: var(--color-primary-700);
font-size: var(--font-size-sm);
font-weight: 600;
letter-spacing: 0.05em;
text-transform: uppercase;
}
h1 {
margin-bottom: var(--space-4);
max-width: 20ch;
font-size: clamp(2rem, 2.5vw + 1rem, 3rem);
line-height: 1.08;
}
.hero-lead {
margin-bottom: var(--space-5);
max-width: 50ch;
color: var(--color-text-secondary);
font-size: 15px;
}
.hero-actions {
display: flex;
flex-wrap: wrap;
gap: var(--space-3);
margin-bottom: var(--space-4);
}
.hero-tags {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
}
.hero-tag {
display: inline-flex;
align-items: center;
padding: 5px var(--space-3);
border: 1px solid var(--color-border-strong);
border-radius: 999px;
color: var(--color-text-secondary);
font-size: var(--font-size-sm);
font-weight: 500;
}
.hero-preview {
align-self: center;
padding: var(--space-5);
width: 100%;
background-color: color-mix(in srgb, var(--color-surface-alt) 86%, var(--color-surface) 14%);
}
.preview-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-2);
margin-bottom: var(--space-4);
}
.preview-title {
color: var(--color-text);
font-weight: 600;
}
.preview-pill {
display: inline-flex;
align-items: center;
padding: 4px var(--space-2);
border-radius: 999px;
color: var(--color-primary-700);
font-size: var(--font-size-xs);
font-weight: 600;
background-color: var(--color-primary-100);
}
.preview-list {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
.preview-row {
display: grid;
grid-template-columns: auto 1fr;
gap: var(--space-3);
align-items: center;
padding: var(--space-3);
border: 1px solid color-mix(in srgb, var(--color-border) 75%, var(--color-surface) 25%);
border-radius: var(--radius-md);
background-color: var(--color-surface);
}
.row-title {
color: var(--color-text);
font-size: var(--font-size-md);
font-weight: 500;
}
.row-meta {
color: var(--color-text-muted);
font-size: var(--font-size-sm);
}
.value-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: var(--space-4);
}
.value-card {
padding: var(--space-5);
}
.value-card h2,
.section-head h2 {
margin: var(--space-3) 0 var(--space-2);
font-size: clamp(1.25rem, 1.2vw + 0.8rem, 1.8rem);
}
.value-card p,
.feature-card p,
.workflow-card p,
.stack-text {
color: var(--color-text-secondary);
}
.feature-section,
.stack-section {
padding: var(--space-6);
}
.section-head {
margin-bottom: var(--space-5);
}
.feature-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: var(--space-4);
}
.feature-card {
padding: var(--space-4);
border: 1px solid color-mix(in srgb, var(--color-border) 75%, var(--color-surface) 25%);
border-radius: var(--radius-md);
background-color: color-mix(in srgb, var(--color-surface) 80%, var(--color-surface-alt) 20%);
}
.feature-card h3,
.workflow-card h3,
.stack-copy h2 {
margin: var(--space-2) 0;
}
.workflow-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: var(--space-4);
}
.workflow-card {
padding: var(--space-5);
}
.workflow-number {
color: var(--color-primary-700);
font-size: var(--font-size-sm);
font-weight: 700;
letter-spacing: 0.05em;
}
.stack-section {
display: grid;
grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
gap: var(--space-6);
align-items: center;
}
.stack-list {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
}
.stack-pill {
padding: 6px var(--space-3);
border: 1px solid var(--color-border-strong);
border-radius: var(--radius-md);
color: var(--color-text);
font-size: var(--font-size-sm);
font-weight: 500;
background-color: color-mix(in srgb, var(--color-surface-alt) 75%, var(--color-surface) 25%);
}
@media (width <= 1100px) {
.hero,
.stack-section {
grid-template-columns: 1fr;
}
.hero-preview {
max-width: 720px;
}
}
@media (width <= 960px) {
.landing-page {
gap: var(--space-5);
padding-block: var(--space-2) var(--space-6);
}
.hero,
.feature-section,
.stack-section {
padding: var(--space-5);
}
.value-grid,
.feature-grid,
.workflow-grid {
grid-template-columns: 1fr;
}
h1 {
max-width: none;
}
}
</style>
+167 -19
View File
@@ -1,24 +1,172 @@
<script setup lang="ts"></script>
<script setup lang="ts">
import { ref } from 'vue'
const showPassword = ref(false)
</script>
<template>
<v-container class="py-10">
<v-row justify="center">
<v-col cols="12" sm="10" md="6" lg="4">
<v-card rounded="lg" elevation="2">
<v-card-title class="text-h5">Login</v-card-title>
<v-card-text>
<v-text-field label="E-Mail" prepend-inner-icon="mdi-email-outline" />
<v-text-field
label="Passwort"
type="password"
prepend-inner-icon="mdi-lock-outline"
/>
<v-btn color="primary" block prepend-icon="mdi-login">Anmelden</v-btn>
</v-card-text>
</v-card>
</v-col>
</v-row>
<v-container fluid class="login-page">
<section class="login-shell hoard-panel">
<aside class="login-brand">
<p class="login-kicker">Willkommen bei Hoard</p>
<h1>Anmelden und weiterarbeiten</h1>
<p class="login-intro">
Deine Dateiablage bleibt aufgeräumt, schnell und direkt im Browser bedienbar.
</p>
<ul class="login-points">
<li>
<v-icon icon="mdi-folder-outline" size="18" />
Ordner und Dateien zentral verwalten
</li>
<li>
<v-icon icon="mdi-file-document-edit-outline" size="18" />
Markdown-Dateien sofort bearbeiten
</li>
<li>
<v-icon icon="mdi-image-outline" size="18" />
Bilder und PDFs direkt als Vorschau ansehen
</li>
</ul>
</aside>
<v-form class="login-form hoard-panel" @submit.prevent>
<div class="form-head">
<h2>Login</h2>
<p>Melde dich mit deinem bestehenden Konto an.</p>
</div>
<v-text-field
label="E-Mail"
type="email"
variant="outlined"
prepend-inner-icon="mdi-email-outline"
autocomplete="email"
required
/>
<v-text-field
label="Passwort"
:type="showPassword ? 'text' : 'password'"
variant="outlined"
prepend-inner-icon="mdi-lock-outline"
:append-inner-icon="showPassword ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
autocomplete="current-password"
required
@click:append-inner="showPassword = !showPassword"
/>
<div class="form-meta">
<v-checkbox hide-details color="primary" density="compact" label="Angemeldet bleiben" />
<v-btn variant="text" size="small">Passwort vergessen?</v-btn>
</div>
<v-btn type="submit" color="primary" block size="large" prepend-icon="mdi-login">Anmelden</v-btn>
<v-btn variant="outlined" block to="/" prepend-icon="mdi-home">Zur Startseite</v-btn>
</v-form>
</section>
</v-container>
</template>
<style scoped></style>
<style scoped>
.login-page {
display: flex;
align-items: center;
justify-content: center;
min-height: calc(100vh - 210px);
padding: var(--space-8) var(--space-4);
}
.login-shell {
display: grid;
grid-template-columns: minmax(280px, 1fr) minmax(320px, 430px);
gap: var(--space-8);
width: min(100%, 1040px);
padding: var(--space-8);
background:
linear-gradient(
115deg,
color-mix(in srgb, var(--color-primary-100) 45%, var(--color-surface) 55%) 0%,
var(--color-surface) 52%
);
}
.login-kicker {
margin: 0 0 var(--space-2);
color: var(--color-primary-700);
font-size: var(--font-size-sm);
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
}
h1 {
margin-bottom: var(--space-3);
max-width: 18ch;
font-size: clamp(1.9rem, 2vw + 1rem, 2.6rem);
font-weight: 700;
}
.login-intro {
margin-bottom: var(--space-5);
max-width: 44ch;
color: var(--color-text-secondary);
}
.login-points {
display: flex;
flex-direction: column;
gap: var(--space-3);
padding: 0;
margin: 0;
list-style: none;
}
.login-points li {
display: flex;
align-items: center;
gap: var(--space-3);
color: var(--color-text-secondary);
font-weight: 500;
}
.login-form {
display: flex;
flex-direction: column;
gap: var(--space-4);
padding: var(--space-6);
border-radius: var(--radius-lg);
}
.form-head h2 {
margin-bottom: var(--space-1);
font-size: 1.45rem;
}
.form-head p {
margin: 0;
color: var(--color-text-secondary);
font-size: var(--font-size-md);
}
.form-meta {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-3);
}
@media (width <= 960px) {
.login-page {
min-height: calc(100vh - 180px);
padding: var(--space-5) var(--space-2);
}
.login-shell {
grid-template-columns: 1fr;
gap: var(--space-5);
padding: var(--space-5);
}
}
</style>