Files
oikos/public/styles/layout.css
T
2026-04-29 21:15:26 +02:00

2251 lines
54 KiB
CSS
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 (10241279px): 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;
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(2, 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-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-tertiary);
text-transform: uppercase;
letter-spacing: 0.06em;
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);
}
/* 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 10241439px) */
@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.3px;
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.3px;
}
@media (min-width: 1024px) {
.page {
padding: var(--space-8) var(--space-8);
}
.page__title {
font-size: var(--text-2xl);
letter-spacing: -0.5px;
}
}
@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: 812px
* - 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);
}
.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: flex-end;
justify-content: center;
overflow: hidden;
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 {
align-items: center;
padding: var(--space-6);
}
}
.modal-panel {
background-color: var(--color-surface);
width: 100%;
max-height: 90dvh;
overflow: hidden;
display: flex;
flex-direction: column;
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
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;
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);
}
.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: var(--space-3);
left: 50%;
transform: translateX(-50%);
width: 36px;
height: var(--space-1);
background: var(--color-border);
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(100%); }
}
@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-sm);
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);
}
.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;
}
/* --------------------------------------------------------
* Toggle-Switch
* Custom iOS-style toggle, ersetzt native Checkboxen.
* Usage: <label class="toggle"><input type="checkbox"><span class="toggle__track"></span></label>
* -------------------------------------------------------- */
.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__icon {
width: 56px;
height: 56px;
color: 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);
}
.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 280320px.
*/
.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;
}
/* 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 (<i data-lucide>). Klassen werden
* vom Lucide-Renderer auf das erzeugte <svg> ü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; }
/* 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);
}
/* 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;
}
}
/* --------------------------------------------------------
* 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; }
}