/* 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: 120rem; /* 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.5rem; /* Knöpfe, Eingabefelder, Auswahllisten: alle gleich hoch */ --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. */ .help { color: var(--text-leise); font-size: var(--t-meta); font-weight: normal; } /* Speicherort eines Workspace (Schema oder Datei) als blasse zweite Zeile in * der Workspace-Übersicht. Festbreitenschrift, weil es ein Bezeichner zum * Abtippen ist; markierbar, damit er sich kopieren lässt. */ .storage-location { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: var(--t-klein); user-select: all; } /* ── 3. Gerüst ───────────────────────────────────────────────────────────── */ /* Schmale Kopfleiste über die ganze Breite. Sie trägt nur Marke und Abmelden — * die eigentliche Navigation steht in der Seitenleiste. */ .topbar { background: var(--flaeche); border-bottom: 1px solid var(--rahmen); position: sticky; top: 0; z-index: 20; } .topbar-inner { 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); } .badge-word { font-weight: 600; color: var(--text); letter-spacing: -0.01em; } .badge-word:hover { text-decoration: none; } /* Workspace-Umschalter in der Kopfleiste: Name des aktuellen Workspace, * klickbar zur Auswahl-/Verwaltungsseite. Dezent gehalten — die Marke bleibt * der optische Ankerpunkt, das hier ist Kontext, kein zweites Logo. */ .workspace { display: inline-flex; align-items: center; gap: var(--s1); padding: var(--s1) var(--s2); border-radius: var(--radius); color: var(--text-leise); font-size: var(--t-meta); font-weight: 500; } /* Nur der Umschalter (Link) reagiert auf die Maus — beim Workspace-Admin * steht der Name als reiner Text ohne Ziel. */ a.workspace:hover { background: var(--flaeche-leis); color: var(--text); text-decoration: none; } .workspace-separator { color: var(--rahmen-stark); font-weight: 400; } .workspace-name { color: inherit; } .workspace-arrow { font-size: 0.7em; opacity: 0.6; } /* Das Gerüst: Seitenleiste links, Inhalt rechts. */ .layout { 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(--s6); 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. */ .sidebar { 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-group { display: flex; flex-direction: column; gap: var(--s1); } .nav-title { 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-list { list-style: none; margin: 0; padding: 0; } .nav-list 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-list 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-list a.active { background: var(--akzent-flach); color: var(--akzent-text); font-weight: 500; } .nav-list.quiet 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-count { color: var(--text-leise); font-size: var(--t-klein); font-variant-numeric: tabular-nums; } .nav-list a.active .nav-count { color: inherit; } /* Inhaltsbereich. */ .content { 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. */ .reading-width { 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. */ .page-header { display: flex; align-items: center; gap: var(--s4); flex-wrap: wrap; margin-bottom: var(--s5); } .page-header-title { flex: 1; min-width: 15rem; } .page-header-title .help { display: block; margin-top: var(--s1); } .page-header-actions { display: flex; gap: var(--s2); align-items: center; } /* Abschnitt innerhalb einer Seite. */ .section { margin-top: var(--s7); } .section > h2 { margin-bottom: var(--s2); } .section > .help { display: block; margin-bottom: var(--s4); } /* ── 4. Bausteine ────────────────────────────────────────────────────────── */ /* Karte: der Standardbehälter für alles Gruppierte. */ .card { background: var(--flaeche); border: 1px solid var(--rahmen); border-radius: var(--radius-gross); padding: var(--s5); box-shadow: var(--schatten); } .card + .card { margin-top: var(--s4); } /* Kennzahlen (Nutzungsseite): eine Reihe kleiner Kacheln, die auf schmalen * Bildschirmen umbrechen. auto-fit + minmax verteilt sie ohne Media-Query: * so viele Spalten, wie mit mindestens 9rem Breite hineinpassen. */ .metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: var(--s3); } .metric { display: flex; flex-direction: column; gap: var(--s1); background: var(--flaeche); border: 1px solid var(--rahmen); border-radius: var(--radius-gross); padding: var(--s4); } .metric-value { font-size: var(--t-h2); font-weight: 600; font-variant-numeric: tabular-nums; } .metric-name { font-size: var(--t-meta); color: var(--text-leise); } /* Kurze Listen ohne Aufzählungspunkte, etwa "Verweise ins Leere". */ .compact-list { list-style: none; padding: 0; margin: 0; } .compact-list li { padding: var(--s1) 0; border-bottom: 1px solid var(--rahmen); } .compact-list li:last-child { border-bottom: none; } /* Summenzeile am Ende einer Tabelle. */ table.styled-table tr.total td { font-weight: 600; border-top: 2px solid var(--rahmen); } /* Meldungen. */ .message { 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); } .message.hint { background: var(--akzent-flach); border-left-color: var(--akzent); } .message.error { 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. */ .empty { text-align: center; padding: var(--s7) var(--s5); border: 1px dashed var(--rahmen-stark); border-radius: var(--radius-gross); color: var(--text-leise); } .empty p { margin-bottom: var(--s4); } .empty p:last-child { margin-bottom: 0; } /* Marken: eine einzige visuelle Familie für alle kleinen Auszeichnungen. */ .badge { 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.badge:hover { border-color: var(--rahmen-stark); color: var(--text); text-decoration: none; } .badge.emphasis { background: var(--akzent-flach); border-color: transparent; color: var(--akzent-text); } /* Etwas, um das sich jemand kümmern sollte, etwa ein seit Langem ungenutzter Key. */ .badge.warning { background: var(--warn-flach); border-color: transparent; color: var(--warn); } /* 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, .button { 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, .button:hover { filter: brightness(1.08); text-decoration: none; } .button.secondary, button.secondary { background: var(--flaeche); color: var(--text); border-color: var(--rahmen-stark); } .button.secondary:hover, button.secondary:hover { background: var(--flaeche-leis); filter: none; } button.warning { background: var(--flaeche); color: var(--warn); border-color: var(--warn); } button.warning: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. */ .link-button { 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; } .link-button:hover { filter: none; text-decoration: underline; } /* Rechte Seite der Kopfleiste: Hilfe und Abmelden, im selben leisen Ton. * Doppelte Klasse, weil .row weiter unten sonst den Abstand überschreibt. */ .row.topbar-actions { gap: var(--s5); } .topbar-link { font-size: var(--t-meta); color: var(--text-leise); } .topbar-link:hover { color: var(--text); } .topbar-link.active { color: var(--text); font-weight: 500; } .row { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap; } form.row { display: inline-flex; } /* In rechtsbündigen Tabellenspalten (z. B. Verlauf) sollen die Aktionen * ebenfalls rechts stehen — ein Flex-Container ignoriert text-align. */ .right > .row { justify-content: flex-end; } /* Formulare. * * Jedes Feld ist ein .field mit Beschriftung, optionalem Hinweis und Bedienelement. * Die Breite des BEDIENELEMENTS 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. Beschriftung und Hinweis dürfen breiter * sein (bis zur Lesebreite) — sonst bricht ein Hinweis in eine schmale Spalte * um und wirkt gedrückt (so war es bis 2026-10-16). */ .form { display: flex; flex-direction: column; gap: var(--s5); } .field { display: flex; flex-direction: column; gap: var(--s1); } .field > label { font-weight: 500; font-size: var(--t-meta); } .field > .help { max-width: var(--breite-lesen); line-height: 1.5; margin-bottom: var(--s1); } input[type=text], input[type=search], input[type=password], input[type=number], input[type=date], input[type=email], input[type=url], 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); } /* Einzeilige Bedienelemente bekommen eine feste Höhe statt nur einer * Mindesthöhe. Sonst sind Textfeld, Auswahlliste und Datumsfeld je nach * Browser unterschiedlich hoch (beobachtet: 40, 36 und 42 Pixel), und in * einer Zeile wie der Filterleiste stehen ihre Beschriftungen versetzt. */ input:is([type=text], [type=search], [type=password], [type=number], [type=date], [type=email], [type=url]), select { height: var(--hoehe-bedienelement); padding-block: 0; } input::placeholder { color: var(--text-leise); opacity: 0.7; } /* Dateiauswahl: kein Textfeld, aber derselbe Rahmen, damit sie im Formular * nicht wie ein Fremdkörper wirkt. */ input[type=file] { width: 100%; padding: var(--s2); border: 1px dashed var(--rahmen-stark); border-radius: var(--radius); background: var(--flaeche); font-size: var(--t-meta); } 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; } /* Reiter "Schreiben"/"Vorschau" über dem Inhaltsfeld. */ .editor-tabs { display: flex; gap: var(--s1); margin-bottom: var(--s2); border-bottom: 1px solid var(--rahmen); } .editor-tab { padding: var(--s2) var(--s3); background: none; border: none; border-bottom: 2px solid transparent; border-radius: 0; min-height: 0; color: var(--text-leise); font-weight: 500; font-size: var(--t-meta); cursor: pointer; margin-bottom: -1px; } .editor-tab:hover { color: var(--text); filter: none; } .editor-tab.active { color: var(--text); border-bottom-color: var(--akzent); } /* Vorschau-Fläche: dieselbe Textdarstellung wie die Detailansicht * (.entry-text), aber als Formularfeld gerahmt, damit der Wechsel * zwischen Schreiben und Vorschau nicht die Höhe des Formulars springen * lässt. */ .entry-text.preview-area { min-height: 20rem; max-width: none; padding: var(--s3); background: var(--flaeche); border: 1px solid var(--rahmen-stark); border-radius: var(--radius); /* Überschreibt den großzügigen margin-top von .entry-text (siehe * weiter unten) — hier steckt es in einem Formularfeld, nicht unter * einer Überschrift. Die doppelte Klasse hebt die Reihenfolge im * Stylesheet auf: diese Regel gewinnt unabhängig davon, wo sie steht. */ margin-top: 0; } .preview-area.preview-loading { color: var(--text-leise); } /* Vorschlagsliste der Autovervollständigung für [[…]] — direkt unter dem * Feld statt an der Schreibmarke ausgerichtet, siehe autocomplete.js. */ .autocomplete { list-style: none; margin: var(--s2) 0 0; padding: 0; max-height: 12rem; overflow-y: auto; background: var(--flaeche); border: 1px solid var(--rahmen-stark); border-radius: var(--radius); box-shadow: var(--schatten); } .autocomplete li { display: flex; align-items: baseline; gap: var(--s2); padding: var(--s2) var(--s3); cursor: pointer; font-size: var(--t-meta); } .autocomplete li:not(:last-child) { border-bottom: 1px solid var(--rahmen); } .autocomplete li span { color: var(--text-leise); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .autocomplete li.active { background: var(--akzent-flach); } .autocomplete li.active span { color: var(--akzent-text); } /* Breiten der Bedienelemente nach Inhalt — nur das Eingabefeld wird * begrenzt, nicht Beschriftung und Hinweis (siehe oben). */ .field.short > :is(input, select) { max-width: 18rem; } .field.medium > :is(input, select) { max-width: 28rem; } .field.long > :is(input, select) { max-width: 40rem; } /* Ankreuzfeld: Kästchen und Text nebeneinander, Text nicht fett. */ .checkbox { 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. */ .checkbox input { width: auto; min-height: 0; margin-top: 0.3rem; } .checkbox label { font-weight: normal; } /* Der Hinweis steht unter der Beschriftung, nicht in derselben Zeile. */ .checkbox label .help { display: block; margin-top: var(--s1); max-width: var(--breite-lesen); } .form-actions { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: center; padding-top: var(--s1); } /* Tabellen. */ .table-wrap { 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.styled-table { width: 100%; border-collapse: collapse; font-size: var(--t-meta); } .styled-table th, .styled-table td { text-align: left; padding: var(--s3) var(--s4); border-bottom: 1px solid var(--rahmen); vertical-align: middle; } .styled-table thead th { background: var(--flaeche-leis); color: var(--text-leise); font-weight: 500; font-size: var(--t-klein); white-space: nowrap; } .styled-table tbody tr:last-child td { border-bottom: none; } .styled-table tbody tr:hover { background: var(--flaeche-leis); } .styled-table .num { text-align: right; font-variant-numeric: tabular-nums; } .styled-table .narrow { width: 1%; white-space: nowrap; } .styled-table .right { text-align: right; } .styled-table tr.muted { color: var(--text-leise); } .styled-table tr.muted td:first-child { text-decoration: line-through; } /* ── 5. Seiten ───────────────────────────────────────────────────────────── */ /* Anmeldung: eigenständige, ruhige Seite ohne Navigation. */ .login-page { min-height: 100dvh; display: flex; align-items: center; justify-content: center; /* Unten mehr Abstand als oben: Das hebt den Block etwas über die * mathematische Mitte. Genau mittig wirkt er optisch zu tief. */ padding: var(--s5) var(--s5) 12vh; } .login-page .card { width: 100%; padding: var(--s6); } .login-page .help { display: block; margin-bottom: var(--s5); } /* Logo zentriert über der Anmeldekarte. */ .login-logo { display: flex; justify-content: center; margin-bottom: var(--s2); } /* 264px, damit auch die kleine Unterzeile im Logo ("MCP Server • Wiki / * Knowledge Base") lesbar bleibt. max-width fängt schmale Handys ab. */ .login-logo img { width: 264px; max-width: 100%; height: auto; } /* Werkzeugleiste über der Eintragsliste: Suche und Filter. */ .toolbar { display: flex; gap: var(--s2); margin-bottom: var(--s4); } .toolbar input[type=search] { flex: 1; } /* Hinweis "Gibt es schon?" unter dem Titel eines neuen Eintrags. */ .similar-hint { margin-top: var(--s2); padding: var(--s2) var(--s3); border-left: 3px solid var(--akzent); background: var(--flaeche-leis); border-radius: var(--radius); } .similar-hint ul { margin: var(--s1) 0 0; padding-left: var(--s4); } .similar-hint li { font-size: var(--t-meta); } /* Eigenschaftsliste (Begriff: Wert), z.B. auf der Systemseite. */ .properties { display: grid; grid-template-columns: max-content 1fr; gap: var(--s2) var(--s4); margin: var(--s3) 0 0; } .properties dt { color: var(--text-leise); } .properties dd { margin: 0; } @media (max-width: 60rem) { .properties { grid-template-columns: 1fr; gap: var(--s1); } .properties dd { margin-bottom: var(--s2); } } /* Systemseite: Kasten "Zustand". Warnungen rot, Hinweise grün — dieselben * Farben wie .message, aber als Liste, damit mehrere untereinander passen. */ .status { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--s2); } .status > li { display: flex; gap: var(--s3); align-items: baseline; padding: var(--s3) var(--s4); border: 1px solid var(--rahmen); border-left-width: 3px; border-radius: var(--radius); font-size: var(--t-meta); line-height: 1.5; } .status > li.status-warning { background: var(--warn-flach); border-left-color: var(--warn); } .status > li.status-hint { background: var(--akzent-flach); border-left-color: var(--akzent); } .status-kind { font-weight: 600; flex: none; min-width: 5rem; max-width: 12rem; overflow-wrap: anywhere; } .status-warning .status-kind { color: var(--warn); } /* Überblick: mehrere Befunde eines Projekts in einer Karte. */ .status-items { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--s1); flex: 1; min-width: 0; } .status-items .urgent { font-weight: 600; } .more-link { white-space: nowrap; font-weight: 400; } .status-hint .status-kind { color: var(--akzent-text); } /* Systemseite: Balkendiagramme (internal/web/chart.go). Untereinander * und höchstens so breit wie Fließtext: Das SVG skaliert samt Schrift mit * seiner Breite — nebeneinander wären die Beschriftungen zu klein, über die * ganze Seite die Balken unnötig flach. */ .charts { display: grid; gap: var(--s4); max-width: var(--breite-lesen); margin-bottom: var(--s2); } .chart-frame { margin: 0; background: var(--flaeche); border: 1px solid var(--rahmen); border-radius: var(--radius-gross); padding: var(--s4); min-width: 0; } .chart-frame figcaption { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s1) var(--s4); margin-bottom: var(--s3); font-size: var(--t-meta); } .legend { display: inline-flex; align-items: center; gap: var(--s1); color: var(--text-leise); } .legend-swatch { display: inline-block; width: 0.75rem; height: 0.75rem; border-radius: 2px; } .chart { display: block; width: 100%; height: auto; } .chart-grid { stroke: var(--rahmen); stroke-width: 1; } .chart-text { fill: var(--text-leise); font-size: 11px; font-variant-numeric: tabular-nums; } /* Unsichtbar, aber da: Über ihr erscheint der Tooltip eines Tages. */ .chart-hitarea { fill: transparent; } .chart-hitarea:hover { fill: var(--flaeche-leis); } .chart-empty { margin: var(--s5) 0; text-align: center; } /* Die Farben gelten für die Balken (SVG: fill) und die Legende (HTML: * background). Menschen in derselben Farbe wie Agenten, nur heller — beides * ist "Schreiben". */ rect.bar-agent { fill: var(--akzent); } rect.bar-human { fill: var(--akzent); fill-opacity: 0.4; } rect.bar-search { fill: var(--text-leise); } .legend-swatch.bar-agent { background: var(--akzent); } .legend-swatch.bar-human { background: var(--akzent); opacity: 0.4; } .legend-swatch.bar-search { background: var(--text-leise); } /* Aufklappbarer Zusatz unter einer Tabelle oder einem Diagramm. */ .disclosure { margin-top: var(--s3); } .disclosure > summary { cursor: pointer; color: var(--akzent-text); font-size: var(--t-meta); } .disclosure[open] > summary { margin-bottom: var(--s3); } .section h3 { margin: var(--s5) 0 var(--s2); } .idle-list { margin: var(--s2) 0 0; padding-left: var(--s5); columns: 2 16rem; font-size: var(--t-meta); } /* Warnender Text in einer Kennzahl. */ .warn-text { color: var(--warn); } /* Filter über einer Tabelle, z.B. im Protokoll. Bricht auf schmalen * Bildschirmen um. */ .filter-bar { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--s2) var(--s3); margin-bottom: var(--s4); } .filter-bar label { display: flex; flex-direction: column; gap: var(--s1); font-size: var(--t-meta); color: var(--text-leise); } .filter-bar a { align-self: center; font-size: var(--t-meta); } /* In der Filterleiste nehmen die Felder ihre natürliche Breite, nicht 100 % * des Etiketts — sonst schrumpfen sie auf die Breite der Beschriftung. */ .filter-bar :is(input, select) { width: auto; min-width: 9rem; } /* Filterlink neben der Suche, z.B. "3 veraltet". */ .toolbar .filter-link { align-self: center; white-space: nowrap; font-size: var(--t-klein); } /* Eintragsliste. */ .entries { 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; } .entries li { display: flex; align-items: flex-start; gap: var(--s3); padding: var(--s4); border-bottom: 1px solid var(--rahmen); } .entries li:last-child { border-bottom: none; } .entries li:hover { background: var(--flaeche-leis); } /* Auswahlkästchen der Sammelbearbeitung: etwas abgesetzt vom Titel, auf * dessen erster Zeile ausgerichtet statt an der Oberkante der ganzen Zeile. */ .entry-select { display: flex; align-items: center; padding-top: 0.35rem; } .entry-content { flex: 1; min-width: 0; } /* Sammelleiste: erscheint unter der Liste, sobald Einträge angehakt sind. * position: sticky hält sie beim Scrollen einer langen Liste in Sichtweite. */ .bulk-bar { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; margin-top: var(--s3); padding: var(--s3) var(--s4); background: var(--flaeche); border: 1px solid var(--rahmen); border-radius: var(--radius-gross); box-shadow: var(--schatten); position: sticky; bottom: var(--s4); } /* Papierkorb: "Alle von … wiederherstellen" steht mit etwas Abstand unter der * Sammelleiste. */ .trash-all { margin-top: var(--s3); } /* Für Screenreader lesbar, aber unsichtbar — etwa die Überschrift einer * Spalte, die nur Kästchen enthält. display:none ginge nicht: Das würde den * Text auch vor Screenreadern verstecken. */ .invisible { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } .bulk-bar input[type=text] { width: auto; flex: 1 1 12rem; min-height: 2rem; padding: var(--s1) var(--s2); } .entry-title { display: block; font-size: var(--t-gross); font-weight: 500; color: var(--text); margin-bottom: var(--s2); } .entry-excerpt { 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; } .entry-excerpt mark { background: var(--akzent-flach); color: var(--akzent-text); padding: 0 0.15em; border-radius: 3px; } /* Einzelner Eintrag: Fließtext in Lesebreite. */ .entry-text { font-size: var(--t-gross); line-height: 1.75; margin-top: var(--s6); } .entry-text > :first-child { margin-top: 0; } .entry-text h1, .entry-text h2, .entry-text h3 { margin: var(--s6) 0 var(--s3); } .entry-text p, .entry-text ul, .entry-text ol { margin: 0 0 var(--s4); } .entry-text ul, .entry-text ol { padding-left: var(--s5); } .entry-text li { margin-bottom: var(--s1); } .entry-text img { max-width: 100%; border-radius: var(--radius); } .entry-text blockquote { margin: var(--s4) 0; padding: var(--s1) var(--s4); border-left: 3px solid var(--rahmen-stark); color: var(--text-leise); } .entry-text pre { margin-bottom: var(--s4); font-size: var(--t-meta); } .entry-text table { border-collapse: collapse; width: 100%; font-size: var(--t-meta); } .entry-text th, .entry-text td { border: 1px solid var(--rahmen); padding: var(--s2) var(--s3); } .entry-text hr { border: none; border-top: 1px solid var(--rahmen); margin: var(--s6) 0; } /* Checklisten (- [ ] / - [x]): Das Kästchen ersetzt den Aufzählungspunkt. * :has() wählt nur Listenpunkte aus, die direkt ein Kästchen enthalten — * gewöhnliche Aufzählungen behalten ihren Punkt. */ .entry-text li:has(> input[type="checkbox"]) { list-style: none; margin-left: calc(-1 * var(--s4)); } .entry-text li > input[type="checkbox"] { margin: 0 var(--s2) 0 0; vertical-align: middle; } .entry-text del { color: var(--text-leise); } .backlinks { margin-top: var(--s7); } .backlinks h2 { font-size: var(--t-meta); color: var(--text-leise); margin-bottom: var(--s3); } .backlinks 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-display { border-color: var(--akzent); background: var(--akzent-flach); } .key-value { 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-instructions { margin-top: var(--s4); } .key-instructions summary { cursor: pointer; font-size: var(--t-meta); color: var(--akzent-text); } .key-instructions pre { margin-top: var(--s3); font-size: var(--t-klein); } /* Fassungsvergleich. */ .diff-heads { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s4); margin-bottom: var(--s4); } .diff-head { display: flex; flex-direction: column; gap: var(--s1); padding: var(--s3) var(--s4); background: var(--flaeche); border: 1px solid var(--rahmen); border-radius: var(--radius); } .diff-wrap { background: var(--flaeche); border: 1px solid var(--rahmen); border-radius: var(--radius-gross); box-shadow: var(--schatten); overflow-x: auto; } .diff { margin: 0; padding: var(--s3) 0; font-size: var(--t-meta); line-height: 1.6; white-space: pre-wrap; word-break: break-word; } .diff span { display: block; padding: 0 var(--s4); } .diff-added { background: var(--akzent-flach); color: var(--akzent-text); } .diff-removed { background: var(--warn-flach); color: var(--warn); } /* Graph-Ansicht der Verweise. */ .graph-wrap { background: var(--flaeche); border: 1px solid var(--rahmen); border-radius: var(--radius-gross); box-shadow: var(--schatten); overflow: hidden; margin-bottom: var(--s4); } .graph-wrap svg { display: block; width: 100%; /* Möglichst viel Fläche, aber auf kleinen und sehr hohen Bildschirmen * in vernünftigen Grenzen. */ height: clamp(30rem, 75vh, 60rem); cursor: grab; touch-action: none; } .graph-wrap svg.graph-dragging { cursor: grabbing; } .graph-edge { stroke: var(--rahmen-stark); stroke-width: 1.5; transition: opacity 0.15s; } .graph-node-group { transition: opacity 0.15s; } .graph-dot { fill: var(--akzent-flach); stroke: var(--akzent); stroke-width: 1.5; } .graph-node-missing .graph-dot { fill: var(--flaeche); stroke: var(--rahmen-stark); stroke-dasharray: 3 2; } .graph-label { font-size: 0.75rem; fill: var(--text); dominant-baseline: middle; /* Beschriftungen sollen das Ziehen des Graphen nicht durch * Textmarkierung stören. */ user-select: none; /* Ein Rand in Hintergrundfarbe um jeden Buchstaben: Kanten, die hinter * dem Text verlaufen, werden so unterbrochen, statt die Schrift zu * durchkreuzen. paint-order malt den Rand zuerst, die Füllung darüber. */ paint-order: stroke; stroke: var(--flaeche); stroke-width: 3px; stroke-linejoin: round; } /* Welche Namen Platz haben, entscheidet static/graph.js (beschrifte()). */ .graph-label-hidden { display: none; } .graph-node-missing .graph-label { fill: var(--text-leise); } .graph-clickable { cursor: pointer; } .graph-clickable:hover .graph-dot { stroke-width: 2.5; } .graph-clickable:hover .graph-label { fill: var(--akzent-text); } /* Hervorhebung beim Überfahren: der Knoten, seine Nachbarn und die * verbindenden Kanten bleiben kräftig, der Rest tritt zurück. */ .graph-focus .graph-node-group:not(.graph-neighbor) { opacity: 0.2; } .graph-focus .graph-edge:not(.graph-edge-active) { opacity: 0.1; } .graph-edge-active { stroke: var(--akzent); } /* Seite "Alle Schlagworte": mehrspaltige Liste, die Spaltenzahl ergibt sich * aus der verfügbaren Breite. */ .tag-list { list-style: none; margin: 0 0 var(--s5); padding: 0; columns: 14rem; column-gap: var(--s5); } .tag-list li { break-inside: avoid; } .tag-list 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); } .tag-list a:hover { background: var(--flaeche-leis); text-decoration: none; } .tag-group h2 { font-size: var(--t-gross); color: var(--text-leise); border-bottom: 1px solid var(--rahmen); padding-bottom: var(--s1); margin-bottom: var(--s2); } /* 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. */ .danger-zone { border-color: var(--warn); } .danger-zone h2 { color: var(--warn); } /* Hilfe: Themen als umbrechende Reiterleiste über dem Text, der Text selbst * wie ein Eintrag in Lesebreite. */ .help-nav { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s5); padding-bottom: var(--s4); border-bottom: 1px solid var(--rahmen); } .help-nav a { padding: var(--s1) var(--s3); border: 1px solid var(--rahmen); border-radius: var(--radius); color: var(--text-leise); font-size: var(--t-meta); } .help-nav a:hover { background: var(--flaeche-leis); color: var(--text); text-decoration: none; } .help-nav a.active { background: var(--akzent-flach); border-color: var(--akzent); color: var(--akzent-text); font-weight: 500; } .help-text { max-width: var(--breite-lesen); } /* Das kleine "?" neben Überschriften und Feldern (Partial "hilfelink"). * Bewusst unaufdringlich: ein Kreis in Textgröße, der erst beim Überfahren * Farbe bekommt. Die Größe in rem (bezogen auf die Grundschrift, nicht auf * die Umgebung), damit der Kreis neben einer großen h1 genauso klein bleibt * wie neben einem Formular-Label. */ .help-link { display: inline-flex; align-items: center; justify-content: center; width: 1.25rem; height: 1.25rem; margin-left: var(--s1); border: 1px solid var(--rahmen); border-radius: 50%; font-size: 0.75rem; font-weight: 600; line-height: 1; color: var(--text-leise); vertical-align: middle; } .help-link:hover { border-color: var(--akzent); color: var(--akzent-text); text-decoration: none; } /* ── 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) { .layout { grid-template-columns: minmax(0, 1fr); gap: 0; padding-inline: var(--s4); } .topbar-inner { padding-inline: var(--s4); gap: var(--s3); } /* Kopfleiste: Auf schmalen Bildschirmen reicht "Hilfe", und ein langer * Workspace-Name wird gekürzt statt die Leiste zu sprengen. */ .wide-only { display: none; } .row.topbar-actions { gap: var(--s4); flex-wrap: nowrap; } .topbar-inner > .row:first-child { min-width: 0; flex-wrap: nowrap; } .workspace { min-width: 0; } .workspace-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .sidebar { 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-group { flex-direction: row; align-items: center; gap: var(--s2); min-width: 0; flex: 1 1 100%; } .nav-title { margin-bottom: 0; padding-inline: 0; white-space: nowrap; } /* min-width: 0, damit die Zeile in sich scrollt, statt die ganze Seite * zu verbreitern — ein Flex-Element gibt sonst nicht unter die Breite * seines Inhalts nach. */ .nav-list { display: flex; gap: var(--s1); min-width: 0; overflow-x: auto; scrollbar-width: none; /* die Leiste scrollt, ohne einen Balken zu zeigen */ } .nav-list::-webkit-scrollbar { display: none; } .nav-list a { white-space: nowrap; } /* Die Schlagwortliste kann lang werden — hier zählt jede Zeile. */ .tags .nav-list a { padding-inline: var(--s2); } .content { padding-block: var(--s5) var(--s7); } .page-header { gap: var(--s3); } .page-header-actions { width: 100%; } .page-header-actions .button { flex: 1; } .card { padding: var(--s4); } .styled-table th, .styled-table td { padding: var(--s2) var(--s3); } .field.short > :is(input, select), .field.medium > :is(input, select), .field.long > :is(input, select) { max-width: none; } textarea { min-height: 14rem; } .diff-heads { grid-template-columns: 1fr; } }