Files
oikos/public/styles/layout.css
T
Ulas Kalayci 201fa05afd feat(ux): microinteraction polish — undo tap feedback, strikethrough transition, modal loading state
- toast__undo: add :active scale + tap-highlight-color for reliable tap feedback
- task titles: animate text-decoration-color instead of snapping for smoother done-state
- modal forms: auto-add btn--loading on submit; rAF guard removes it on validation fail;
  MutationObserver removes it on error re-enable; btnSuccess clears it before checkmark

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 00:20:42 +02:00

2095 lines
50 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); }
}
.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;
}
@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;
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%);
}
/* ── Items-Reihe ── */
.nav-bottom__items {
display: flex;
height: var(--nav-height-mobile);
}
/* ── More-Button (Button-Basis, ansonsten wie nav-item) ── */
.nav-item--more {
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(4, 1fr);
gap: var(--space-3);
transform: translateY(100%);
transition: transform 0.25s var(--ease-out);
}
.more-sheet[aria-hidden="false"] {
transform: translateY(0);
}
/* ── 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-4) + 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-4);
}
.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__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: 1440px) {
: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__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: 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-md);
height: var(--target-md);
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 (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 var(--color-bg);
outline-offset: 2px;
box-shadow: 0 0 0 4px var(--color-accent);
}
@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%;
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--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);
}
/* --------------------------------------------------------
* 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__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);
}
.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;
}
/* 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;
}
}
/* --------------------------------------------------------
* 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; }
}