/** * Modul: Layout * Zweck: App-Shell-Layout, Navigation (Bottom Mobile / Sidebar Desktop), Responsive Grid * Abhängigkeiten: tokens.css, reset.css * * Navigation: * Mobile (<1024px): Bottom-Nav-Bar, 5 Haupt-Module * Desktop (1024–1279px): Collapsed Sidebar, nur Icons (56px) * Wide Desktop (>1280px): Expanded Sidebar mit Labels (220px) * * Referenz: Apple HIG Sidebar-Pattern */ /* -------------------------------------------------------- * App-Shell * -------------------------------------------------------- */ .app-shell { display: flex; flex-direction: column; /* -webkit-fill-available als iOS-Fallback: In manchen iOS-WebKit-Versionen * ist 100dvh minimal kleiner als die tatsächliche WKWebView-Höhe, was zu einem * sichtbaren Streifen unten führt. -webkit-fill-available füllt zuverlässig. */ height: -webkit-fill-available; height: 100dvh; } /* -------------------------------------------------------- * Loading-Screen * -------------------------------------------------------- */ .app-loading { display: flex; align-items: center; justify-content: center; min-height: 100dvh; background-color: var(--color-bg); } .app-loading__logo { font-size: var(--text-2xl); font-weight: var(--font-weight-bold); color: var(--color-accent); animation: pulse 1.5s ease-in-out infinite; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } } /* -------------------------------------------------------- * Seiten-Übergangs-Animation (direktional) * -------------------------------------------------------- */ /* Basis: unsichtbar vor Animation */ .page-transition { opacity: 0; } /* Nach der In-Animation soll es sichtbar bleiben → direkt auf der Klasse */ .page-transition--in-right, .page-transition--in-left { opacity: 1; transform: none; } /* Keyframes OHNE forwards */ @keyframes page-slide-in-right { from { opacity: 0; transform: translateX(20px); } to { opacity: 1; transform: none; } } @keyframes page-slide-in-left { from { opacity: 0; transform: translateX(-20px); } to { opacity: 1; transform: none; } } @keyframes page-out-left { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateX(-20px); } } @keyframes page-out-right { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateX(20px); } } @keyframes page-crossfade-in { from { opacity: 0; } to { opacity: 1; } } .page-transition--in-right { animation: page-slide-in-right 0.2s var(--ease-out); } .page-transition--in-left { animation: page-slide-in-left 0.2s var(--ease-out); } .page-transition--out-left { animation: page-out-left 0.12s ease forwards; /* ← out braucht forwards, wird ja eh entfernt */ pointer-events: none; } .page-transition--out-right { animation: page-out-right 0.12s ease forwards; pointer-events: none; } .page-transition--crossfade { animation: page-crossfade-in 0.18s var(--ease-out); opacity: 1; } @media (prefers-reduced-motion: reduce) { .page-transition--in-right, .page-transition--in-left { animation: none; opacity: 1; } .page-transition--out-left, .page-transition--out-right { animation: none; } .page-transition--crossfade { animation: none; opacity: 1; } } /* -------------------------------------------------------- * Layout: Mobile (Standard, < 1024px) * -------------------------------------------------------- */ .app-content { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; overscroll-behavior-y: contain; -webkit-overflow-scrolling: touch; } /* Sidebar auf Mobile verstecken */ .nav-sidebar { display: none; } /* -------------------------------------------------------- * Bottom Navigation (Mobil + Tablet) * * Kompakte Bar: 56px Höhe, Icons 22px, Labels 11px. * Leichter Blur-Effekt für Glassmorphismus. * -------------------------------------------------------- */ .nav-bottom { /* Flex-Kind von .app-shell statt position: fixed - zuverlässiger auf iOS PWA. * position: fixed kann durch will-change: transform in iOS-Compositor-Layern * falsch referenziert werden. Als Flex-Kind am Ende der 100dvh-App-Shell ist * die Position garantiert am Viewport-Bottom. */ position: relative; flex-shrink: 0; width: 100%; background-color: color-mix(in srgb, var(--color-surface) 85%, transparent); border-top: 1px solid var(--color-border-subtle); display: flex; flex-direction: column; z-index: var(--z-nav); backdrop-filter: var(--blur-md) saturate(180%); -webkit-backdrop-filter: var(--blur-md) saturate(180%); transition: transform 0.2s var(--ease-out); } .nav-bottom--hidden { transform: translateY(100%); } /* ── Items-Reihe ── */ .nav-bottom__items { display: flex; height: var(--nav-height-mobile); } /* ── More-Button (Button-Basis, ansonsten wie nav-item) ── */ .nav-item--more, .nav-item--kitchen, .nav-item--search { background: none; border: none; cursor: pointer; font-family: inherit; } .nav-item--active { color: var(--active-module-accent, var(--color-accent)); } /* ── More-Backdrop ── */ .more-backdrop { display: none; position: fixed; inset: 0; background-color: var(--color-overlay); z-index: calc(var(--z-nav) + 1); backdrop-filter: var(--blur-2xs); -webkit-backdrop-filter: var(--blur-2xs); } .more-backdrop--visible { display: block; animation: fade-in 0.15s ease; } @keyframes fade-in { from { opacity: 0; } to { opacity: 1; } } /* ── More-Sheet ── */ .more-sheet { position: fixed; bottom: 0; left: 0; right: 0; background-color: var(--color-surface); border-top: 1px solid var(--color-border-subtle); border-radius: var(--radius-xl) var(--radius-xl) 0 0; padding: var(--space-4) var(--space-4) calc(var(--space-4) + var(--safe-area-inset-bottom)); z-index: calc(var(--z-nav) + 2); display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-3); transform: translateY(100%); transition: transform 0.25s var(--ease-out); } .more-sheet[aria-hidden="false"] { transform: translateY(0); } /* ── More-Sheet Drag-Handle ── */ .more-sheet__handle { grid-column: 1 / -1; width: 36px; height: 4px; border-radius: var(--radius-full); background-color: var(--color-border); margin: 0 auto var(--space-2); } /* ── More-Sheet Suchleiste ── */ .more-sheet__search { grid-column: 1 / -1; display: flex; align-items: center; gap: var(--space-2); padding: var(--space-2) var(--space-3); border-radius: var(--radius-lg); background-color: var(--color-surface-elevated); border: 1.5px solid var(--color-border); color: var(--color-text-tertiary); cursor: text; margin-bottom: var(--space-1); -webkit-tap-highlight-color: transparent; transition: border-color var(--transition-fast), background-color var(--transition-fast), transform 0.1s ease; user-select: none; } .more-sheet__search:hover { border-color: var(--color-border-strong, var(--color-border)); background-color: var(--color-surface-hover); } .more-sheet__search:active { outline: none; border-color: var(--color-accent); background-color: color-mix(in srgb, var(--color-accent) 8%, var(--color-surface-elevated)); transform: scale(0.985); } .more-sheet__search:focus { outline: none; border-color: var(--color-accent); } .more-sheet__search-icon { width: var(--space-4); height: var(--space-4); flex-shrink: 0; transition: color var(--transition-fast); } .more-sheet__search:hover .more-sheet__search-icon, .more-sheet__search:active .more-sheet__search-icon { color: var(--color-accent); } .more-sheet__search-placeholder { font-size: var(--text-sm); color: var(--color-text-tertiary); flex: 1; } .more-sheet__search-kbd { display: none; font-size: var(--text-xs); color: var(--color-text-quaternary, var(--color-text-tertiary)); background-color: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-sm); padding: 1px 5px; font-family: inherit; line-height: 1.4; flex-shrink: 0; } @media (min-width: 1024px) { html:not(.search-kbd-done) .more-sheet__search-kbd { display: inline; } } /* ── More-Item ── */ .more-item { display: flex; flex-direction: column; align-items: center; gap: var(--space-1); padding: var(--space-3) var(--space-2); border-radius: var(--radius-lg); background-color: var(--color-surface-elevated); color: var(--color-text-secondary); text-decoration: none; border: none; cursor: pointer; font-family: inherit; transition: background-color var(--transition-fast), color var(--transition-fast); -webkit-tap-highlight-color: transparent; min-height: var(--target-lg); } .more-item:active { background-color: var(--color-surface-hover); transform: scale(0.96); } .more-item[aria-current="page"] { color: var(--active-module-accent, var(--color-accent)); background-color: color-mix(in srgb, var(--active-module-accent, var(--color-accent)) 12%, transparent); } .more-item__icon { width: var(--space-6); height: var(--space-6); flex-shrink: 0; } .more-item__label { font-size: var(--text-xs); font-weight: var(--font-weight-medium); text-align: center; line-height: 1.2; } /* ── Such-Overlay ── */ .search-overlay { position: fixed; inset: 0; background-color: var(--color-surface); z-index: calc(var(--z-nav) + 3); display: flex; flex-direction: column; transform: translateY(100%); transition: transform 0.25s var(--ease-out); } .search-overlay--visible { transform: translateY(0); } .search-overlay__header { display: flex; align-items: center; gap: var(--space-3); padding: calc(var(--space-3) + var(--safe-area-inset-top)) var(--space-4) var(--space-3); border-bottom: 1px solid var(--color-border-subtle); } .search-overlay__input { flex: 1; height: var(--target-lg); padding: 0 var(--space-3); border-radius: var(--radius-lg); border: 1.5px solid var(--color-border); background-color: var(--color-surface-elevated); color: var(--color-text-primary); font-size: var(--text-base); } .search-overlay__input:focus { outline: none; border-color: var(--color-accent); } .search-overlay__close { display: flex; align-items: center; justify-content: center; width: var(--target-base); height: var(--target-base); border-radius: var(--radius-full); border: none; background: var(--color-surface-elevated); color: var(--color-text-secondary); cursor: pointer; flex-shrink: 0; -webkit-tap-highlight-color: transparent; } .search-overlay__close-icon { width: var(--space-5); height: var(--space-5); } .search-overlay__results { flex: 1; overflow-y: auto; padding: var(--space-3) var(--space-4) calc(var(--space-4) + var(--safe-area-inset-bottom)); } .search-overlay__empty { text-align: center; color: var(--color-text-tertiary); margin-top: var(--space-8); } .search-section { margin-bottom: var(--space-5); } .search-section__heading { font-size: var(--text-xs); font-weight: var(--font-weight-semibold); color: var(--color-text-secondary); letter-spacing: 0.01em; margin-bottom: var(--space-2); } .search-result { display: block; width: 100%; text-align: left; padding: var(--space-3); border-radius: var(--radius-md); border: none; background: var(--color-surface-elevated); color: var(--color-text-primary); font-size: var(--text-sm); cursor: pointer; margin-bottom: var(--space-2); transition: background-color var(--transition-fast); -webkit-tap-highlight-color: transparent; } .search-result:active { background-color: var(--color-surface-hover); } /* ── Nav-Item (Bottom-Bar): Basis-State ── */ .nav-item { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-0h); padding: var(--space-1) var(--space-1); color: var(--color-text-tertiary); transition: color var(--transition-fast), transform 0.12s ease; -webkit-tap-highlight-color: transparent; min-height: var(--target-lg); min-width: var(--target-lg); text-decoration: none; position: relative; } .nav-item__icon-wrap { position: relative; display: inline-flex; flex-shrink: 0; } .nav-badge { display: inline-flex; align-items: center; justify-content: center; min-width: 18px; height: 18px; padding: 0 var(--space-1); border-radius: var(--radius-full); background-color: var(--color-danger); color: var(--color-text-on-accent); font-size: var(--text-xs); font-weight: var(--font-weight-bold); } .nav-item__icon-wrap .nav-badge { position: absolute; top: calc(-1 * var(--space-1)); right: calc(-1 * var(--space-1)); } .nav-item:active { transform: scale(0.92); transition-duration: 0.06s; } /* ── Nav-Item: Aktiv ── */ .nav-item[aria-current="page"] { color: var(--active-module-accent, var(--color-accent)); } /* Pill-Hintergrund hinter dem Icon (nur Bottom-Nav, nicht Sidebar) */ .nav-bottom .nav-item[aria-current="page"] .nav-item__icon-wrap { background-color: color-mix(in srgb, var(--active-module-accent, var(--color-accent)) 14%, transparent); border-radius: var(--radius-lg); padding: var(--space-1) var(--space-4); margin-bottom: 1px; transition: background-color var(--transition-fast); } .nav-item__icon { width: var(--space-5); height: var(--space-5); flex-shrink: 0; } .nav-item__label { font-size: var(--text-xs); font-weight: var(--font-weight-medium); line-height: 1; } /* -------------------------------------------------------- * Page FAB - Schwebender Erstellen-Button (alle Breakpoints) * * Einheitlicher runder Plus-Button auf allen Unterseiten. * Mobile: über der Bottom-Nav. Desktop: unten rechts im Content. * Toolbar-"Neu"-Buttons werden überall versteckt. * -------------------------------------------------------- */ @keyframes fab-in { from { transform: scale(0.6) translateY(8px); opacity: 0; } to { transform: scale(1) translateY(0); opacity: 1; } } .page-fab { position: fixed; bottom: calc(var(--nav-bottom-height) + 24px + var(--safe-area-inset-bottom)); right: var(--space-4); width: 52px; height: 52px; border-radius: var(--radius-full); background-color: var(--module-accent, var(--color-accent)); color: var(--color-text-on-accent); box-shadow: var(--shadow-lg); display: flex; align-items: center; justify-content: center; cursor: pointer; border: none; z-index: calc(var(--z-nav) - 1); transition: transform var(--transition-base), background-color var(--transition-fast); -webkit-tap-highlight-color: transparent; animation: fab-in 0.35s var(--ease-out) backwards; } .page-fab:hover { background-color: color-mix(in srgb, var(--module-accent, var(--color-accent)) 85%, black); } .page-fab:active { transform: scale(0.92); } .page-fab:focus-visible { outline: 2px solid var(--color-bg); outline-offset: 2px; box-shadow: 0 0 0 4px var(--color-accent); } /* Long Loop: FAB-Animation nach N Aufrufen deaktivieren */ html.fab-anim-done .page-fab { animation: none; } /* Desktop: FAB Position anpassen (keine Bottom-Nav) und etwas kleiner */ @media (min-width: 1024px) { .page-fab { bottom: var(--space-8); right: var(--space-8); width: var(--target-lg); height: var(--target-lg); } } /* Toolbar-"Neu"-Buttons überall verstecken - FAB übernimmt */ #btn-new-task, #notes-add-btn, #contacts-add-btn, #budget-add, #cal-add { display: none !important; } /* ================================================================ * Sidebar Navigation - Desktop (≥ 1024px) * * Design: Flach, kein Neumorphismus. Dezenter Seitenrand. * Aktiver State: Hintergrund-Highlight + Akzentstreifen links. * Hover: Subtile Hintergrundfarbe, kein Shadow. * ================================================================ */ @media (min-width: 1024px) { .app-shell { flex-direction: row; } .app-content { flex: 1; min-height: 0; padding-bottom: 0; margin-left: var(--sidebar-width); transition: margin-left var(--transition-slow); } .nav-bottom { display: none; } /* ── Sidebar anzeigen ── */ .nav-sidebar { display: flex; position: fixed; top: 0; left: 0; bottom: 0; width: var(--sidebar-width); background: var(--sidebar-bg); border-right: 1px solid var(--color-border-subtle); flex-direction: column; z-index: var(--z-nav); padding: var(--space-4) 0 var(--space-4); transition: width var(--transition-slow); overflow: hidden; } /* ── Logo: Icon-only bei collapsed ── */ .nav-sidebar__logo { display: flex; justify-content: center; align-items: center; padding: var(--space-2) 0 var(--space-4); margin-bottom: var(--space-1); flex-shrink: 0; } /* SVG-Logomark (via DOM API in router.js injiziert, Gradient im SVG selbst) */ .nav-sidebar__logomark { width: var(--target-sm); height: var(--target-sm); flex-shrink: 0; } .nav-sidebar__logomark svg { width: 100%; height: 100%; display: block; } /* Logo-Text verstecken im collapsed-Modus */ .nav-sidebar__brand-text { display: none; } /* ── Nav-Items Container ── */ .nav-sidebar__items { display: flex; flex-direction: column; gap: var(--space-0h); padding: 0 var(--space-2); flex: 1; overflow-y: auto; scrollbar-width: none; } .nav-sidebar__items::-webkit-scrollbar { display: none; } /* ── Nav-Item: Sidebar-Basis ── */ .nav-sidebar .nav-item { flex-direction: row; justify-content: center; align-items: center; border-radius: var(--radius-sm); padding: var(--space-2) var(--space-2); gap: 0; min-height: var(--target-lg); font-size: var(--text-sm); color: var(--color-text-secondary); background: transparent; transition: color var(--transition-fast), background-color var(--transition-fast); position: relative; } .nav-sidebar .nav-item__icon { width: 20px; height: 20px; flex-shrink: 0; } .nav-sidebar .nav-item__label { display: none; font-size: var(--text-sm); font-weight: var(--font-weight-medium); white-space: nowrap; overflow: hidden; } /* ── Hover: dezente Hintergrundfarbe ── */ .nav-sidebar .nav-item:hover:not([aria-current="page"]) { color: var(--color-text-primary); background-color: var(--color-surface-3); } /* ── Aktiv: Hintergrund-Highlight + Akzent-Indikator ── */ .nav-sidebar .nav-item[aria-current="page"] { color: var(--active-module-accent, var(--color-accent)); font-weight: var(--font-weight-semibold); background-color: color-mix(in srgb, var(--active-module-accent, var(--color-accent)) 12%, transparent); } /* Akzentstreifen links */ .nav-sidebar .nav-item[aria-current="page"]::before { content: ''; position: absolute; left: 0; top: var(--space-2); bottom: var(--space-2); width: 3px; border-radius: 0 var(--radius-full) var(--radius-full) 0; background: var(--active-module-accent, var(--color-accent)); } /* Active-Press auf Desktop */ .nav-sidebar .nav-item:active { transform: scale(0.97); } /* ── Einstellungen ans Ende ── */ .nav-sidebar__items > a:last-child { margin-top: auto; } } /* Tooltip für collapsed Sidebar (nur Icons sichtbar bei 1024–1439px) */ @media (min-width: 1024px) and (max-width: 1279px) { .nav-sidebar .nav-item { overflow: visible; } .nav-sidebar .nav-item::after { content: attr(title); position: absolute; left: calc(var(--sidebar-width) + var(--space-2)); top: 50%; transform: translateY(-50%); background: var(--color-surface); color: var(--color-text-primary); padding: var(--space-1) var(--space-3); border-radius: var(--radius-sm); box-shadow: var(--shadow-md); border: 1px solid var(--color-border-subtle); white-space: nowrap; font-size: var(--text-sm); font-weight: var(--font-weight-medium); pointer-events: none; opacity: 0; transition: opacity 0.15s ease; z-index: calc(var(--z-nav) + 10); } .nav-sidebar .nav-item:hover::after { opacity: 1; } } /* ================================================================ * Sidebar Expanded (≥ 1280px) - Labels sichtbar * ================================================================ */ @media (min-width: 1280px) { :root { --sidebar-width: var(--sidebar-width-expanded); } /* Logo: Text-Variante */ .nav-sidebar__logo { justify-content: flex-start; padding: var(--space-2) var(--space-5) var(--space-4); gap: var(--space-3); } .nav-sidebar__logomark { width: 28px; height: 28px; flex-shrink: 0; } .nav-sidebar__brand-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.1; } .nav-sidebar__brand-name { font-size: var(--text-lg); font-weight: var(--font-weight-bold); letter-spacing: -0.6px; color: var(--color-text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .nav-sidebar__version { margin-top: var(--space-0h); font-size: 10px; font-weight: var(--font-weight-medium); letter-spacing: 0.04em; color: var(--color-text-tertiary); } .nav-sidebar__items { padding: 0 var(--space-2); gap: var(--space-0h); } /* Nav-Items: horizontal mit Label */ .nav-sidebar .nav-item { justify-content: flex-start; padding: var(--space-2) var(--space-3); gap: var(--space-3); } .nav-sidebar .nav-item__icon { width: 18px; height: 18px; } .nav-sidebar .nav-item__label { display: block; font-weight: inherit; } } /* -------------------------------------------------------- * Seiten-Container * -------------------------------------------------------- */ .page { padding: var(--space-4); max-width: var(--content-max-width); margin: 0 auto; } .page__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--space-5); gap: var(--space-4); } .page__title { font-size: var(--text-xl); font-weight: var(--font-weight-bold); letter-spacing: -0.5px; text-wrap: balance; } @media (min-width: 1024px) { .page { padding: var(--space-8) var(--space-8); } .page__title { font-size: var(--text-2xl); letter-spacing: -0.8px; } } @media (min-width: 1440px) { .page { padding: var(--space-8) var(--space-10); } } /* -------------------------------------------------------- * Cards * * Einheitliches Card-Pattern für alle Module: * - Padding: 16px (mobile), 20px (desktop) * - Radius: 12px * - Shadow: subtle (sm) * - Internes Spacing: 8–12px * - Hover auf Desktop: leichter Lift (1px) * * Varianten: * .card - Basis (kein Padding) * .card--padded - Mit Padding * .card--compact - Enges Padding (12px) * .card--flat - Kein Shadow, nur Border * .card--interactive - Hover-Lift + Cursor * -------------------------------------------------------- */ .card { background-color: var(--color-surface); border-radius: var(--radius-md); box-shadow: var(--shadow-sm); overflow: hidden; } .card--padded { padding: var(--space-4); } .card--compact { padding: var(--space-3); } .card--flat { box-shadow: none; border: 1px solid var(--color-border-subtle); } .card--interactive { cursor: pointer; transition: transform var(--transition-fast), box-shadow var(--transition-fast), background-color var(--transition-fast); } .card--interactive:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); } .card--interactive:active { transform: scale(0.98); background-color: var(--color-surface-3); box-shadow: var(--shadow-sm); } .card--interactive:focus-visible { outline: 2px solid var(--active-module-accent, var(--color-accent)); outline-offset: 2px; } @media (min-width: 1024px) { .card--padded { padding: var(--space-5); } } /* ── Card-Inhalts-Utilities ── */ .card__header { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); padding-bottom: var(--space-3); margin-bottom: var(--space-3); border-bottom: 1px solid var(--color-border-subtle); } .card__title { font-size: var(--text-sm); font-weight: var(--font-weight-semibold); color: var(--color-text-primary); text-wrap: balance; } .card__body { display: flex; flex-direction: column; gap: var(--space-2); } /* -------------------------------------------------------- * Modal-Basis * * Einheitliches Modal-Pattern für alle Module: * - Mobile: Bottom-Sheet (gerundet oben) * - Desktop: Zentriert (max-width variabel) * - Overlay: var(--color-overlay) * - Padding: 16px * - Radius: 16px * -------------------------------------------------------- */ .modal-overlay { position: fixed; inset: 0; background-color: var(--color-overlay); z-index: var(--z-modal); display: flex; align-items: center; justify-content: center; overflow: hidden; padding: max(var(--space-3), env(safe-area-inset-top)) max(var(--space-3), env(safe-area-inset-right)) max(var(--space-3), env(safe-area-inset-bottom)) max(var(--space-3), env(safe-area-inset-left)); animation: modal-overlay-in 0.2s ease forwards; /* iOS Safari: click-Events auf non-interactive divs erfordern cursor:pointer */ cursor: pointer; } @media (min-width: 768px) { .modal-overlay { padding: var(--space-6); } } .modal-panel { background-color: var(--color-surface); width: 100%; max-height: calc(100dvh - (2 * var(--space-3)) - env(safe-area-inset-top) - env(safe-area-inset-bottom)); overflow: hidden; display: flex; flex-direction: column; border-radius: var(--radius-lg); border: 1px solid var(--color-border-subtle); box-shadow: var(--shadow-lg); animation: modal-slide-up 0.25s var(--ease-out) forwards; /* Cursor vom Overlay zurücksetzen */ cursor: default; } @media (min-width: 768px) { .modal-panel { max-width: 520px; max-height: calc(100dvh - (2 * var(--space-6))); border-radius: var(--radius-lg); animation: modal-scale-in 0.2s var(--ease-out) forwards; } .modal-panel--sm { max-width: 400px; } .modal-panel--lg { max-width: 680px; } } .modal-panel__header { display: flex; align-items: center; justify-content: space-between; padding: var(--space-4); border-bottom: 1px solid var(--color-border-subtle); background-color: var(--color-surface); flex-shrink: 0; } .modal-panel__title { font-size: var(--text-md); font-weight: var(--font-weight-semibold); text-wrap: balance; } .modal-panel__close { width: var(--target-base); height: var(--target-base); display: flex; align-items: center; justify-content: center; border-radius: var(--radius-full); color: var(--color-text-secondary); transition: background-color var(--transition-fast); position: relative; flex-shrink: 0; } .modal-panel__close:hover { background-color: var(--color-surface-3); } .modal-panel__body { padding: var(--space-4); display: flex; flex-direction: column; gap: var(--space-4); flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; -webkit-overflow-scrolling: touch; } /* Modal-Content-Grids (z. B. 2 Spalten in Formularen) */ .modal-grid { display: grid; grid-template-columns: 1fr; gap: var(--space-3); align-items: stretch; } .modal-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } .modal-grid > * { min-width: 0; } .modal-grid > .form-group { margin-bottom: 0; } /* Gleich hohe Karten/Boxen in Modal-Grids */ .modal-panel__body .grid { align-items: stretch; } .modal-panel__body .grid > .card { height: 100%; display: flex; flex-direction: column; } @media (max-width: 639px) { .modal-grid--2 { grid-template-columns: 1fr; } } .form-stack { display: flex; flex-direction: column; gap: var(--space-4); } .modal-actions { display: flex; justify-content: flex-end; gap: var(--space-2); } .modal-panel__footer { display: flex; align-items: center; justify-content: flex-end; gap: var(--space-3); padding: var(--space-3) var(--space-4); border-top: 1px solid var(--color-border-subtle); } @keyframes modal-overlay-in { from { opacity: 0; } to { opacity: 1; } } @keyframes modal-slide-up { from { transform: translateY(100%); } to { transform: translateY(0); } } @keyframes modal-scale-in { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } } /* ── Bottom Sheet: Handle + Closing Animation (Mobile < 768px) ── */ @media (max-width: 767px) { .modal-panel { /* Extra top padding for the drag handle */ padding-top: calc(var(--space-4) + 20px); position: relative; } .modal-panel::before { content: ''; position: absolute; top: 0; left: 50%; transform: translateX(-50%); /* Visuelle Linie bleibt klein, Hit-Area ist 44px hoch */ width: 36px; height: 44px; background-color: transparent; background-image: linear-gradient( to bottom, transparent calc(50% - 2px), var(--color-border) calc(50% - 2px), var(--color-border) calc(50% + 2px), transparent calc(50% + 2px) ); border-radius: var(--radius-full); } .modal-panel--closing { animation: sheet-out 0.2s ease forwards; } } @keyframes sheet-out { from { transform: translateY(0); } to { transform: translateY(16px); opacity: 0; } } @media (prefers-reduced-motion: reduce) { .modal-panel { animation: none; } .modal-panel--closing { animation: none; } } /* -------------------------------------------------------- * Buttons * -------------------------------------------------------- */ .btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); padding: var(--space-2) var(--space-4); border-radius: var(--radius-md); font-size: var(--text-sm); font-weight: var(--font-weight-medium); min-height: var(--target-lg); transition: background-color var(--transition-fast), box-shadow var(--transition-fast), transform var(--transition-fast); cursor: pointer; white-space: nowrap; } @media (min-width: 1024px) { .btn { min-height: var(--target-md); } } .btn:active { transform: scale(0.98); } .btn:focus-visible { outline: 2px solid var(--active-module-accent, var(--color-accent)); outline-offset: 2px; } .btn--primary { background-color: var(--color-btn-primary); color: var(--color-text-on-accent); box-shadow: var(--shadow-sm); } .btn--primary:hover { background-color: var(--color-btn-primary-hover); box-shadow: var(--shadow-md); } .btn--secondary { background-color: transparent; color: var(--color-accent); border: 1.5px solid var(--color-border); } .btn--secondary:hover { background-color: var(--color-accent-light); border-color: var(--color-accent); } .btn--danger { background-color: var(--color-danger); color: var(--color-text-on-accent); } .btn--danger:hover { background-color: var(--color-danger-hover); } .btn--ghost { background-color: transparent; color: var(--color-text-secondary); } .btn--ghost:hover { background-color: var(--color-surface-2); color: var(--color-text-primary); } .btn:disabled { opacity: 0.4; cursor: not-allowed; pointer-events: none; } .btn--icon { padding: var(--space-2); min-height: var(--target-lg); min-width: var(--target-lg); border-radius: var(--radius-sm); } @media (min-width: 1024px) { .btn--icon { min-height: var(--target-md); min-width: var(--target-md); } } /* FAB und page-fab ausblenden wenn virtuelle Tastatur offen (nur Mobile) */ @media (max-width: 1023px) { .keyboard-visible .fab, .keyboard-visible .page-fab { visibility: hidden; pointer-events: none; } } /* -------------------------------------------------------- * Form-Elemente * -------------------------------------------------------- */ .input, .form-input { width: 100%; min-width: 0; box-sizing: border-box; padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm); border: 1.5px solid var(--color-border); background-color: var(--color-surface); color: var(--color-text-primary); font-size: var(--text-md); transition: border-color var(--transition-fast), box-shadow var(--transition-fast); min-height: var(--target-lg); } @media (min-width: 1024px) { .input, .form-input { min-height: var(--target-md); font-size: var(--text-base); } } .input:focus, .form-input:focus { outline: none; border-color: var(--color-accent); box-shadow: 0 0 0 3px var(--color-accent-light); } .input::placeholder, .form-input::placeholder { color: var(--color-text-disabled); } .label, .form-label { display: block; font-size: var(--text-sm); font-weight: var(--font-weight-medium); color: var(--color-text-secondary); margin-bottom: var(--space-1); } .required-marker { color: var(--color-danger); margin-left: var(--space-0h); font-size: var(--text-xs); font-weight: var(--font-weight-bold); line-height: 1; } .form-group { display: flex; flex-direction: column; gap: var(--space-1); margin-bottom: var(--space-4); } /* ── Inline-Validierung ── */ .form-field { display: flex; flex-direction: column; gap: var(--space-1); } .form-field--error .input, .form-field--error .form-input { border-color: var(--color-danger); } .form-field--valid .input, .form-field--valid .form-input { border-color: var(--color-success); } .form-field__error { display: none; font-size: var(--text-sm); color: var(--color-danger); gap: var(--space-1); align-items: center; } .form-field--error .form-field__error { display: flex; } /* Pulse-Animation bei wiederholtem Fehler */ @keyframes field-error-pulse { 0%, 100% { box-shadow: none; } 40% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-danger) 35%, transparent); } } .form-field--error-repeat .input, .form-field--error-repeat .form-input { animation: field-error-pulse 500ms var(--ease-out); } @media (prefers-reduced-motion: reduce) { .form-field--error-repeat .input, .form-field--error-repeat .form-input { animation: none; } } /* -------------------------------------------------------- * Toggle-Switch * Custom iOS-style toggle, ersetzt native Checkboxen. * Usage: * -------------------------------------------------------- */ .toggle { display: inline-flex; align-items: center; cursor: pointer; gap: var(--space-3); } .toggle input { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0); } .toggle__track { position: relative; width: 44px; height: 26px; background-color: var(--neutral-300); border-radius: var(--radius-full); transition: background-color var(--transition-fast); flex-shrink: 0; } .toggle__track::after { content: ''; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; background: var(--color-surface); border-radius: var(--radius-full); box-shadow: var(--shadow-sm); transition: transform var(--transition-fast) var(--ease-out); } .toggle input:checked + .toggle__track { background-color: var(--active-module-accent, var(--color-accent)); } .toggle input:checked + .toggle__track::after { transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1); } .toggle input:checked + .toggle__track::after { transform: translateX(18px); } .toggle input:focus-visible + .toggle__track { outline: 2px solid var(--active-module-accent, var(--color-accent)); outline-offset: 2px; } .toggle input:disabled + .toggle__track { opacity: 0.4; cursor: not-allowed; } @media (prefers-reduced-motion: reduce) { .toggle__track, .toggle__track::after { transition: none; } .fab, .page-fab { animation: none; } .list-stagger > * { animation: none; } .swipe-row--hint > :first-child { animation: none; } .btn--loading::after { animation: none; } } /* -------------------------------------------------------- * Skeleton-Loading * -------------------------------------------------------- */ .skeleton { background: linear-gradient( 90deg, var(--color-border-subtle) 25%, var(--color-surface-2) 50%, var(--color-border-subtle) 75% ); background-size: 200% 100%; animation: skeleton-shimmer 1.5s infinite; border-radius: var(--radius-sm); } @keyframes skeleton-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } } /* -------------------------------------------------------- * Leer-Zustände (Empty States) * -------------------------------------------------------- */ .empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-3); padding: var(--space-12) var(--space-6); text-align: center; animation: empty-state-in 0.4s var(--ease-out) both; } @keyframes empty-state-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } } @media (prefers-reduced-motion: reduce) { .empty-state { animation: none; } .empty-state__cta { animation: none; } } .empty-state__icon { width: 56px; height: 56px; color: color-mix(in srgb, var(--module-accent, var(--color-accent)) 40%, var(--color-text-disabled)); } .empty-state__title { font-size: var(--text-md); font-weight: var(--font-weight-semibold); color: var(--color-text-primary); } .empty-state__description { font-size: var(--text-sm); color: var(--color-text-secondary); max-width: 280px; line-height: var(--line-height-base); } .empty-state__hint { font-size: var(--text-xs); color: var(--color-text-tertiary); max-width: 280px; line-height: var(--line-height-relaxed); padding: var(--space-2) var(--space-3); background-color: var(--color-surface-2); border-radius: var(--radius-sm); border: 1px solid var(--color-border-subtle); text-align: center; } .empty-state__cta { margin-top: var(--space-2); animation: list-item-in 300ms var(--ease-out) 300ms both; } .empty-state--compact { padding: var(--space-4) var(--space-3); gap: var(--space-2); } .empty-state--compact .empty-state__description { font-size: var(--text-sm); } /* -------------------------------------------------------- * Responsive Grid (Utility) * -------------------------------------------------------- */ .grid { display: grid; gap: var(--space-4); grid-template-columns: 1fr; } @media (min-width: 768px) { .grid--2 { grid-template-columns: repeat(2, 1fr); } } @media (min-width: 1024px) { .grid--3 { grid-template-columns: repeat(3, 1fr); } } @media (min-width: 1440px) { .grid--4 { grid-template-columns: repeat(4, 1fr); } } /* -------------------------------------------------------- * Layout-Primitives * * Wiederverwendbare Content-Area-Patterns für Desktop. * Mobile: immer single-column (Stacking). * * .layout-master-detail - Liste links, Detail rechts (Aufgaben, Einkauf) * .layout-content-aside - Hauptinhalt + schmale Seitenleiste (Kalender) * .layout-center - Zentrierter schmaler Content (Settings, Login) * .layout-wide - Volle Breite mit max-width (Dashboard) * -------------------------------------------------------- */ /* ── Master-Detail ── * Mobile: gestapelt (Detail wird programmatisch ein-/ausgeblendet). * Desktop: 2 Spalten - Liste ~40%, Detail ~60%. */ .layout-master-detail { display: flex; flex-direction: column; gap: var(--space-4); max-width: var(--content-max-width); margin: 0 auto; } .layout-master-detail__master { flex: 1; min-width: 0; } .layout-master-detail__detail { flex: 1; min-width: 0; } @media (min-width: 1024px) { .layout-master-detail { flex-direction: row; gap: var(--space-6); } .layout-master-detail__master { flex: 0 0 380px; max-width: 420px; overflow-y: auto; max-height: calc(100dvh - var(--space-16)); position: sticky; top: var(--space-8); } .layout-master-detail__detail { flex: 1; } } @media (min-width: 1440px) { .layout-master-detail__master { flex: 0 0 420px; max-width: 460px; } } /* ── Content + Aside ── * Mobile: gestapelt. * Desktop: Hauptinhalt nimmt Platz ein, Aside ist 280–320px. */ .layout-content-aside { display: flex; flex-direction: column; gap: var(--space-4); max-width: var(--content-max-width); margin: 0 auto; } .layout-content-aside__main { flex: 1; min-width: 0; } .layout-content-aside__aside { min-width: 0; } @media (min-width: 1024px) { .layout-content-aside { flex-direction: row; gap: var(--space-6); } .layout-content-aside__main { flex: 1; } .layout-content-aside__aside { flex: 0 0 280px; overflow-y: auto; max-height: calc(100dvh - var(--space-16)); position: sticky; top: var(--space-8); } } @media (min-width: 1440px) { .layout-content-aside__aside { flex: 0 0 320px; } } /* ── Center ── * Schmaler, zentrierter Content-Bereich (max. 720px). * Ideal für Settings, Formulare, Einzelansichten. */ .layout-center { max-width: 720px; margin: 0 auto; } /* ── Wide ── * Volle verfügbare Breite mit max-width. * Für Dashboard und andere Multi-Column-Ansichten. */ .layout-wide { max-width: var(--content-max-width); margin: 0 auto; } /* ── Prose ── * Text-Content mit optimaler Lesebreite. */ .prose { max-width: 720px; } /* ── Sticky-Header (Modul-Toolbars) ── * Klebt am oberen Rand beim Scrollen. */ .sticky-header { position: sticky; top: 0; z-index: var(--z-sticky); background-color: color-mix(in srgb, var(--color-bg) 90%, transparent); backdrop-filter: var(--blur-sm); -webkit-backdrop-filter: var(--blur-sm); padding-bottom: var(--space-3); margin-bottom: var(--space-4); } /* ── prefers-reduced-transparency Fallback ── * Deaktiviert Blur und transparente Hintergründe für bessere Zugänglichkeit. */ @media (prefers-reduced-transparency: reduce) { .tasks-toolbar, .notes-toolbar, .cal-toolbar, .contacts-toolbar, .list-header { background-color: var(--color-surface); backdrop-filter: none; -webkit-backdrop-filter: none; } } /* -------------------------------------------------------- * Toast-Benachrichtigungen * -------------------------------------------------------- */ .toast-container { position: fixed; bottom: calc(var(--nav-bottom-height) + var(--safe-area-inset-bottom) + var(--space-4)); left: 50%; transform: translateX(-50%); display: flex; flex-direction: column; gap: var(--space-2); z-index: var(--z-toast); pointer-events: none; width: min(calc(100% - var(--space-8)), 380px); } @media (min-width: 768px) and (max-width: 1023px) { .toast-container { bottom: var(--space-8); } } @media (min-width: 1024px) { .toast-container { bottom: var(--space-6); left: calc(var(--sidebar-width) + (100% - var(--sidebar-width)) / 2); } } .toast { display: flex; align-items: center; gap: var(--space-2); background-color: var(--neutral-800); color: var(--neutral-50); padding: var(--space-3) var(--space-4); border-radius: var(--radius-sm); font-size: var(--text-sm); box-shadow: var(--shadow-lg); pointer-events: auto; animation: toast-in 0.25s var(--ease-out) forwards; } .toast__icon { flex-shrink: 0; width: 16px; height: 16px; } .toast__undo { margin-left: auto; flex-shrink: 0; padding: var(--space-1) var(--space-2); background: transparent; border: 1px solid currentColor; border-radius: var(--radius-sm); color: inherit; font-size: var(--text-xs); font-weight: var(--font-weight-semibold); cursor: pointer; white-space: nowrap; opacity: 0.85; } .toast__undo { -webkit-tap-highlight-color: transparent; transition: opacity var(--transition-fast), transform 0.08s ease; } .toast__undo:hover { opacity: 1; } .toast__undo:active { transform: scale(0.94); opacity: 1; } .toast--success { background-color: var(--color-success); color: var(--toast-success-text); } .toast--danger { background-color: var(--color-danger); color: var(--toast-danger-text); } .toast--warning { background-color: var(--color-warning); color: var(--toast-warning-text); } @keyframes toast-in { from { opacity: 0; transform: translateY(6px) scale(0.98); } to { opacity: 1; transform: translateY(0) scale(1); } } .toast--out { animation: toast-out 0.2s ease forwards; } @keyframes toast-out { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(0.95) translateY(4px); } } /* -------------------------------------------------------- * RRULE-Felder (Wiederholungs-Formular, shared Tasks + Kalender) * -------------------------------------------------------- */ .rrule-fields { margin-top: var(--space-4); border-top: 1px solid var(--color-border-subtle); padding-top: var(--space-4); } .rrule-details { display: flex; flex-direction: column; gap: var(--space-3); margin-top: var(--space-3); padding: var(--space-3); background: var(--color-surface-2); border-radius: var(--radius-sm); } .rrule-row { display: flex; align-items: flex-end; gap: var(--space-3); flex-wrap: wrap; } .rrule-interval-wrap { display: flex; align-items: center; gap: var(--space-2); } .rrule-interval-unit { font-size: var(--text-sm); color: var(--color-text-secondary); white-space: nowrap; } .rrule-until-field { flex: 1; min-width: 220px; } .rrule-day-grid { display: flex; gap: var(--space-1); flex-wrap: wrap; margin-top: var(--space-1); } .rrule-day { width: var(--target-md); height: var(--target-md); border-radius: var(--radius-sm); border: 1.5px solid var(--color-border); background: transparent; color: var(--color-text-secondary); font-size: var(--text-sm); font-weight: var(--font-weight-medium); cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all var(--transition-fast); position: relative; } .rrule-day::before { content: ''; position: absolute; inset: -4px; } .rrule-day:hover { border-color: var(--color-accent); color: var(--color-accent); } .rrule-day--active { background: var(--color-accent); border-color: var(--color-accent); color: var(--color-text-on-accent); } .rrule-day--active:hover { background: var(--color-accent-hover); border-color: var(--color-accent-hover); } /* ── Submit-Feedback Animationen ── */ /* Checkbox-Pop: kanonische Definition - shopping.css + tasks.css verwenden diese */ @keyframes check-pop { 0% { transform: scale(1); } 30% { transform: scale(0.8); } 60% { transform: scale(1.3); } 80% { transform: scale(0.95); } 100% { transform: scale(1); } } @keyframes btn-shake { 0%, 100% { transform: translateX(0); } 20% { transform: translateX(-4px); } 40% { transform: translateX(4px); } 60% { transform: translateX(-4px); } 80% { transform: translateX(4px); } } .btn--shaking { animation: btn-shake 0.3s ease; } /* prefers-reduced-motion: kein Schütteln, nur Farb-Feedback */ .btn--error-static { background-color: var(--color-danger) !important; color: var(--color-text-on-accent) !important; pointer-events: none; } .btn--success { background-color: var(--color-success) !important; color: var(--color-text-on-accent) !important; pointer-events: none; } /* Button loading state: hides text, shows spinner */ @keyframes btn-spin { to { transform: rotate(360deg); } } .btn--loading { position: relative; color: transparent !important; pointer-events: none; } .btn--loading::after { content: ''; position: absolute; top: 50%; left: 50%; width: 14px; height: 14px; margin-top: -7px; margin-left: -7px; border: 2px solid var(--color-glass-hover); border-top-color: var(--color-text-on-accent); border-radius: var(--radius-full); animation: btn-spin 0.55s linear infinite; } /* For non-primary buttons (secondary, ghost) */ .btn--secondary.btn--loading::after, .btn--ghost.btn--loading::after { border-color: var(--color-border); border-top-color: var(--color-accent); } /* -------------------------------------------------------- * Swipe-Wrapper - Gemeinsame Basis (Tasks + Shopping) * Modul-spezifische Styles (.swipe-reveal--edit, .swipe-reveal--delete, * .swipe-row .task-card, .swipe-row .shopping-item) liegen in den Modul-CSS. * -------------------------------------------------------- */ .swipe-row { position: relative; overflow: hidden; border-radius: var(--radius-md); margin-bottom: var(--space-2); /* Verhindert ungewolltes Flackern auf iOS */ -webkit-backface-visibility: hidden; } /* Reveal-Panels hinter der Karte */ .swipe-reveal { position: absolute; top: 0; bottom: 0; width: 50%; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-1); font-size: var(--text-xs); font-weight: var(--font-weight-semibold); opacity: 0; pointer-events: none; z-index: 0; transition: opacity 0.05s linear; } /* -------------------------------------------------------- * List Stagger * Füge .list-stagger zum Container hinzu. * Items erscheinen sequenziell beim ersten Rendern. * -------------------------------------------------------- */ @keyframes list-item-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } } .list-stagger > * { animation: list-item-in 0.2s var(--ease-out) both; } .list-stagger > *:nth-child(1) { animation-delay: 0ms; } .list-stagger > *:nth-child(2) { animation-delay: 35ms; } .list-stagger > *:nth-child(3) { animation-delay: 65ms; } .list-stagger > *:nth-child(4) { animation-delay: 90ms; } .list-stagger > *:nth-child(5) { animation-delay: 112ms; } .list-stagger > *:nth-child(6) { animation-delay: 130ms; } .list-stagger > *:nth-child(7) { animation-delay: 145ms; } .list-stagger > *:nth-child(8) { animation-delay: 157ms; } .list-stagger > *:nth-child(9) { animation-delay: 166ms; } .list-stagger > *:nth-child(n+10) { animation-delay: 173ms; } /* -------------------------------------------------------- * Swipe Affordance Hint * Einmalig per JS: .swipe-row--hint auf das erste Element. * Gibt nach Seitenladezeit einen kurzen Nudge-Hinweis. * -------------------------------------------------------- */ @keyframes swipe-nudge { 0% { transform: translateX(0); } 20% { transform: translateX(-18px); } 45% { transform: translateX(0); } 60% { transform: translateX(-9px); } 80% { transform: translateX(0); } 100% { transform: translateX(0); } } .swipe-row--hint > :first-child { animation: swipe-nudge 0.8s var(--ease-out) 1.2s both; } /* Swipe-Affordanz: permanente Chevron-Hints an den Kartenrändern */ .swipe-row::after { content: ''; position: absolute; right: var(--space-2); top: 50%; transform: translateY(-50%); width: 12px; height: 20px; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 20' fill='none' stroke='%23888' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='3,4 9,10 3,16'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-size: contain; opacity: 0.3; pointer-events: none; z-index: 1; transition: opacity var(--transition-fast); } .swipe-row:hover::after, .swipe-row:focus-within::after { opacity: 0.55; } @media (prefers-reduced-motion: reduce) { .swipe-row::after { display: none; } } /* Gemeinsam: Erledigt / Abhaken (Swipe nach links) */ .swipe-reveal--done { right: 0; background-color: var(--color-success); color: var(--color-text-on-accent); border-radius: 0 var(--radius-md) var(--radius-md) 0; } /* -------------------------------------------------------- * Reduced Transparency — layout.css Fallbacks * glass.css deckt Glass-Komponenten ab; diese drei Elemente * nutzen backdrop-filter außerhalb von @supports und brauchen * eigene Fallbacks. * -------------------------------------------------------- */ @media (prefers-reduced-transparency: reduce) { .nav-bottom { background-color: var(--color-surface); backdrop-filter: none; -webkit-backdrop-filter: none; } .more-backdrop { backdrop-filter: none; -webkit-backdrop-filter: none; } .sticky-header { background-color: var(--color-bg); backdrop-filter: none; -webkit-backdrop-filter: none; } } /* -------------------------------------------------------- * Keyboard Shortcut Modal * -------------------------------------------------------- */ .shortcuts-list { display: flex; flex-direction: column; gap: var(--space-1); } .shortcuts-row { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-2) 0; border-bottom: 1px solid var(--color-border-subtle); } .shortcuts-row:last-child { border-bottom: none; } .shortcut-kbd { display: inline-flex; align-items: center; justify-content: center; min-width: 32px; padding: var(--space-0h) var(--space-2); background: var(--color-surface-3); border: 1px solid var(--color-border); border-bottom: 2px solid var(--color-border); border-radius: var(--radius-xs); font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-text-primary); white-space: nowrap; flex-shrink: 0; } .shortcut-desc { font-size: var(--text-sm); color: var(--color-text-secondary); } /* -------------------------------------------------------- * Offline-Banner * -------------------------------------------------------- */ .offline-banner { position: fixed; top: 0; left: 0; right: 0; z-index: calc(var(--z-toast) + 1); display: flex; align-items: center; gap: var(--space-2); padding: var(--space-2) var(--space-4); background-color: var(--color-warning-light); color: var(--color-warning); font-size: var(--text-sm); font-weight: var(--font-weight-medium); border-bottom: 1px solid color-mix(in srgb, var(--color-warning) 20%, transparent); animation: offline-banner-in 0.2s var(--ease-out); } .offline-banner[hidden] { display: none; } @keyframes offline-banner-in { from { opacity: 0; transform: translateY(-100%); } to { opacity: 1; transform: translateY(0); } } @media (prefers-reduced-motion: reduce) { .offline-banner { animation: none; } } /* -------------------------------------------------------- * Print-Styles * -------------------------------------------------------- */ @media print { .nav-sidebar, .nav-bottom, .fab, .toast-container, .modal-overlay, .modal-backdrop { display: none !important; } .app-content { margin-left: 0 !important; padding-bottom: 0 !important; } .app-shell { display: block; } body { background: #ffffff; color: #000000; } .card { box-shadow: none; border: 1px solid #cccccc; break-inside: avoid; } a[href]::after { content: " (" attr(href) ")"; font-size: var(--text-sm); color: var(--color-text-secondary); } .nav-item, a[data-route]::after { content: none; } } /* -------------------------------------------------------- * Skip-Link (sichtbar bei Keyboard-Focus) * -------------------------------------------------------- */ .sr-only:focus-visible { position: fixed; top: 0; left: 0; z-index: 9999; width: auto; height: auto; padding: var(--space-2) var(--space-4); margin: 0; clip: auto; white-space: normal; overflow: visible; background: var(--color-accent); color: var(--color-text-on-accent); font-size: var(--text-sm); border-radius: 0 0 var(--radius-sm) 0; } /* -------------------------------------------------------- * Icon-Größen-Utilities * Größen für Lucide-Icons (). Klassen werden * vom Lucide-Renderer auf das erzeugte übertragen. * -------------------------------------------------------- */ .icon-xs { width: 10px; height: 10px; flex-shrink: 0; } .icon-11 { width: 11px; height: 11px; flex-shrink: 0; } .icon-sm { width: 12px; height: 12px; flex-shrink: 0; } .icon-md { width: 14px; height: 14px; flex-shrink: 0; } .icon-base { width: 16px; height: 16px; flex-shrink: 0; } .icon-lg { width: 18px; height: 18px; flex-shrink: 0; } .icon-xl { width: 22px; height: 22px; flex-shrink: 0; } .icon-2xl { width: 24px; height: 24px; flex-shrink: 0; } /* Icons innerhalb von Buttons sollen keine Pointer-Events fangen */ button i[data-lucide], button svg { pointer-events: none; } /* Tabular numbers: gleichbreite Ziffern für Zahlen/Beträge */ .tabular-nums { font-variant-numeric: tabular-nums; } /* Kompakter Icon-Button: 44px Klickfläche, optisch kompakt durch geringes Padding */ .btn--icon-sm { padding: var(--space-1); min-height: var(--target-base); min-width: var(--target-base); border-radius: var(--radius-sm); } /* Textarea: vertikale Größenänderung ist nutzbar */ textarea.input { resize: vertical; } /* -------------------------------------------------------- * High Contrast (prefers-contrast: more) * Tokens in tokens.css stellen bereits opake Glass-Werte bereit. * Hier kommen komponentenspezifische Korrekturen hinzu. * -------------------------------------------------------- */ @media (prefers-contrast: more) { /* Fokus: dicker und weiter versetzt */ :focus-visible { outline-width: 3px; outline-offset: 4px; } /* Ghost- und Secondary-Buttons: explizite Umrandung */ .btn--ghost, .btn--secondary { border: 1.5px solid currentColor; } .btn--ghost:hover { background-color: var(--color-surface-3); } /* Karten: Schatten entfernen, Border verstärken */ .card { box-shadow: none; border: 1px solid var(--color-border); } .card--flat { border-color: var(--color-text-secondary); } /* Formulareingaben: kräftigere Border */ .input, .form-input { border-color: var(--color-text-primary); border-width: 2px; } /* Aktiver Nav-Eintrag: zusätzliche Unterstreichung als farb-unabhängiger Indikator */ .nav-item[aria-current="page"] { text-decoration: underline; text-underline-offset: 3px; } } /* -------------------------------------------------------- * Signature Moment: Eingabefeld-Glow nach erfolgreichem Item-Add * -------------------------------------------------------- */ @keyframes input-add-flash { 0% { box-shadow: none; } 25% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--module-accent, var(--color-accent)) 45%, transparent); } 100% { box-shadow: none; } } .quick-add__input--flash { animation: input-add-flash 550ms var(--ease-out) forwards; } @media (prefers-reduced-motion: reduce) { .quick-add__input--flash { animation: none; } } /* -------------------------------------------------------- * Windows High Contrast / Forced Colors * -------------------------------------------------------- */ @media (forced-colors: active) { .btn, .page-fab, .fab { border: 1px solid ButtonText; } .card { border: 1px solid CanvasText; } .modal-panel { border: 2px solid CanvasText; } .nav-item[aria-current="page"] { border-bottom: 2px solid Highlight; } }