From 2a2249182e89d24ffe9a5c9bddba079f6c769cb0 Mon Sep 17 00:00:00 2001 From: Ulas Date: Mon, 13 Apr 2026 17:11:38 +0200 Subject: [PATCH] feat: Phase 3 - Micro-Interactions + Polish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit glass.css - Phase 3 Ergänzungen: Nav Auto-Hide (Section 18): - .nav-bottom: will-change: transform + transition für smooth slide - .nav-bottom--hidden: translateY(100% + safe-area) + pointer-events: none Modal Spring-Entrance (Section 19): - Desktop: glass-modal-scale-in mit --ease-glass (spring overshoot) 0.32s, scale(0.90) → scale(1) + translateY(8px) → 0 - Mobile: glass-sheet-in, sanfterer Slide (40% statt 100%) + opacity ramp - Beide Animationen ersetzen die linearen layout.css-Varianten Seitentransitionen (Section 20): - In-Animationen: 0.30s mit --ease-glass statt 0.20s ease - Out-Animationen: 0.14s mit --ease-out (schnell raus, langsam rein) List-Stagger (Section 21): - 0.28s + --ease-glass für physikalisch plausibleren Erscheinungseffekt Focus-Ring (Section 22): - transition auf outline-offset + box-shadow für sanften Focus-Pop Skeleton Shimmer (Section 23): - 105° Gradient mit glass-highlight-Tint statt flat-grey - Hellerer Mittelpunkt simuliert Lichtreflexion FAB Attention Pulse (Section 24): - Einmaliger Ring-Expand 0.6s nach Erscheinen (fab-ring-pulse) - Kombinierte animation-Deklaration mit fab-in Accessibility (Section 25): - prefers-reduced-motion deaktiviert alle Phase-3-Animationen router.js: - initNavHideOnScroll(): scroll-Listener auf #main-content versteckt .nav-bottom beim Runterscrollen (+ 4px Hysterese) zeigt wieder beim Hochscrollen (- 4px) oder bei < 10px nur aktiv bei < 1024px (Mobile/Tablet, kein Desktop) - wird in renderAppShell() nach initBottomNavSwipe() aufgerufen --- public/router.js | 29 +++++++ public/styles/glass.css | 177 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 206 insertions(+) diff --git a/public/router.js b/public/router.js index 154ce35..16903a6 100644 --- a/public/router.js +++ b/public/router.js @@ -296,6 +296,35 @@ function renderAppShell(container) { // Bottom-Nav: Scroll-Snap + Dot-Indikator initBottomNavSwipe(container); + + // Bottom-Nav: Auto-Hide beim Runterscrollen (Mobile) + initNavHideOnScroll(container); +} + +/** + * Versteckt die Bottom-Nav beim Runterscrollen, zeigt sie beim Hochscrollen. + * Nur auf Mobile aktiv (< 1024px), da auf Desktop die Sidebar fest sichtbar ist. + */ +function initNavHideOnScroll(container) { + const content = container.querySelector('#main-content'); + const nav = container.querySelector('.nav-bottom'); + if (!content || !nav) return; + + let lastY = 0; + + content.addEventListener('scroll', () => { + if (window.innerWidth >= 1024) return; + + const y = content.scrollTop; + if (y < 10) { + nav.classList.remove('nav-bottom--hidden'); + } else if (y > lastY + 4) { + nav.classList.add('nav-bottom--hidden'); + } else if (y < lastY - 4) { + nav.classList.remove('nav-bottom--hidden'); + } + lastY = y; + }, { passive: true }); } /** diff --git a/public/styles/glass.css b/public/styles/glass.css index 350d48d..ba63fc1 100644 --- a/public/styles/glass.css +++ b/public/styles/glass.css @@ -440,3 +440,180 @@ textarea.form-input { animation-duration: 0.01ms !important; } } + +/* ================================================================ + * PHASE 3 — Micro-Interactions + Polish + * ================================================================ */ + +/* ================================================================ + * 18. Bottom-Nav Auto-Hide on Scroll + * + * .nav-bottom--hidden: Nav gleitet nach unten aus dem Viewport. + * Per JS über scroll-Listener auf .app-content gesetzt. + * FAB bleibt sichtbar (Daumen-Erreichbarkeit wichtiger als Konsistenz). + * ================================================================ */ +.nav-bottom { + transition: + transform var(--transition-base) var(--ease-out), + background-color var(--transition-fast), + box-shadow var(--transition-fast); + will-change: transform; +} + +.nav-bottom--hidden { + transform: translateY(calc(100% + var(--safe-area-inset-bottom))); + pointer-events: none; +} + +/* ================================================================ + * 19. Modal-Entrance — Glass Spring + * + * Overlay: Blur-Ramp statt harten Fade (backdrop-filter via opacity). + * Panel Desktop: Spring-Overshooting statt linearem scale-in. + * Panel Mobile: Spring-Slide-up mit leichtem Bounce. + * ================================================================ */ +@keyframes glass-modal-scale-in { + from { + opacity: 0; + transform: scale(0.90) translateY(8px); + } + to { + opacity: 1; + transform: scale(1) translateY(0); + } +} + +@keyframes glass-sheet-in { + from { + opacity: 0.6; + transform: translateY(40%); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@media (min-width: 768px) { + .modal-panel { + animation: glass-modal-scale-in 0.32s var(--ease-glass) forwards; + } +} + +@media (max-width: 767px) { + .modal-panel { + animation: glass-sheet-in 0.30s var(--ease-glass) forwards; + } +} + +/* ================================================================ + * 20. Seitentransitionen — Spring Easing + * + * Ersetzt die festen 0.2s ease durch --transition-base + --ease-glass + * für eine weichere, physikalisch plausible Bewegung. + * ================================================================ */ +.page-transition--in-right, +.page-transition--in-left { + animation-duration: 0.30s; + animation-timing-function: var(--ease-glass); +} + +.page-transition--out-left, +.page-transition--out-right { + animation-duration: 0.14s; + animation-timing-function: var(--ease-out); +} + +/* ================================================================ + * 21. List-Stagger — Spring Timing + erhöhter Offset + * + * Leicht längere Laufzeit + spring-Easing für einen + * physikalischeren Erscheinungseffekt. + * ================================================================ */ +.list-stagger > * { + animation-duration: 0.28s; + animation-timing-function: var(--ease-glass); +} + +/* ================================================================ + * 22. Focus-Ring — Scale-In Animation + * + * Fokus-Indikator erscheint mit sanftem Scale-Pop statt hart. + * Kein outline-transition (outline ist nicht transition-fähig), + * daher box-shadow als Alternative für focus-visible. + * ================================================================ */ +:focus-visible { + transition: + box-shadow var(--transition-fast), + outline-offset var(--transition-fast); + outline-offset: 3px; +} + +/* ================================================================ + * 23. Skeleton Shimmer — Glass Shimmer + * + * Helleres, richtungstreues Shimmer mit glass-artiger Lichtquelle. + * Behält bestehende Animation, verbessert nur den Farbverlauf. + * ================================================================ */ +.skeleton { + background: linear-gradient( + 105deg, + var(--color-border-subtle) 0%, + var(--color-border-subtle) 30%, + color-mix(in srgb, var(--color-surface) 90%, var(--glass-highlight)) 50%, + var(--color-border-subtle) 70%, + var(--color-border-subtle) 100% + ); + background-size: 250% 100%; +} + +/* ================================================================ + * 24. FAB — Attention Pulse (subtil, einmalig beim Erscheinen) + * + * Leichter Ring-Expand nach dem fab-in, signalisiert Interaktivität. + * Nur einmal, kein dauerhaftes Blinken (kein infinite loop). + * ================================================================ */ +@keyframes fab-ring-pulse { + 0% { box-shadow: var(--shadow-lg), inset 0 1px 0 rgba(255,255,255,0.28), 0 0 0 0 color-mix(in srgb, var(--module-accent, var(--color-btn-primary)) 50%, transparent); } + 60% { box-shadow: var(--shadow-lg), inset 0 1px 0 rgba(255,255,255,0.28), 0 0 0 10px transparent; } + 100% { box-shadow: var(--shadow-lg), inset 0 1px 0 rgba(255,255,255,0.28), 0 0 0 0 transparent; } +} + +.fab { + animation: + fab-in 0.35s var(--ease-out) backwards, + fab-ring-pulse 0.6s var(--ease-out) 0.4s 1 backwards; +} + +/* ================================================================ + * 25. Accessibility — Phase 3 Overrides + * ================================================================ */ +@media (prefers-reduced-motion: reduce) { + .nav-bottom { + transition: none; + } + + .nav-bottom--hidden { + /* Sofort verstecken ohne Animation */ + transition: none; + } + + .modal-panel { + animation: none; + } + + .page-transition--in-right, + .page-transition--in-left, + .page-transition--out-left, + .page-transition--out-right { + animation-duration: 0.01ms !important; + } + + .list-stagger > * { + animation-duration: 0.01ms !important; + } + + .fab { + animation: none; + } +}