diff --git a/GUI/src/Layout.vue b/GUI/src/Layout.vue index c15b758..47808cf 100644 --- a/GUI/src/Layout.vue +++ b/GUI/src/Layout.vue @@ -153,51 +153,53 @@ watch( - + -
+

{{ pageName }}

{{ pageDescription }}

- - - {{ themeLabel }} - +
+ + + {{ themeLabel }} + - - - Zum Login - + + + Zum Login + - - - Zum Login - + + + Zum Login + +
Navigation

- +
- Zur Startseite + + Zur Startseite +
@@ -312,6 +316,16 @@ watch( min-width: 0; } +.topbar-context-divider { + display: flex; +} + +.topbar-actions { + display: inline-flex; + align-items: center; + gap: 0; +} + .page-name, .page-description, .drawer-kicker, @@ -335,10 +349,32 @@ watch( text-overflow: ellipsis; } +@media (width <= 1360px) { + .page-description { + display: none; + } + + .page-context { + gap: 0; + } +} + +@media (width <= 1180px) { + .topbar-context-divider { + display: none; + } +} + .hoard-drawer { padding-top: var(--space-2); } +.hoard-drawer--mobile { + border-top-left-radius: var(--radius-lg); + border-top-right-radius: var(--radius-lg); + max-height: min(72vh, 560px); +} + .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%); @@ -395,6 +431,51 @@ watch( } @media (width <= 960px) { + .hoard-app-bar { + padding-inline: + max(var(--space-1), env(safe-area-inset-left)) + max(var(--space-1), env(safe-area-inset-right)); + } + + .brand-button { + min-width: 0; + gap: var(--space-2); + } + + .brand-logo { + width: 40px; + height: 40px; + } + + .brand-title { + font-size: 15px; + } + + .topbar-actions { + gap: 2px; + } + + .topbar-context-divider, + .page-context { + display: none; + } + + .hoard-drawer { + padding-top: var(--space-1); + } + + .drawer-top { + padding: var(--space-3) var(--space-4); + } + + .drawer-bottom { + padding: var(--space-2) var(--space-3) calc(var(--space-3) + env(safe-area-inset-bottom)); + } + + :deep(.drawer-bottom .v-btn) { + min-height: 44px; + } + .main-shell { padding: var(--space-4); } @@ -405,7 +486,36 @@ watch( .hoard-footer { justify-content: center !important; - padding-inline: var(--space-4); + gap: var(--space-2); + padding-inline: + max(var(--space-2), env(safe-area-inset-left)) + max(var(--space-2), env(safe-area-inset-right)); + } + + .footer-links { + width: 100%; + justify-content: center; + gap: var(--space-2); + } +} + +@media (width <= 600px) { + .main-shell { + padding: var(--space-3); + } + + .brand-title { + font-size: var(--font-size-md); + } + + .footer-links { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); + } + + :deep(.footer-links .v-btn) { + width: 100%; + min-height: 44px; } } diff --git a/GUI/src/global.css b/GUI/src/global.css index b344e2b..a78c2eb 100644 --- a/GUI/src/global.css +++ b/GUI/src/global.css @@ -402,14 +402,64 @@ p { } @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)); + } + + .v-main { + padding-bottom: var(--hoard-mobile-safe-bottom); + } + + .v-btn { + min-height: 44px; + } + + .v-btn--icon.v-btn { + width: 44px; + height: 44px; + } + + .v-navigation-drawer .v-list-item { + min-height: 48px; + margin: 4px var(--space-2); + } + .hoard-list-row { grid-template-columns: 1fr; gap: var(--space-2); align-items: start; + padding-block: var(--space-4); + } + + .hoard-toolbar { + flex-wrap: wrap; + align-items: stretch; + padding: var(--space-3); + } + + .hoard-empty-state { + padding: var(--space-6) 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); + } +} + +@media (width <= 600px) { + .hoard-toolbar { + gap: var(--space-2); + } + + .hoard-list-row { + padding-inline: var(--space-3); + } + + .hoard-meta { + font-size: var(--font-size-xs); } } diff --git a/GUI/src/routes/404NotFound.vue b/GUI/src/routes/404NotFound.vue index bc1dab0..2b34d08 100644 --- a/GUI/src/routes/404NotFound.vue +++ b/GUI/src/routes/404NotFound.vue @@ -102,6 +102,10 @@ h1 { grid-template-columns: 1fr; } + .image-frame { + width: min(100%, 320px); + } + .not-found-content { text-align: center; align-items: center; @@ -109,6 +113,42 @@ h1 { .not-found-actions { justify-content: center; + gap: var(--space-2); + } + + :deep(.not-found-actions .v-btn) { + min-height: 44px; + } +} + +@media (width <= 600px) { + .not-found-shell { + --hoard-shell-padding-block-mobile-xs: var(--space-4); + --hoard-shell-padding-inline-mobile-xs: var(--space-3); + } + + h1 { + font-size: clamp(1.5rem, 7vw, 1.9rem); + } + + .not-found-text { + margin-bottom: var(--space-4); + font-size: var(--font-size-md); + } + + .image-frame { + width: min(100%, 260px); + padding: var(--space-3); + } + + .not-found-actions { + width: 100%; + display: grid; + grid-template-columns: 1fr; + } + + :deep(.not-found-actions .v-btn) { + width: 100%; } } diff --git a/GUI/src/routes/Home.vue b/GUI/src/routes/Home.vue index 3b4baba..80e677d 100644 --- a/GUI/src/routes/Home.vue +++ b/GUI/src/routes/Home.vue @@ -399,6 +399,18 @@ h1 { padding: var(--space-5); } + .hero-actions { + gap: var(--space-2); + } + + .hero-preview { + padding: var(--space-4); + } + + .preview-row { + padding: var(--space-4); + } + .value-grid, .feature-grid, .workflow-grid { @@ -409,4 +421,50 @@ h1 { max-width: none; } } + +@media (width <= 600px) { + .hero, + .feature-section, + .stack-section { + padding: var(--space-4); + } + + h1 { + font-size: clamp(1.6rem, 8vw, 2rem); + } + + .hero-lead { + margin-bottom: var(--space-4); + font-size: var(--font-size-md); + } + + .hero-actions { + width: 100%; + } + + :deep(.hero-actions .v-btn) { + width: 100%; + min-height: 44px; + } + + .hero-tags { + gap: 6px; + } + + .hero-tag { + padding: 4px var(--space-2); + font-size: var(--font-size-xs); + } + + .value-card, + .workflow-card, + .feature-card { + padding: var(--space-4); + } + + .stack-pill { + width: 100%; + text-align: center; + } +} diff --git a/GUI/src/routes/Impressum.vue b/GUI/src/routes/Impressum.vue index 748c148..2a72e7e 100644 --- a/GUI/src/routes/Impressum.vue +++ b/GUI/src/routes/Impressum.vue @@ -269,11 +269,48 @@ h3 { .hero-actions { justify-content: flex-start; + gap: var(--space-2); } .details-grid, .notes-grid { grid-template-columns: 1fr; } + + :deep(.hero-actions .v-btn) { + min-height: 44px; + } +} + +@media (width <= 600px) { + .impressum-hero, + .notes-section { + padding: var(--space-4); + } + + h1 { + font-size: clamp(1.55rem, 7vw, 1.95rem); + } + + .hero-meta { + margin-top: var(--space-4); + gap: var(--space-2); + } + + .hero-actions { + width: 100%; + } + + :deep(.hero-actions .v-btn) { + width: 100%; + } + + .detail-card { + padding: var(--space-4); + } + + .note-card { + padding: var(--space-3); + } } diff --git a/GUI/src/routes/authentication/Login.vue b/GUI/src/routes/authentication/Login.vue index 10634a1..24a8b37 100644 --- a/GUI/src/routes/authentication/Login.vue +++ b/GUI/src/routes/authentication/Login.vue @@ -140,5 +140,52 @@ h1 { .login-shell { grid-template-columns: 1fr; } + + .login-form { + padding: var(--space-5); + } + + .form-meta { + flex-wrap: wrap; + } +} + +@media (width <= 600px) { + h1 { + max-width: none; + font-size: clamp(1.55rem, 7vw, 1.95rem); + } + + .login-intro { + margin-bottom: var(--space-4); + } + + .login-points { + gap: var(--space-2); + } + + .login-points li { + align-items: flex-start; + } + + .login-form { + gap: var(--space-3); + padding: var(--space-4); + } + + .form-meta { + flex-direction: column; + align-items: stretch; + gap: var(--space-2); + } + + :deep(.form-meta .v-btn) { + width: 100%; + min-height: 44px; + } + + :deep(.login-form .v-btn) { + min-height: 44px; + } } diff --git a/GUI/src/styles/global/page-layouts.css b/GUI/src/styles/global/page-layouts.css index fb9b2d2..7d17d85 100644 --- a/GUI/src/styles/global/page-layouts.css +++ b/GUI/src/styles/global/page-layouts.css @@ -28,19 +28,48 @@ @media (width <= 960px) { .hoard-page { + width: 100%; gap: var(--hoard-page-gap-mobile, var(--space-5)); + padding-inline: + var(--hoard-page-padding-inline-start-mobile, max(var(--space-2), env(safe-area-inset-left))) + var(--hoard-page-padding-inline-end-mobile, max(var(--space-2), env(safe-area-inset-right))); padding-block: var(--hoard-page-padding-start-mobile, var(--space-2)) var(--hoard-page-padding-end-mobile, var(--space-6)); } .hoard-page--centered { + width: 100%; 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 { + width: 100%; gap: var(--hoard-shell-gap-mobile, var(--space-5)); - padding: var(--hoard-shell-padding-mobile, var(--space-5)); + padding: + var(--hoard-shell-padding-block-mobile, var(--space-5)) + var(--hoard-shell-padding-inline-mobile, var(--space-4)); + } +} + +@media (width <= 600px) { + .hoard-page { + gap: var(--hoard-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-5)); + } + + .hoard-page--centered { + min-height: calc(100vh - var(--hoard-centered-offset-mobile-xs, 164px)); + padding: var(--hoard-centered-padding-mobile-xs, var(--space-4) var(--space-2)); + } + + .hoard-shell-grid { + gap: var(--hoard-shell-gap-mobile-xs, var(--space-4)); + padding: + var(--hoard-shell-padding-block-mobile-xs, var(--space-4)) + var(--hoard-shell-padding-inline-mobile-xs, var(--space-3)); } } diff --git a/GUI/src/styles/global/surface-patterns.css b/GUI/src/styles/global/surface-patterns.css index f7020a6..c45af94 100644 --- a/GUI/src/styles/global/surface-patterns.css +++ b/GUI/src/styles/global/surface-patterns.css @@ -31,3 +31,25 @@ var(--hoard-gradient-end, var(--color-surface)) var(--hoard-gradient-end-stop, 52%) ); } + +@media (width <= 960px) { + .hoard-action-row { + gap: var(--space-2); + } +} + +@media (width <= 600px) { + .hoard-kicker { + margin-bottom: var(--space-1); + } + + .hoard-action-row { + display: grid; + grid-template-columns: 1fr; + width: 100%; + } + + .hoard-action-row > * { + width: 100%; + } +} diff --git a/GUI/style.md b/GUI/style.md index 910ec8b..94e96de 100644 --- a/GUI/style.md +++ b/GUI/style.md @@ -313,6 +313,51 @@ Desktop ist der Hauptfokus. Mobile muss funktionieren, aber nicht die Priorität - Fokus auf Navigation und Öffnen - Bearbeitung von Markdown darf reduziert sein, solange Lesen und einfache Bedienung sauber funktionieren +### Umsetzungsstandard Responsivität (verbindlich) +Die folgenden Regeln bilden den aktuellen Responsive-Standard von Hoard und sollen bei allen kommenden UI-Aufgaben eingehalten werden. + +1. **Desktop-first, Mobile-only Overrides** +- Desktop-Styles bleiben Basis. +- Mobile-Anpassungen ausschließlich in Media Queries, keine Änderungen an Desktop-Baseregeln. + +2. **Breakpoints** +- `@media (width <= 960px)` für Tablet/Mobile-Umbruch (Layout-Stacks, Spacing-Reduktion, Drawer-/Footer-Verhalten). +- `@media (width <= 600px)` für Phone-Feinschliff (volle Breite für CTAs, kompaktere Typo/Abstände). + +3. **Globale Responsive-Patterns zuerst nutzen** +- Wiederverwendbare Anpassungen immer zuerst in den globalen Dateien pflegen: + - `GUI/src/global.css` + - `GUI/src/styles/global/page-layouts.css` + - `GUI/src/styles/global/surface-patterns.css` +- Seiten-spezifisches `scoped` CSS nur für wirklich lokale Sonderfälle. + +4. **Touch-Zielgrößen und Bedienbarkeit** +- Interaktive Elemente mobil mit klarer Daumen-Bedienbarkeit: + - Buttons mindestens `44px` Höhe. + - Icon-Buttons mindestens `44x44px`. + - Navigations-Listeneinträge mindestens `48px` Höhe. +- Aktionszeilen (`hoard-action-row`) auf kleinen Geräten vertikal stapeln, damit Primäraktionen gut erreichbar sind. + +5. **Safe-Area-Unterstützung** +- Bei mobilen Außenabständen `env(safe-area-inset-*)` berücksichtigen (iOS/Android). +- Muster: `max(var(--space-x), env(safe-area-inset-...))` als Fallback-sicherer Abstand. +- Besonders relevant für App-Bar-Ränder, Seiten-Padding und Bottom-Bereiche (Drawer/Footer). + +6. **App-Shell-Muster** +- Mobile Navigation bleibt als Bottom-Sheet-Drawer. +- Desktop-Navigation bleibt unverändert (keine visuelle Regression auf großen Viewports). +- Footer-Links auf kleinen Screens umbauen (Wrap/Grid), mit ausreichend großen Tap-Flächen. + +7. **Seiten-Muster** +- Content-Änderungen vermeiden; nur Layout/Bedienung anpassen. +- Karten-/Grid-Bereiche bei `<= 960px` in Einspalten-Layout überführen. +- Primäre CTAs bei `<= 600px` in voller Breite anzeigen. + +8. **Responsive QA vor Abschluss** +- Pflicht-Viewports: `360x800`, `390x844`, `768x1024`, `1024x768`, `>=1280`. +- Prüfen: Navigation, Scroll-Verhalten, CTA-Erreichbarkeit, Formular-Bedienbarkeit. +- Desktop-Regression-Check: bei `>=1024` darf sich das gewollte Desktop-Erscheinungsbild nicht ändern. + ## Interaktionsprinzipien - Primäraktionen immer klar sichtbar - destruktive Aktionen nie zu nah an Standardaktionen diff --git a/codexInfo.md b/codexInfo.md index 672dcd6..f8fbc9c 100644 --- a/codexInfo.md +++ b/codexInfo.md @@ -85,6 +85,8 @@ Ich baue alleine neben meiner Ausbildung eine einfache self-hosted Web-App für - Das Topbar-Branding nutzt das App-Icon aus `GUI/src/assets/images/icon.svg`. - Globale CSS-Struktur ist aktiv: `GUI/src/global.css` (Tokens/Basis) sowie `GUI/src/styles/global/page-layouts.css` und `GUI/src/styles/global/surface-patterns.css` für wiederverwendbare Patterns. - Sidebar-Sichtbarkeit unterstützt `Visibility.Route` mit optionalem `visibilityRoute` in `GUI/src/plugins/routesLayout.ts`. +- Mobile-Touch-Optimierung ist für alle aktuellen öffentlichen Oberflächen aktiv (Shell, Home, Login, Impressum, 404), inklusive Safe-Area-Unterstützung. +- Desktop-Ansicht bleibt unverändert, da alle neuen Anpassungen ausschließlich in mobilen Breakpoints (`<= 960px`, Feinschliff `<= 600px`) umgesetzt sind. ## Änderungen durch Codex - Grundlegender UI-Neuaufbau der App-Shell (`GUI/src/Layout.vue`) inklusive Navigation, Footer und Seitenkontext. @@ -95,3 +97,8 @@ Ich baue alleine neben meiner Ausbildung eine einfache self-hosted Web-App für - Aufbau und fortlaufende Konsolidierung der globalen CSS-Basis (`global.css`) inkl. Fokus-/Auswahl-Polish. - CSS-Debloat-Refactor: gemeinsame Oberflächen-Patterns in `GUI/src/styles/global/page-layouts.css` und `GUI/src/styles/global/surface-patterns.css` ausgelagert und zentral in `GUI/src/main.ts` eingebunden. - `codexInfo.md` um eine kompakte Nutzunganleitung für die globalen CSS-Patterns ergänzt. +- Mobile-Usability über globale Styles erweitert: größere Touch-Ziele (`v-btn`, Navigationspunkte), Safe-Area-Paddings und mobile Spacing-Feinschliff in `GUI/src/global.css` sowie den globalen Pattern-Dateien. +- `GUI/src/Layout.vue` für Mobile optimiert: entzerrte App-Bar-Abstände, touchfreundlicher Bottom-Sheet-Drawer und besser bedienbarer Footer auf kleinen Viewports. +- Mobile-spezifische Detailoptimierungen in `Home.vue`, `Login.vue`, `Impressum.vue` und `404NotFound.vue` ergänzt (Actions, Card-/Form-Spacing, CTA-Stacking), ohne Desktop-Basislayout zu verändern. +- `GUI/style.md` um einen verbindlichen Abschnitt „Umsetzungsstandard Responsivität“ ergänzt (Breakpoints, Touch-Zielgrößen, Safe-Area, globale Pattern-Nutzung, QA-Checkliste), damit Folgeaufgaben denselben Stil beibehalten. +- Topbar-Kontext in `GUI/src/Layout.vue` für schmalere Breiten beruhigt: auf Mobile wird der Seitenkontext komplett ausgeblendet, auf mittleren Breiten bleibt nur der Seitentitel (ohne Unterzeile), damit das Header-Layout sauber und nicht gequetscht wirkt.