3b9459cf10
min-height: 100dvh lets the shell grow unbounded, so app-content never overflows and touch scroll has nothing to scroll. height: 100dvh constrains the shell to exactly the viewport, making overflow-y: auto on app-content work.
1678 lines
39 KiB
CSS
1678 lines
39 KiB
CSS
/**
|
||
* 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;
|
||
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)
|
||
* -------------------------------------------------------- */
|
||
@keyframes page-slide-in-right {
|
||
from { opacity: 0; transform: translateX(20px); }
|
||
to { opacity: 1; transform: translateX(0); }
|
||
}
|
||
@keyframes page-slide-in-left {
|
||
from { opacity: 0; transform: translateX(-20px); }
|
||
to { opacity: 1; transform: translateX(0); }
|
||
}
|
||
@keyframes page-out-left {
|
||
from { opacity: 1; transform: translateX(0); }
|
||
to { opacity: 0; transform: translateX(-20px); }
|
||
}
|
||
@keyframes page-out-right {
|
||
from { opacity: 1; transform: translateX(0); }
|
||
to { opacity: 0; transform: translateX(20px); }
|
||
}
|
||
|
||
.page-transition--in-right {
|
||
animation: page-slide-in-right 0.2s var(--ease-out) forwards;
|
||
}
|
||
.page-transition--in-left {
|
||
animation: page-slide-in-left 0.2s var(--ease-out) forwards;
|
||
}
|
||
.page-transition--out-left {
|
||
animation: page-out-left 0.12s ease forwards;
|
||
pointer-events: none;
|
||
}
|
||
.page-transition--out-right {
|
||
animation: page-out-right 0.12s ease forwards;
|
||
pointer-events: none;
|
||
}
|
||
|
||
@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;
|
||
}
|
||
}
|
||
|
||
/* --------------------------------------------------------
|
||
* Layout: Mobile (Standard, < 1024px)
|
||
* -------------------------------------------------------- */
|
||
.app-content {
|
||
flex: 1;
|
||
min-height: 0;
|
||
padding-bottom: calc(var(--nav-bottom-height) + var(--safe-area-inset-bottom));
|
||
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 {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
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: blur(16px) saturate(180%);
|
||
-webkit-backdrop-filter: blur(16px) saturate(180%);
|
||
padding-bottom: var(--safe-area-inset-bottom);
|
||
}
|
||
|
||
/* ── Dot-Indikator ── */
|
||
.nav-bottom__dots {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: var(--space-2);
|
||
padding: var(--space-1) 0 var(--space-0h);
|
||
}
|
||
|
||
.nav-bottom__dot {
|
||
width: var(--space-1);
|
||
height: var(--space-1);
|
||
border-radius: var(--radius-full);
|
||
background-color: var(--color-text-tertiary);
|
||
opacity: 0.25;
|
||
transition: opacity var(--transition-fast), transform var(--transition-fast);
|
||
}
|
||
|
||
.nav-bottom__dot--active {
|
||
opacity: 0.7;
|
||
transform: scale(1.2);
|
||
}
|
||
|
||
/* ── Scroll-Container ── */
|
||
.nav-bottom__scroll {
|
||
display: flex;
|
||
overflow-x: auto;
|
||
scroll-snap-type: x mandatory;
|
||
-webkit-overflow-scrolling: touch;
|
||
scrollbar-width: none; /* Firefox */
|
||
height: var(--nav-height-mobile);
|
||
}
|
||
|
||
.nav-bottom__scroll::-webkit-scrollbar {
|
||
display: none; /* Chrome/Safari */
|
||
}
|
||
|
||
/* ── Einzelne Seiten ── */
|
||
.nav-bottom__page {
|
||
display: flex;
|
||
min-width: 100%;
|
||
flex-shrink: 0;
|
||
scroll-snap-align: start;
|
||
}
|
||
|
||
/* ── 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;
|
||
}
|
||
|
||
.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));
|
||
}
|
||
|
||
.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 #fff;
|
||
outline-offset: 2px;
|
||
}
|
||
|
||
/* 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;
|
||
}
|
||
|
||
/* App-Icon als kompaktes Logo */
|
||
.nav-sidebar__logo::before {
|
||
content: 'O';
|
||
width: var(--target-sm);
|
||
height: var(--target-sm);
|
||
border-radius: var(--radius-sm);
|
||
background: linear-gradient(135deg, var(--color-accent) 0%, #7C5CFC 100%);
|
||
color: var(--color-text-on-accent);
|
||
font-size: var(--text-sm);
|
||
font-weight: var(--font-weight-bold);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* Logo-Text verstecken im collapsed-Modus */
|
||
.nav-sidebar__logo > span {
|
||
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;
|
||
}
|
||
}
|
||
|
||
/* ================================================================
|
||
* 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__logo::before {
|
||
width: 28px;
|
||
height: 28px;
|
||
font-size: var(--text-xs);
|
||
}
|
||
|
||
.nav-sidebar__logo > span {
|
||
display: inline-block;
|
||
font-size: var(--text-lg);
|
||
font-weight: var(--font-weight-bold);
|
||
letter-spacing: -0.3px;
|
||
color: var(--color-text-primary);
|
||
}
|
||
|
||
.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: 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);
|
||
}
|
||
|
||
.card--interactive:hover {
|
||
transform: translateY(-1px);
|
||
box-shadow: var(--shadow-md);
|
||
}
|
||
|
||
.card--interactive:active {
|
||
transform: scale(0.99);
|
||
}
|
||
|
||
.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;
|
||
animation: modal-overlay-in 0.2s ease forwards;
|
||
}
|
||
|
||
@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-y: auto;
|
||
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
||
animation: modal-slide-up 0.25s var(--ease-out) forwards;
|
||
}
|
||
|
||
@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);
|
||
position: sticky;
|
||
top: 0;
|
||
background-color: var(--color-surface);
|
||
z-index: 1;
|
||
}
|
||
|
||
.modal-panel__title {
|
||
font-size: var(--text-md);
|
||
font-weight: var(--font-weight-semibold);
|
||
}
|
||
|
||
.modal-panel__close {
|
||
width: var(--target-sm);
|
||
height: var(--target-sm);
|
||
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;
|
||
}
|
||
|
||
.modal-panel__close::before {
|
||
content: '';
|
||
position: absolute;
|
||
inset: -8px;
|
||
}
|
||
|
||
.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);
|
||
}
|
||
|
||
.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 (Floating Action Button) */
|
||
.fab {
|
||
position: fixed;
|
||
bottom: calc(var(--nav-bottom-height) + var(--safe-area-inset-bottom) + var(--space-4));
|
||
right: var(--space-4);
|
||
width: 52px;
|
||
height: 52px;
|
||
border-radius: var(--radius-full);
|
||
background-color: var(--module-accent, var(--color-btn-primary));
|
||
color: var(--color-text-on-accent);
|
||
box-shadow: var(--shadow-lg);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: calc(var(--z-nav) - 1);
|
||
transition:
|
||
transform var(--transition-fast),
|
||
background-color var(--transition-fast),
|
||
box-shadow var(--transition-fast);
|
||
animation: fab-in 0.35s var(--ease-out) backwards;
|
||
}
|
||
|
||
.fab:hover {
|
||
background-color: color-mix(in srgb, var(--module-accent, var(--color-btn-primary)) 85%, black);
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
.fab:active {
|
||
transform: scale(0.95);
|
||
}
|
||
|
||
.fab:focus-visible {
|
||
outline: 2px solid #fff;
|
||
outline-offset: 2px;
|
||
}
|
||
|
||
@media (min-width: 1024px) {
|
||
.fab {
|
||
bottom: var(--space-8);
|
||
width: var(--target-lg);
|
||
height: var(--target-lg);
|
||
}
|
||
}
|
||
|
||
/* 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%;
|
||
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--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: blur(12px);
|
||
-webkit-backdrop-filter: blur(12px);
|
||
padding-bottom: var(--space-3);
|
||
margin-bottom: var(--space-4);
|
||
}
|
||
|
||
/* --------------------------------------------------------
|
||
* 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: 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--success { background-color: var(--color-success); color: var(--color-text-on-accent); }
|
||
.toast--danger { background-color: var(--color-danger); color: var(--color-text-on-accent); }
|
||
.toast--warning { background-color: var(--color-warning); color: var(--color-text-on-accent); }
|
||
|
||
@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);
|
||
}
|
||
|
||
.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-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;
|
||
}
|
||
|
||
.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: 70ms; }
|
||
.list-stagger > *:nth-child(4) { animation-delay: 105ms; }
|
||
.list-stagger > *:nth-child(5) { animation-delay: 140ms; }
|
||
.list-stagger > *:nth-child(n+6) { animation-delay: 175ms; }
|
||
|
||
/* --------------------------------------------------------
|
||
* 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;
|
||
}
|
||
|
||
/* --------------------------------------------------------
|
||
* 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: #fff;
|
||
color: #000;
|
||
}
|
||
|
||
.card {
|
||
box-shadow: none;
|
||
border: 1px solid #ddd;
|
||
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;
|
||
}
|