style(web): Design-Tokens Mosaik (Fluent 2 / GitHub)

Kuehlgraue Arbeitsflaeche, weisse Karten mit Fluent-Schatten statt
Rahmen, dunkle App-Leiste, Link-Blau, neutraler Fokusring, 6/8/12 px
Radien, Segoe-UI-Schriftstapel, Knopf-/Link-Klassen, Fluent-Eingabefeld
und reduzierte Bewegung. Die persoenliche Akzentfarbe setzt nur noch
--primary.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-25 14:32:37 +02:00
parent 3fc33e3507
commit 3c62a1a29c
2 changed files with 264 additions and 56 deletions
+256 -42
View File
@@ -57,45 +57,85 @@
--color-status-idle-fg: var(--status-idle-fg);
--color-status-orphan-fg: var(--status-orphan-fg);
--color-well: var(--well);
--color-link: var(--link);
--color-primary-strong: var(--primary-strong);
--color-input-strong: var(--input-strong);
--color-appbar: var(--appbar);
--color-appbar-foreground: var(--appbar-foreground);
--color-appbar-muted: var(--appbar-muted);
--color-appbar-hover: var(--appbar-hover);
--color-sidebar-hover: var(--sidebar-hover);
--radius-sm: calc(var(--radius) - 2px);
--radius-md: var(--radius);
--radius-lg: calc(var(--radius) + 2px);
}
/* Fluent-2-Hoehenstufen: shadow-sm = shadow2 (ruhende Karte), shadow-md =
shadow4 (angehobene/Hover-Karte), shadow-lg = shadow16 (Menues, Dialoge).
Im dunklen Erscheinungsbild tragen Haarlinien statt Schatten (--border). */
@theme {
--shadow-sm: 0 0 2px oklch(0 0 0 / 0.12), 0 1px 2px oklch(0 0 0 / 0.14);
--shadow-md: 0 0 2px oklch(0 0 0 / 0.12), 0 4px 8px oklch(0 0 0 / 0.14);
--shadow-lg: 0 0 2px oklch(0 0 0 / 0.12), 0 8px 16px oklch(0 0 0 / 0.14);
--shadow-xl: 0 0 8px oklch(0 0 0 / 0.12), 0 14px 28px oklch(0 0 0 / 0.14);
}
:root {
--radius: 0.5rem;
--font-sans: "Inter", system-ui, -apple-system, sans-serif;
/* Design „Mosaik“ (Fluent 2 / GitHub als Vorbild): 6 px fuer Bedien-
elemente (rounded-md), 8 px fuer Karten (rounded-lg), 12 px fuer grosse
Flaechen (rounded-xl). */
--radius: 0.375rem;
/* Segoe UI Variable zuerst: unter Windows (Desktop-Client) wirkt die
Oberflaeche damit wie eine native Anwendung; sonst Inter bzw. Systemschrift. */
--font-sans: "Segoe UI Variable Text", "Segoe UI", Inter, system-ui, -apple-system, sans-serif;
/* Layout dimensions */
--sidebar-width: 240px;
--sidebar-width-collapsed: 64px;
--header-height: 60px;
--header-height: 48px;
/* Light mode colors (OKLCH) */
--background: oklch(0.99 0 0);
--foreground: oklch(0.15 0 0);
/* Light mode colors (OKLCH) — Arbeitsflaeche leicht kuehlgrau, Karten weiss */
--background: oklch(0.972 0.003 260);
--foreground: oklch(0.2 0.006 260);
--card: oklch(1 0 0);
--card-foreground: oklch(0.15 0 0);
--card-foreground: oklch(0.2 0.006 260);
--primary: oklch(0.91 0.19 102);
--primary-foreground: oklch(0.20 0.02 90);
--secondary: oklch(0.96 0 0);
--secondary-foreground: oklch(0.15 0 0);
--muted: oklch(0.96 0 0);
--muted-foreground: oklch(0.55 0 0);
--accent: oklch(0.96 0 0);
--accent-foreground: oklch(0.15 0 0);
--secondary: oklch(0.955 0.003 260);
--secondary-foreground: oklch(0.2 0.006 260);
--muted: oklch(0.955 0.003 260);
--muted-foreground: oklch(0.49 0.01 260);
--accent: oklch(0.955 0.003 260);
--accent-foreground: oklch(0.2 0.006 260);
--destructive: oklch(0.55 0.2 27);
--destructive-foreground: oklch(0.98 0 0);
--border: oklch(0.90 0 0);
--input: oklch(0.90 0 0);
--ring: oklch(0.91 0.19 102);
--border: oklch(0.915 0.004 260);
--input: oklch(0.87 0 0);
/* Untere Kante von Eingabefeldern (Fluent-Eingabefeld). */
--input-strong: oklch(0.58 0.005 260);
/* Tastaturfokus: dunkler Ring statt Gelb — Gelb auf Weiss erreicht nur
1,3:1 und waere als Fokusanzeige kaum sichtbar. */
--ring: oklch(0.3 0.01 260);
/* Verweise: nie gelbe Schrift auf Weiss. 5,9:1 auf --card. */
--link: oklch(0.50 0.15 255);
/* Akzent fuer duenne Markierungen (Auswahlpille, Reiter-Unterstrich) auf
hellem Grund: die Akzentfarbe um knapp ein Drittel abgedunkelt, damit
auch Gelb neben Hellgrau noch erkennbar bleibt. */
--primary-strong: color-mix(in oklab, var(--primary) 70%, oklch(0.3 0.03 90));
/* Kopfzeile (dunkle App-Leiste wie GitHub / Microsoft 365) */
--appbar: oklch(0.24 0.012 260);
--appbar-foreground: oklch(0.985 0 0);
--appbar-muted: oklch(0.985 0 0 / 0.72);
--appbar-hover: oklch(1 0 0 / 0.08);
/* Sidebar tokens */
--sidebar: oklch(0.97 0 0);
--sidebar-foreground: oklch(0.15 0 0);
--sidebar-accent: oklch(0.93 0 0);
--sidebar-accent-foreground: oklch(0.25 0 0);
--sidebar-border: oklch(0.90 0 0);
--sidebar: oklch(0.975 0.003 260);
--sidebar-foreground: oklch(0.2 0.006 260);
--sidebar-accent: oklch(0.925 0.005 260);
--sidebar-accent-foreground: oklch(0.16 0.006 260);
--sidebar-hover: oklch(0.945 0.004 260);
--sidebar-border: oklch(0.915 0.004 260);
/* Statusfarben (quick-260924-h7x). Die Flaechentoene stammen aus dem
Design-Plan; als Schrift auf Weiss erreichen sie aber nur 2,6:1 bis
@@ -113,34 +153,44 @@
--status-idle-fg: oklch(0.50 0.02 260);
--status-orphan-fg: oklch(0.50 0.01 260);
/* Eingelassene Messfelder. */
--well: oklch(0.975 0.003 260);
--well: oklch(0.972 0.003 260);
}
.dark {
--background: oklch(0.23 0.01 260);
--foreground: oklch(0.95 0 0);
--card: oklch(0.27 0.01 260);
--card-foreground: oklch(0.95 0 0);
--background: oklch(0.2 0.01 260);
--foreground: oklch(0.95 0.003 260);
--card: oklch(0.245 0.01 260);
--card-foreground: oklch(0.95 0.003 260);
--primary: oklch(0.91 0.19 102);
--primary-foreground: oklch(0.20 0.02 90);
--secondary: oklch(0.30 0.01 260);
--secondary-foreground: oklch(0.95 0 0);
--muted: oklch(0.30 0.01 260);
--muted-foreground: oklch(0.65 0 0);
--accent: oklch(0.30 0.01 260);
--accent-foreground: oklch(0.95 0 0);
--destructive: oklch(0.55 0.2 27);
--secondary: oklch(0.28 0.01 260);
--secondary-foreground: oklch(0.95 0.003 260);
--muted: oklch(0.28 0.01 260);
--muted-foreground: oklch(0.72 0.01 260);
--accent: oklch(0.28 0.01 260);
--accent-foreground: oklch(0.95 0.003 260);
--destructive: oklch(0.6 0.2 27);
--destructive-foreground: oklch(0.98 0 0);
--border: oklch(0.30 0.01 260);
--input: oklch(0.30 0.01 260);
--ring: oklch(0.91 0.19 102);
/* Dunkel: feine helle Haarlinie statt Schatten. */
--border: oklch(1 0 0 / 0.09);
--input: oklch(1 0 0 / 0.16);
--input-strong: oklch(1 0 0 / 0.42);
--ring: oklch(0.92 0.005 260);
--link: oklch(0.78 0.11 250);
--primary-strong: var(--primary);
--appbar: oklch(0.18 0.01 260);
--appbar-foreground: oklch(0.985 0 0);
--appbar-muted: oklch(0.985 0 0 / 0.7);
--appbar-hover: oklch(1 0 0 / 0.08);
/* Sidebar tokens (dark) */
--sidebar: oklch(0.20 0.01 260);
--sidebar-foreground: oklch(0.95 0 0);
--sidebar-accent: oklch(0.91 0.19 102 / 0.15);
--sidebar-accent-foreground: oklch(0.91 0.19 102);
--sidebar-border: oklch(0.30 0.01 260);
--sidebar: oklch(0.21 0.01 260);
--sidebar-foreground: oklch(0.93 0.003 260);
--sidebar-accent: oklch(1 0 0 / 0.08);
--sidebar-accent-foreground: oklch(0.98 0 0);
--sidebar-hover: oklch(1 0 0 / 0.05);
--sidebar-border: oklch(1 0 0 / 0.08);
/* Statusfarben (dark). Textvariante auf --card gemessen: ok 7,4:1,
warn 8,5:1, down 6,1:1, idle 6,1:1, orphan 5,6:1. */
@@ -154,11 +204,13 @@
--status-down-fg: oklch(0.74 0.16 27);
--status-idle-fg: oklch(0.72 0.02 260);
--status-orphan-fg: oklch(0.70 0.01 260);
--well: oklch(0.235 0.01 260);
--well: oklch(0.215 0.01 260);
}
body {
font-family: var(--font-sans);
font-size: 0.875rem;
line-height: 1.25rem;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@@ -181,3 +233,165 @@ body {
.wmde-markdown ul ul { list-style: circle; }
.wmde-markdown ol { list-style: decimal; }
.wmde-markdown ul.contains-task-list, .wmde-markdown li.task-list-item { list-style: none; }
/* Tastaturfokus sichtbar ueberall, auch wo ein Bauteil keinen eigenen
Fokusring setzt (im Basis-Layer, damit `outline-none` + eigener Ring
eines Bauteils Vorrang behalten). Mausklicks loesen :focus-visible nicht aus. */
@layer base {
:where(a, button, [role="tab"], [role="button"], summary, [tabindex]):focus-visible {
outline: 2px solid var(--ring);
outline-offset: 2px;
}
}
/* Fluent-Eingabefeld: untere Kante eine Stufe dunkler; Fokus = dunklerer
Rahmen plus 2 px Akzentlinie unten. Gilt fuer Textfelder und Auswahllisten
mit dem ueblichen `border-input`/`border-border`-Rahmen; Fehlerzustaende
(aria-invalid, border-destructive) behalten ihre Farbe. Bewusst ausserhalb
eines @layer, damit die Regel die Tailwind-Utilities schlaegt. */
:is(input:not([type="checkbox"], [type="radio"], [type="range"], [type="color"], [type="file"]), select, textarea):is(.border-input, .border-border):not([aria-invalid="true"], .border-destructive) {
border-bottom-color: var(--input-strong);
}
:is(input:not([type="checkbox"], [type="radio"], [type="range"], [type="color"], [type="file"]), select, textarea):is(.border-input, .border-border):not([aria-invalid="true"], .border-destructive):focus {
outline: none;
border-color: var(--input-strong);
border-bottom-color: var(--primary);
box-shadow: inset 0 -1px 0 var(--primary);
}
/* Karteneffekt in einer Klasse fuer neue Flaechen: hell Schatten, dunkel Haarlinie. */
.surface {
background: var(--card);
border-radius: 0.5rem;
box-shadow: var(--shadow-sm);
}
.dark .surface {
box-shadow: none;
outline: 1px solid var(--border);
outline-offset: -1px;
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* Knoepfe (Design „Mosaik“, Fluent 2): 32 px hoch, 6 px Radius, 14 px
halbfett. Primaer = Akzentflaeche mit dunkler Schrift, Sekundaer = weisse
Flaeche mit Rahmen, Subtil = ohne Flaeche. Im Komponenten-Layer, damit
einzelne Utilities (etwa `w-full`) weiter ueberschreiben koennen. */
@layer components {
.btn {
display: inline-flex;
height: 2rem;
align-items: center;
justify-content: center;
gap: 0.5rem;
border-radius: 0.375rem;
border: 1px solid transparent;
padding-inline: 0.75rem;
font-size: 0.875rem;
line-height: 1.25rem;
font-weight: 600;
white-space: nowrap;
transition: background-color 120ms, border-color 120ms, color 120ms;
}
.btn:focus-visible {
outline: 2px solid var(--ring);
outline-offset: 2px;
}
.btn:disabled {
cursor: not-allowed;
opacity: 0.55;
}
.btn-primary {
background-color: var(--primary);
color: var(--primary-foreground);
}
.btn-primary:hover:not(:disabled) {
background-color: color-mix(in oklab, var(--primary) 88%, black);
}
.btn-secondary {
background-color: var(--card);
color: var(--foreground);
border-color: oklch(0.87 0 0);
box-shadow: 0 1px 2px oklch(0 0 0 / 0.06);
}
.btn-secondary:hover:not(:disabled) {
background-color: oklch(0.965 0 0);
}
.dark .btn-secondary {
border-color: var(--input);
box-shadow: none;
}
.dark .btn-secondary:hover:not(:disabled) {
background-color: var(--muted);
}
.btn-subtle {
background-color: transparent;
color: var(--foreground);
}
.btn-subtle:hover:not(:disabled) {
background-color: var(--muted);
}
.btn-icon {
width: 2rem;
padding-inline: 0;
}
/* Verweis: dunkle bzw. blaue Schrift, nie gelb auf Weiss. */
.link {
color: var(--link);
text-decoration-line: underline;
text-decoration-thickness: 1px;
text-underline-offset: 3px;
text-decoration-color: color-mix(in oklab, var(--link) 40%, transparent);
}
.link:hover {
text-decoration-color: currentColor;
}
}
/* Markdown (Notiz-Kachel, „Was ist neu“): Fluent-Schriftgroessen statt der
GitHub-Vorgaben (16 px Grundschrift, 2em-Ueberschriften), Hintergrund
transparent, Farben aus den Tokens. */
:root .wmde-markdown {
font-family: var(--font-sans);
font-size: 0.875rem;
line-height: 1.5;
background-color: transparent !important;
color: var(--foreground) !important;
--color-border-default: var(--border);
--color-border-muted: var(--border);
--color-accent-fg: var(--link);
--color-fg-default: var(--foreground);
--color-fg-muted: var(--muted-foreground);
}
:root .wmde-markdown h1 { font-size: 1.25rem; line-height: 1.75rem; font-weight: 600; }
:root .wmde-markdown h2 { font-size: 1rem; line-height: 1.375rem; font-weight: 600; }
:root .wmde-markdown h3, :root .wmde-markdown h4 { font-size: 0.875rem; line-height: 1.25rem; font-weight: 600; }
:root .w-md-editor {
background-color: transparent !important;
box-shadow: none !important;
}
/* Navigationseintrag mit Fluent-Auswahlpille, gesteuert ueber aria-current
(Einstellungs- und Verwaltungsnavigation). */
.nav-item {
position: relative;
}
.nav-item[aria-current="page"]::before {
content: "";
position: absolute;
left: 0;
top: 50%;
width: 3px;
height: 1rem;
margin-top: -0.5rem;
border-radius: 9999px;
background-color: var(--primary-strong);
}
+8 -14
View File
@@ -21,24 +21,18 @@ interface AuthState {
function applyAccentColor(color: string | null | undefined) {
if (typeof document === 'undefined') return;
const root = document.documentElement;
// Design „Mosaik“: die persoenliche Akzentfarbe setzt nur noch `--primary`
// (Hauptknoepfe, Auswahlpille der Seitenleiste, Reiter-Unterstrich, Logo).
// Fokusring und Auswahlflaeche der Seitenleiste bleiben neutral — eine
// helle Akzentfarbe (Standard Gelb) waere als Fokusring auf Weiss kaum
// sichtbar (1,3:1). Alte Werte frueherer Fassungen werden entfernt.
root.style.removeProperty('--ring');
root.style.removeProperty('--sidebar-accent');
root.style.removeProperty('--sidebar-accent-foreground');
if (color) {
root.style.setProperty('--primary', color);
root.style.setProperty('--ring', color);
// Sidebar active state: accent color with 15% opacity (#rrggbbaa, 0x26 ≈ 15%)
root.style.setProperty('--sidebar-accent', `${color}26`);
// Full-saturation accent text only reads well against the dark-mode sidebar's
// tinted-dark background; in light mode it's near-white behind it, so keep
// the theme's default (dark, high-contrast) foreground there instead.
if (root.classList.contains('dark')) {
root.style.setProperty('--sidebar-accent-foreground', color);
} else {
root.style.removeProperty('--sidebar-accent-foreground');
}
} else {
root.style.removeProperty('--primary');
root.style.removeProperty('--ring');
root.style.removeProperty('--sidebar-accent');
root.style.removeProperty('--sidebar-accent-foreground');
}
}