Files
certain/web/src/finance.css
T
GuidoandClaude Opus 5 63de8368f0 feat: Kontostand von der Bank – die eigene Rechnung wird geprueft
Certain rechnet den Saldo bisher ausschliesslich selbst aus: Anfangsbestand
plus alle Buchungen. Stimmt eine Buchung nicht, faellt das erst auf, wenn man
von Hand mit dem Auszug vergleicht.

Der von der Bank gemeldete Saldo wird jetzt mitgefuehrt und danebengestellt.
Weicht er ab, fehlt oder doppelt eine Buchung – und man sieht es auf der
Kontenseite und in den Einstellungen sofort, statt es zu suchen.

Banken melden mehrere Saldi nebeneinander; ausgewaehlt wird der gebuchte, denn
nur der ist mit Certains Rechnung vergleichbar. Ein verfuegbarer Saldo enthaelt
Vorgemerktes oder einen Ueberziehungsrahmen.

Die Saldi kommen auch fuer noch nicht zugeordnete Konten: Bei mehreren
Revolut-Pockets ist der Betrag oft das Einzige, woran man sie unterscheidet.
Ein Fehler bei einem Konto beendet den Durchlauf nicht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 23:08:56 +02:00

1156 lines
24 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.
/* Eigene Stil-Ergänzungen für Certain Finance – bewusst getrennt von der aus
der Vorlage übernommenen styles.css, damit Basis und Ergänzung sauber bleiben. */
.brand-mark {
display: flex;
align-items: center;
gap: var(--sp-2);
}
.brand-logo {
display: block;
flex: 0 0 auto;
}
.brand-name {
font-weight: 650;
font-size: 1rem;
letter-spacing: -0.01em;
color: var(--text);
}
/* Beträge: Vorzeichenfarbe. Grün = Einnahme/im Budget, Rot = Ausgabe/über Budget. */
.amount.pos { color: var(--ok); }
.amount.neg { color: var(--danger); }
.amount.muted { color: var(--muted); }
/* Umgerechneter Zweitbetrag (Originalwährung -> Hauptwährung) klein und gedämpft. */
.amount-converted {
color: var(--muted);
font-size: 0.82em;
margin-left: 4px;
}
/* Schlichtes zentriertes Layout für Login / Passwort ändern. */
.auth-screen {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: var(--sp-5);
background: var(--bg);
}
.auth-card {
width: 100%;
max-width: 380px;
}
.auth-card .brand-mark {
justify-content: center;
margin-bottom: var(--sp-4);
}
/* Eingabefeld mit angehängtem Icon-Knopf (z. B. Passwort ein-/ausblenden). */
.input-with-button {
display: flex;
align-items: center;
gap: var(--sp-2);
}
.input-with-button > input {
flex: 1 1 auto;
min-width: 0;
}
/* Flex-Kinder dürfen schrumpfen – sonst drängt ein 100%-breites select/input
ein danebenliegendes Feld auf 0 (das war der Grund für das winzige IBAN-Feld). */
.row > * {
min-width: 0;
}
/* Leerer Zustand (z. B. Dashboard ohne Karten). */
.empty-state {
text-align: center;
color: var(--muted);
padding: var(--sp-6) var(--sp-4);
}
.empty-state .icon {
opacity: 0.5;
margin-bottom: var(--sp-3);
}
/* Farbiges Icon-Plättchen (Konten/Kategorien). */
.icon-chip {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-sm);
flex: 0 0 auto;
}
/* Betrag + umgerechneter Wert (rechtsbündig gestapelt). */
.money {
display: inline-flex;
flex-direction: column;
align-items: flex-end;
line-height: 1.3;
}
.money .amount {
font-variant-numeric: tabular-nums;
font-weight: 560;
}
/* Icon-Auswahlgitter. */
.icon-picker {
display: flex;
flex-wrap: wrap;
gap: var(--sp-2);
margin-top: 5px;
}
.icon-picker-item {
width: 38px;
height: 38px;
display: inline-flex;
align-items: center;
justify-content: center;
border: 1px solid var(--border-strong);
border-radius: var(--radius-sm);
background: var(--surface);
color: var(--muted);
cursor: pointer;
transition: border-color .12s, color .12s, background .12s;
}
.icon-picker-item:hover {
color: var(--text);
background: var(--surface-2);
}
.icon-picker-item.selected {
background: var(--accent-soft);
}
/* Farbwähler-Zeile. */
.color-row {
display: flex;
align-items: center;
gap: var(--sp-2);
margin-top: 5px;
}
.color-row input[type="color"] {
width: 44px;
height: 34px;
padding: 2px;
cursor: pointer;
}
/* Name + Icon nebeneinander (Listen). */
.named {
display: flex;
align-items: center;
gap: var(--sp-3);
min-width: 0;
}
.named .named-text {
min-width: 0;
}
.named .named-sub {
color: var(--muted);
font-size: 0.8rem;
}
/* Kategorien-Verwaltung: durchgehende Liste in EINER Karte, die Gruppen nur als
dezente Zwischenüberschrift dazwischen (keine abgesetzten Gruppen-Karten). */
.cat-section + .cat-section {
margin-top: var(--sp-5);
}
.cat-section-head {
display: flex;
align-items: center;
gap: var(--sp-2);
padding-bottom: var(--sp-2);
margin-bottom: 2px;
border-bottom: 2px solid var(--border-strong);
}
.cat-section-name {
flex: 1;
min-width: 0;
font-size: 1.05rem;
font-weight: 650;
}
.cat-row {
display: flex;
align-items: center;
gap: var(--sp-3);
padding: 9px 4px;
border-bottom: 1px solid var(--border);
}
.cat-section .cat-row:last-child {
border-bottom: none;
}
.cat-row:hover {
background: var(--surface-2);
}
.cat-row .grow {
flex: 1;
min-width: 0;
}
/* Seitenkopf mit Titel + Aktion rechts. */
.page-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--sp-3);
margin-bottom: var(--sp-4);
}
.page-head h1 {
margin: 0;
}
/* Konten. */
.account-total {
display: flex;
align-items: baseline;
justify-content: space-between;
padding-bottom: var(--sp-3);
margin-bottom: var(--sp-2);
border-bottom: 1px solid var(--border);
}
.account-total strong {
font-size: 1.2rem;
font-variant-numeric: tabular-nums;
}
.account-list {
display: flex;
flex-direction: column;
}
.account-row {
display: flex;
align-items: center;
gap: var(--sp-3);
padding: var(--sp-3) 0;
border-bottom: 1px solid var(--border);
}
.account-row:last-child {
border-bottom: none;
}
/* Die Kategorie-Auswahl ist `width: 100%` – als Flex-Element heisst das „ich
brauche die ganze Zeile", und alles daneben wird bis zum Umbruch
zusammengedrückt. In einer Listenzeile bekommt sie deshalb eine eigene
Breite, die nur noch schrumpfen darf. */
.account-row .catsel {
flex: 0 1 clamp(180px, 26%, 300px);
width: auto;
/* Ohne das schrumpft ein Flex-Element nie unter die Breite seines längsten
Wortes – die Auswahl bliebe breiter als vorgesehen und schöbe die Knöpfe
aus der Zeile. Der Text darin kürzt sich selbst mit „…". */
min-width: 0;
}
/* Der Abstand darüber ist für Formulare gedacht, wo die Auswahl unter ihrer
Beschriftung steht. In einer mittig ausgerichteten Zeile verrutscht sie nur. */
.account-row .catsel-btn {
margin-top: 0;
}
/* Beträge in einer Spalte statt ausgefranst. */
.account-row .money {
flex: 0 0 auto;
min-width: 104px;
white-space: nowrap;
}
/* Der Name nimmt den übrigen Platz. Nicht mit „…" abschneiden: Die Texte
sollen sich weiterhin vollständig markieren und kopieren lassen. */
.account-row .named-text {
flex: 1 1 auto;
min-width: 0;
}
@media (max-width: 820px) {
/* Auf dem Telefon passt das nicht in eine Zeile. Zwei Zeilen: oben Name und
Betrag, darunter Auswahl und Knöpfe. Die Prozentwerte sind die Bruchstelle
– sie sind gross genug, dass unten kein Platz mehr für Name und Betrag
bleibt, und klein genug, dass die Knöpfe neben der Auswahl bleiben. */
.account-row {
flex-wrap: wrap;
row-gap: var(--sp-2);
}
.account-row .named-text {
flex: 1 1 50%;
}
.account-row .money {
min-width: 0;
}
.account-row .catsel {
flex: 1 1 40%;
}
}
.row-actions {
display: flex;
gap: 4px;
flex: 0 0 auto;
}
/* Filterleiste. */
.filter-bar {
display: flex;
gap: var(--sp-3);
flex-wrap: wrap;
margin-bottom: var(--sp-4);
}
.filter-bar select {
width: auto;
min-width: 160px;
margin-top: 0;
}
/* Segmentierte Umschaltung (Ausgabe/Einnahme/Transfer). */
.seg {
display: flex;
border: 1px solid var(--border-strong);
border-radius: var(--radius-sm);
overflow: hidden;
margin-bottom: var(--sp-4);
}
.seg-btn {
flex: 1;
padding: 8px 10px;
border: none;
background: var(--surface);
color: var(--muted);
font-weight: 560;
cursor: pointer;
border-right: 1px solid var(--border);
}
.seg-btn:last-child {
border-right: none;
}
.seg-btn.active {
background: var(--accent);
color: #fff;
}
.nowrap {
white-space: nowrap;
}
/* Nimmt den freien Platz in einer Flex-Zeile und darf schrumpfen (Ellipsis). */
.grow {
flex: 1 1 auto;
min-width: 0;
}
/* Nutzer-Chip in der Seitenleisten-Fußzeile ist ein Link auf „Mein Konto". */
a.sidebar-user {
text-decoration: none;
border-radius: var(--radius-sm);
padding: 2px 4px;
margin: -2px -4px;
transition: background .12s;
}
a.sidebar-user:hover {
background: var(--surface-2);
}
a.sidebar-user:hover .name {
color: var(--accent);
}
/* Budget: mehrere Monate nebeneinander. */
.budget-months {
display: grid;
gap: var(--sp-4);
grid-template-columns: repeat(var(--cols, 1), minmax(0, 1fr));
align-items: start;
}
@media (max-width: 820px) {
.budget-months {
grid-template-columns: 1fr;
}
}
.budget-col {
min-width: 0;
}
.budget-col-head {
display: flex;
align-items: center;
gap: 4px;
padding-bottom: var(--sp-2);
margin-bottom: var(--sp-2);
border-bottom: 2px solid var(--border-strong);
}
.budget-col-head strong {
font-size: 1.05rem;
}
.budget-col-income {
display: flex;
align-items: center;
gap: var(--sp-2);
padding: 4px 0 var(--sp-2);
margin-bottom: var(--sp-2);
border-bottom: 1px solid var(--border);
}
.budget-crow {
display: flex;
align-items: center;
gap: var(--sp-2);
padding: 6px 0;
border-bottom: 1px solid var(--border);
}
.budget-crow:last-child {
border-bottom: none;
}
.budget-crow .amount.small {
flex: 0 0 auto;
}
/* Budget. */
.budget-navbar {
display: flex;
align-items: center;
gap: var(--sp-3);
}
.budget-month {
font-size: 1.05rem;
min-width: 170px;
text-align: center;
}
.flip {
transform: rotate(180deg);
}
.budget-group + .budget-group {
margin-top: var(--sp-4);
}
.budget-group-head {
display: flex;
align-items: center;
gap: var(--sp-2);
padding: var(--sp-2) 0;
margin-bottom: 4px;
border-bottom: 2px solid var(--border-strong);
}
.budget-cat {
display: flex;
align-items: center;
gap: var(--sp-3);
padding: 7px 0;
border-bottom: 1px solid var(--border);
}
.budget-cat:last-child {
border-bottom: none;
}
.budget-cat-name {
display: flex;
align-items: center;
gap: var(--sp-2);
min-width: 0;
}
.budget-cat-bar {
flex: 1;
}
.amount-input {
width: 96px;
text-align: right;
margin-top: 0;
padding: 5px 8px;
font-variant-numeric: tabular-nums;
}
.budget-actual {
width: 96px;
text-align: right;
font-variant-numeric: tabular-nums;
}
.budget-slash {
margin: 0 -4px;
}
.budget-bar {
height: 7px;
background: var(--surface-2);
border-radius: 4px;
overflow: hidden;
}
.budget-bar-fill {
height: 100%;
background: var(--ok);
border-radius: 4px;
transition: width 0.2s;
}
.budget-bar-fill.over {
background: var(--danger);
}
.budget-total-row.card-total {
display: flex;
align-items: center;
gap: var(--sp-2);
padding-top: var(--sp-3);
margin-top: var(--sp-2);
border-top: 2px solid var(--border-strong);
}
tr.budget-total-row td {
border-top: 2px solid var(--border-strong);
padding-top: var(--sp-2);
}
/* Sparen-Option im Transfer-Formular. */
.savings-opt {
padding: var(--sp-3);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
background: var(--surface-2);
margin-bottom: var(--sp-4);
}
.checkline {
display: flex;
align-items: center;
gap: var(--sp-2);
margin-bottom: var(--sp-2);
}
.checkline span {
color: var(--text);
font-weight: 560;
}
/* ---------- Dashboard-Grid ---------- */
.dash-toolbar {
display: flex;
align-items: center;
gap: var(--sp-2);
position: relative;
}
.dash-add {
position: relative;
}
.dash-add-menu {
position: absolute;
right: 0;
top: calc(100% + 4px);
z-index: 20;
background: var(--surface);
border: 1px solid var(--border-strong);
border-radius: var(--radius-sm);
box-shadow: var(--shadow);
min-width: 200px;
overflow: hidden;
}
.dash-add-item {
display: block;
width: 100%;
text-align: left;
padding: 8px 12px;
border: none;
background: none;
color: var(--text);
cursor: pointer;
}
.dash-add-item:hover {
background: var(--surface-2);
}
/* Karten-Optionen im Anordnen-Modus (z. B. Gruppen vs. Kategorien). */
.dash-config-bar {
display: flex;
flex-wrap: wrap;
gap: var(--sp-3);
padding-bottom: var(--sp-2);
margin-bottom: var(--sp-2);
border-bottom: 1px solid var(--border);
}
.dash-config-opt {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 4px;
min-width: 0;
flex: 1 1 140px;
}
.seg.sm {
margin-bottom: 0;
width: 100%;
}
.seg.sm .seg-btn {
padding: 4px 9px;
font-size: 0.78rem;
}
.dash-grid {
position: relative;
}
.dash-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow);
display: flex;
flex-direction: column;
overflow: hidden;
}
.dash-card-head {
display: flex;
align-items: center;
gap: var(--sp-2);
padding: 8px 12px;
border-bottom: 1px solid var(--border);
flex: 0 0 auto;
}
.dash-card-title {
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--muted);
font-weight: 650;
flex: 1;
}
.dash-grid.editing .dash-card-head {
cursor: move;
background: var(--surface-2);
}
.dash-remove {
color: var(--danger);
}
.dash-card-body {
flex: 1;
min-height: 0;
overflow: auto;
padding: var(--sp-3);
}
.dash-list {
display: flex;
flex-direction: column;
gap: 2px;
}
.dash-line {
display: flex;
align-items: center;
gap: var(--sp-2);
padding: 6px 0;
border-bottom: 1px solid var(--border);
}
.dash-line:last-child {
border-bottom: none;
}
.dash-total {
display: flex;
align-items: baseline;
gap: var(--sp-2);
padding-top: var(--sp-2);
margin-top: 2px;
border-top: 2px solid var(--border-strong);
font-variant-numeric: tabular-nums;
}
.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dash-loading, .dash-empty {
padding: var(--sp-4);
text-align: center;
}
.dash-progress {
padding: 6px 0;
}
.dash-progress-top {
display: flex;
align-items: center;
gap: var(--sp-2);
margin-bottom: 4px;
}
.dash-kpis {
display: flex;
flex-direction: column;
justify-content: center;
gap: var(--sp-2);
height: 100%;
}
.dash-kpi {
display: flex;
align-items: baseline;
justify-content: space-between;
}
.dash-kpi strong {
font-size: 1.1rem;
font-variant-numeric: tabular-nums;
}
/* react-grid-layout: Platzhalter + Resize-Griff an das Theme angleichen. */
.react-grid-item.react-grid-placeholder {
background: var(--accent);
opacity: 0.15;
border-radius: var(--radius);
}
.react-grid-item > .react-resizable-handle::after {
border-right-color: var(--border-strong);
border-bottom-color: var(--border-strong);
}
/* ---------- CSV-Import ---------- */
.table-scroll {
overflow-x: auto;
}
.raw-table {
font-size: 0.8rem;
}
.raw-table th,
.raw-table td {
white-space: nowrap;
max-width: 220px;
overflow: hidden;
text-overflow: ellipsis;
}
.raw-table th {
color: var(--muted);
font-weight: 600;
}
.map-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
gap: var(--sp-3);
margin-top: var(--sp-3);
}
.map-grid label {
margin-bottom: 0;
}
tr.row-invalid {
opacity: 0.45;
}
tr.row-dup {
opacity: 0.55;
}
.doc-text {
max-height: 240px;
overflow: auto;
font-size: 0.78rem;
white-space: pre-wrap;
color: var(--muted);
background: var(--surface-2);
padding: var(--sp-3);
border-radius: var(--radius-sm);
margin-top: var(--sp-2);
}
/* Beleg-Seiten als Bildstreifen (horizontal scrollbar). */
.doc-pages {
display: flex;
gap: var(--sp-3);
overflow-x: auto;
padding-bottom: var(--sp-2);
}
.doc-page {
height: 440px;
width: auto;
flex: 0 0 auto;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
background: #fff;
}
.import-more {
margin-top: var(--sp-3);
}
.import-more > summary {
cursor: pointer;
}
/* DataTable-Zellen einzeilig halten, damit „…" greift (Excel-artig). */
.datatable td { white-space: nowrap; vertical-align: middle; }
.datatable td .named-text { min-width: 0; }
/* Interaktive Zellen (Import-Prüfansicht) füllen ihre Spalte, statt auf 60% zu
schrumpfen (globale .table td select/input-Regel überschreiben). */
.datatable td select,
.datatable td input[inputmode],
.datatable td input[type="text"] { max-width: none; width: 100%; }
.datatable td .catsel { width: 100%; }
/* ---------- Kategorie-Auswahl (auf-/zuklappbarer Baum, Ersatz für <select>) ---------- */
.catsel {
width: 100%;
}
.catsel-btn {
width: 100%;
margin-top: 5px;
display: flex;
align-items: center;
gap: var(--sp-2);
padding: 7px 10px;
border: 1px solid var(--border-strong);
border-radius: var(--radius-sm);
background: var(--surface);
color: var(--text);
font: inherit;
text-align: left;
cursor: pointer;
transition: border-color .12s, box-shadow .12s;
}
.catsel-btn:hover:not(:disabled) {
border-color: var(--accent);
}
.catsel-btn:disabled {
opacity: 0.6;
cursor: default;
}
.catsel-btn.open {
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-soft);
}
.catsel-value {
flex: 1 1 auto;
min-width: 0;
display: flex;
align-items: center;
gap: var(--sp-2);
}
.catsel-caret {
flex: 0 0 auto;
color: var(--muted);
}
.catsel-panel {
position: fixed;
z-index: 1000;
display: flex;
flex-direction: column;
background: var(--surface);
border: 1px solid var(--border-strong);
border-radius: var(--radius);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
overflow: hidden;
}
.catsel-search {
display: flex;
align-items: center;
gap: var(--sp-2);
padding: 8px 10px;
border-bottom: 1px solid var(--border);
color: var(--muted);
flex: 0 0 auto;
}
.catsel-search input {
width: 100%;
border: none;
background: none;
outline: none;
margin: 0;
padding: 0;
color: var(--text);
font: inherit;
}
.catsel-list {
overflow-y: auto;
padding: 5px;
}
.catsel-group + .catsel-group {
margin-top: 2px;
}
.catsel-group-head {
display: flex;
align-items: center;
gap: 6px;
width: 100%;
padding: 6px;
border: none;
background: none;
cursor: pointer;
color: var(--muted);
font-weight: 650;
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.catsel-group-head:hover {
color: var(--text);
}
.catsel-gchev {
flex: 0 0 auto;
transition: transform .12s ease;
}
.catsel-gchev.collapsed {
transform: rotate(-90deg);
}
.catsel-opt {
display: flex;
align-items: center;
gap: var(--sp-2);
width: 100%;
padding: 6px 8px 6px 22px;
border: none;
border-radius: var(--radius-sm);
background: none;
color: var(--text);
font: inherit;
text-align: left;
cursor: pointer;
}
.catsel-opt:hover {
background: var(--surface-2);
}
.catsel-opt.selected {
background: var(--accent-soft);
color: var(--accent);
font-weight: 600;
}
.catsel-check {
margin-left: auto;
flex: 0 0 auto;
color: var(--accent);
}
.catsel-empty {
padding: 10px;
}
/* ---------- Schlagwörter (Kategorie-Popup + Prüfmeldungen) ---------- */
.kw-add {
display: flex;
gap: var(--sp-2);
margin-bottom: var(--sp-3);
}
.kw-add input {
flex: 1 1 auto;
min-width: 0;
margin-top: 0;
}
.kw-chips {
display: flex;
flex-wrap: wrap;
gap: var(--sp-2);
}
.kw-chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 6px 4px 11px;
border-radius: 20px;
background: var(--surface-2);
border: 1px solid var(--border);
font-size: 0.85rem;
}
.kw-amount {
color: var(--muted);
font-variant-numeric: tabular-nums;
font-size: 0.8em;
}
.kw-chip button {
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
padding: 0;
margin: 0;
border: none;
border-radius: 50%;
background: none;
color: var(--muted);
cursor: pointer;
}
.kw-chip button:hover {
background: var(--danger);
color: #fff;
}
/* Schlagwort-Knopf in der Kategorienliste. Belegte Kategorien tragen die
Akzentfarbe und die Anzahl – so sieht man der Liste an, wo schon etwas
hinterlegt ist, ohne jede Kategorie einzeln zu öffnen. */
.kw-btn {
gap: 5px;
}
.kw-btn.belegt {
color: var(--accent);
border-color: var(--accent-soft);
background: var(--accent-soft);
}
.kw-btn-zahl {
font-size: 0.72rem;
font-weight: 640;
font-variant-numeric: tabular-nums;
line-height: 1;
}
/* Das Schlagwort, das gerade oben in der Zeile liegt. Bearbeitet wird dort,
nicht im Chip – im Chip war für den Betrag kein Platz. */
.kw-chip.aktiv {
border-color: var(--accent);
box-shadow: 0 0 0 2px var(--accent-soft);
}
/* Passend dazu die Eingabezeile hervorheben, damit klar ist, dass dort gerade
ein vorhandenes Wort liegt und nicht ein neues entsteht. */
.kw-add.bearbeitet input {
border-color: var(--accent);
}
.kw-flag {
display: flex;
align-items: center;
gap: var(--sp-3);
padding: var(--sp-3) 0;
border-bottom: 1px solid var(--border);
}
.kw-flag:last-child {
border-bottom: none;
}
.kw-flag-main {
flex: 1;
min-width: 0;
}
.kw-flag-word {
font-weight: 600;
}
/* ---------- Passwort: Anforderungsliste, Zwei-Faktor ---------- */
/* Nur für Screenreader – optisch weg, aber vorlesbar. */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
border: 0;
}
/* Hakt sich beim Tippen ab. Farben ausschliesslich aus Tokens, sonst bricht
das dunkle Thema. */
.pw-rules {
list-style: none;
margin: var(--sp-2) 0 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 3px;
font-size: 0.82rem;
}
.pw-rule {
display: flex;
align-items: center;
gap: var(--sp-2);
color: var(--muted);
transition: color 0.16s ease;
}
.pw-rule.erfuellt {
color: var(--ok);
}
.pw-rule-mark {
flex: 0 0 auto;
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
}
/* Offener Punkt, solange die Regel nicht erfüllt ist. */
.pw-rule-dot {
width: 7px;
height: 7px;
border-radius: 50%;
border: 1.5px solid currentColor;
opacity: 0.55;
}
/* Kurzer Impuls, wenn das Häkchen erscheint. */
.pw-rule.erfuellt .pw-rule-mark {
animation: pw-rule-pop 0.18s ease-out;
}
@keyframes pw-rule-pop {
from { transform: scale(0.4); opacity: 0; }
60% { transform: scale(1.18); opacity: 1; }
to { transform: scale(1); opacity: 1; }
}
/* styles.css hat keinen globalen Bewegungs-Reset, nur eine Aufzählung –
deshalb hier noch einmal ausdrücklich für die neuen Bewegungen. */
@media (prefers-reduced-motion: reduce) {
.pw-rule { transition: none; }
.pw-rule.erfuellt .pw-rule-mark { animation: none; }
}
/* Zwei-Faktor: QR-Code, Secret und Wiederherstellungscodes. */
.tfa-qr {
display: block;
width: 190px;
height: 190px;
/* Der QR-Code braucht weissen Grund, damit er auch im dunklen Thema
zuverlässig gescannt wird. */
background: #fff;
padding: var(--sp-2);
border-radius: var(--radius-sm);
border: 1px solid var(--border);
}
.tfa-secret {
display: inline-block;
padding: 6px var(--sp-2);
border-radius: var(--radius-sm);
background: var(--surface-2);
border: 1px solid var(--border);
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 0.9rem;
letter-spacing: 0.06em;
/* styles.css setzt body { user-select: none } – ohne das hier liesse sich
das Secret nicht markieren und kopieren. */
user-select: text;
}
.tfa-codes {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: var(--sp-2);
margin: var(--sp-3) 0;
padding: var(--sp-3);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
background: var(--surface-2);
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 0.9rem;
user-select: text;
}
.tfa-codes .verbraucht {
color: var(--muted);
text-decoration: line-through;
}
/* Zusammenfassung der Prüfansicht beim Wiederherstellen. */
.restore-summary {
width: 100%;
border-collapse: collapse;
margin-top: var(--sp-2);
font-size: 0.88rem;
}
.restore-summary td {
padding: 3px 0;
border-bottom: 1px solid var(--border);
}
.restore-summary td:last-child {
text-align: right;
font-variant-numeric: tabular-nums;
color: var(--muted);
}
.restore-summary tr:last-child td {
border-bottom: none;
}
/* Zweiter Buchen-Knopf unter der Prüfliste – bei vielen Zeilen ist der obere
längst aus dem Bild gescrollt. */
.commit-footer {
display: flex;
justify-content: flex-end;
padding-top: var(--sp-3);
margin-top: var(--sp-2);
border-top: 1px solid var(--border);
}
/* Ausgaben-Anteile: der Prozentwert ist die Kernaussage der Karte und steht
deshalb vor dem Betrag, mit gleichbleibender Ziffernbreite. */
.share-pct {
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
/* Wird die Karte schmal, gibt zuerst der Betrag nach – ohne den Kategorienamen
sagt die Zeile nichts mehr aus, ohne den Betrag bleibt der Anteil lesbar. */
.share-row .share-name {
min-width: 4.5rem;
}
.share-row .share-amount {
flex: 0 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Abgleich des selbst gerechneten Saldos mit dem, den die Bank fuehrt.
Eine Abweichung ist kein Fehler der Bank, sondern ein Hinweis auf eine
fehlende oder doppelte Buchung – deshalb warnend, nicht rot. */
.saldo-ok { color: var(--ok); }
.saldo-abweichung { color: var(--warn); font-weight: 560; }