Rename hoard- to ui- and add UI components
Mass rename of CSS classes, tokens and animations from the hoard- namespace to ui- (classes, variables like --ui-*, and keyframes). Introduces new UI components: EmptyState, StatusPill, and UserAvatar and updates admin views to import and use them. Updates many route/layout components and global.css to use the new ui- patterns and responsive variables. Also updates Impressum contact emails and adds .claude/settings.local.json to allow running npm scripts in the Claude local settings.
This commit is contained in:
+24
-44
@@ -176,8 +176,8 @@ watch(
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-app class="hoard-shell">
|
||||
<v-app-bar class="hoard-app-bar" elevation="0" height="68">
|
||||
<v-app class="ui-shell">
|
||||
<v-app-bar class="ui-app-bar" elevation="0" height="68">
|
||||
<template #prepend>
|
||||
<v-app-bar-nav-icon
|
||||
v-tooltip="!showDrawer ? 'Menü öffnen' : 'Menü schließen'"
|
||||
@@ -314,7 +314,7 @@ watch(
|
||||
|
||||
<v-navigation-drawer
|
||||
v-model="showDrawer"
|
||||
:class="['hoard-drawer', { 'hoard-drawer--mobile': display.mobile.value }]"
|
||||
:class="['ui-drawer', { 'ui-drawer--mobile': display.mobile.value }]"
|
||||
:location="display.mobile.value ? 'bottom' : 'left'"
|
||||
:temporary="display.mobile.value"
|
||||
:permanent="!display.mobile.value"
|
||||
@@ -322,7 +322,7 @@ watch(
|
||||
:elevation="display.mobile.value ? 6 : 0"
|
||||
>
|
||||
<div class="drawer-top">
|
||||
<p class="drawer-kicker hoard-kicker hoard-kicker--xs">Navigation</p>
|
||||
<p class="drawer-kicker ui-kicker ui-kicker--xs">Navigation</p>
|
||||
</div>
|
||||
|
||||
<v-list nav :density="display.mobile.value ? 'default' : 'comfortable'" class="px-1">
|
||||
@@ -333,15 +333,15 @@ watch(
|
||||
:active="route.path === item.path"
|
||||
:prepend-icon="item.icon"
|
||||
:title="item.name"
|
||||
class="hoard-nav-item"
|
||||
class="ui-nav-item"
|
||||
rounded="md"
|
||||
link
|
||||
/>
|
||||
|
||||
<template v-if="adminSidebarRoutes.length > 0">
|
||||
<hr class="hoard-divider-soft drawer-section-divider" />
|
||||
<hr class="ui-divider-soft drawer-section-divider" />
|
||||
<div class="drawer-section-head">
|
||||
<p class="drawer-kicker hoard-kicker hoard-kicker--xs">Admin</p>
|
||||
<p class="drawer-kicker ui-kicker ui-kicker--xs">Admin</p>
|
||||
</div>
|
||||
|
||||
<v-list-item
|
||||
@@ -351,7 +351,7 @@ watch(
|
||||
:active="route.path === item.path"
|
||||
:prepend-icon="item.icon"
|
||||
:title="item.name"
|
||||
class="hoard-nav-item"
|
||||
class="ui-nav-item"
|
||||
rounded="md"
|
||||
link
|
||||
/>
|
||||
@@ -359,28 +359,8 @@ watch(
|
||||
</v-list>
|
||||
|
||||
<template #append>
|
||||
<div class="drawer-bottom">
|
||||
<div v-if="isAuthenticated" class="drawer-card">
|
||||
<span class="drawer-card__avatar">{{ userInitials }}</span>
|
||||
<div class="drawer-card__body">
|
||||
<p class="drawer-card__hint">Eingeloggt</p>
|
||||
<p class="drawer-card__name">{{ userLabel }}</p>
|
||||
</div>
|
||||
<v-btn
|
||||
v-tooltip="'Abmelden'"
|
||||
icon
|
||||
size="small"
|
||||
variant="text"
|
||||
:aria-label="isLoggingOut ? 'Abmelden …' : 'Abmelden'"
|
||||
:disabled="isLoggingOut"
|
||||
@click="handleLogout"
|
||||
>
|
||||
<v-icon>mdi-logout</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<div v-if="!isAuthenticated" class="drawer-bottom">
|
||||
<v-btn
|
||||
v-else
|
||||
variant="text"
|
||||
prepend-icon="mdi-home-outline"
|
||||
to="/welcome"
|
||||
@@ -392,7 +372,7 @@ watch(
|
||||
</template>
|
||||
</v-navigation-drawer>
|
||||
|
||||
<v-main class="hoard-main">
|
||||
<v-main class="ui-main">
|
||||
<div class="main-shell">
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition name="route-fade" mode="out-in">
|
||||
@@ -403,7 +383,7 @@ watch(
|
||||
|
||||
<v-footer
|
||||
v-if="shouldShowFooter"
|
||||
class="hoard-footer"
|
||||
class="ui-footer"
|
||||
>
|
||||
<div class="footer-inner">
|
||||
<div class="footer-brand">
|
||||
@@ -455,11 +435,11 @@ watch(
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.hoard-shell {
|
||||
.ui-shell {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.hoard-app-bar {
|
||||
.ui-app-bar {
|
||||
padding-inline: var(--space-3);
|
||||
}
|
||||
|
||||
@@ -676,11 +656,11 @@ watch(
|
||||
}
|
||||
|
||||
/* ---------- Drawer ---------- */
|
||||
.hoard-drawer {
|
||||
.ui-drawer {
|
||||
padding-top: var(--space-2);
|
||||
}
|
||||
|
||||
.hoard-drawer--mobile {
|
||||
.ui-drawer--mobile {
|
||||
border-top-left-radius: var(--radius-xl);
|
||||
border-top-right-radius: var(--radius-xl);
|
||||
max-height: min(74vh, 580px);
|
||||
@@ -702,11 +682,11 @@ watch(
|
||||
padding: 0 var(--space-5) var(--space-2);
|
||||
}
|
||||
|
||||
:deep(.hoard-nav-item) {
|
||||
:deep(.ui-nav-item) {
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
:deep(.hoard-nav-item .v-list-item-title) {
|
||||
:deep(.ui-nav-item .v-list-item-title) {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@@ -768,7 +748,7 @@ watch(
|
||||
}
|
||||
|
||||
/* ---------- Main ---------- */
|
||||
.hoard-main {
|
||||
.ui-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: calc(100vh - 68px);
|
||||
@@ -797,7 +777,7 @@ watch(
|
||||
}
|
||||
|
||||
/* ---------- Footer ---------- */
|
||||
.hoard-footer {
|
||||
.ui-footer {
|
||||
flex: 0 0 auto;
|
||||
height: auto !important;
|
||||
min-height: 0 !important;
|
||||
@@ -927,7 +907,7 @@ watch(
|
||||
|
||||
/* ---------- Responsive ---------- */
|
||||
@media (width <= 960px) {
|
||||
.hoard-app-bar {
|
||||
.ui-app-bar {
|
||||
padding-inline:
|
||||
max(var(--space-1), env(safe-area-inset-left))
|
||||
max(var(--space-1), env(safe-area-inset-right));
|
||||
@@ -962,7 +942,7 @@ watch(
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hoard-drawer {
|
||||
.ui-drawer {
|
||||
padding-top: var(--space-1);
|
||||
}
|
||||
|
||||
@@ -995,9 +975,9 @@ watch(
|
||||
}
|
||||
|
||||
.app-banner-stack {
|
||||
right: var(--hoard-mobile-safe-right);
|
||||
left: var(--hoard-mobile-safe-left);
|
||||
bottom: calc(var(--hoard-mobile-safe-bottom) + var(--space-1));
|
||||
right: var(--ui-mobile-safe-right);
|
||||
left: var(--ui-mobile-safe-left);
|
||||
bottom: calc(var(--ui-mobile-safe-bottom) + var(--space-1));
|
||||
}
|
||||
|
||||
.app-banner-list {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
icon: string
|
||||
title: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="ui-panel ui-empty-state">
|
||||
<span class="ui-icon-tile ui-icon-tile--lg">
|
||||
<v-icon :icon="icon" size="22" />
|
||||
</span>
|
||||
<h2>{{ title }}</h2>
|
||||
<p><slot /></p>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
variant: 'success' | 'danger' | 'warning' | 'info' | 'muted'
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span :class="['ui-status', `ui-status--${variant}`]">
|
||||
<slot />
|
||||
</span>
|
||||
</template>
|
||||
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
initials: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>{{ initials }}</span>
|
||||
</template>
|
||||
+33
-33
@@ -506,7 +506,7 @@ pre {
|
||||
}
|
||||
|
||||
/* ---------- Status pills ---------- */
|
||||
.hoard-status {
|
||||
.ui-status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
@@ -518,7 +518,7 @@ pre {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.hoard-status::before {
|
||||
.ui-status::before {
|
||||
content: '';
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
@@ -527,37 +527,37 @@ pre {
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, currentcolor 22%, transparent);
|
||||
}
|
||||
|
||||
.hoard-status--success {
|
||||
.ui-status--success {
|
||||
color: var(--color-success);
|
||||
background-color: color-mix(in srgb, var(--color-success) 14%, var(--color-surface) 86%);
|
||||
}
|
||||
|
||||
.hoard-status--warning {
|
||||
.ui-status--warning {
|
||||
color: var(--color-warning);
|
||||
background-color: color-mix(in srgb, var(--color-warning) 16%, var(--color-surface) 84%);
|
||||
}
|
||||
|
||||
.hoard-status--danger {
|
||||
.ui-status--danger {
|
||||
color: var(--color-danger);
|
||||
background-color: color-mix(in srgb, var(--color-danger) 14%, var(--color-surface) 86%);
|
||||
}
|
||||
|
||||
.hoard-status--info {
|
||||
.ui-status--info {
|
||||
color: var(--color-info);
|
||||
background-color: color-mix(in srgb, var(--color-info) 14%, var(--color-surface) 86%);
|
||||
}
|
||||
|
||||
.hoard-status--muted {
|
||||
.ui-status--muted {
|
||||
color: var(--color-text-muted);
|
||||
background-color: color-mix(in srgb, var(--color-surface-alt) 70%, var(--color-surface) 30%);
|
||||
}
|
||||
|
||||
.hoard-status--muted::before {
|
||||
.ui-status--muted::before {
|
||||
background-color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
/* ---------- Reusable surfaces ---------- */
|
||||
.hoard-panel {
|
||||
.ui-panel {
|
||||
position: relative;
|
||||
background:
|
||||
linear-gradient(
|
||||
@@ -574,19 +574,19 @@ pre {
|
||||
transform var(--transition-fast);
|
||||
}
|
||||
|
||||
.hoard-panel--elevated {
|
||||
.ui-panel--elevated {
|
||||
background-color: var(--color-surface-elevated);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.hoard-panel--ghost {
|
||||
.ui-panel--ghost {
|
||||
background:
|
||||
color-mix(in srgb, var(--color-surface-alt) 60%, var(--color-surface) 40%);
|
||||
border-color: var(--color-border-subtle);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.hoard-toolbar {
|
||||
.ui-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -598,7 +598,7 @@ pre {
|
||||
border-top-right-radius: inherit;
|
||||
}
|
||||
|
||||
.hoard-list-row {
|
||||
.ui-list-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(220px, 2fr) minmax(120px, 1fr) minmax(100px, 1fr) minmax(120px, 1fr);
|
||||
align-items: center;
|
||||
@@ -610,7 +610,7 @@ pre {
|
||||
transform var(--transition-fast);
|
||||
}
|
||||
|
||||
.hoard-list-row:hover {
|
||||
.ui-list-row:hover {
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--color-primary-100) 32%,
|
||||
@@ -619,7 +619,7 @@ pre {
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
.hoard-list-row.is-selected {
|
||||
.ui-list-row.is-selected {
|
||||
color: var(--color-primary-700);
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
@@ -628,24 +628,24 @@ pre {
|
||||
);
|
||||
}
|
||||
|
||||
.hoard-meta {
|
||||
.ui-meta {
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.hoard-empty-state {
|
||||
.ui-empty-state {
|
||||
padding: var(--space-10) var(--space-6);
|
||||
text-align: center;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.hoard-empty-state h2 {
|
||||
.ui-empty-state h2 {
|
||||
margin-bottom: var(--space-2);
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.hoard-empty-state p {
|
||||
.ui-empty-state p {
|
||||
margin: 0 auto;
|
||||
max-width: 44ch;
|
||||
}
|
||||
@@ -678,7 +678,7 @@ pre {
|
||||
}
|
||||
|
||||
/* ---------- Animations ---------- */
|
||||
@keyframes hoard-soft-enter {
|
||||
@keyframes ui-soft-enter {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
@@ -690,7 +690,7 @@ pre {
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes hoard-pulse-ring {
|
||||
@keyframes ui-pulse-ring {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 color-mix(in srgb, var(--color-primary-300) 35%, transparent);
|
||||
}
|
||||
@@ -716,7 +716,7 @@ pre {
|
||||
|
||||
.v-btn,
|
||||
.v-navigation-drawer .v-list-item,
|
||||
.hoard-list-row {
|
||||
.ui-list-row {
|
||||
transform: none !important;
|
||||
}
|
||||
}
|
||||
@@ -724,13 +724,13 @@ pre {
|
||||
/* ---------- Responsive ---------- */
|
||||
@media (width <= 960px) {
|
||||
:root {
|
||||
--hoard-mobile-safe-left: max(var(--space-2), env(safe-area-inset-left));
|
||||
--hoard-mobile-safe-right: max(var(--space-2), env(safe-area-inset-right));
|
||||
--hoard-mobile-safe-bottom: max(var(--space-3), env(safe-area-inset-bottom));
|
||||
--ui-mobile-safe-left: max(var(--space-2), env(safe-area-inset-left));
|
||||
--ui-mobile-safe-right: max(var(--space-2), env(safe-area-inset-right));
|
||||
--ui-mobile-safe-bottom: max(var(--space-3), env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.v-main {
|
||||
padding-bottom: var(--hoard-mobile-safe-bottom);
|
||||
padding-bottom: var(--ui-mobile-safe-bottom);
|
||||
}
|
||||
|
||||
.v-btn {
|
||||
@@ -747,40 +747,40 @@ pre {
|
||||
margin: 4px var(--space-2);
|
||||
}
|
||||
|
||||
.hoard-list-row {
|
||||
.ui-list-row {
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--space-2);
|
||||
align-items: start;
|
||||
padding-block: var(--space-4);
|
||||
}
|
||||
|
||||
.hoard-toolbar {
|
||||
.ui-toolbar {
|
||||
flex-wrap: wrap;
|
||||
align-items: stretch;
|
||||
padding: var(--space-3);
|
||||
}
|
||||
|
||||
.hoard-empty-state {
|
||||
.ui-empty-state {
|
||||
padding: var(--space-8) var(--space-4);
|
||||
}
|
||||
|
||||
.v-navigation-drawer {
|
||||
border-right: none !important;
|
||||
border-top: 1px solid var(--color-border) !important;
|
||||
padding-bottom: var(--hoard-mobile-safe-bottom);
|
||||
padding-bottom: var(--ui-mobile-safe-bottom);
|
||||
}
|
||||
}
|
||||
|
||||
@media (width <= 600px) {
|
||||
.hoard-toolbar {
|
||||
.ui-toolbar {
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.hoard-list-row {
|
||||
.ui-list-row {
|
||||
padding-inline: var(--space-3);
|
||||
}
|
||||
|
||||
.hoard-meta {
|
||||
.ui-meta {
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,8 +72,8 @@ onBeforeUnmount(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-container fluid class="not-found-page hoard-page hoard-page--centered">
|
||||
<section class="not-found-shell hoard-panel hoard-panel-gradient hoard-spotlight hoard-shell-grid">
|
||||
<v-container fluid class="not-found-page ui-page ui-page--centered">
|
||||
<section class="not-found-shell ui-panel ui-panel-gradient ui-spotlight ui-shell-grid">
|
||||
<div class="not-found-visual">
|
||||
<div class="image-frame">
|
||||
<img
|
||||
@@ -86,14 +86,14 @@ onBeforeUnmount(() => {
|
||||
|
||||
<div class="not-found-content">
|
||||
<p class="not-found-code">404</p>
|
||||
<p class="hoard-kicker hoard-kicker--wide">Seite nicht gefunden</p>
|
||||
<p class="ui-kicker ui-kicker--wide">Seite nicht gefunden</p>
|
||||
<h1>Diese Spur führt ins Leere.</h1>
|
||||
<p class="not-found-text">
|
||||
Der Link ist ungültig oder die Seite wurde verschoben. Wir leiten dich gleich zur
|
||||
{{ autoRedirectTargetLabel }} weiter – oder du nutzt direkt einen der Buttons unten.
|
||||
</p>
|
||||
|
||||
<div class="not-found-actions hoard-action-row">
|
||||
<div class="not-found-actions ui-action-row">
|
||||
<v-btn
|
||||
variant="elevated"
|
||||
:prepend-icon="redirectButtonIcon"
|
||||
@@ -117,11 +117,11 @@ onBeforeUnmount(() => {
|
||||
|
||||
<style scoped>
|
||||
.not-found-shell {
|
||||
--hoard-shell-width: 1040px;
|
||||
--hoard-gradient-angle: 130deg;
|
||||
--hoard-gradient-start: color-mix(in srgb, var(--color-primary-100) 60%, var(--color-surface) 40%);
|
||||
--hoard-gradient-end: var(--color-surface);
|
||||
--hoard-gradient-end-stop: 65%;
|
||||
--ui-shell-width: 1040px;
|
||||
--ui-gradient-angle: 130deg;
|
||||
--ui-gradient-start: color-mix(in srgb, var(--color-primary-100) 60%, var(--color-surface) 40%);
|
||||
--ui-gradient-end: var(--color-surface);
|
||||
--ui-gradient-end-stop: 65%;
|
||||
|
||||
grid-template-columns: minmax(260px, 1fr) minmax(320px, 1fr);
|
||||
border-radius: var(--radius-xl);
|
||||
@@ -211,7 +211,7 @@ h1 {
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.not-found-visual,
|
||||
.not-found-content {
|
||||
animation: hoard-soft-enter 320ms both;
|
||||
animation: ui-soft-enter 320ms both;
|
||||
}
|
||||
|
||||
.not-found-content {
|
||||
|
||||
@@ -47,15 +47,15 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-container fluid class="forbidden-page hoard-page hoard-page--centered">
|
||||
<section class="forbidden-shell hoard-panel hoard-panel-gradient hoard-spotlight">
|
||||
<v-container fluid class="forbidden-page ui-page ui-page--centered">
|
||||
<section class="forbidden-shell ui-panel ui-panel-gradient ui-spotlight">
|
||||
<div class="forbidden-icon">
|
||||
<span class="forbidden-icon__halo" aria-hidden="true" />
|
||||
<v-icon icon="mdi-shield-alert-outline" size="40" />
|
||||
</div>
|
||||
|
||||
<header class="forbidden-head">
|
||||
<p class="hoard-kicker hoard-kicker--wide">Fehlende Berechtigung</p>
|
||||
<p class="ui-kicker ui-kicker--wide">Fehlende Berechtigung</p>
|
||||
<h1>Kein Zugriff.</h1>
|
||||
<p>
|
||||
Dein Konto hat aktuell keine ausreichende Rolle, um diese Seite zu sehen. Falls das ein Fehler ist,
|
||||
@@ -63,7 +63,7 @@ onMounted(() => {
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div class="forbidden-actions hoard-action-row">
|
||||
<div class="forbidden-actions ui-action-row">
|
||||
<v-btn
|
||||
variant="elevated"
|
||||
:prepend-icon="primaryActionIcon"
|
||||
@@ -83,14 +83,14 @@ onMounted(() => {
|
||||
|
||||
<style scoped>
|
||||
.forbidden-page {
|
||||
--hoard-centered-offset: 200px;
|
||||
--ui-centered-offset: 200px;
|
||||
}
|
||||
|
||||
.forbidden-shell {
|
||||
--hoard-gradient-angle: 130deg;
|
||||
--hoard-gradient-start: color-mix(in srgb, var(--color-warning) 14%, var(--color-surface) 86%);
|
||||
--hoard-gradient-end: var(--color-surface);
|
||||
--hoard-gradient-end-stop: 65%;
|
||||
--ui-gradient-angle: 130deg;
|
||||
--ui-gradient-start: color-mix(in srgb, var(--color-warning) 14%, var(--color-surface) 86%);
|
||||
--ui-gradient-end: var(--color-surface);
|
||||
--ui-gradient-end-stop: 65%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -152,7 +152,7 @@ onMounted(() => {
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.forbidden-shell {
|
||||
animation: hoard-soft-enter 280ms both;
|
||||
animation: ui-soft-enter 280ms both;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+35
-35
@@ -71,10 +71,10 @@ const techStack = [
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-container fluid class="landing-page hoard-page">
|
||||
<section class="hero hoard-panel hoard-panel-gradient hoard-spotlight">
|
||||
<v-container fluid class="landing-page ui-page">
|
||||
<section class="hero ui-panel ui-panel-gradient ui-spotlight">
|
||||
<div class="hero-copy">
|
||||
<p class="hero-kicker hoard-kicker hoard-kicker--wide">Self-hosted Datei-Workspace</p>
|
||||
<p class="hero-kicker ui-kicker ui-kicker--wide">Self-hosted Datei-Workspace</p>
|
||||
<h1>
|
||||
Eine ruhige Heimat für deine
|
||||
<span class="hero-accent">Dateien, Ordner und Notizen.</span>
|
||||
@@ -84,7 +84,7 @@ const techStack = [
|
||||
Daten, Struktur und Workflow behalten wollen – ohne Cloud-Lock-in, ohne SaaS-Abo.
|
||||
</p>
|
||||
|
||||
<div class="hero-actions hoard-action-row">
|
||||
<div class="hero-actions ui-action-row">
|
||||
<v-btn variant="elevated" size="large" prepend-icon="mdi-login" to="/login">
|
||||
Anmelden
|
||||
</v-btn>
|
||||
@@ -94,13 +94,13 @@ const techStack = [
|
||||
</div>
|
||||
|
||||
<div class="hero-tags">
|
||||
<span class="hoard-chip hoard-chip--brand">
|
||||
<span class="ui-chip ui-chip--brand">
|
||||
<v-icon icon="mdi-shield-check-outline" size="14" /> Datenhoheit
|
||||
</span>
|
||||
<span class="hoard-chip">
|
||||
<span class="ui-chip">
|
||||
<v-icon icon="mdi-account-multiple-outline" size="14" /> Mehrbenutzerfähig
|
||||
</span>
|
||||
<span class="hoard-chip">
|
||||
<span class="ui-chip">
|
||||
<v-icon icon="mdi-weather-night" size="14" /> Light- und Dark-Mode
|
||||
</span>
|
||||
</div>
|
||||
@@ -120,7 +120,7 @@ const techStack = [
|
||||
<div class="preview-window__body">
|
||||
<header class="preview-toolbar">
|
||||
<span class="preview-toolbar__title">Dokumentation</span>
|
||||
<span class="hoard-chip hoard-chip--brand">
|
||||
<span class="ui-chip ui-chip--brand">
|
||||
<v-icon icon="mdi-folder-outline" size="14" /> 3 Ordner · 12 Dateien
|
||||
</span>
|
||||
</header>
|
||||
@@ -134,7 +134,7 @@ const techStack = [
|
||||
<p class="preview-row__title">Konzepte</p>
|
||||
<p class="preview-row__meta">Ordner · vor 2 Tagen</p>
|
||||
</div>
|
||||
<span class="hoard-status hoard-status--muted">Geteilt</span>
|
||||
<span class="ui-status ui-status--muted">Geteilt</span>
|
||||
</article>
|
||||
|
||||
<article class="preview-row preview-row--active">
|
||||
@@ -145,7 +145,7 @@ const techStack = [
|
||||
<p class="preview-row__title">roadmap.md</p>
|
||||
<p class="preview-row__meta">Markdown · 18 KB</p>
|
||||
</div>
|
||||
<span class="hoard-status hoard-status--success">Editor</span>
|
||||
<span class="ui-status ui-status--success">Editor</span>
|
||||
</article>
|
||||
|
||||
<article class="preview-row">
|
||||
@@ -156,7 +156,7 @@ const techStack = [
|
||||
<p class="preview-row__title">api-reference.pdf</p>
|
||||
<p class="preview-row__meta">PDF · 1,2 MB</p>
|
||||
</div>
|
||||
<span class="hoard-status hoard-status--info">Vorschau</span>
|
||||
<span class="ui-status ui-status--info">Vorschau</span>
|
||||
</article>
|
||||
|
||||
<article class="preview-row">
|
||||
@@ -167,7 +167,7 @@ const techStack = [
|
||||
<p class="preview-row__title">screen-2026-04.png</p>
|
||||
<p class="preview-row__meta">Bild · 480 KB</p>
|
||||
</div>
|
||||
<span class="hoard-status hoard-status--muted">Bereit</span>
|
||||
<span class="ui-status ui-status--muted">Bereit</span>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
@@ -180,8 +180,8 @@ const techStack = [
|
||||
</section>
|
||||
|
||||
<section class="value-grid">
|
||||
<article v-for="item in valueProps" :key="item.title" class="value-card hoard-panel">
|
||||
<span class="hoard-icon-tile hoard-icon-tile--lg">
|
||||
<article v-for="item in valueProps" :key="item.title" class="value-card ui-panel">
|
||||
<span class="ui-icon-tile ui-icon-tile--lg">
|
||||
<v-icon :icon="item.icon" size="22" />
|
||||
</span>
|
||||
<h2>{{ item.title }}</h2>
|
||||
@@ -189,16 +189,16 @@ const techStack = [
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="feature-section hoard-panel">
|
||||
<header class="hoard-section-head">
|
||||
<p class="hoard-kicker">Für den Produktivalltag</p>
|
||||
<section class="feature-section ui-panel">
|
||||
<header class="ui-section-head">
|
||||
<p class="ui-kicker">Für den Produktivalltag</p>
|
||||
<h2>Weniger Tool-Chaos, mehr Fokus auf Inhalte.</h2>
|
||||
<p>Hoard bündelt Datei- und Markdown-Workflows in einer ruhigen Oberfläche, die nicht ablenkt.</p>
|
||||
</header>
|
||||
|
||||
<div class="feature-grid">
|
||||
<article v-for="feature in coreFeatures" :key="feature.title" class="feature-card">
|
||||
<span class="hoard-icon-tile">
|
||||
<span class="ui-icon-tile">
|
||||
<v-icon :icon="feature.icon" size="20" />
|
||||
</span>
|
||||
<div>
|
||||
@@ -210,12 +210,12 @@ const techStack = [
|
||||
</section>
|
||||
|
||||
<section class="workflow-section">
|
||||
<header class="hoard-section-head">
|
||||
<p class="hoard-kicker">So funktioniert Hoard</p>
|
||||
<header class="ui-section-head">
|
||||
<p class="ui-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">
|
||||
<article v-for="step in workflowSteps" :key="step.number" class="workflow-card ui-panel">
|
||||
<p class="workflow-number">{{ step.number }}</p>
|
||||
<h3>{{ step.title }}</h3>
|
||||
<p>{{ step.text }}</p>
|
||||
@@ -223,9 +223,9 @@ const techStack = [
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="stack-section hoard-panel hoard-panel-gradient">
|
||||
<section class="stack-section ui-panel ui-panel-gradient">
|
||||
<div class="stack-copy">
|
||||
<p class="hoard-kicker">Technische Basis</p>
|
||||
<p class="ui-kicker">Technische Basis</p>
|
||||
<h2>Schlank gebaut für ein realistisches MVP.</h2>
|
||||
<p>
|
||||
Hoard kombiniert einen modernen Frontend-Stack mit einem pragmatischen Backend-Setup,
|
||||
@@ -244,15 +244,15 @@ const techStack = [
|
||||
|
||||
<style scoped>
|
||||
.landing-page {
|
||||
--hoard-page-width: 1200px;
|
||||
--ui-page-width: 1200px;
|
||||
}
|
||||
|
||||
/* ---------- Hero ---------- */
|
||||
.hero {
|
||||
--hoard-gradient-angle: 130deg;
|
||||
--hoard-gradient-start: color-mix(in srgb, var(--color-primary-100) 70%, var(--color-surface) 30%);
|
||||
--hoard-gradient-end: var(--color-surface);
|
||||
--hoard-gradient-end-stop: 60%;
|
||||
--ui-gradient-angle: 130deg;
|
||||
--ui-gradient-start: color-mix(in srgb, var(--color-primary-100) 70%, var(--color-surface) 30%);
|
||||
--ui-gradient-end: var(--color-surface);
|
||||
--ui-gradient-end-stop: 60%;
|
||||
|
||||
position: relative;
|
||||
display: grid;
|
||||
@@ -586,10 +586,10 @@ h1 {
|
||||
|
||||
/* ---------- Stack ---------- */
|
||||
.stack-section {
|
||||
--hoard-gradient-angle: 110deg;
|
||||
--hoard-gradient-start: color-mix(in srgb, var(--color-primary-100) 50%, var(--color-surface) 50%);
|
||||
--hoard-gradient-end: var(--color-surface);
|
||||
--hoard-gradient-end-stop: 70%;
|
||||
--ui-gradient-angle: 110deg;
|
||||
--ui-gradient-start: color-mix(in srgb, var(--color-primary-100) 50%, var(--color-surface) 50%);
|
||||
--ui-gradient-end: var(--color-surface);
|
||||
--ui-gradient-end-stop: 70%;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
|
||||
@@ -648,7 +648,7 @@ h1 {
|
||||
.feature-section,
|
||||
.workflow-section,
|
||||
.stack-section {
|
||||
animation: hoard-soft-enter 320ms both;
|
||||
animation: ui-soft-enter 320ms both;
|
||||
}
|
||||
|
||||
.hero-preview {
|
||||
@@ -673,7 +673,7 @@ h1 {
|
||||
}
|
||||
|
||||
.preview-row {
|
||||
animation: hoard-soft-enter 240ms both;
|
||||
animation: ui-soft-enter 240ms both;
|
||||
}
|
||||
|
||||
.preview-row:nth-child(2) {
|
||||
@@ -778,7 +778,7 @@ h1 {
|
||||
grid-template-columns: auto 1fr;
|
||||
}
|
||||
|
||||
.preview-row .hoard-status {
|
||||
.preview-row .ui-status {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ const registerDetails = [
|
||||
|
||||
const contactDetails = [
|
||||
{ label: 'Telefon', value: '+49 30 1234567-0', href: 'tel:+493012345670' },
|
||||
{ label: 'E-Mail', value: 'kontakt@hoard-demo.de', href: 'mailto:kontakt@hoard-demo.de' },
|
||||
{ label: 'Support', value: 'support@hoard-demo.de', href: 'mailto:support@hoard-demo.de' },
|
||||
{ label: 'E-Mail', value: 'kontakt@ui-demo.de', href: 'mailto:kontakt@ui-demo.de' },
|
||||
{ label: 'Support', value: 'support@ui-demo.de', href: 'mailto:support@ui-demo.de' },
|
||||
]
|
||||
|
||||
const legalNotes = [
|
||||
@@ -42,10 +42,10 @@ const legalNotes = [
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-container fluid class="impressum-page hoard-page">
|
||||
<section class="impressum-hero hoard-panel hoard-panel-gradient hoard-spotlight">
|
||||
<v-container fluid class="impressum-page ui-page">
|
||||
<section class="impressum-hero ui-panel ui-panel-gradient ui-spotlight">
|
||||
<div class="impressum-hero__copy">
|
||||
<p class="hoard-kicker hoard-kicker--wide">Rechtliche Angaben</p>
|
||||
<p class="ui-kicker ui-kicker--wide">Rechtliche Angaben</p>
|
||||
<h1>Impressum</h1>
|
||||
<p class="impressum-hero__lead">
|
||||
Diese Seite ist im Hoard-Design aufgebaut und mit Testdaten gefüllt. Vor produktivem
|
||||
@@ -53,16 +53,16 @@ const legalNotes = [
|
||||
</p>
|
||||
|
||||
<div class="impressum-hero__meta">
|
||||
<span class="hoard-chip hoard-chip--brand">
|
||||
<span class="ui-chip ui-chip--brand">
|
||||
<v-icon icon="mdi-information-outline" size="14" /> Testdaten
|
||||
</span>
|
||||
<span class="hoard-chip">
|
||||
<span class="ui-chip">
|
||||
<v-icon icon="mdi-calendar-month-outline" size="14" /> Stand: 26. April 2026
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="impressum-hero__actions hoard-action-row">
|
||||
<div class="impressum-hero__actions ui-action-row">
|
||||
<v-btn variant="elevated" prepend-icon="mdi-home-outline" to="/welcome">
|
||||
Zur Startseite
|
||||
</v-btn>
|
||||
@@ -71,9 +71,9 @@ const legalNotes = [
|
||||
</section>
|
||||
|
||||
<section class="details-grid">
|
||||
<article class="detail-card hoard-panel">
|
||||
<article class="detail-card ui-panel">
|
||||
<header class="detail-card__head">
|
||||
<span class="hoard-icon-tile"><v-icon icon="mdi-domain" size="20" /></span>
|
||||
<span class="ui-icon-tile"><v-icon icon="mdi-domain" size="20" /></span>
|
||||
<h2>Anbieterangaben</h2>
|
||||
</header>
|
||||
<dl class="detail-list">
|
||||
@@ -84,9 +84,9 @@ const legalNotes = [
|
||||
</dl>
|
||||
</article>
|
||||
|
||||
<article class="detail-card hoard-panel">
|
||||
<article class="detail-card ui-panel">
|
||||
<header class="detail-card__head">
|
||||
<span class="hoard-icon-tile"><v-icon icon="mdi-email-outline" size="20" /></span>
|
||||
<span class="ui-icon-tile"><v-icon icon="mdi-email-outline" size="20" /></span>
|
||||
<h2>Kontakt</h2>
|
||||
</header>
|
||||
<dl class="detail-list">
|
||||
@@ -99,9 +99,9 @@ const legalNotes = [
|
||||
</dl>
|
||||
</article>
|
||||
|
||||
<article class="detail-card hoard-panel">
|
||||
<article class="detail-card ui-panel">
|
||||
<header class="detail-card__head">
|
||||
<span class="hoard-icon-tile"><v-icon icon="mdi-clipboard-text-outline" size="20" /></span>
|
||||
<span class="ui-icon-tile"><v-icon icon="mdi-clipboard-text-outline" size="20" /></span>
|
||||
<h2>Register & Steuer</h2>
|
||||
</header>
|
||||
<dl class="detail-list">
|
||||
@@ -113,16 +113,16 @@ const legalNotes = [
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="notes-section hoard-panel">
|
||||
<header class="hoard-section-head">
|
||||
<p class="hoard-kicker">Rechtliche Hinweise</p>
|
||||
<section class="notes-section ui-panel">
|
||||
<header class="ui-section-head">
|
||||
<p class="ui-kicker">Rechtliche Hinweise</p>
|
||||
<h2>Wichtige Zusatzinformationen</h2>
|
||||
<p>Standardklauseln, die im Produktivbetrieb durch eine juristische Prüfung ersetzt werden sollten.</p>
|
||||
</header>
|
||||
|
||||
<div class="notes-grid">
|
||||
<article v-for="note in legalNotes" :key="note.title" class="note-card">
|
||||
<span class="hoard-icon-tile">
|
||||
<span class="ui-icon-tile">
|
||||
<v-icon :icon="note.icon" size="20" />
|
||||
</span>
|
||||
<div>
|
||||
@@ -137,15 +137,15 @@ const legalNotes = [
|
||||
|
||||
<style scoped>
|
||||
.impressum-page {
|
||||
--hoard-page-width: 1180px;
|
||||
--ui-page-width: 1180px;
|
||||
}
|
||||
|
||||
/* ---------- Hero ---------- */
|
||||
.impressum-hero {
|
||||
--hoard-gradient-angle: 130deg;
|
||||
--hoard-gradient-start: color-mix(in srgb, var(--color-primary-100) 60%, var(--color-surface) 40%);
|
||||
--hoard-gradient-end: var(--color-surface);
|
||||
--hoard-gradient-end-stop: 65%;
|
||||
--ui-gradient-angle: 130deg;
|
||||
--ui-gradient-start: color-mix(in srgb, var(--color-primary-100) 60%, var(--color-surface) 40%);
|
||||
--ui-gradient-end: var(--color-surface);
|
||||
--ui-gradient-end-stop: 65%;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.2fr) auto;
|
||||
@@ -296,7 +296,7 @@ dd {
|
||||
.impressum-hero,
|
||||
.detail-card,
|
||||
.notes-section {
|
||||
animation: hoard-soft-enter 280ms both;
|
||||
animation: ui-soft-enter 280ms both;
|
||||
}
|
||||
|
||||
.detail-card:nth-child(2) { animation-delay: 80ms; }
|
||||
|
||||
@@ -4,6 +4,8 @@ import { useRoute, useRouter } from 'vue-router'
|
||||
import { fetchAdminUserById, type AdminUser } from '@/services/adminUsers'
|
||||
import { AuthRequestError } from '@/services/authSession'
|
||||
import { useAppBannersStore } from '@/stores/appBanners'
|
||||
import StatusPill from '@/components/ui/StatusPill.vue'
|
||||
import UserAvatar from '@/components/ui/UserAvatar.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -95,15 +97,15 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-container fluid class="admin-user-detail-page hoard-page">
|
||||
<header class="admin-user-detail-head hoard-panel hoard-panel-gradient">
|
||||
<v-container fluid class="admin-user-detail-page ui-page">
|
||||
<header class="admin-user-detail-head ui-panel ui-panel-gradient">
|
||||
<div class="admin-user-detail-head__copy">
|
||||
<p class="hoard-kicker hoard-kicker--wide">Adminbereich</p>
|
||||
<p class="ui-kicker ui-kicker--wide">Adminbereich</p>
|
||||
<h1>Benutzerdetails</h1>
|
||||
<p>Read-only Ansicht des ausgewählten Kontos. Änderungen erfolgen aktuell außerhalb der App.</p>
|
||||
</div>
|
||||
|
||||
<div class="admin-user-detail-head__actions hoard-action-row">
|
||||
<div class="admin-user-detail-head__actions ui-action-row">
|
||||
<v-btn
|
||||
variant="outlined"
|
||||
prepend-icon="mdi-arrow-left"
|
||||
@@ -129,35 +131,25 @@ onMounted(() => {
|
||||
|
||||
<p v-else-if="isLoading" class="admin-user-detail-loading">Benutzerdetails werden geladen …</p>
|
||||
|
||||
<article v-else-if="user" class="admin-user-detail-card hoard-panel">
|
||||
<article v-else-if="user" class="admin-user-detail-card ui-panel">
|
||||
<header class="admin-user-detail-card__head">
|
||||
<span class="admin-user-detail-avatar">{{ userInitials }}</span>
|
||||
<UserAvatar class="admin-user-detail-avatar" :initials="userInitials" />
|
||||
<div>
|
||||
<p class="hoard-kicker hoard-kicker--xs">Konto</p>
|
||||
<p class="ui-kicker ui-kicker--xs">Konto</p>
|
||||
<h2>{{ user.userName || '(ohne Benutzername)' }}</h2>
|
||||
<p class="admin-user-detail-card__id">{{ user.id }}</p>
|
||||
</div>
|
||||
<div class="admin-user-detail-card__pills">
|
||||
<span
|
||||
:class="[
|
||||
'hoard-status',
|
||||
user.isActive ? 'hoard-status--success' : 'hoard-status--danger',
|
||||
]"
|
||||
>
|
||||
<StatusPill :variant="user.isActive ? 'success' : 'danger'">
|
||||
{{ user.isActive ? 'Aktiv' : 'Inaktiv' }}
|
||||
</span>
|
||||
<span
|
||||
:class="[
|
||||
'hoard-status',
|
||||
user.mustChangePassword ? 'hoard-status--warning' : 'hoard-status--info',
|
||||
]"
|
||||
>
|
||||
</StatusPill>
|
||||
<StatusPill :variant="user.mustChangePassword ? 'warning' : 'info'">
|
||||
{{ user.mustChangePassword ? 'Passwort wechseln' : 'Passwort aktuell' }}
|
||||
</span>
|
||||
</StatusPill>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<hr class="hoard-divider-soft" />
|
||||
<hr class="ui-divider-soft" />
|
||||
|
||||
<dl class="admin-user-detail-grid">
|
||||
<div class="admin-user-detail-item">
|
||||
@@ -187,14 +179,14 @@ onMounted(() => {
|
||||
|
||||
<style scoped>
|
||||
.admin-user-detail-page {
|
||||
--hoard-page-width: 1080px;
|
||||
--ui-page-width: 1080px;
|
||||
}
|
||||
|
||||
.admin-user-detail-head {
|
||||
--hoard-gradient-angle: 120deg;
|
||||
--hoard-gradient-start: color-mix(in srgb, var(--color-primary-100) 55%, var(--color-surface) 45%);
|
||||
--hoard-gradient-end: var(--color-surface);
|
||||
--hoard-gradient-end-stop: 65%;
|
||||
--ui-gradient-angle: 120deg;
|
||||
--ui-gradient-start: color-mix(in srgb, var(--color-primary-100) 55%, var(--color-surface) 45%);
|
||||
--ui-gradient-end: var(--color-surface);
|
||||
--ui-gradient-end-stop: 65%;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.1fr) auto;
|
||||
@@ -315,7 +307,7 @@ onMounted(() => {
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.admin-user-detail-head,
|
||||
.admin-user-detail-card {
|
||||
animation: hoard-soft-enter 280ms both;
|
||||
animation: ui-soft-enter 280ms both;
|
||||
}
|
||||
|
||||
.admin-user-detail-card {
|
||||
|
||||
@@ -4,6 +4,9 @@ import { useRouter } from 'vue-router'
|
||||
import { fetchAdminUsers, type AdminUser } from '@/services/adminUsers'
|
||||
import { AuthRequestError } from '@/services/authSession'
|
||||
import { useAppBannersStore } from '@/stores/appBanners'
|
||||
import StatusPill from '@/components/ui/StatusPill.vue'
|
||||
import UserAvatar from '@/components/ui/UserAvatar.vue'
|
||||
import EmptyState from '@/components/ui/EmptyState.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const appBannersStore = useAppBannersStore()
|
||||
@@ -111,15 +114,15 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-container fluid class="admin-users-page hoard-page">
|
||||
<header class="admin-users-header hoard-panel hoard-panel-gradient">
|
||||
<v-container fluid class="admin-users-page ui-page">
|
||||
<header class="admin-users-header ui-panel ui-panel-gradient">
|
||||
<div class="admin-users-header__copy">
|
||||
<p class="hoard-kicker hoard-kicker--wide">Adminbereich</p>
|
||||
<p class="ui-kicker ui-kicker--wide">Adminbereich</p>
|
||||
<h1>Benutzerverwaltung</h1>
|
||||
<p>Alle Hoard-Konten mit Rollen, Status und Passwortwechselpflicht – read-only.</p>
|
||||
</div>
|
||||
|
||||
<div class="admin-users-header__actions hoard-action-row">
|
||||
<div class="admin-users-header__actions ui-action-row">
|
||||
<v-text-field
|
||||
v-model="searchQuery"
|
||||
variant="outlined"
|
||||
@@ -144,7 +147,7 @@ onMounted(() => {
|
||||
|
||||
<section v-if="!isLoading && !errorMessage" class="admin-users-stats" aria-label="Benutzerübersicht">
|
||||
<article class="admin-users-stat">
|
||||
<span class="hoard-icon-tile">
|
||||
<span class="ui-icon-tile">
|
||||
<v-icon icon="mdi-account-group-outline" size="20" />
|
||||
</span>
|
||||
<div>
|
||||
@@ -153,7 +156,7 @@ onMounted(() => {
|
||||
</div>
|
||||
</article>
|
||||
<article class="admin-users-stat">
|
||||
<span class="hoard-icon-tile">
|
||||
<span class="ui-icon-tile">
|
||||
<v-icon icon="mdi-account-check-outline" size="20" />
|
||||
</span>
|
||||
<div>
|
||||
@@ -162,7 +165,7 @@ onMounted(() => {
|
||||
</div>
|
||||
</article>
|
||||
<article class="admin-users-stat">
|
||||
<span class="hoard-icon-tile">
|
||||
<span class="ui-icon-tile">
|
||||
<v-icon icon="mdi-shield-account-outline" size="20" />
|
||||
</span>
|
||||
<div>
|
||||
@@ -171,7 +174,7 @@ onMounted(() => {
|
||||
</div>
|
||||
</article>
|
||||
<article class="admin-users-stat">
|
||||
<span class="hoard-icon-tile">
|
||||
<span class="ui-icon-tile">
|
||||
<v-icon icon="mdi-lock-reset" size="20" />
|
||||
</span>
|
||||
<div>
|
||||
@@ -187,16 +190,16 @@ onMounted(() => {
|
||||
|
||||
<p v-else-if="isLoading" class="admin-users-loading">Benutzer werden geladen …</p>
|
||||
|
||||
<section v-else-if="!hasUsers" class="hoard-panel hoard-empty-state">
|
||||
<span class="hoard-icon-tile hoard-icon-tile--lg">
|
||||
<v-icon icon="mdi-account-question-outline" size="22" />
|
||||
</span>
|
||||
<h2>Keine Benutzer gefunden</h2>
|
||||
<p>Aktuell sind keine Konten vorhanden. Ein neuer Account muss vom Admin manuell angelegt werden.</p>
|
||||
</section>
|
||||
<EmptyState
|
||||
v-else-if="!hasUsers"
|
||||
icon="mdi-account-question-outline"
|
||||
title="Keine Benutzer gefunden"
|
||||
>
|
||||
Aktuell sind keine Konten vorhanden. Ein neuer Account muss vom Admin manuell angelegt werden.
|
||||
</EmptyState>
|
||||
|
||||
<section v-else class="admin-users-listing hoard-panel">
|
||||
<header class="admin-users-listing__head hoard-toolbar">
|
||||
<section v-else class="admin-users-listing ui-panel">
|
||||
<header class="admin-users-listing__head ui-toolbar">
|
||||
<div>
|
||||
<p class="admin-users-listing__title">Benutzer</p>
|
||||
<p class="admin-users-listing__meta">{{ filteredUsers.length }} von {{ users.length }} angezeigt</p>
|
||||
@@ -222,7 +225,7 @@ onMounted(() => {
|
||||
<tr v-for="user in filteredUsers" :key="user.id">
|
||||
<td>
|
||||
<div class="admin-users-cell-user">
|
||||
<span class="admin-users-cell-user__avatar">{{ userInitials(user) }}</span>
|
||||
<UserAvatar class="admin-users-cell-user__avatar" :initials="userInitials(user)" />
|
||||
<div>
|
||||
<p class="admin-users-cell-user__name">{{ user.userName || '(ohne Benutzername)' }}</p>
|
||||
<p class="admin-users-cell-user__id">{{ user.id }}</p>
|
||||
@@ -231,24 +234,14 @@ onMounted(() => {
|
||||
</td>
|
||||
<td>{{ formatRoles(user.roles) }}</td>
|
||||
<td>
|
||||
<span
|
||||
:class="[
|
||||
'hoard-status',
|
||||
user.isActive ? 'hoard-status--success' : 'hoard-status--danger',
|
||||
]"
|
||||
>
|
||||
<StatusPill :variant="user.isActive ? 'success' : 'danger'">
|
||||
{{ user.isActive ? 'Aktiv' : 'Inaktiv' }}
|
||||
</span>
|
||||
</StatusPill>
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
:class="[
|
||||
'hoard-status',
|
||||
user.mustChangePassword ? 'hoard-status--warning' : 'hoard-status--info',
|
||||
]"
|
||||
>
|
||||
<StatusPill :variant="user.mustChangePassword ? 'warning' : 'info'">
|
||||
{{ user.mustChangePassword ? 'Erforderlich' : 'Aktuell' }}
|
||||
</span>
|
||||
</StatusPill>
|
||||
</td>
|
||||
<td class="admin-users-col-actions">
|
||||
<v-btn
|
||||
@@ -268,7 +261,7 @@ onMounted(() => {
|
||||
<div v-if="filteredUsers.length > 0" class="admin-users-mobile-list" aria-label="Benutzerliste">
|
||||
<article v-for="user in filteredUsers" :key="user.id" class="admin-users-mobile-card">
|
||||
<header class="admin-users-mobile-head">
|
||||
<span class="admin-users-mobile-avatar">{{ userInitials(user) }}</span>
|
||||
<UserAvatar class="admin-users-mobile-avatar" :initials="userInitials(user)" />
|
||||
<div>
|
||||
<p class="admin-users-mobile-label">Benutzer</p>
|
||||
<h2>{{ user.userName || '(ohne Benutzername)' }}</h2>
|
||||
@@ -284,27 +277,17 @@ onMounted(() => {
|
||||
<div>
|
||||
<dt>Aktiv</dt>
|
||||
<dd>
|
||||
<span
|
||||
:class="[
|
||||
'hoard-status',
|
||||
user.isActive ? 'hoard-status--success' : 'hoard-status--danger',
|
||||
]"
|
||||
>
|
||||
<StatusPill :variant="user.isActive ? 'success' : 'danger'">
|
||||
{{ user.isActive ? 'Aktiv' : 'Inaktiv' }}
|
||||
</span>
|
||||
</StatusPill>
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Passwortwechsel</dt>
|
||||
<dd>
|
||||
<span
|
||||
:class="[
|
||||
'hoard-status',
|
||||
user.mustChangePassword ? 'hoard-status--warning' : 'hoard-status--info',
|
||||
]"
|
||||
>
|
||||
<StatusPill :variant="user.mustChangePassword ? 'warning' : 'info'">
|
||||
{{ user.mustChangePassword ? 'Erforderlich' : 'Nein' }}
|
||||
</span>
|
||||
</StatusPill>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
@@ -325,15 +308,15 @@ onMounted(() => {
|
||||
|
||||
<style scoped>
|
||||
.admin-users-page {
|
||||
--hoard-page-width: 1200px;
|
||||
--ui-page-width: 1200px;
|
||||
}
|
||||
|
||||
/* ---------- Header ---------- */
|
||||
.admin-users-header {
|
||||
--hoard-gradient-angle: 120deg;
|
||||
--hoard-gradient-start: color-mix(in srgb, var(--color-primary-100) 55%, var(--color-surface) 45%);
|
||||
--hoard-gradient-end: var(--color-surface);
|
||||
--hoard-gradient-end-stop: 65%;
|
||||
--ui-gradient-angle: 120deg;
|
||||
--ui-gradient-start: color-mix(in srgb, var(--color-primary-100) 55%, var(--color-surface) 45%);
|
||||
--ui-gradient-end: var(--color-surface);
|
||||
--ui-gradient-end-stop: 65%;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
|
||||
@@ -505,7 +488,7 @@ onMounted(() => {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.hoard-empty-state {
|
||||
.ui-empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@@ -516,7 +499,7 @@ onMounted(() => {
|
||||
.admin-users-header,
|
||||
.admin-users-stat,
|
||||
.admin-users-listing {
|
||||
animation: hoard-soft-enter 280ms both;
|
||||
animation: ui-soft-enter 280ms both;
|
||||
}
|
||||
|
||||
.admin-users-stat:nth-child(2) { animation-delay: 60ms; }
|
||||
|
||||
@@ -98,14 +98,14 @@ async function handleSubmit() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-container fluid class="change-password-page hoard-page hoard-page--centered">
|
||||
<section class="change-password-shell hoard-panel hoard-shell-grid">
|
||||
<v-container fluid class="change-password-page ui-page ui-page--centered">
|
||||
<section class="change-password-shell ui-panel ui-shell-grid">
|
||||
<header class="change-password-head">
|
||||
<span class="hoard-icon-tile hoard-icon-tile--lg">
|
||||
<span class="ui-icon-tile ui-icon-tile--lg">
|
||||
<v-icon icon="mdi-shield-key-outline" size="24" />
|
||||
</span>
|
||||
<div>
|
||||
<p class="hoard-kicker hoard-kicker--xs">Sicherheitsvorgabe</p>
|
||||
<p class="ui-kicker ui-kicker--xs">Sicherheitsvorgabe</p>
|
||||
<h1>Passwort ändern</h1>
|
||||
<p>Aktualisiere dein Hoard-Passwort. Nach der Änderung wirst du erneut zur Anmeldung weitergeleitet.</p>
|
||||
</div>
|
||||
@@ -121,7 +121,7 @@ async function handleSubmit() {
|
||||
|
||||
<v-form class="change-password-form" @submit.prevent="handleSubmit">
|
||||
<section class="change-password-section">
|
||||
<p class="hoard-kicker hoard-kicker--xs hoard-kicker--plain">Aktuell</p>
|
||||
<p class="ui-kicker ui-kicker--xs ui-kicker--plain">Aktuell</p>
|
||||
<v-text-field
|
||||
v-model="oldPassword"
|
||||
label="Altes Passwort"
|
||||
@@ -135,10 +135,10 @@ async function handleSubmit() {
|
||||
/>
|
||||
</section>
|
||||
|
||||
<hr class="hoard-divider-soft" />
|
||||
<hr class="ui-divider-soft" />
|
||||
|
||||
<section class="change-password-section">
|
||||
<p class="hoard-kicker hoard-kicker--xs hoard-kicker--plain">Neues Passwort</p>
|
||||
<p class="ui-kicker ui-kicker--xs ui-kicker--plain">Neues Passwort</p>
|
||||
|
||||
<v-text-field
|
||||
v-model="newPassword"
|
||||
@@ -204,7 +204,7 @@ async function handleSubmit() {
|
||||
|
||||
<style scoped>
|
||||
.change-password-page {
|
||||
--hoard-shell-width: min(720px, 100%);
|
||||
--ui-shell-width: min(720px, 100%);
|
||||
}
|
||||
|
||||
.change-password-shell {
|
||||
@@ -286,7 +286,7 @@ async function handleSubmit() {
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.change-password-shell {
|
||||
animation: hoard-soft-enter 280ms both;
|
||||
animation: ui-soft-enter 280ms both;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -80,15 +80,15 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-container fluid class="login-page hoard-page hoard-page--centered">
|
||||
<section class="login-shell hoard-panel hoard-panel-gradient hoard-spotlight">
|
||||
<v-container fluid class="login-page ui-page ui-page--centered">
|
||||
<section class="login-shell ui-panel ui-panel-gradient ui-spotlight">
|
||||
<aside class="login-brand">
|
||||
<div class="login-brand__logo">
|
||||
<span class="login-brand__halo" aria-hidden="true" />
|
||||
<img :src="iconImage" alt="Hoard Icon" />
|
||||
</div>
|
||||
|
||||
<p class="hoard-kicker hoard-kicker--wide">Willkommen bei Hoard</p>
|
||||
<p class="ui-kicker ui-kicker--wide">Willkommen bei Hoard</p>
|
||||
<h1>
|
||||
Deine Dateien.<br />
|
||||
<span class="login-brand__accent">Aufgeräumt.</span>
|
||||
@@ -100,21 +100,21 @@ onMounted(() => {
|
||||
|
||||
<ul class="login-points">
|
||||
<li>
|
||||
<span class="hoard-icon-tile"><v-icon icon="mdi-folder-outline" size="18" /></span>
|
||||
<span class="ui-icon-tile"><v-icon icon="mdi-folder-outline" size="18" /></span>
|
||||
<div>
|
||||
<p class="login-points__title">Ordner und Dateien</p>
|
||||
<p class="login-points__text">Zentral organisieren, schnell finden, sauber strukturieren.</p>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<span class="hoard-icon-tile"><v-icon icon="mdi-language-markdown-outline" size="18" /></span>
|
||||
<span class="ui-icon-tile"><v-icon icon="mdi-language-markdown-outline" size="18" /></span>
|
||||
<div>
|
||||
<p class="login-points__title">Markdown direkt im Browser</p>
|
||||
<p class="login-points__text">Notizen lesen und bearbeiten, ohne externes Tool.</p>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<span class="hoard-icon-tile"><v-icon icon="mdi-shield-check-outline" size="18" /></span>
|
||||
<span class="ui-icon-tile"><v-icon icon="mdi-shield-check-outline" size="18" /></span>
|
||||
<div>
|
||||
<p class="login-points__title">Self-hosted & sicher</p>
|
||||
<p class="login-points__text">Cookie-Auth, Rollenmodell, deine Infrastruktur.</p>
|
||||
@@ -125,7 +125,7 @@ onMounted(() => {
|
||||
|
||||
<v-form class="login-form" @submit.prevent="handleSubmit">
|
||||
<header class="login-form__head">
|
||||
<p class="hoard-kicker hoard-kicker--xs">Login</p>
|
||||
<p class="ui-kicker ui-kicker--xs">Login</p>
|
||||
<h2>Anmelden</h2>
|
||||
<p>Melde dich mit deinem bestehenden Hoard-Konto an.</p>
|
||||
</header>
|
||||
@@ -181,19 +181,21 @@ onMounted(() => {
|
||||
|
||||
<style scoped>
|
||||
.login-page {
|
||||
--hoard-centered-offset: 200px;
|
||||
--ui-centered-offset: 200px;
|
||||
}
|
||||
|
||||
.login-shell {
|
||||
--hoard-shell-width: 1080px;
|
||||
--hoard-shell-padding: 0;
|
||||
--hoard-shell-gap: 0;
|
||||
--hoard-gradient-angle: 120deg;
|
||||
--hoard-gradient-start: color-mix(in srgb, var(--color-primary-100) 70%, var(--color-surface) 30%);
|
||||
--hoard-gradient-end: var(--color-surface);
|
||||
--hoard-gradient-end-stop: 60%;
|
||||
--ui-shell-width: 1080px;
|
||||
--ui-shell-padding: 0;
|
||||
--ui-shell-gap: 0;
|
||||
--ui-gradient-angle: 120deg;
|
||||
--ui-gradient-start: color-mix(in srgb, var(--color-primary-100) 70%, var(--color-surface) 30%);
|
||||
--ui-gradient-end: var(--color-surface);
|
||||
--ui-gradient-end-stop: 60%;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: minmax(280px, 1.1fr) minmax(320px, 460px);
|
||||
align-items: stretch;
|
||||
border-radius: var(--radius-xl);
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -356,7 +358,7 @@ h1 {
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.login-brand,
|
||||
.login-form {
|
||||
animation: hoard-soft-enter 320ms both;
|
||||
animation: ui-soft-enter 320ms both;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
@@ -366,6 +368,7 @@ h1 {
|
||||
|
||||
@media (width <= 960px) {
|
||||
.login-shell {
|
||||
display: block;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import { computed, onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import { AuthRequestError, fetchCurrentUser, type CurrentUser } from '@/services/authSession'
|
||||
import StatusPill from '@/components/ui/StatusPill.vue'
|
||||
import UserAvatar from '@/components/ui/UserAvatar.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const isLoading = ref(true)
|
||||
@@ -94,10 +96,10 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-container fluid class="dashboard-page hoard-page">
|
||||
<header class="dashboard-greeting hoard-panel hoard-panel-gradient hoard-spotlight">
|
||||
<v-container fluid class="dashboard-page ui-page">
|
||||
<header class="dashboard-greeting ui-panel ui-panel-gradient ui-spotlight">
|
||||
<div class="dashboard-greeting__copy">
|
||||
<p class="hoard-kicker hoard-kicker--wide">Geschützter Bereich</p>
|
||||
<p class="ui-kicker ui-kicker--wide">Geschützter Bereich</p>
|
||||
<h1>
|
||||
<template v-if="user">Hallo, {{ user.userName || 'willkommen' }}.</template>
|
||||
<template v-else>Dashboard</template>
|
||||
@@ -108,18 +110,18 @@ onMounted(() => {
|
||||
</p>
|
||||
|
||||
<div class="dashboard-greeting__chips">
|
||||
<span v-for="role in roleChips" :key="role" class="hoard-chip hoard-chip--brand">
|
||||
<span v-for="role in roleChips" :key="role" class="ui-chip ui-chip--brand">
|
||||
<v-icon icon="mdi-shield-account-outline" size="14" />
|
||||
{{ role }}
|
||||
</span>
|
||||
<span v-if="roleChips.length === 0" class="hoard-chip">
|
||||
<span v-if="roleChips.length === 0" class="ui-chip">
|
||||
<v-icon icon="mdi-account-outline" size="14" /> Keine Rollen
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="user" class="dashboard-greeting__avatar">
|
||||
<span class="dashboard-avatar">{{ userInitials }}</span>
|
||||
<UserAvatar class="dashboard-avatar" :initials="userInitials" />
|
||||
<p class="dashboard-avatar__caption">{{ user.userName }}</p>
|
||||
</div>
|
||||
</header>
|
||||
@@ -133,9 +135,9 @@ onMounted(() => {
|
||||
</v-alert>
|
||||
|
||||
<section v-else class="dashboard-grid">
|
||||
<article class="dashboard-stat hoard-panel">
|
||||
<article class="dashboard-stat ui-panel">
|
||||
<div class="dashboard-stat__head">
|
||||
<span class="hoard-icon-tile">
|
||||
<span class="ui-icon-tile">
|
||||
<v-icon icon="mdi-account-outline" size="20" />
|
||||
</span>
|
||||
<p class="dashboard-stat__label">Konto</p>
|
||||
@@ -144,9 +146,9 @@ onMounted(() => {
|
||||
<p class="dashboard-stat__hint">Angemeldet als interner Benutzer</p>
|
||||
</article>
|
||||
|
||||
<article class="dashboard-stat hoard-panel">
|
||||
<article class="dashboard-stat ui-panel">
|
||||
<div class="dashboard-stat__head">
|
||||
<span class="hoard-icon-tile">
|
||||
<span class="ui-icon-tile">
|
||||
<v-icon icon="mdi-shield-account-outline" size="20" />
|
||||
</span>
|
||||
<p class="dashboard-stat__label">Rollen</p>
|
||||
@@ -155,34 +157,30 @@ onMounted(() => {
|
||||
<p class="dashboard-stat__hint">Definiert deine sichtbaren Bereiche</p>
|
||||
</article>
|
||||
|
||||
<article class="dashboard-stat hoard-panel">
|
||||
<article class="dashboard-stat ui-panel">
|
||||
<div class="dashboard-stat__head">
|
||||
<span class="hoard-icon-tile">
|
||||
<span class="ui-icon-tile">
|
||||
<v-icon icon="mdi-pulse" size="20" />
|
||||
</span>
|
||||
<p class="dashboard-stat__label">Status</p>
|
||||
</div>
|
||||
<div class="dashboard-stat__pill-row">
|
||||
<span :class="['hoard-status', `hoard-status--${accountStatusVariant}`]">
|
||||
{{ accountStatusLabel }}
|
||||
</span>
|
||||
<span :class="['hoard-status', `hoard-status--${passwordStatusVariant}`]">
|
||||
{{ passwordStatusLabel }}
|
||||
</span>
|
||||
<StatusPill :variant="accountStatusVariant">{{ accountStatusLabel }}</StatusPill>
|
||||
<StatusPill :variant="passwordStatusVariant">{{ passwordStatusLabel }}</StatusPill>
|
||||
</div>
|
||||
<p class="dashboard-stat__hint">Konto- und Passwortzustand</p>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section v-if="!errorMessage" class="dashboard-detail hoard-panel">
|
||||
<section v-if="!errorMessage" class="dashboard-detail ui-panel">
|
||||
<header class="dashboard-detail__head">
|
||||
<div>
|
||||
<p class="hoard-kicker">Auth-Antwort</p>
|
||||
<p class="ui-kicker">Auth-Antwort</p>
|
||||
<h2>Aktueller Session-Snapshot</h2>
|
||||
<p>Direkt aus <code>GET /auth/me</code> – nützlich zum Debuggen und für Plausibilitätschecks.</p>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-detail__actions hoard-action-row">
|
||||
<div class="dashboard-detail__actions ui-action-row">
|
||||
<v-btn
|
||||
variant="outlined"
|
||||
prepend-icon="mdi-lock-reset"
|
||||
@@ -210,15 +208,15 @@ onMounted(() => {
|
||||
|
||||
<style scoped>
|
||||
.dashboard-page {
|
||||
--hoard-page-width: 1120px;
|
||||
--ui-page-width: 1120px;
|
||||
}
|
||||
|
||||
/* ---------- Greeting ---------- */
|
||||
.dashboard-greeting {
|
||||
--hoard-gradient-angle: 120deg;
|
||||
--hoard-gradient-start: color-mix(in srgb, var(--color-primary-100) 60%, var(--color-surface) 40%);
|
||||
--hoard-gradient-end: var(--color-surface);
|
||||
--hoard-gradient-end-stop: 65%;
|
||||
--ui-gradient-angle: 120deg;
|
||||
--ui-gradient-start: color-mix(in srgb, var(--color-primary-100) 60%, var(--color-surface) 40%);
|
||||
--ui-gradient-end: var(--color-surface);
|
||||
--ui-gradient-end-stop: 65%;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
@@ -394,7 +392,7 @@ h1 {
|
||||
.dashboard-greeting,
|
||||
.dashboard-stat,
|
||||
.dashboard-detail {
|
||||
animation: hoard-soft-enter 280ms both;
|
||||
animation: ui-soft-enter 280ms both;
|
||||
}
|
||||
|
||||
.dashboard-stat:nth-child(2) {
|
||||
|
||||
@@ -2,54 +2,54 @@
|
||||
Hoard – Page-Layout-Primitives
|
||||
============================================================================= */
|
||||
|
||||
.hoard-page {
|
||||
.ui-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--hoard-page-gap, var(--space-6));
|
||||
gap: var(--ui-page-gap, var(--space-6));
|
||||
margin-inline: auto;
|
||||
width: min(100%, var(--hoard-page-width, 1180px));
|
||||
width: min(100%, var(--ui-page-width, 1180px));
|
||||
padding-block:
|
||||
var(--hoard-page-padding-start, var(--space-5))
|
||||
var(--hoard-page-padding-end, var(--space-12));
|
||||
var(--ui-page-padding-start, var(--space-5))
|
||||
var(--ui-page-padding-end, var(--space-12));
|
||||
}
|
||||
|
||||
.hoard-page--centered {
|
||||
.ui-page--centered {
|
||||
width: 100%;
|
||||
margin-inline: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: calc(100vh - var(--hoard-centered-offset, 220px));
|
||||
padding: var(--hoard-centered-padding, var(--space-10) var(--space-4));
|
||||
min-height: calc(100vh - var(--ui-centered-offset, 220px));
|
||||
padding: var(--ui-centered-padding, var(--space-10) var(--space-4));
|
||||
}
|
||||
|
||||
.hoard-shell-grid {
|
||||
.ui-shell-grid {
|
||||
display: grid;
|
||||
gap: var(--hoard-shell-gap, var(--space-8));
|
||||
width: min(100%, var(--hoard-shell-width, 1080px));
|
||||
padding: var(--hoard-shell-padding, var(--space-8));
|
||||
gap: var(--ui-shell-gap, var(--space-8));
|
||||
width: min(100%, var(--ui-shell-width, 1080px));
|
||||
padding: var(--ui-shell-padding, var(--space-8));
|
||||
}
|
||||
|
||||
.hoard-page-header {
|
||||
.ui-page-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
max-width: 78ch;
|
||||
}
|
||||
|
||||
.hoard-page-header > h1 {
|
||||
.ui-page-header > h1 {
|
||||
margin: 0;
|
||||
font-size: var(--font-size-3xl);
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.hoard-page-header > p {
|
||||
.ui-page-header > p {
|
||||
margin: 0;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--font-size-lg);
|
||||
line-height: var(--line-height-loose);
|
||||
}
|
||||
|
||||
.hoard-page-header__meta {
|
||||
.ui-page-header__meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-2);
|
||||
@@ -57,57 +57,57 @@
|
||||
}
|
||||
|
||||
@media (width <= 960px) {
|
||||
.hoard-page {
|
||||
.ui-page {
|
||||
width: 100%;
|
||||
gap: var(--hoard-page-gap-mobile, var(--space-5));
|
||||
gap: var(--ui-page-gap-mobile, var(--space-5));
|
||||
padding-inline:
|
||||
var(--hoard-page-padding-inline-start-mobile, max(var(--space-3), env(safe-area-inset-left)))
|
||||
var(--hoard-page-padding-inline-end-mobile, max(var(--space-3), env(safe-area-inset-right)));
|
||||
var(--ui-page-padding-inline-start-mobile, max(var(--space-3), env(safe-area-inset-left)))
|
||||
var(--ui-page-padding-inline-end-mobile, max(var(--space-3), env(safe-area-inset-right)));
|
||||
padding-block:
|
||||
var(--hoard-page-padding-start-mobile, var(--space-3))
|
||||
var(--hoard-page-padding-end-mobile, var(--space-8));
|
||||
var(--ui-page-padding-start-mobile, var(--space-3))
|
||||
var(--ui-page-padding-end-mobile, var(--space-8));
|
||||
}
|
||||
|
||||
.hoard-page--centered {
|
||||
.ui-page--centered {
|
||||
width: 100%;
|
||||
min-height: calc(100vh - var(--hoard-centered-offset-mobile, 200px));
|
||||
padding: var(--hoard-centered-padding-mobile, var(--space-6) var(--space-3));
|
||||
min-height: calc(100vh - var(--ui-centered-offset-mobile, 200px));
|
||||
padding: var(--ui-centered-padding-mobile, var(--space-6) var(--space-3));
|
||||
}
|
||||
|
||||
.hoard-shell-grid {
|
||||
.ui-shell-grid {
|
||||
width: 100%;
|
||||
gap: var(--hoard-shell-gap-mobile, var(--space-5));
|
||||
gap: var(--ui-shell-gap-mobile, var(--space-5));
|
||||
padding:
|
||||
var(--hoard-shell-padding-block-mobile, var(--space-6))
|
||||
var(--hoard-shell-padding-inline-mobile, var(--space-5));
|
||||
var(--ui-shell-padding-block-mobile, var(--space-6))
|
||||
var(--ui-shell-padding-inline-mobile, var(--space-5));
|
||||
}
|
||||
|
||||
.hoard-page-header > h1 {
|
||||
.ui-page-header > h1 {
|
||||
font-size: var(--font-size-2xl);
|
||||
}
|
||||
|
||||
.hoard-page-header > p {
|
||||
.ui-page-header > p {
|
||||
font-size: var(--font-size-md);
|
||||
}
|
||||
}
|
||||
|
||||
@media (width <= 600px) {
|
||||
.hoard-page {
|
||||
gap: var(--hoard-page-gap-mobile-xs, var(--space-4));
|
||||
.ui-page {
|
||||
gap: var(--ui-page-gap-mobile-xs, var(--space-4));
|
||||
padding-block:
|
||||
var(--hoard-page-padding-start-mobile-xs, var(--space-2))
|
||||
var(--hoard-page-padding-end-mobile-xs, var(--space-6));
|
||||
var(--ui-page-padding-start-mobile-xs, var(--space-2))
|
||||
var(--ui-page-padding-end-mobile-xs, var(--space-6));
|
||||
}
|
||||
|
||||
.hoard-page--centered {
|
||||
min-height: calc(100vh - var(--hoard-centered-offset-mobile-xs, 180px));
|
||||
padding: var(--hoard-centered-padding-mobile-xs, var(--space-5) var(--space-3));
|
||||
.ui-page--centered {
|
||||
min-height: calc(100vh - var(--ui-centered-offset-mobile-xs, 180px));
|
||||
padding: var(--ui-centered-padding-mobile-xs, var(--space-5) var(--space-3));
|
||||
}
|
||||
|
||||
.hoard-shell-grid {
|
||||
gap: var(--hoard-shell-gap-mobile-xs, var(--space-4));
|
||||
.ui-shell-grid {
|
||||
gap: var(--ui-shell-gap-mobile-xs, var(--space-4));
|
||||
padding:
|
||||
var(--hoard-shell-padding-block-mobile-xs, var(--space-5))
|
||||
var(--hoard-shell-padding-inline-mobile-xs, var(--space-4));
|
||||
var(--ui-shell-padding-block-mobile-xs, var(--space-5))
|
||||
var(--ui-shell-padding-inline-mobile-xs, var(--space-4));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Hoard – Surface- und Inhaltspattern
|
||||
============================================================================= */
|
||||
|
||||
.hoard-kicker {
|
||||
.ui-kicker {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
@@ -14,7 +14,7 @@
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.hoard-kicker::before {
|
||||
.ui-kicker::before {
|
||||
content: '';
|
||||
width: 18px;
|
||||
height: 1px;
|
||||
@@ -22,32 +22,32 @@
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.hoard-kicker--wide {
|
||||
.ui-kicker--wide {
|
||||
letter-spacing: 0.12em;
|
||||
}
|
||||
|
||||
.hoard-kicker--xs {
|
||||
.ui-kicker--xs {
|
||||
margin-bottom: var(--space-2);
|
||||
font-size: var(--font-size-2xs);
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
|
||||
.hoard-kicker--plain::before {
|
||||
.ui-kicker--plain::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hoard-action-row {
|
||||
.ui-action-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.hoard-action-row--end {
|
||||
.ui-action-row--end {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.hoard-panel-gradient {
|
||||
.ui-panel-gradient {
|
||||
background:
|
||||
radial-gradient(
|
||||
120% 80% at 100% 0%,
|
||||
@@ -55,21 +55,21 @@
|
||||
transparent 60%
|
||||
),
|
||||
linear-gradient(
|
||||
var(--hoard-gradient-angle, 130deg),
|
||||
var(--hoard-gradient-start, color-mix(in srgb, var(--color-primary-100) 65%, var(--color-surface) 35%))
|
||||
var(--ui-gradient-angle, 130deg),
|
||||
var(--ui-gradient-start, color-mix(in srgb, var(--color-primary-100) 65%, var(--color-surface) 35%))
|
||||
0%,
|
||||
var(--hoard-gradient-end, var(--color-surface)) var(--hoard-gradient-end-stop, 60%)
|
||||
var(--ui-gradient-end, var(--color-surface)) var(--ui-gradient-end-stop, 60%)
|
||||
);
|
||||
}
|
||||
|
||||
.hoard-spotlight {
|
||||
.ui-spotlight {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
.hoard-spotlight::before,
|
||||
.hoard-spotlight::after {
|
||||
.ui-spotlight::before,
|
||||
.ui-spotlight::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
@@ -78,7 +78,7 @@
|
||||
filter: blur(60px);
|
||||
}
|
||||
|
||||
.hoard-spotlight::before {
|
||||
.ui-spotlight::before {
|
||||
inset: -10% auto auto -10%;
|
||||
width: 360px;
|
||||
height: 360px;
|
||||
@@ -86,7 +86,7 @@
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.hoard-spotlight::after {
|
||||
.ui-spotlight::after {
|
||||
inset: auto -10% -10% auto;
|
||||
width: 320px;
|
||||
height: 320px;
|
||||
@@ -94,17 +94,17 @@
|
||||
opacity: 0.35;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .hoard-spotlight::before {
|
||||
[data-theme='dark'] .ui-spotlight::before {
|
||||
background: color-mix(in srgb, var(--color-primary-700) 50%, transparent);
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .hoard-spotlight::after {
|
||||
[data-theme='dark'] .ui-spotlight::after {
|
||||
background: color-mix(in srgb, var(--color-primary-600) 30%, transparent);
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.hoard-chip {
|
||||
.ui-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
@@ -119,23 +119,23 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.hoard-chip--brand {
|
||||
.ui-chip--brand {
|
||||
border-color: color-mix(in srgb, var(--color-primary-300) 60%, var(--color-border) 40%);
|
||||
background-color: color-mix(in srgb, var(--color-primary-100) 70%, var(--color-surface) 30%);
|
||||
color: var(--color-primary-700);
|
||||
}
|
||||
|
||||
.hoard-chip--ghost {
|
||||
.ui-chip--ghost {
|
||||
border-color: var(--color-border-subtle);
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.hoard-chip > .v-icon {
|
||||
.ui-chip > .v-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.hoard-icon-tile {
|
||||
.ui-icon-tile {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -153,19 +153,19 @@
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.hoard-icon-tile--lg {
|
||||
.ui-icon-tile--lg {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
.hoard-icon-tile--ghost {
|
||||
.ui-icon-tile--ghost {
|
||||
color: var(--color-text-secondary);
|
||||
background: var(--color-surface);
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
.hoard-divider-soft {
|
||||
.ui-divider-soft {
|
||||
border: 0;
|
||||
height: 1px;
|
||||
background:
|
||||
@@ -179,7 +179,7 @@
|
||||
);
|
||||
}
|
||||
|
||||
.hoard-section-head {
|
||||
.ui-section-head {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
@@ -187,24 +187,24 @@
|
||||
max-width: 70ch;
|
||||
}
|
||||
|
||||
.hoard-section-head > h2 {
|
||||
.ui-section-head > h2 {
|
||||
margin: 0;
|
||||
font-size: var(--font-size-2xl);
|
||||
letter-spacing: -0.015em;
|
||||
}
|
||||
|
||||
.hoard-section-head > p {
|
||||
.ui-section-head > p {
|
||||
margin: 0;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
@media (width <= 960px) {
|
||||
.hoard-action-row {
|
||||
.ui-action-row {
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.hoard-spotlight::before,
|
||||
.hoard-spotlight::after {
|
||||
.ui-spotlight::before,
|
||||
.ui-spotlight::after {
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
filter: blur(48px);
|
||||
@@ -212,21 +212,21 @@
|
||||
}
|
||||
|
||||
@media (width <= 600px) {
|
||||
.hoard-kicker {
|
||||
.ui-kicker {
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.hoard-action-row {
|
||||
.ui-action-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hoard-action-row > * {
|
||||
.ui-action-row > * {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hoard-section-head > h2 {
|
||||
.ui-section-head > h2 {
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-6
@@ -126,7 +126,7 @@ Modulare Skala in 4er-Schritten:
|
||||
--space-16: 64px
|
||||
```
|
||||
|
||||
Hauptseiten: `--hoard-page-width` 1120–1200px, Padding orientiert sich an `--space-6`/`--space-8`.
|
||||
Hauptseiten: `--ui-page-width` 1120–1200px, Padding orientiert sich an `--space-6`/`--space-8`.
|
||||
|
||||
## Formensprache (Border-Radien)
|
||||
- `--radius-xs`: 6px – kleine Pills, Status-Badges.
|
||||
@@ -214,9 +214,9 @@ Regeln:
|
||||
- Hover: sehr subtile Primary-Tint-Tönung.
|
||||
- Selected Row: Primary-100 Hintergrund, `--color-primary-700` Text.
|
||||
- Border-Bottom in `--color-border-subtle`.
|
||||
- `hoard-list-row` (Datei-/Item-Zeilen): luftiges Padding, klare Spalten, `transform: translateX(2px)` beim Hover.
|
||||
- `ui-list-row` (Datei-/Item-Zeilen): luftiges Padding, klare Spalten, `transform: translateX(2px)` beim Hover.
|
||||
|
||||
### Status-Pills (`hoard-status`)
|
||||
### Status-Pills (`ui-status`)
|
||||
- Kompakt, `--radius-xs`, mit dezentem Text/Background-Tint pro Status (Success/Info/Warning/Danger/Neutral).
|
||||
- Optional kleines Punktindikator-Dot vor dem Text.
|
||||
|
||||
@@ -227,11 +227,11 @@ Regeln:
|
||||
- Enter/Leave: 180 ms Fade + 10 px Y.
|
||||
|
||||
## Hero-/Marketing-Bereiche
|
||||
- Erlauben einen **ambient Verlauf** (`hoard-panel-gradient` + Variablen).
|
||||
- Erlauben einen **ambient Verlauf** (`ui-panel-gradient` + Variablen).
|
||||
- Optional dezenter „Spotlight"-Glow (radialer Verlauf, `--color-primary-300` mit niedriger Opazität, blur).
|
||||
- Inhaltsbreite max ~64ch.
|
||||
- Display-Headlines mit `--font-size-3xl` oder `--font-size-display` (nur Hero).
|
||||
- Hero-Tags (`hoard-chip`/`hoard-tag`) als kompakte, dezent gerahmte Pills.
|
||||
- Hero-Tags (`ui-chip`/`ui-tag`) als kompakte, dezent gerahmte Pills.
|
||||
|
||||
## Vorschau-Bereich (Files)
|
||||
- **PDF-Vorschau:** neutraler Hintergrund, weiße „Papier"-Fläche mit `--shadow-md`, genug Rand. Controls minimal.
|
||||
@@ -268,7 +268,7 @@ Desktop ist Hauptfokus, Mobile muss aber sauber funktionieren.
|
||||
|
||||
### Immer
|
||||
- Tokens nutzen (Farben, Spacing, Radius, Shadow).
|
||||
- Patterns wiederverwenden (`hoard-panel`, `hoard-page`, `hoard-action-row`, `hoard-kicker`, `hoard-status`, `hoard-chip`, `hoard-spotlight`).
|
||||
- Patterns wiederverwenden (`ui-panel`, `ui-page`, `ui-action-row`, `ui-kicker`, `ui-status`, `ui-chip`, `ui-spotlight`).
|
||||
- Light- und Dark-Mode gleichwertig prüfen.
|
||||
- Animationen kurz halten und `prefers-reduced-motion` respektieren.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user