Modernize frontend: new design system, redesign all pages

- Convert codexInfo.md to CLAUDE.md as the central project context.
- Rewrite GUI/style.md with a modernized, file-first direction (layered
  surfaces, refined typography, motion budget, ambient gradients).
- Refresh global tokens in global.css, page-layouts.css and
  surface-patterns.css: extended palette (primary 050/800, surface
  elevated, border subtle), four-tier shadow system, dark-mode parity,
  new utilities (hoard-chip, hoard-icon-tile, hoard-spotlight,
  hoard-section-head, hoard-divider-soft, status pulse dot).
- Rebuild Layout.vue: premium app shell with brand halo, animated
  active-indicator, account pill with avatar/initials, drawer footer
  card, refined banner stack and footer.
- Redesign every route while preserving routing and API contracts:
  Home, Login, ChangePassword, Dashboard, AdminUsers, AdminUserDetail,
  Impressum, 404, Forbidden. Adds search/admin stats, password hint
  list, dashboard greeting with avatar, modernized hero/spotlight
  treatments and consistent mobile layouts (safe-areas, 44/48px tap
  targets).
- Drop @fontsource/roboto import in vuetify.ts and load Inter via the
  rsms.me CSS in index.html; update Vuetify defaults and palette to
  match the new tokens.
This commit is contained in:
Claude
2026-04-26 15:24:52 +00:00
parent 10bf4b94ad
commit 6740038e9a
18 changed files with 3478 additions and 1881 deletions
+98 -14
View File
@@ -8,6 +8,9 @@ const isLoading = ref(true)
const isAuthenticated = ref(false)
const primaryActionLabel = computed(() => (isAuthenticated.value ? 'Zum Dashboard' : 'Zum Login'))
const primaryActionIcon = computed(() =>
isAuthenticated.value ? 'mdi-view-dashboard-outline' : 'mdi-login',
)
async function resolveAuthState() {
try {
@@ -29,6 +32,15 @@ async function navigatePrimaryAction() {
await router.replace({ name: 'Login' })
}
async function navigateBack() {
if (window.history.length > 1) {
router.back()
return
}
await router.replace({ name: isAuthenticated.value ? 'Dashboard' : 'Home' })
}
onMounted(() => {
void resolveAuthState()
})
@@ -36,23 +48,34 @@ onMounted(() => {
<template>
<v-container fluid class="forbidden-page hoard-page hoard-page--centered">
<section class="forbidden-shell hoard-shell-grid hoard-panel">
<section class="forbidden-shell hoard-panel hoard-panel-gradient hoard-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">Fehlende Berechtigung</p>
<h1>Kein Zugriff</h1>
<p>Dein Konto hat keine ausreichende Rolle für diese Seite.</p>
<p class="hoard-kicker hoard-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,
wende dich an einen Admin oder wechsle zurück in deinen freigegebenen Bereich.
</p>
</header>
<div class="forbidden-actions hoard-action-row">
<v-btn
color="primary"
prepend-icon="mdi-arrow-right"
variant="elevated"
:prepend-icon="primaryActionIcon"
:loading="isLoading"
:disabled="isLoading"
@click="navigatePrimaryAction"
>
{{ primaryActionLabel }}
</v-btn>
<v-btn variant="outlined" prepend-icon="mdi-arrow-left" @click="navigateBack">
Zurück
</v-btn>
</div>
</section>
</v-container>
@@ -60,30 +83,91 @@ onMounted(() => {
<style scoped>
.forbidden-page {
--hoard-shell-width: min(640px, 100%);
--hoard-centered-offset: 200px;
}
.forbidden-shell {
gap: var(--space-4);
--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%;
display: flex;
flex-direction: column;
gap: var(--space-5);
align-items: center;
text-align: center;
width: min(640px, 100%);
padding: var(--space-10) var(--space-7);
border-radius: var(--radius-xl);
}
.forbidden-head h1,
.forbidden-head p {
margin: 0;
.forbidden-icon {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
width: 80px;
height: 80px;
border-radius: var(--radius-full);
background:
linear-gradient(135deg, var(--color-warning), color-mix(in srgb, var(--color-warning) 60%, var(--color-danger) 40%));
color: #fff;
box-shadow:
var(--shadow-md),
inset 0 0 0 2px color-mix(in srgb, var(--color-accent-lime) 18%, transparent);
}
.forbidden-icon__halo {
position: absolute;
inset: -10px;
border-radius: var(--radius-full);
background:
radial-gradient(
closest-side,
color-mix(in srgb, var(--color-warning) 38%, transparent),
transparent 70%
);
filter: blur(12px);
opacity: 0.6;
}
.forbidden-head h1 {
margin-top: var(--space-2);
margin-bottom: var(--space-2);
margin: 0 0 var(--space-2);
font-size: clamp(1.8rem, 1.4rem + 1vw, 2.4rem);
font-weight: 700;
letter-spacing: -0.02em;
}
.forbidden-head p {
margin: 0 auto;
max-width: 50ch;
color: var(--color-text-secondary);
line-height: 1.6;
}
.forbidden-actions {
justify-content: center;
}
@media (prefers-reduced-motion: no-preference) {
.forbidden-shell {
animation: hoard-soft-enter 260ms both;
animation: hoard-soft-enter 280ms both;
}
}
@media (width <= 600px) {
.forbidden-shell {
padding: var(--space-7) var(--space-5);
}
.forbidden-actions {
width: 100%;
}
:deep(.forbidden-actions .v-btn) {
width: 100%;
min-height: 44px;
}
}
</style>