/* Aussehen der Weboberfläche. * * ── Aufbau dieser Datei ────────────────────────────────────────────────────── * 1. Grundwerte Farben, Abstände, Schriftgrößen, Breiten * 2. Grundlagen body, Überschriften, Links, Code * 3. Gerüst Kopfleisten und Inhaltsbereich * 4. Bausteine Karten, Meldungen, Marken, Tabellen, Formulare, Knöpfe * 5. Seiten das Wenige, das wirklich nur eine Seite betrifft * 6. Schmale Geräte * * ── Zwei Regeln, die alles zusammenhalten ──────────────────────────────────── * * ERSTENS: Abstände kommen ausschließlich aus der Skala (--s1 bis --s8). Keine * frei gewählten Werte. Vorher standen hier 29 verschiedene Abstandswerte — das * Ergebnis war, dass nichts zueinander ausgerichtet war und jede neue Seite * wieder ein bisschen anders aussah. * * ZWEITENS: Die Breite richtet sich nach dem Inhalt, nicht nach dem Bildschirm. * Eine Tabelle mit acht Spalten braucht Platz, ein Fließtext dagegen darf nicht * über die ganze Breite laufen — ab etwa 75 Zeichen je Zeile findet das Auge den * Zeilenanfang nicht mehr zuverlässig. Deshalb gibt es drei Breiten, und jede * Seite wählt die passende Darstellung. */ /* ── 1. Grundwerte ────────────────────────────────────────────────────────── */ :root { /* Farben: eine neutrale Grundfläche, ein Akzent, eine Warnfarbe. */ --grund: #faf9f7; /* Seitenhintergrund */ --flaeche: #ffffff; /* Karten, Tabellen, Eingabefelder */ --flaeche-leis: #f4f2ef; /* Tabellenköpfe, gedämpfte Bereiche */ --rahmen: #e4e1dc; --rahmen-stark: #cfcbc4; --text: #23211e; --text-leise: #6d6862; --akzent: #2c6e4e; --akzent-flach: #ebf3ee; --akzent-text: #205138; --warn: #a13a2b; --warn-flach: #fbeeeb; /* Abstandsskala — alles Weitere ist ein Vielfaches davon. */ --s1: 0.25rem; --s2: 0.5rem; --s3: 0.75rem; --s4: 1rem; --s5: 1.5rem; --s6: 2rem; --s7: 3rem; --s8: 4rem; /* Schriftgrößen. */ --t-klein: 0.8125rem; --t-meta: 0.875rem; --t-normal: 1rem; --t-gross: 1.125rem; --t-h2: 1.25rem; --t-h1: 1.625rem; /* Breiten. Die Seite nutzt den Platz aus; nur der Fließtext begrenzt sich * selbst (siehe Regel zwei oben). */ --breite-seite: 92rem; /* Seitenleiste plus Inhalt */ --breite-leiste: 15rem; /* Seitenleiste */ --breite-lesen: 44rem; /* Fließtext eines Eintrags */ --breite-schmal: 25rem; /* Anmeldung */ /* Höhe der Bedienelemente. Wichtig ist nicht der Wert, sondern dass Knopf * und Eingabefeld denselben verwenden — sonst stehen sie in einer Zeile * nebeneinander unterschiedlich hoch, und das fällt sofort auf. */ --hoehe-bedienelement: 2.25rem; --hoehe-kopfleiste: 3.5rem; --radius: 6px; --radius-gross: 10px; --schatten: 0 1px 2px rgb(0 0 0 / 0.04), 0 1px 3px rgb(0 0 0 / 0.06); } /* Dunkle Darstellung: nur die Farben werden getauscht, alle Regeln darunter * gelten unverändert weiter. */ @media (prefers-color-scheme: dark) { :root { --grund: #1a1918; --flaeche: #232120; --flaeche-leis: #2b2927; --rahmen: #383533; --rahmen-stark: #4a4643; --text: #e9e6e2; --text-leise: #a09a94; --akzent: #6fbf96; --akzent-flach: #1f2d26; --akzent-text: #8fd3ae; --warn: #e08d7e; --warn-flach: #2e2320; --schatten: 0 1px 3px rgb(0 0 0 / 0.4); } } *, *::before, *::after { box-sizing: border-box; } /* ── 2. Grundlagen ───────────────────────────────────────────────────────── */ body { margin: 0; background: var(--grund); color: var(--text); font: var(--t-normal)/1.6 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; -webkit-font-smoothing: antialiased; } h1, h2, h3 { line-height: 1.3; font-weight: 600; margin: 0; } h1 { font-size: var(--t-h1); letter-spacing: -0.01em; } h2 { font-size: var(--t-h2); } h3 { font-size: var(--t-gross); } p { margin: 0 0 var(--s4); } p:last-child { margin-bottom: 0; } a { color: var(--akzent-text); text-decoration: none; } a:hover { text-decoration: underline; } /* Sichtbarer Fokusrahmen für alles, was per Tastatur erreichbar ist. * :focus-visible statt :focus — so erscheint der Rahmen bei Tastaturbedienung, * aber nicht bei jedem Mausklick. */ :focus-visible { outline: 2px solid var(--akzent); outline-offset: 2px; border-radius: var(--radius); } code, pre { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 0.9em; } code { background: var(--flaeche-leis); border: 1px solid var(--rahmen); padding: 0.1em 0.35em; border-radius: 4px; } pre { background: var(--flaeche-leis); border: 1px solid var(--rahmen); border-radius: var(--radius); padding: var(--s4); overflow-x: auto; margin: 0; } pre code { background: none; border: none; padding: 0; } /* Kleiner erklärender Text. Als Blockelement UND innerhalb von Beschriftungen * verwendbar, deshalb kein eigenes display. */ .hilfe { color: var(--text-leise); font-size: var(--t-meta); font-weight: normal; } /* ── 3. Gerüst ───────────────────────────────────────────────────────────── */ /* Schmale Kopfleiste über die ganze Breite. Sie trägt nur Marke und Abmelden — * die eigentliche Navigation steht in der Seitenleiste. */ .kopfleiste { background: var(--flaeche); border-bottom: 1px solid var(--rahmen); position: sticky; top: 0; z-index: 20; } .kopfleiste-innen { max-width: var(--breite-seite); margin-inline: auto; padding-inline: var(--s5); min-height: var(--hoehe-kopfleiste); display: flex; align-items: center; justify-content: space-between; gap: var(--s4); } .marke-wort { font-weight: 600; color: var(--text); letter-spacing: -0.01em; } .marke-wort:hover { text-decoration: none; } /* Das Gerüst: Seitenleiste links, Inhalt rechts. */ .gerüst { max-width: var(--breite-seite); margin-inline: auto; padding-inline: var(--s5); display: grid; grid-template-columns: var(--breite-leiste) minmax(0, 1fr); gap: var(--s7); align-items: start; } /* Seitenleiste. * * position: sticky hält sie beim Scrollen sichtbar. Der Wert für top entspricht * der Höhe der Kopfleiste, damit sie genau darunter stehen bleibt und nicht * dahinter verschwindet. */ .seitenleiste { position: sticky; top: var(--hoehe-kopfleiste); max-height: calc(100dvh - var(--hoehe-kopfleiste)); overflow-y: auto; padding-block: var(--s6); display: flex; flex-direction: column; gap: var(--s5); } .nav-gruppe { display: flex; flex-direction: column; gap: var(--s1); } .nav-titel { font-size: var(--t-klein); font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-leise); margin-bottom: var(--s1); padding-inline: var(--s2); } .nav-liste { list-style: none; margin: 0; padding: 0; } .nav-liste a { display: flex; align-items: center; gap: var(--s2); padding: var(--s1) var(--s2); border-radius: var(--radius); color: var(--text); font-size: var(--t-meta); line-height: 1.5; } .nav-liste a:hover { background: var(--flaeche-leis); text-decoration: none; } /* Der aktive Punkt wird durch Fläche markiert, nicht durch Farbe allein — * so bleibt er auch bei eingeschränktem Farbsehen erkennbar. */ .nav-liste a.aktiv { background: var(--akzent-flach); color: var(--akzent-text); font-weight: 500; } .nav-liste.leise a { color: var(--text-leise); } /* Schlagwort und Anzahl: Name links, Zahl rechtsbündig. */ .nav-text { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .nav-zahl { color: var(--text-leise); font-size: var(--t-klein); font-variant-numeric: tabular-nums; } .nav-liste a.aktiv .nav-zahl { color: inherit; } /* Inhaltsbereich. */ .inhalt { padding-block: var(--s6) var(--s8); min-width: 0; } /* Fließtext auf Lesebreite. Nur der Text selbst — Überschrift, Metazeile und * Tabellen dürfen die volle Breite nutzen. */ .lesebreite { max-width: var(--breite-lesen); } /* Seitenkopf: Titel links, Aktionen rechts — auf einer Linie. * * align-items: center statt flex-start, damit der Knopf mittig zur Überschrift * sitzt und nicht an deren Oberkante klebt. */ .seitenkopf { display: flex; align-items: center; gap: var(--s4); flex-wrap: wrap; margin-bottom: var(--s5); } .seitenkopf-titel { flex: 1; min-width: 15rem; } .seitenkopf-titel .hilfe { display: block; margin-top: var(--s1); } .seitenkopf-aktionen { display: flex; gap: var(--s2); align-items: center; } /* Abschnitt innerhalb einer Seite. */ .abschnitt { margin-top: var(--s7); } .abschnitt > h2 { margin-bottom: var(--s2); } .abschnitt > .hilfe { display: block; margin-bottom: var(--s4); } /* ── 4. Bausteine ────────────────────────────────────────────────────────── */ /* Karte: der Standardbehälter für alles Gruppierte. */ .karte { background: var(--flaeche); border: 1px solid var(--rahmen); border-radius: var(--radius-gross); padding: var(--s5); box-shadow: var(--schatten); } .karte + .karte { margin-top: var(--s4); } /* Meldungen. */ .meldung { padding: var(--s3) var(--s4); border: 1px solid var(--rahmen); border-left-width: 3px; border-radius: var(--radius); margin-bottom: var(--s5); font-size: var(--t-meta); } .meldung.hinweis { background: var(--akzent-flach); border-left-color: var(--akzent); } .meldung.fehler { background: var(--warn-flach); border-left-color: var(--warn); } /* Leerzustand: nicht nur ein grauer Absatz, sondern ein erkennbarer Bereich mit * einem Hinweis, was als Nächstes zu tun wäre. */ .leer { text-align: center; padding: var(--s7) var(--s5); border: 1px dashed var(--rahmen-stark); border-radius: var(--radius-gross); color: var(--text-leise); } .leer p { margin-bottom: var(--s4); } .leer p:last-child { margin-bottom: 0; } /* Marken: eine einzige visuelle Familie für alle kleinen Auszeichnungen. */ .marke { display: inline-flex; align-items: center; gap: var(--s1); padding: 0.1rem var(--s2); border: 1px solid var(--rahmen); border-radius: 999px; font-size: var(--t-klein); line-height: 1.5; color: var(--text-leise); white-space: nowrap; } a.marke:hover { border-color: var(--rahmen-stark); color: var(--text); text-decoration: none; } .marke.betont { background: var(--akzent-flach); border-color: transparent; color: var(--akzent-text); } /* Metazeile unter einem Titel: Marken und Kleintext in einer Reihe. */ .meta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); color: var(--text-leise); font-size: var(--t-klein); } /* Knöpfe. Drei Ausprägungen, mehr braucht es nicht. */ button, .knopf { display: inline-flex; align-items: center; justify-content: center; gap: var(--s2); padding: var(--s2) var(--s4); min-height: var(--hoehe-bedienelement); background: var(--akzent); color: #fff; border: 1px solid transparent; border-radius: var(--radius); font: inherit; font-size: var(--t-meta); font-weight: 500; cursor: pointer; text-decoration: none; white-space: nowrap; } button:hover, .knopf:hover { filter: brightness(1.08); text-decoration: none; } .knopf.zweitrangig, button.zweitrangig { background: var(--flaeche); color: var(--text); border-color: var(--rahmen-stark); } .knopf.zweitrangig:hover, button.zweitrangig:hover { background: var(--flaeche-leis); filter: none; } button.warnung { background: var(--flaeche); color: var(--warn); border-color: var(--warn); } button.warnung:hover { background: var(--warn-flach); filter: none; } /* Ein Knopf, der wie ein Link aussieht — nötig, weil Abmelden ein POST sein muss * (siehe CSRF-Schutz), aber wie ein Menüpunkt wirken soll. */ .wie-link { background: none; border: none; padding: 0; min-height: 0; font: inherit; font-size: var(--t-meta); font-weight: normal; color: var(--text-leise); cursor: pointer; } .wie-link:hover { filter: none; text-decoration: underline; } .reihe { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap; } form.reihe { display: inline-flex; } /* Formulare. * * Jedes Feld ist ein .feld mit Beschriftung, optionalem Hinweis und Bedienelement. * Die Breite richtet sich nach dem erwarteten Inhalt statt immer 100 % zu sein: * Ein Feld für einen Bezeichner, das über die halbe Seite läuft, sieht aus wie * ein Fehler. */ .formular { display: flex; flex-direction: column; gap: var(--s4); } .feld { display: flex; flex-direction: column; gap: var(--s1); } .feld > label { font-weight: 500; font-size: var(--t-meta); } .feld > .hilfe { margin-bottom: var(--s1); } input[type=text], input[type=search], input[type=password], select, textarea { width: 100%; padding: var(--s2) var(--s3); min-height: var(--hoehe-bedienelement); background: var(--flaeche); border: 1px solid var(--rahmen-stark); border-radius: var(--radius); color: var(--text); font: inherit; font-size: var(--t-meta); } input::placeholder { color: var(--text-leise); opacity: 0.7; } input:focus, select:focus, textarea:focus { border-color: var(--akzent); outline: 2px solid var(--akzent-flach); outline-offset: 0; } textarea { resize: vertical; line-height: 1.6; font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; min-height: 20rem; } /* Feldbreiten nach Inhalt. */ .feld.kurz { max-width: 18rem; } .feld.mittel { max-width: 28rem; } .feld.lang { max-width: 40rem; } /* Ankreuzfeld: Kästchen und Text nebeneinander, Text nicht fett. */ .ankreuz { flex-direction: row; align-items: flex-start; gap: var(--s2); } /* Das Kästchen sitzt etwas tiefer als die Oberkante des Textes, damit es * optisch auf der ersten Textzeile liegt statt darüber. */ .ankreuz input { width: auto; min-height: 0; margin-top: 0.3rem; } .ankreuz label { font-weight: normal; } .formular-aktionen { display: flex; gap: var(--s2); align-items: center; padding-top: var(--s2); } /* Tabellen. */ .tabelle-huelle { background: var(--flaeche); border: 1px solid var(--rahmen); border-radius: var(--radius-gross); box-shadow: var(--schatten); overflow-x: auto; /* breite Tabellen scrollen in sich, nicht die Seite */ } table.tabelle { width: 100%; border-collapse: collapse; font-size: var(--t-meta); } .tabelle th, .tabelle td { text-align: left; padding: var(--s3) var(--s4); border-bottom: 1px solid var(--rahmen); vertical-align: middle; } .tabelle thead th { background: var(--flaeche-leis); color: var(--text-leise); font-weight: 500; font-size: var(--t-klein); white-space: nowrap; } .tabelle tbody tr:last-child td { border-bottom: none; } .tabelle tbody tr:hover { background: var(--flaeche-leis); } .tabelle .zahl { text-align: right; font-variant-numeric: tabular-nums; } .tabelle .schmal { width: 1%; white-space: nowrap; } .tabelle .rechts { text-align: right; } .tabelle tr.gedaempft { color: var(--text-leise); } .tabelle tr.gedaempft td:first-child { text-decoration: line-through; } /* ── 5. Seiten ───────────────────────────────────────────────────────────── */ /* Anmeldung: eigenständige, ruhige Seite ohne Navigation. */ .anmeldeseite { min-height: 100dvh; display: flex; align-items: center; justify-content: center; padding: var(--s5); } .anmeldeseite .karte { width: 100%; padding: var(--s6); } .anmeldeseite h1 { margin-bottom: var(--s2); } .anmeldeseite .hilfe { display: block; margin-bottom: var(--s5); } /* Werkzeugleiste über der Eintragsliste: Suche und Filter. */ .werkzeugleiste { display: flex; gap: var(--s2); margin-bottom: var(--s4); } .werkzeugleiste input[type=search] { flex: 1; } /* Eintragsliste. */ .eintraege { list-style: none; margin: 0; padding: 0; background: var(--flaeche); border: 1px solid var(--rahmen); border-radius: var(--radius-gross); box-shadow: var(--schatten); overflow: hidden; } .eintraege li { padding: var(--s4); border-bottom: 1px solid var(--rahmen); } .eintraege li:last-child { border-bottom: none; } .eintraege li:hover { background: var(--flaeche-leis); } .eintrag-titel { display: block; font-size: var(--t-gross); font-weight: 500; color: var(--text); margin-bottom: var(--s2); } .eintrag-ausschnitt { margin: var(--s2) 0 0; color: var(--text-leise); font-size: var(--t-meta); /* Auf zwei Zeilen begrenzen, damit alle Zeilen der Liste gleich hoch bleiben. */ display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .eintrag-ausschnitt mark { background: var(--akzent-flach); color: var(--akzent-text); padding: 0 0.15em; border-radius: 3px; } /* Einzelner Eintrag: Fließtext in Lesebreite. */ .eintrag-text { font-size: var(--t-gross); line-height: 1.75; margin-top: var(--s6); } .eintrag-text > :first-child { margin-top: 0; } .eintrag-text h1, .eintrag-text h2, .eintrag-text h3 { margin: var(--s6) 0 var(--s3); } .eintrag-text p, .eintrag-text ul, .eintrag-text ol { margin: 0 0 var(--s4); } .eintrag-text ul, .eintrag-text ol { padding-left: var(--s5); } .eintrag-text li { margin-bottom: var(--s1); } .eintrag-text img { max-width: 100%; border-radius: var(--radius); } .eintrag-text blockquote { margin: var(--s4) 0; padding: var(--s1) var(--s4); border-left: 3px solid var(--rahmen-stark); color: var(--text-leise); } .eintrag-text pre { margin-bottom: var(--s4); font-size: var(--t-meta); } .eintrag-text table { border-collapse: collapse; width: 100%; font-size: var(--t-meta); } .eintrag-text th, .eintrag-text td { border: 1px solid var(--rahmen); padding: var(--s2) var(--s3); } .eintrag-text hr { border: none; border-top: 1px solid var(--rahmen); margin: var(--s6) 0; } .rueckverweise { margin-top: var(--s7); } .rueckverweise h2 { font-size: var(--t-meta); color: var(--text-leise); margin-bottom: var(--s3); } .rueckverweise ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s2); } /* Neu erzeugter Key: wird genau einmal angezeigt, also deutlich hervorheben. */ .key-anzeige { border-color: var(--akzent); background: var(--akzent-flach); } .key-wert { display: block; margin: var(--s3) 0; padding: var(--s3); background: var(--flaeche); border: 1px solid var(--rahmen-stark); border-radius: var(--radius); font-family: ui-monospace, Menlo, Consolas, monospace; font-size: var(--t-meta); word-break: break-all; user-select: all; /* ein Klick markiert den ganzen Key */ } .key-anleitung { margin-top: var(--s4); } .key-anleitung summary { cursor: pointer; font-size: var(--t-meta); color: var(--akzent-text); } .key-anleitung pre { margin-top: var(--s3); font-size: var(--t-klein); } /* Gefahrenzone: Löschvorgänge stehen abgesetzt am Seitenende, nicht zwischen * den harmlosen Aktionen. Vorher steckte das Löschen eines Projekts samt * Bestätigungsfeld in einer Tabellenzelle — zu eng und viel zu beiläufig für * eine Aktion, die alle Einträge mitnimmt. */ .gefahrenzone { border-color: var(--warn); } .gefahrenzone h2 { color: var(--warn); } /* ── 6. Schmale Geräte ───────────────────────────────────────────────────── */ /* Unterhalb dieser Breite ist für zwei Spalten kein Platz mehr: Die Seitenleiste * rückt über den Inhalt und wird flach — aus den Listen werden Zeilen, die bei * Bedarf seitlich scrollen. So bleibt alles erreichbar, ohne dass eine * ausklappbare Navigation samt JavaScript nötig wäre. */ @media (max-width: 60rem) { .gerüst { grid-template-columns: minmax(0, 1fr); gap: 0; padding-inline: var(--s4); } .kopfleiste-innen { padding-inline: var(--s4); } .seitenleiste { position: static; max-height: none; overflow: visible; flex-direction: row; flex-wrap: wrap; gap: var(--s4); padding-block: var(--s4); border-bottom: 1px solid var(--rahmen); } /* Jede Gruppe wird zu einer Zeile: Überschrift links, Punkte daneben. */ .nav-gruppe { flex-direction: row; align-items: center; gap: var(--s2); min-width: 0; flex: 1 1 100%; } .nav-titel { margin-bottom: 0; padding-inline: 0; white-space: nowrap; } .nav-liste { display: flex; gap: var(--s1); overflow-x: auto; scrollbar-width: none; /* die Leiste scrollt, ohne einen Balken zu zeigen */ } .nav-liste::-webkit-scrollbar { display: none; } .nav-liste a { white-space: nowrap; } /* Die Schlagwortliste kann lang werden — hier zählt jede Zeile. */ .schlagworte .nav-liste a { padding-inline: var(--s2); } .inhalt { padding-block: var(--s5) var(--s7); } .seitenkopf { gap: var(--s3); } .seitenkopf-aktionen { width: 100%; } .seitenkopf-aktionen .knopf { flex: 1; } .karte { padding: var(--s4); } .tabelle th, .tabelle td { padding: var(--s2) var(--s3); } .feld.kurz, .feld.mittel, .feld.lang { max-width: none; } textarea { min-height: 14rem; } }