Add global page and surface CSS patterns

Introduce reusable global CSS modules and apply them across routes to centralize layout/surface patterns. Added GUI/src/styles/global/page-layouts.css and GUI/src/styles/global/surface-patterns.css and imported both in GUI/src/main.ts. Updated Layout.vue, Home.vue, 404NotFound.vue, Impressum.vue and authentication/Login.vue to use hoard-* utility classes (hoard-page, hoard-shell-grid, hoard-kicker, hoard-action-row, hoard-panel-gradient, etc.) and removed duplicated scoped styles. Also updated codexInfo.md to document the new CSS modules and provide usage guidance. This reduces per-page CSS duplication and standardizes gradients, spacing and page-shell behavior.
This commit is contained in:
Jonas
2026-04-17 23:42:35 +02:00
parent d8ae756948
commit 8ccc515a7b
9 changed files with 151 additions and 191 deletions
+1 -9
View File
@@ -205,7 +205,7 @@ watch(
:elevation="display.mobile.value ? 6 : 0"
>
<div class="drawer-top">
<p class="drawer-kicker">Navigation</p>
<p class="drawer-kicker hoard-kicker hoard-kicker--xs">Navigation</p>
</div>
<v-list nav density="comfortable" class="px-1">
@@ -344,14 +344,6 @@ watch(
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);
+2
View File
@@ -1,6 +1,8 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import './global.css'
import './styles/global/page-layouts.css'
import './styles/global/surface-patterns.css'
import App from './Layout.vue'
import router from './router'
+11 -41
View File
@@ -16,8 +16,8 @@ function navigateBack() {
</script>
<template>
<v-container fluid class="not-found-page">
<section class="not-found-shell hoard-panel">
<v-container fluid class="not-found-page hoard-page hoard-page--centered">
<section class="not-found-shell hoard-panel hoard-shell-grid hoard-panel-gradient">
<div class="not-found-visual">
<div class="image-frame">
<img :src="notFoundImage" alt="Illustration für eine nicht gefundene Seite" class="not-found-image" />
@@ -25,14 +25,14 @@ function navigateBack() {
</div>
<div class="not-found-content">
<p class="not-found-kicker">Fehler 404</p>
<p class="not-found-kicker hoard-kicker hoard-kicker--wide">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">
<div class="not-found-actions hoard-action-row">
<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>
@@ -42,26 +42,14 @@ function navigateBack() {
</template>
<style scoped>
.not-found-page {
display: flex;
align-items: center;
justify-content: center;
min-height: calc(100vh - 210px);
padding: var(--space-8) var(--space-4);
}
.not-found-shell {
display: grid;
--hoard-shell-width: 980px;
--hoard-gradient-angle: 180deg;
--hoard-gradient-start: color-mix(in srgb, var(--color-surface) 94%, var(--color-primary-100) 6%);
--hoard-gradient-end: color-mix(in srgb, var(--color-surface) 82%, var(--color-surface-alt) 18%);
--hoard-gradient-end-stop: 100%;
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%
);
}
.not-found-visual {
@@ -92,15 +80,6 @@ function navigateBack() {
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);
@@ -115,21 +94,12 @@ h1 {
}
.not-found-actions {
display: flex;
flex-wrap: wrap;
gap: var(--space-3);
align-items: center;
}
@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 {
+13 -39
View File
@@ -62,17 +62,17 @@ const techStack = ['Vue 3', 'ASP.NET Core', 'PostgreSQL', 'MinIO', 'md-editor-v3
</script>
<template>
<v-container fluid class="landing-page">
<section class="hero hoard-panel">
<v-container fluid class="landing-page hoard-page">
<section class="hero hoard-panel hoard-panel-gradient">
<div class="hero-copy">
<p class="hero-kicker">Self-hosted Datei-Workspace</p>
<p class="hero-kicker hoard-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">
<div class="hero-actions hoard-action-row">
<v-btn color="primary" size="large" prepend-icon="mdi-login" to="/login">
Zum Login
</v-btn>
@@ -129,7 +129,7 @@ const techStack = ['Vue 3', 'ASP.NET Core', 'PostgreSQL', 'MinIO', 'md-editor-v3
<section class="feature-section hoard-panel">
<header class="section-head">
<p class="section-kicker">Für den Produktivalltag</p>
<p class="section-kicker hoard-kicker">Für den Produktivalltag</p>
<h2>Weniger Tool-Chaos, mehr Fokus auf Inhalte</h2>
</header>
<div class="feature-grid">
@@ -143,7 +143,7 @@ const techStack = ['Vue 3', 'ASP.NET Core', 'PostgreSQL', 'MinIO', 'md-editor-v3
<section class="workflow-section">
<header class="section-head">
<p class="section-kicker">So funktioniert Hoard</p>
<p class="section-kicker hoard-kicker">So funktioniert Hoard</p>
<h2>In drei klaren Schritten produktiv starten</h2>
</header>
<div class="workflow-grid">
@@ -157,7 +157,7 @@ const techStack = ['Vue 3', 'ASP.NET Core', 'PostgreSQL', 'MinIO', 'md-editor-v3
<section class="stack-section hoard-panel">
<div class="stack-copy">
<p class="section-kicker">Technische Basis</p>
<p class="section-kicker hoard-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,
@@ -173,29 +173,21 @@ const techStack = ['Vue 3', 'ASP.NET Core', 'PostgreSQL', 'MinIO', 'md-editor-v3
<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);
--hoard-page-width: 1180px;
}
.hero {
--hoard-gradient-angle: 120deg;
--hoard-gradient-start: color-mix(in srgb, var(--color-primary-100) 34%, var(--color-surface) 66%);
--hoard-gradient-end: var(--color-surface);
--hoard-gradient-end-stop: 52%;
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,
@@ -204,16 +196,6 @@ const techStack = ['Vue 3', 'ASP.NET Core', 'PostgreSQL', 'MinIO', 'md-editor-v3
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;
@@ -229,9 +211,6 @@ h1 {
}
.hero-actions {
display: flex;
flex-wrap: wrap;
gap: var(--space-3);
margin-bottom: var(--space-4);
}
@@ -414,11 +393,6 @@ h1 {
}
@media (width <= 960px) {
.landing-page {
gap: var(--space-5);
padding-block: var(--space-2) var(--space-6);
}
.hero,
.feature-section,
.stack-section {
+12 -38
View File
@@ -38,10 +38,10 @@ const legalNotes = [
</script>
<template>
<v-container fluid class="impressum-page">
<section class="impressum-hero hoard-panel">
<v-container fluid class="impressum-page hoard-page">
<section class="impressum-hero hoard-panel hoard-panel-gradient">
<div class="hero-copy">
<p class="hero-kicker">Rechtliche Angaben</p>
<p class="hero-kicker hoard-kicker">Rechtliche Angaben</p>
<h1>Impressum</h1>
<p class="hero-lead">
Diese Seite ist im Hoard-Design aufgebaut und mit Testdaten gefüllt. Ersetze die Angaben
@@ -54,7 +54,7 @@ const legalNotes = [
</div>
</div>
<div class="hero-actions">
<div class="hero-actions hoard-action-row">
<v-btn color="primary" prepend-icon="mdi-home" to="/">Zur Startseite</v-btn>
<v-btn variant="outlined" prepend-icon="mdi-login" to="/login">Zum Login</v-btn>
</div>
@@ -96,7 +96,7 @@ const legalNotes = [
<section class="notes-section hoard-panel">
<header class="notes-head">
<p class="notes-kicker">Rechtliche Hinweise</p>
<p class="notes-kicker hoard-kicker">Rechtliche Hinweise</p>
<h2>Wichtige Zusatzinformationen</h2>
</header>
@@ -112,45 +112,27 @@ const legalNotes = [
<style scoped>
.impressum-page {
display: flex;
flex-direction: column;
gap: var(--space-6);
margin-inline: auto;
width: min(100%, 1120px);
padding-block: var(--space-4) var(--space-8);
--hoard-page-width: 1120px;
}
.impressum-hero {
--hoard-gradient-angle: 125deg;
--hoard-gradient-start: color-mix(in srgb, var(--color-primary-100) 40%, var(--color-surface) 60%);
--hoard-gradient-end: var(--color-surface);
--hoard-gradient-end-stop: 56%;
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: var(--space-6);
align-items: end;
padding: var(--space-8);
background:
linear-gradient(
125deg,
color-mix(in srgb, var(--color-primary-100) 40%, var(--color-surface) 60%) 0%,
var(--color-surface) 56%
);
}
.hero-kicker,
.hero-lead,
.meta-text,
.notes-kicker {
.meta-text {
margin: 0;
}
.hero-kicker,
.notes-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-3);
font-size: clamp(1.9rem, 2.2vw + 1rem, 2.5rem);
@@ -187,10 +169,7 @@ h1 {
}
.hero-actions {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: var(--space-3);
}
.details-grid {
@@ -278,11 +257,6 @@ h3 {
}
@media (width <= 960px) {
.impressum-page {
gap: var(--space-5);
padding-block: var(--space-2) var(--space-6);
}
.impressum-hero,
.notes-section {
padding: var(--space-5);
+9 -37
View File
@@ -5,10 +5,10 @@ const showPassword = ref(false)
</script>
<template>
<v-container fluid class="login-page">
<section class="login-shell hoard-panel">
<v-container fluid class="login-page hoard-page hoard-page--centered">
<section class="login-shell hoard-panel hoard-shell-grid hoard-panel-gradient">
<aside class="login-brand">
<p class="login-kicker">Willkommen bei Hoard</p>
<p class="login-kicker hoard-kicker hoard-kicker--wide">Willkommen bei Hoard</p>
<h1>Anmelden und weiterarbeiten</h1>
<p class="login-intro">
Deine Dateiablage bleibt aufgeräumt, schnell und direkt im Browser bedienbar.
@@ -70,35 +70,14 @@ const showPassword = ref(false)
</template>
<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%
);
}
--hoard-shell-width: 1040px;
--hoard-gradient-angle: 115deg;
--hoard-gradient-start: color-mix(in srgb, var(--color-primary-100) 45%, var(--color-surface) 55%);
--hoard-gradient-end: var(--color-surface);
--hoard-gradient-end-stop: 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;
grid-template-columns: minmax(280px, 1fr) minmax(320px, 430px);
}
h1 {
@@ -158,15 +137,8 @@ h1 {
}
@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>
+46
View File
@@ -0,0 +1,46 @@
/* Shared layout primitives for route-level page shells */
.hoard-page {
display: flex;
flex-direction: column;
gap: var(--hoard-page-gap, var(--space-6));
margin-inline: auto;
width: min(100%, var(--hoard-page-width, 1120px));
padding-block:
var(--hoard-page-padding-start, var(--space-4))
var(--hoard-page-padding-end, var(--space-8));
}
.hoard-page--centered {
width: 100%;
margin-inline: 0;
align-items: center;
justify-content: center;
min-height: calc(100vh - var(--hoard-centered-offset, 210px));
padding: var(--hoard-centered-padding, var(--space-8) var(--space-4));
}
.hoard-shell-grid {
display: grid;
gap: var(--hoard-shell-gap, var(--space-8));
width: min(100%, var(--hoard-shell-width, 1040px));
padding: var(--hoard-shell-padding, var(--space-8));
}
@media (width <= 960px) {
.hoard-page {
gap: var(--hoard-page-gap-mobile, var(--space-5));
padding-block:
var(--hoard-page-padding-start-mobile, var(--space-2))
var(--hoard-page-padding-end-mobile, var(--space-6));
}
.hoard-page--centered {
min-height: calc(100vh - var(--hoard-centered-offset-mobile, 180px));
padding: var(--hoard-centered-padding-mobile, var(--space-5) var(--space-2));
}
.hoard-shell-grid {
gap: var(--hoard-shell-gap-mobile, var(--space-5));
padding: var(--hoard-shell-padding-mobile, var(--space-5));
}
}
@@ -0,0 +1,33 @@
/* Shared surface and content patterns */
.hoard-kicker {
margin: 0 0 var(--space-2);
color: var(--color-primary-700);
font-size: var(--font-size-sm);
font-weight: 600;
letter-spacing: 0.05em;
text-transform: uppercase;
}
.hoard-kicker--wide {
letter-spacing: 0.06em;
}
.hoard-kicker--xs {
font-size: var(--font-size-xs);
letter-spacing: 0.04em;
}
.hoard-action-row {
display: flex;
flex-wrap: wrap;
gap: var(--space-3);
}
.hoard-panel-gradient {
background: linear-gradient(
var(--hoard-gradient-angle, 120deg),
var(--hoard-gradient-start, color-mix(in srgb, var(--color-primary-100) 34%, var(--color-surface) 66%))
0%,
var(--hoard-gradient-end, var(--color-surface)) var(--hoard-gradient-end-stop, 52%)
);
}