Files
oikos/public/styles/layout.css
T
Ulas 187af593f7 fix(styles): resolve design system audit violations
- Replace off-grid spacing (3px, 5px) with space tokens (--space-0h, --space-1)
- Replace below-minimum font-size 9px with var(--text-xs) in calendar, dashboard, notes
- Replace hardcoded 2.5rem with var(--text-4xl) in weather widget
- Replace hardcoded box-shadow with var(--shadow-sm) in toggle thumb
- Replace 0.85em and #666 with type/color tokens in print styles
2026-04-04 23:07:39 +02:00

1573 lines
36 KiB
CSS
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;
min-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;
padding-bottom: calc(var(--nav-bottom-height) + var(--safe-area-inset-bottom));
overflow-y: auto;
overscroll-behavior-y: contain;
}
/* 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 0.2s ease, transform 0.2s ease;
}
.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: 2px;
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.
* -------------------------------------------------------- */
.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: #ffffff;
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;
}
.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: 48px;
height: 48px;
}
}
/* 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;
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: 32px;
height: 32px;
border-radius: var(--radius-sm);
background: linear-gradient(135deg, var(--color-accent) 0%, #7C5CFC 100%);
color: #ffffff;
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: 8px 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: 8px 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);
}
.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: 32px;
height: 32px;
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: 4px;
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: #ffffff;
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: #ffffff;
}
.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: #ffffff;
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);
}
.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: 48px;
height: 48px;
}
}
/* 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 0.2s ease;
flex-shrink: 0;
}
.toggle__track::after {
content: '';
position: absolute;
top: 3px;
left: 3px;
width: 20px;
height: 20px;
background: #fff;
border-radius: var(--radius-full);
box-shadow: var(--shadow-sm);
transition: transform 0.2s var(--ease-out);
}
.toggle input:checked + .toggle__track {
background-color: var(--active-module-accent, var(--color-accent));
}
.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;
}
}
/* --------------------------------------------------------
* 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 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: 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: #fff; }
.toast--danger { background-color: var(--color-danger); color: #fff; }
.toast--warning { background-color: var(--color-warning); color: #fff; }
@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: 40px;
height: 40px;
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: #fff;
}
.rrule-day--active:hover {
background: var(--color-accent-hover);
border-color: var(--color-accent-hover);
}
/* ── Submit-Feedback Animationen ── */
@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: #fff !important;
pointer-events: none;
}
/* --------------------------------------------------------
* 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;
}
/* Gemeinsam: Erledigt / Abhaken (Swipe nach links) */
.swipe-reveal--done {
right: 0;
background-color: var(--color-success);
color: #fff;
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: #fff;
font-size: var(--text-sm);
border-radius: 0 0 var(--radius-sm) 0;
}