Files
troogs e6cd5f5a1d Usability U01–U16: sichere Abläufe und Bedienkorrekturen (#1)
Dieser PR setzt die Usability-Maßnahmen U01–U16 und die Nacharbeit an Betragseingabe, Toasts und Aktionsleisten um. Analyse: `docs/usability-analyse.md`; Plan und fortlaufendes Prüfprotokoll: `docs/usability-umsetzung.md`.

- **U01–U03:** konsistente SQLite-Sicherung der aktiven Daten einschließlich WAL, geprüfte Wiederherstellung mit Rückfallsicherung; strukturierte Dateioperationen; PDF-Auszüge nur mit wirksamen Buchungen, erklärtem Zeitraum, Kontostand und internen Umbuchungen.
- **U04–U07:** gemeinsamer Jahreskontext einschließlich Alle Jahre; Hinweise und Rückweg für abweichendes Buchungsjahr; Alltagssprache; erkennbare Konto-/Buchungsart und Validierung; Schutz vor mehrfacher Speicherung einschließlich Retry nach bereits bestätigtem Schreiben.
- **Nacharbeit:** gemeinsames Betragscontrol weist zusätzliche Nachkommastellen und ungültige Einfügungen vollständig ab, ohne zu kürzen oder zu runden. Globale stapelbare Toasts erscheinen oben, laufen zeitgesteuert ab und pausieren bei Hover/Fokus. Laufende Vorgänge bleiben bis Abschluss sichtbar. Einheitliche Aktionsbuttons reservieren Symbol und zwei Textzeilen. Debug-Datenordner für isolierte Frontend-Tests.
- **U08:** native Modaldialoge mit Fokus, inaktivem Hintergrund, Escape, Rückkehr zum Auslöser und Verlustschutz; semantische Kontolinks; sichere Bestätigungen starten auf Abbrechen.
- **U09:** größere beschriftete Zeilenaktionen mit Buchungsbezug und Kartenansicht im schmalen Fenster.
- **U10:** gelöschte Buchungen standardmäßig ausgeblendet, gezählt erreichbar und ausdrücklich nicht mitgerechnet; Bestätigungen erklären beide Transferkonten, gezieltes Rückgängig mit Zustandsprüfung.
- **U11:** dieselbe Von→Nach-Richtung auf beiden Seiten, festes/änderbares Konto und Minus/Plus-Vorschau; Dienst verhindert auch beim Bearbeiten gleiche Konten.
- **U12:** Anfangsbestand bei Einrichtung getrennt von späterer Korrektur. Korrektur zeigt Jahr/Konto und vorher/nachher, warnt vor Auswirkungen auf frühere/spätere Jahre und benötigt Zustimmung. Transaktionale Prüfung weist veraltete Vorschau ab. Die gemeinsame Bestandssemantik bleibt erhalten; keine Migration oder neue Jahresspeicherung.

- **U13:** lesbare Grundschrift, lokal gespeicherte Textgröße bis 200 %, umbrechende Namen, responsive Formulare/Karten und im Fenster scrollbar bleibende Dialoge. Stichproben der Text-/Betragsrollen mindestens 5,91:1 (hell) bzw. 8,37:1 (dunkel); primäre Aktionsfarben mindestens 6,47:1.
- **U14:** sichtbare lokale Hilfe und überspringbare Einführung mit gespeichertem Fortschritt: Vereinsname, eigenes Konto/Anfangsbestand, tatsächliche erste Buchung und Sicherung. Keine Registrierung oder automatisch erzeugte Beispielbuchungen.
- **U15:** Suche über Beschreibung, Nummer, Datum, Gegenkonto und deutschen Betrag; Sortierung nach Datum, vorzeichenrichtigem Betrag oder Beschreibung. Trefferzahl und wirksame Treffermengensumme sind vom unveränderten Kontostand getrennt. Reset erhält den gewählten Jahreskontext.
- **U16:** Bootstrap 5.3.3 und Bootstrap Icons 1.11.3 samt lokalen Schriftdateien und MIT-Lizenzen werden ausgeliefert; keine CDN-Ressourcen beim Start. Unerwartete Fehler bieten Wiederholung/Übersicht und kopierbare Referenz/Fehlertyp ohne Buchungsdaten. Nicht gefunden bietet Rückweg. Erwartbare Speicherfehler erhalten Eingaben.

Validierung: **60/60 Tests in Debug und 60/60 in Release**, einschließlich SQLite-WAL/Restore, PDF-Textextraktion, mehrjähriger Bestandskorrektur, Transferänderungen von beiden Seiten, Löschung/Undo beider Transferbuchungen, Formular-/Navigationsabläufen, Einführungsfortsetzung, Suche aus 400 Einträgen und anonymisierter Fehleranzeige. Nach jeder Maßnahme Desktop gebaut und Debug-Tests ausgeführt; final auch Release-Desktop-Build. Keine Buildwarnungen, `git diff --check` sauber. PDF-Einzelkonto, Übersicht und mehrseitiger Auszug visuell geprüft.

Computer Use mit künstlichem Bestand: zusätzliche Nachkommastelle abgewiesen, gültige 12,50-€-Einnahme gespeichert, Kontostand und Toast kontrolliert. U08-Fokus im Eingabefeld, ausschließlich modaler Accessibility-Baum und Escape-Schließen beobachtet. Nach erneuter Fensterzuordnung finale Anwendung mit lokalen Ressourcen gestartet; beschriftetes Zeilenmenü und korrekte Umbuchungsrichtung von der empfangenen Seite kontrolliert. Bestandskorrektur zeigt Konto/Jahr, Formel, Vorher/Nachher und Warnung für alle Jahre, ohne zu speichern. Dabei gefundene Textbindung im Betragsfeld korrigiert und als Regression geprüft; ebenso erklärter Leerzustand, sichtbares Undo-Refresh und zweites Escape im Verlustschutz.

Offline-Layoutprüfung: tatsächliche gerenderte Komponenten mit Produktions-CSS/Schriften in Headless Chromium, Browsernetz blockiert, **0 externe Requests**. **16 Kombinationen** bei 768 × 480 CSS-Pixeln, hell/dunkel und 100/125/150/200 % Textgröße bestehen; keine horizontale Seitenüberschreitung, Dialog im Fenster, Speichern/Abbrechen durch Scrollen erreichbar. Repräsentative PNGs visuell geprüft.

Grenzen: Browser-Textvergrößerung ist kein vollständiger Test realer Windows-DPI-Einstellungen. Keine vollständige Screenreader-/Zielgruppenabnahme und kein Nutzertest mit unerfahrenen Personen behauptet. Native Fensterzuordnung war zeitweise instabil; deshalb sind automatisierte Komponentenprüfungen und Browser-Layoutnachweise ausdrücklich von den beschriebenen nativen Beobachtungen getrennt. Keine Vereinsdaten geändert oder zurückgespielt, keine Bankvorgänge ausgeführt. Keine neue Jahres-Speichersemantik; Rückfallsicherungen werden nicht automatisch gelöscht. Vereinsname, Einführungsfortschritt und Darstellung gehören nicht zum Datenbank-Backup.

U08–U12 wurden einzeln committed und als Paket gepusht; U13–U16 ebenfalls in eigenen sachlichen Commits. Nacharbeit an Betragseingabe/Toasts/Aktionsleisten liegt in `86a73b8`. Plan und Prüfprotokoll: `docs/usability-umsetzung.md`. Kein Merge; zur manuellen Analyse durch den Auftraggeber.

Weitere Bedienkorrekturen nach manueller Analyse:

- Betragscontrol blockiert zusätzliche Nachkommastellen und ungültige Einfügungen jetzt ohne Warnmeldung.
- Jahreswechselhinweis als Warnbox mit Symbol, Datum und beiden Jahren. Er erscheint beim Verlassen des Datumsfelds; der native Kalender bleibt beim Blättern über den Jahreswechsel geöffnet. Submit ohne vorherigen Fokuswechsel prüft den aktuellen Datumsentwurf; Verlustschutz berücksichtigt ihn sofort. Gilt auch für Umbuchungen.
- Historie als beschrifteter Switch mit vertikalem Abstand. Direkt sichtbare Stift-/Papierkorbbuttons mit mindestens 44 × 44 Pixeln, Title und buchungsbezogenem Accessibility-Namen ersetzen das Zeilenmenü.
- Windows-Anwendungssymbol: ungenutzte native Photino-Benachrichtigungsregistrierung deaktiviert, damit sie die explizite Windows-App-ID nicht überschreibt und keine dotnet-Host-Verknüpfungen erzeugt. Bestehendes Icon bleibt in EXE und nativem Fenster eingebunden.

Zusätzliche Validierung: 60/60 Debug- und 60/60 Release-Tests; beide Desktop-Builds und eigenständige Windows-Einzeldatei ohne Warnungen/Fehler. Alle 16 Offline-Layoutkombinationen erneut bestanden. Computer Use bestätigt Switch, direkten Bearbeiten-Button, stille Abweisung der dritten Nachkommastelle und geöffneten nativen Kalender beim Blättern von Dezember 2026 nach Januar 2027; Warnbox erscheint nach Wechsel zur Beschreibung. Kein Speichern in diesem Prüflauf. Das aus der veröffentlichten EXE extrahierte Icon und das Titelleistensymbol wurden visuell geprüft; Taskleistendarstellung nicht separat erfasst. UI-Nacharbeit in aa39281; Windows-Zuordnung in eigenem Commit. Kein Merge.

Weitere UI-Korrekturen in 038fbc0: Stift-/Papierkorbsymbole horizontal und vertikal zentriert, Tabellenkopf mit Designfarben statt festem hellem Hintergrund. Alle sechs allgemeinen „Dateiaktion läuft“-Meldungen durch konkrete Angaben zu Sicherung, Wiederherstellung, PDF-Übersicht, Kontoauszug bzw. PDF-Öffnen ersetzt. 60/60 Tests in Debug und Release, beide Desktop-Builds ohne Warnungen/Fehler. Zusätzliche Browserprüfung bei 100/200 % in hell/dunkel: Symbolzentrierung innerhalb 0,6 Pixeln, mindestens 44 × 44 Pixel große Buttons, Headerkontrast 17,85:1 (hell) bzw. 12,89:1 (dunkel); alle 16 bisherigen Offline-Layoutkombinationen bestanden und dunkler Tabellenkopf visuell geprüft.

Textgröße um „Klein (75 %)“ ergänzt, einschließlich Speicherung und Wiederherstellung beim Start. Debug-Suite 60/60 bestanden, Desktop-Build ohne Warnungen/Fehler. JavaScript-Prüfung bestätigt 12-Pixel-Grundschrift bei 75 % und korrektes Speichern/erneutes Laden der Auswahl.

Anzeige gelöschter Buchungen dauerhaft und kontenübergreifend: gespeicherte benutzerweite Einstellung wird bei jedem Konto geladen und bleibt über Neustarts sowie „Filter zurücksetzen“ bestehen, bis der Switch ausgeschaltet wird. Neue Regressionen prüfen Kontowechsel, Reset, Ausschalten, Neustart und Erhalt anderer Einstellungen. 62/62 Tests jeweils in Debug und Release; Debug-Build ohne Warnungen/Fehler. Vorhandenen Publish-Task erfolgreich ausgeführt: Publish/Duempelkas-v26.306.exe. Die bereits vorhandene lokale Kontokarten-Anpassung des Auftraggebers ist in der lokalen Veröffentlichung enthalten und bleibt außerhalb dieses Commits.

Über-Dialog zeigt neben der Version das Builddatum im Format TT.MM.JJJJ. Zeitstempel wird beim Desktop-Build als Assembly-Metadatum eingebettet und funktioniert dadurch auch bei der Einzeldatei-Veröffentlichung. Eingebettetes Datum aus erzeugter Desktop-Assembly geprüft; 62/62 Tests jeweils in Debug und Release, Debug-Build ohne Warnungen/Fehler. Publish-Task erneut erfolgreich ausgeführt und Publish/Duempelkas-v26.306.exe aktualisiert.

Reviewed-on: #1
2026-11-02 21:47:44 +01:00

718 lines
22 KiB
CSS
Raw Permalink 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.
/* Duempelkas App Styles */
:root {
--color-income: #4ade80;
--color-expense: #f87171;
--color-transfer: #60a5fa;
--color-bg: #ddd;
--color-surface: #ffffff;
--color-surface-hover: #f1f5f9;
--color-border: #d9e2ef;
--color-text: #0f172a;
--color-text-muted: #64748b;
--color-accent: #4f46e5;
}
html[data-ui-theme="dark"] {
--color-bg: #1a1a2e;
--color-surface: #16213e;
--color-surface-hover: #1e2d4a;
--color-border: #2a2a4a;
--color-text: #e2e8f0;
--color-text-muted: #94a3b8;
--color-accent: #818cf8;
}
html, body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: var(--color-bg);
color: var(--color-text);
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
}
/* Blazor error UI – hidden by default, shown by framework JS on unhandled errors */
#blazor-error-ui {
display: none;
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 0.5rem 1rem;
background: rgba(248, 113, 113, 0.95);
color: #fff;
font-size: 0.85rem;
z-index: 9999;
text-align: center;
}
#blazor-error-ui a {
color: #fff;
text-decoration: underline;
margin-left: 0.5rem;
}
/* Cards */
.account-card {
cursor: pointer;
transition: all 0.2s ease;
background-color: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: 0.75rem;
padding: .5rem;
margin-bottom: .5rem;
}
.account-card .card-title {
color: var(--color-text);
font-weight: normal;
}
.account-card:hover {
background-color: var(--color-surface-hover);
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(0,0,0,0.4);
border-color: var(--color-accent);
}
/* Amounts */
.amount-positive {
color: var(--color-income);
font-weight: 600;
}
.amount-negative {
color: var(--color-expense);
font-weight: 600;
}
/* Entry rows */
.entry-row-transfer {
background-color: rgba(96, 165, 250, 0.08);
}
.entry-deleted {
opacity: 0.5;
}
.entry-deleted td {
text-decoration: line-through;
}
.entry-deleted td:last-child {
text-decoration: none;
}
/* Summary boxes */
.summary-section {
background-color: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: 0.75rem;
padding: .5rem .75rem;
margin-bottom: .5rem;
}
.summary-flex {
display: flex;
align-items: center;
gap: 2rem;
flex-wrap: wrap;
}
/* Dialog */
.dialog-backdrop {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background-color: rgba(0,0,0,0.6);
z-index: 1040;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(4px);
}
.dialog-content {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: 0.75rem;
padding: 1rem 1.25rem;
min-width: 400px;
max-width: 600px;
box-shadow: 0 8px 32px rgba(0,0,0,0.5);
}
.dialog-content h5 {
margin-top: 0 !important;
margin-bottom: 0.5rem !important;
}
.filter-options-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 0.5rem;
}
.filter-options-grid .btn {
justify-content: center;
}
/* Navbar */
.app-navbar {
background-color: var(--color-surface);
border-bottom: 1px solid var(--color-border);
padding: 0.5rem 1rem;
}
.app-navbar .navbar-brand {
color: var(--color-text);
text-decoration: none;
}
.app-navbar .navbar-brand:hover {
color: var(--color-accent);
}
.app-navbar .version-label {
color: var(--color-text-muted);
font-size: 0.7rem;
opacity: 0.6;
}
/* Footer */
.app-footer {
background-color: var(--color-surface);
border-top: 1px solid var(--color-border);
color: var(--color-text-muted);
padding: 0.4rem 1rem;
font-size: 0.75rem;
text-align: center;
}
.app-footer-about {
color: var(--color-text-muted);
text-decoration: underline;
padding: 0;
}
.app-footer-about:hover,
.app-footer-about:focus {
color: var(--color-text);
cursor: pointer;
}
/* Buttons – modern style */
.btn {
font-size: 0.85rem;
padding: 0.35rem 0.85rem;
display: inline-flex;
align-items: center;
gap: 0.35rem;
transition: all 0.15s ease;
}
.btn i {
font-size: 0.9rem;
}
.btn-nav {
width: auto;
min-width: 7rem;
max-width: 15rem;
min-height: 75px;
height: auto;
white-space: normal;
overflow-wrap: anywhere;
text-align: center;
display: flex;
flex-direction: column;
gap: 0;
justify-content: center;
font-size: 0.8rem !important;
}
.btn-nav i {
font-size: 2rem;
}
.btn-primary {
background-color: var(--color-accent);
border-color: var(--color-accent);
color: #fff;
}
.btn-primary:hover {
background-color: #6366f1;
border-color: #6366f1;
}
.btn-success {
background-color: #22c55e;
border-color: #22c55e;
color: #fff;
}
.btn-success:hover {
background-color: #16a34a;
border-color: #16a34a;
}
.btn-danger {
background-color: #ef4444;
border-color: #ef4444;
color: #fff;
}
.btn-danger:hover {
background-color: #dc2626;
border-color: #dc2626;
}
.btn-warning {
background-color: #facc15;
border-color: #facc15;
color: #000;
}
.btn-warning:hover {
background-color: #eab308;
border-color: #eab308;
}
.btn-info {
background-color: #3b82f6;
border-color: #3b82f6;
color: #fff;
}
.btn-info:hover {
background-color: #2563eb;
border-color: #2563eb;
}
.btn-outline-secondary {
border-color: var(--color-border);
color: var(--color-text-muted);
}
.btn-outline-secondary:hover {
background-color: var(--color-surface-hover);
border-color: var(--color-text-muted);
color: var(--color-text);
}
.btn-outline-danger {
border-color: rgba(248, 113, 113, 0.4);
color: var(--color-expense);
}
.btn-outline-danger:hover {
background-color: rgba(248, 113, 113, 0.15);
border-color: var(--color-expense);
color: var(--color-expense);
}
.btn-outline-success {
border-color: rgba(74, 222, 128, 0.4);
color: var(--color-income);
}
.btn-outline-success:hover {
background-color: rgba(74, 222, 128, 0.15);
border-color: var(--color-income);
color: var(--color-income);
}
.btn-dark {
background-color: #334155;
border-color: #475569;
color: #f8fafc;
}
.btn-dark:hover {
background-color: #475569;
border-color: #64748b;
}
html[data-ui-theme="light"] .btn-dark {
background-color: #334155;
border-color: #334155;
color: #f8fafc;
}
/* Table */
.table {
--bs-table-bg: transparent;
--bs-table-color: var(--color-text);
--bs-table-hover-bg: rgba(255,255,255,0.04);
border-color: var(--color-border);
}
.table thead th {
background-color: var(--color-surface);
border-bottom-color: var(--color-border);
color: var(--color-text);
font-weight: 600;
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.table td {
border-bottom-color: var(--color-border);
}
/* Form container */
.form-container {
display: grid;
/* Col 1: fits largest label | Col 2: takes the rest */
grid-template-columns: max-content 1fr;
gap: 1rem;
align-items: center;
max-width: 500px;
padding-bottom: 1rem;
}
/* Ensure the label and input stay on the same row */
.form-container label {
grid-column: 1;
font-weight: 600;
}
.form-container input,
.form-container select,
.form-container textarea {
grid-column: 2;
padding: 0.5rem;
width: 100%; /* Fill the available space in col 2 */
box-sizing: border-box;
}
/* Form controls in dark */
.form-control, .form-select {
background-color: var(--color-bg);
border-color: var(--color-border);
color: var(--color-text);
}
.form-control:focus, .form-select:focus {
background-color: var(--color-bg);
border-color: var(--color-accent);
color: var(--color-text);
box-shadow: 0 0 0 0.2rem rgba(129, 140, 248, 0.25);
}
.form-label {
color: var(--color-text-muted);
font-size: 0.85rem;
}
/* Edit button pen */
.btn-edit-pen {
background: none;
border: none;
color: var(--color-text-muted);
padding: 0.1rem 0.3rem;
font-size: 0.9rem;
cursor: pointer;
transition: color 0.15s;
}
.btn-edit-pen:hover {
color: var(--color-accent);
}
/* Spinner */
.spinner-border {
color: var(--color-accent);
}
/* Text muted override */
.text-muted {
color: var(--color-text-muted) !important;
}
/* Scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: var(--color-bg);
}
::-webkit-scrollbar-thumb {
background: var(--color-border);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #3a3a5a;
}
/* ===== Bootstrap utility class fallbacks =====
Ensures layout works even when Bootstrap CDN is unavailable */
/* Display */
.d-flex { display: flex !important; }
.d-inline-flex { display: inline-flex !important; }
.d-block { display: block !important; }
.d-none { display: none !important; }
/* Flex direction */
.flex-column { flex-direction: column !important; }
.flex-row { flex-direction: row !important; }
.flex-wrap { flex-wrap: wrap !important; }
.flex-nowrap { flex-wrap: nowrap !important; }
/* Flex sizing */
.flex-grow-1 { flex-grow: 1 !important; }
.flex-shrink-0 { flex-shrink: 0 !important; }
/* Justify content */
.justify-content-start { justify-content: flex-start !important; }
.justify-content-end { justify-content: flex-end !important; }
.justify-content-center { justify-content: center !important; }
.justify-content-between { justify-content: space-between !important; }
/* Align items */
.align-items-start { align-items: flex-start !important; }
.align-items-center { align-items: center !important; }
.align-items-end { align-items: flex-end !important; }
/* Gap */
.gap-1 { gap: 0.25rem !important; }
.gap-2 { gap: 0.5rem !important; }
.gap-3 { gap: 1rem !important; }
.gap-4 { gap: 1.5rem !important; }
/* Height / overflow */
.vh-100 { height: 100vh !important; }
.h-100 { height: 100% !important; }
.overflow-auto { overflow: auto !important; }
.overflow-hidden { overflow: hidden !important; }
/* Margin */
.mb-0 { margin-bottom: 0 !important; }
.mb-1 { margin-bottom: 0.25rem !important; }
.mb-2 { margin-bottom: 0.5rem !important; }
.mb-3 { margin-bottom: 1rem !important; }
.mb-4 { margin-bottom: 1.5rem !important; }
.mt-0 { margin-top: 0 !important; }
.mt-1 { margin-top: 0.25rem !important; }
.mt-2 { margin-top: 0.5rem !important; }
.mt-3 { margin-top: 1rem !important; }
.mt-4 { margin-top: 1.5rem !important; }
.me-1 { margin-right: 0.25rem !important; }
.me-2 { margin-right: 0.5rem !important; }
.ms-auto { margin-left: auto !important; }
/* Padding */
.p-0 { padding: 0 !important; }
.p-1 { padding: 0.25rem !important; }
.p-2 { padding: 0.5rem !important; }
.p-3 { padding: 1rem !important; }
.px-1 { padding-left: 0.25rem !important; padding-right: 0.25rem !important; }
.px-2 { padding-left: 0.5rem !important; padding-right: 0.5rem !important; }
.px-3 { padding-left: 1rem !important; padding-right: 1rem !important; }
.py-1 { padding-top: 0.25rem !important; padding-bottom: 0.25rem !important; }
.py-2 { padding-top: 0.5rem !important; padding-bottom: 0.5rem !important; }
.py-3 { padding-top: 1rem !important; padding-bottom: 1rem !important; }
.py-4 { padding-top: 1.5rem !important; padding-bottom: 1.5rem !important; }
.py-5 { padding-top: 3rem !important; padding-bottom: 3rem !important; }
/* Text */
.text-center { text-align: center !important; }
.text-end { text-align: right !important; }
.text-start { text-align: left !important; }
.fw-bold { font-weight: 700 !important; }
.fw-600 { font-weight: 600 !important; }
.fs-3 { font-size: 1.75rem !important; }
.small { font-size: 0.875rem !important; }
/* Button sizes */
.btn-sm {
padding: 0.25rem 0.6rem !important;
font-size: 0.8rem !important;
}
/* Container */
.container-fluid {
}
/* Spinner (Bootstrap component fallback) */
.spinner-border {
display: inline-block;
width: 2rem;
height: 2rem;
border: 0.25em solid var(--color-accent);
border-right-color: transparent;
border-radius: 50%;
animation: spinner-border 0.75s linear infinite;
}
@keyframes spinner-border {
to { transform: rotate(360deg); }
}
/* ===== Scrollable entry table ===== */
.entries-scroll-wrapper {
overflow-x: auto;
overflow-y: auto;
flex: 1;
min-height: 0;
}
/* ===== Modern form inputs ===== */
.form-control[type="number"],
.dialog-content input[type="number"] {
text-align: right;
}
.no-spinner::-webkit-outer-spin-button,
.no-spinner::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type="number"].no-spinner {
-moz-appearance: textfield;
appearance: textfield;
}
.form-control, .form-select {
border-radius: 0.5rem;
transition: border-color 0.15s, box-shadow 0.15s;
}
.form-label {
font-size: 0.78rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--color-text-muted);
margin-bottom: 0.3rem;
}
.more-actions { align-self: center; }
.more-actions[open] { padding: .5rem; border: 1px solid var(--color-border); border-radius: .5rem; }
.form-container > div { min-width: 0; }
.form-container p { margin: .35rem 0 0; font-size: .9rem; }
.booking-dialog { max-height: calc(100vh - 2rem); overflow-y: auto; }
fieldset { min-width: 0; }
.entry-recent > td { animation: saved-booking 6s ease-out; }
@keyframes saved-booking {
from { background-color: var(--color-surface-hover); box-shadow: inset 0 2px var(--color-accent), inset 0 -2px var(--color-accent); }
to { background-color: transparent; box-shadow: none; }
}
@media (prefers-reduced-motion: reduce) {
.entry-recent > td { animation: none; border-bottom: 2px solid var(--color-accent); }
}
/* One icon row plus two reserved text rows on every page. */
.action-toolbar { display: flex; flex-wrap: wrap; gap: .5rem; padding: 1rem 0; align-items: flex-start; }
.action-toolbar .btn-nav { box-sizing: border-box; width: 10.5rem; min-width: 10.5rem; max-width: 10.5rem; height: 6.25rem; min-height: 6.25rem; padding: .45rem .6rem; font-size: .9rem !important; line-height: 1.15rem; gap: .2rem; border-radius: .5rem; border: 1px solid transparent; overflow-wrap: normal; }
.action-toolbar .btn-nav i { display: block; font-size: 1.65rem; line-height: 1.85rem; }
.action-toolbar .btn-nav span { display: block; min-height: 2.3rem; }
.action-toolbar .more-actions { align-self: flex-start; position: relative; }
.action-toolbar .more-actions[open] { padding: 0; border: 0; }
.more-actions > button { position: absolute; z-index: 20; right: 0; min-width: 15rem; box-shadow: 0 4px 16px #0004; }
.toast-stack { position: fixed; z-index: 3000; inset: .75rem .75rem auto auto; width: min(28rem, calc(100vw - 1.5rem)); max-height: calc(100vh - 1.5rem); overflow-y: auto; display: flex; flex-direction: column; gap: .65rem; pointer-events: none; }
.app-toast { pointer-events: auto; padding: 1rem 2.5rem 1rem 1rem; color: var(--color-text); background: var(--color-surface); border: 1px solid var(--color-border); border-left: .35rem solid var(--color-accent); border-radius: .65rem; box-shadow: 0 5px 22px #0004; position: relative; animation: toast-enter .2s ease-out; overflow-wrap: anywhere; }
.toast-success { border-left-color: var(--color-income); }
.toast-error { border-left-color: var(--color-expense); }
.app-toast p { margin: 0 0 .5rem; }
.toast-close { position: absolute; right: .3rem; top: .3rem; background: transparent; border: 0; color: inherit; width: 2rem; height: 2rem; font-size: 1.5rem; }
@keyframes toast-enter { from { transform: translateY(-2rem); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .app-toast { animation: none; } }
.app-modal { color: var(--color-text); max-height: calc(100dvh - 2rem); overflow-y: auto; }
.app-modal::backdrop { background: #0009; backdrop-filter: blur(3px); }
.dialog-actions { display: flex; gap: .5rem; flex-wrap: wrap; justify-content: flex-end; }
.account-card { display: block; width: 100%; color: inherit; text-decoration: none; }
:focus-visible { outline: 3px solid var(--color-accent); outline-offset: 3px; }
fieldset { border: 0; margin: 0; padding: 0; }
body:has(dialog[open]) .toast-stack:not(.modal-toasts) { display: none; }
.visually-hidden { position: absolute !important; width: 1px !important; height: 1px !important; padding: 0 !important; margin: -1px !important; overflow: hidden !important; clip: rect(0,0,0,0) !important; white-space: nowrap !important; border: 0 !important; }
.entry-actions button { min-height: 44px; min-width: 44px; padding: .6rem; }
.entry-action-buttons { display: flex; gap: .5rem; }
.entry-action-buttons .btn { display: inline-flex; align-items: center; justify-content: center; }
.entry-action-buttons .btn i { margin: 0; line-height: 1; }
.entry-action-buttons .btn i::before { display: block; }
.entry-description { overflow-wrap: anywhere; }
@media (max-width: 900px) {
.entry-list thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); }
.entry-list table, .entry-list tbody { display: block; width: 100%; }
.entry-list tbody tr { display: grid; grid-template-columns: 1fr 1fr; border: 1px solid var(--color-border); border-radius: .5rem; padding: .6rem; margin-bottom: .75rem; }
.entry-list td { display: block; border: 0; min-width: 0; padding: .3rem; text-align: left !important; }
.entry-list td::before { content: attr(data-label); display: block; color: var(--color-text-muted); font-size: .85rem; }
.entry-list .entry-description, .entry-list .entry-actions { grid-column: 1 / -1; }
}
.history-toggle { margin-block: 1rem; min-height: 2rem; display: flex; align-items: center; gap: .75rem; }
.history-toggle .form-check-input { flex-shrink: 0; margin-top: 0; }
.booking-year-warning { display: flex; gap: .75rem; align-items: flex-start; }
.booking-year-warning > i { font-size: 1.4rem; }
.booking-year-warning label { display: block; margin-top: .75rem; }
dialog.app-modal { scrollbar-gutter: stable; }
.entry-deleted { opacity: 1 !important; }
.deleted-status { text-decoration: none; color: var(--color-text-muted); margin: .3rem 0; }
/* Readable theme roles, also used when external styles are unavailable. */
:root { --color-bg: #f1f5f9; --color-income: #166534; --color-expense: #b91c1c; --color-transfer: #1d4ed8; --color-text-muted: #475569; --color-border: #94a3b8; }
html[data-ui-theme="dark"] { --color-income: #4ade80; --color-expense: #fca5a5; --color-transfer: #93c5fd; --color-text-muted: #cbd5e1; --color-border: #64748b; }
html { font-size: var(--app-font-size, 16px); }
body { line-height: 1.5; }
h1,h2,h3,h4,h5,h6 { font-family: inherit; }
small, .text-muted { color: var(--color-text-muted) !important; }
small { font-size: .9rem; }
.form-label, .table thead th { text-transform: none; letter-spacing: normal; font-size: 1rem; }
.app-shell { height: 100dvh; display: grid; grid-template-rows: auto minmax(0, 1fr) auto; }
.app-navbar, .app-footer { flex-wrap: wrap; gap: .5rem; height: auto; min-height: 2.5rem; }
.app-footer { font-size: .9rem; }
.reading-controls { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.reading-controls select { padding: .35rem; color: var(--color-text); background: var(--color-surface); border: 1px solid var(--color-border); }
.page-heading, .card-title { overflow-wrap: anywhere; white-space: normal; }
.app-modal { box-sizing: border-box; width: min(38rem, calc(100vw - 2rem)); min-width: 0; max-width: calc(100vw - 2rem); max-height: calc(100dvh - 2rem); }
.app-modal p, .app-modal label { overflow-wrap: anywhere; }
.app-modal .dialog-actions, .app-modal form > .d-flex { flex-wrap: wrap; position: sticky; bottom: -1rem; background: var(--color-surface); padding: .75rem 0; }
.form-container { max-width: none; }
.btn-primary { background: #4338ca; border-color: #4338ca; color: #fff; }
.btn-success { background: #166534; border-color: #166534; color: #fff; }
.btn-info { background: #1e40af; border-color: #1e40af; color: #fff; }
.btn-danger { background: #b91c1c; border-color: #b91c1c; color: #fff; }
.btn-warning { background: #fbbf24; border-color: #fbbf24; color: #111827; }
.btn-secondary { background: var(--color-surface-hover); color: var(--color-text); border-color: var(--color-border); }
.btn:hover { filter: brightness(.92); }
input,select,textarea,button { font: inherit; }
button,summary { touch-action: manipulation; }
@media (max-width: 1000px) {
.form-container { grid-template-columns: minmax(0, 1fr); gap: .5rem; }
.form-container label,.form-container input,.form-container select,.form-container textarea { grid-column: auto; }
.action-toolbar { gap: .5rem; }
}
.btn-primary:hover { background: #4338ca; border-color: #4338ca; color: #fff; }
.btn-success:hover { background: #166534; border-color: #166534; color: #fff; }
.btn-info:hover { background: #1e40af; border-color: #1e40af; color: #fff; }
.btn-danger:hover { background: #b91c1c; border-color: #b91c1c; color: #fff; }
.entry-filters { display: flex; flex-wrap: wrap; align-items: flex-end; gap: .75rem; padding: .75rem 0; }
.entry-filters > div { flex: 1 1 14rem; min-width: 0; }
.entry-filters input,.entry-filters select { width: 100%; box-sizing: border-box; padding: .5rem; color: var(--color-text); background: var(--color-surface); border: 1px solid var(--color-border); }
.search-summary { padding: .6rem; border-left: 3px solid var(--color-accent); }
.text-danger { color: var(--color-expense) !important; }
.text-success { color: var(--color-income) !important; }
.entry-deleted td { text-decoration: none; }
.entry-deleted .entry-title, .entry-deleted td[data-label="Betrag"] { text-decoration: line-through; }