From 8403f1c39d38f9761d006ccb927d79469fbf3179 Mon Sep 17 00:00:00 2001 From: Sascha Wolf Date: Wed, 9 Sep 2026 13:47:08 +0200 Subject: [PATCH] feat(ui): vierte Farbwelt "Cyberpunk" in hell und dunkel Neon in der Nacht: fast schwarze Flaechen mit blauem Stich, violettes Leuchten, Blaustahl statt Neutralgrau und Kanten von 2 px - scharf genug fuer HUD-Elemente, aber nicht eckig, das ist schon Pixel Art. Schatten sind hier kein Schatten, sondern ein Schein: farbige Kontur plus weicher Halo in der Markenfarbe. Schrift ist Chakra Petch, die Feldbeschriftungen laufen gesperrt. Der Akzent ist bewusst Violett und nicht Neon-Cyan: auf brand-500 und brand-600 steht 69 Mal weisse Schrift, und auf Cyan waere die nicht zu lesen. Cyan sitzt deshalb nur in --color-brand-accent und in den Statusfarben, wo dunkler Text darauf steht. Der helle Modus ist der Konzern bei Tageslicht: kuehles Blaugrau, dasselbe Violett nur dunkler, und aus dem Leuchten wird ein knapper kalter Schatten - ein Halo auf hellem Grund sieht nach Fehler aus, nicht nach Neon. Es waren vier Zeilen ausserhalb von index.css: die Whitelist in userFields.js, die Liste in src/types.ts mit ihrer Beschriftung, die Whitelist im Inline-Skript und die Schrift im Google-Fonts-Aufruf. Keine Komponente wurde angefasst. Co-Authored-By: Claude Opus 5 (1M context) --- docs/05-frontend.md | 2 +- docs/07-anleitung-benutzer.md | 3 +- docs/08-anleitung-entwickler.md | 4 +- docs/09-quellcode.md | 2 +- index.html | 10 +-- server/src/userFields.js | 2 +- src/index.css | 152 ++++++++++++++++++++++++++++++-- src/types.ts | 6 +- 8 files changed, 162 insertions(+), 19 deletions(-) diff --git a/docs/05-frontend.md b/docs/05-frontend.md index 7adc15a..e628c2f 100644 --- a/docs/05-frontend.md +++ b/docs/05-frontend.md @@ -138,7 +138,7 @@ Die Darstellung hat **zwei unabhängige Achsen**, beide als Klasse am ``: | Achse | Klasse | Werte | | --- | --- | --- | -| Farbwelt | `theme-perinco`, `theme-pixel`, `theme-outdoor` | perinco ist der Standard | +| Farbwelt | `theme-perinco`, `theme-pixel`, `theme-outdoor`, `theme-cyberpunk` | perinco ist der Standard | | Modus | `dark` bzw. keine | hell, dunkel, oder der Systemeinstellung folgen | `src/index.css` tauscht darin nur Variablenwerte: `canvas`, `surface`, `ink-*`, diff --git a/docs/07-anleitung-benutzer.md b/docs/07-anleitung-benutzer.md index d41fc22..f66ac8a 100644 --- a/docs/07-anleitung-benutzer.md +++ b/docs/07-anleitung-benutzer.md @@ -66,7 +66,7 @@ die Scanner-Einstellungen. Alles andere ist für beide Rollen gleich. zusammen - hilfreich auf kleinen Bildschirmen. * Rechts oben stehen zwei Schalter. Der rechte wechselt zwischen **hell und dunkel**; ein **Doppelklick** darauf folgt wieder der Systemeinstellung. -* Links daneben steht das **Design**. Es gibt drei zur Wahl, jedes in hell und +* Links daneben steht das **Design**. Es gibt vier zur Wahl, jedes in hell und dunkel: | Design | Aussehen | @@ -74,6 +74,7 @@ die Scanner-Einstellungen. Alles andere ist für beide Rollen gleich. | **perinco** | Das Design des Hauses: Blattgrün auf neutralem Grau | | **Pixel Art** | 8-bit: eckige Kanten, harte Schatten, Pixelschrift in den Überschriften | | **Outdoor** | Wanderkarte: Sandpapier, Bergsee-Türkis, weiche Ecken | + | **Cyberpunk** | Neon in der Nacht: scharfe Kanten, violettes Leuchten, Blaustahl | > **Die Wahl gehört dir, nicht dem Rechner.** Sie wird an deinem Benutzerkonto > gespeichert. Meldest du dich an einem anderen Rechner an, ist dein Design diff --git a/docs/08-anleitung-entwickler.md b/docs/08-anleitung-entwickler.md index 8b825fa..11be383 100644 --- a/docs/08-anleitung-entwickler.md +++ b/docs/08-anleitung-entwickler.md @@ -222,8 +222,8 @@ Browser-Download. * **Kommentare erklären das Warum.** Was der Code tut, steht im Code. * **Kein `dark:` und keine Theme-Klasse im Markup.** Nur die Skalen `ink-*`, `brand-*`, `canvas`, `surface` benutzen, den Rest macht `index.css`. Das ist - nicht Kosmetik: daran hängen drei Farbwelten in je zwei Modi, und eine feste - Farbe im Markup bricht fünf davon. Die Rollen der Skalen und die Regeln für ein + nicht Kosmetik: daran hängen vier Farbwelten in je zwei Modi, und eine feste + Farbe im Markup bricht sieben davon. Die Rollen der Skalen und die Regeln für ein neues Theme stehen in [05 Frontend](05-frontend.md#themes-ohne-dark-varianten) - wichtigste davon: auf `brand-500`/`brand-600` steht weisse Schrift. * **Gemeinsame Klassen** statt kopierter Utility-Ketten: `inputClass`, diff --git a/docs/09-quellcode.md b/docs/09-quellcode.md index c3cf22a..a8b2d82 100644 --- a/docs/09-quellcode.md +++ b/docs/09-quellcode.md @@ -247,7 +247,7 @@ Uhrzeit, dieses Jahr Tag und Monat, sonst mit Jahr. | Datei | Hält | Eigenheit | | --- | --- | --- | | `auth.tsx` | Benutzer, `isAdmin`, `login`, `logout`, `updateUser` | prüft beim Start das gespeicherte Token gegen `/auth/me` | -| `theme.tsx` | Farbwelt (perinco/Pixel Art/Outdoor) und Modus (hell/dunkel/System) | Beides gehört dem Benutzer und steht in `crm_users`; `localStorage` ist nur der Zwischenspeicher fürs Inline-Skript in `index.html`. Doppelklick auf den Modus-Schalter folgt wieder dem System | +| `theme.tsx` | Farbwelt (perinco/Pixel Art/Outdoor/Cyberpunk) und Modus (hell/dunkel/System) | Beides gehört dem Benutzer und steht in `crm_users`; `localStorage` ist nur der Zwischenspeicher fürs Inline-Skript in `index.html`. Doppelklick auf den Modus-Schalter folgt wieder dem System | | `navigation.tsx` | `currentView`, `goTo(view, { tab, leadId })` | erspart es, Callbacks durch vier Ebenen zu reichen; `leadId` öffnet den Kontakt direkt | | `whisper.tsx` | Modellstatus, Worker, Fortschritt, `transcribe` | ein Worker für die ganze App | diff --git a/index.html b/index.html index e8f75bc..e06cf29 100644 --- a/index.html +++ b/index.html @@ -26,7 +26,7 @@ var palette = localStorage.getItem('perinco_crm_palette'); // Whitelist, damit ein manipulierter Eintrag keine Klasse setzt. - if (palette === 'pixel' || palette === 'outdoor') { + if (palette === 'pixel' || palette === 'outdoor' || palette === 'cyberpunk') { document.documentElement.classList.add('theme-' + palette); } } catch (e) { @@ -38,12 +38,12 @@ diff --git a/server/src/userFields.js b/server/src/userFields.js index 6e36051..c088946 100644 --- a/server/src/userFields.js +++ b/server/src/userFields.js @@ -48,7 +48,7 @@ export function normalizeAvatar(value) { * Standard gesetzt: sonst faellt ein Tippfehler im Frontend nie auf, die * Oberflaeche zeigt weiter das alte Design und niemand weiss, warum. */ -export const THEMES = ['perinco', 'pixel', 'outdoor']; +export const THEMES = ['perinco', 'pixel', 'outdoor', 'cyberpunk']; export const THEME_MODES = ['light', 'dark']; /** Farbwelt; null bzw. '' bedeutet "perinco", der Standard. */ diff --git a/src/index.css b/src/index.css index 44892eb..3752576 100644 --- a/src/index.css +++ b/src/index.css @@ -10,9 +10,9 @@ */ /* - * Drei Farbwelten, jede in hell und dunkel. Sie haengen an einer Klasse am - * (theme-pixel, theme-outdoor); ohne Klasse gilt perinco. Der Modus - * bleibt die vorhandene Klasse "dark". + * Vier Farbwelten, jede in hell und dunkel. Sie haengen an einer Klasse am + * (theme-pixel, theme-outdoor, theme-cyberpunk); ohne Klasse gilt + * perinco. Der Modus bleibt die vorhandene Klasse "dark". * * Warum das ohne Aenderung an den Komponenten geht: im Markup steht nirgends * eine feste Farbe, sondern durchgaengig ink-*, brand-*, canvas und surface - @@ -36,10 +36,10 @@ --font-sans: 'Avenir Next', 'Avenir', 'Nunito Sans', ui-sans-serif, system-ui, sans-serif; /* - * Zweite Schrift fuer Ueberschriften und Feldbeschriftungen. Bei perinco und - * Outdoor ist sie mit der Grundschrift identisch, nur "Pixel Art" setzt hier - * eine Pixelschrift ein - ohne dass ein Theme dafuer eigene Selektoren - * braucht. + * Zweite Schrift fuer Ueberschriften und Feldbeschriftungen. Meist ist sie mit + * der Grundschrift identisch; nur "Pixel Art" setzt hier eine andere ein - die + * Pixelschrift bleibt damit aus dem Fliesstext heraus, ohne dass es dafuer + * eigene Selektoren braucht. */ --font-display: 'Avenir Next', 'Avenir', 'Nunito Sans', ui-sans-serif, system-ui, sans-serif; @@ -358,6 +358,127 @@ html.theme-pixel.dark { --font-display: 'Silkscreen', 'Courier New', ui-monospace, monospace; } +/* =========================================================================== + * Cyberpunk + * + * Neon in der Nacht: fast schwarze Flaechen mit blauem Stich, violettes + * Leuchten, Kanten so scharf, dass die Karten wie HUD-Elemente wirken (2 px + * statt 12 px Radius - nicht ganz eckig, das ist schon Pixel Art). + * + * Der Akzent ist bewusst Violett und nicht Neon-Cyan: auf brand-500 und + * brand-600 steht 69 Mal weisse Schrift, und auf Cyan waere die nicht zu lesen. + * Cyan sitzt deshalb nur in --color-brand-accent und in den Statusfarben, wo + * dunkler Text drauf steht. + * + * Schatten sind hier kein Schatten, sondern ein Schein: eine farbige Kontur + * plus weicher Halo in der Markenfarbe. + * =========================================================================== */ +html.theme-cyberpunk.dark { + --color-canvas: #07080f; + --color-surface: #0f1120; + + --color-brand-50: #1a0e2e; + --color-brand-100: #24123f; + --color-brand-200: #4a1f7a; + --color-brand-300: #5f27a0; + --color-brand-400: #7a30cc; + --color-brand-500: #8b31f0; + --color-brand-600: #a24cff; + --color-brand-700: #c79bff; + --color-brand-800: #ddc4ff; + --color-brand-900: #efe3ff; + --color-brand-accent: #00e5ff; + + /* Blaustahl statt Neutralgrau - Grau waere hier zu brav. */ + --color-ink-50: #141728; + --color-ink-100: #1c2135; + --color-ink-200: #2b4560; + --color-ink-300: #6b7899; + --color-ink-400: #8e9bbd; + --color-ink-500: #a9b5d4; + --color-ink-600: #c1cbe4; + --color-ink-700: #d6ddf0; + --color-ink-800: #e9eefa; + --color-ink-900: #f7f9ff; + --color-ink-950: #000000; + + --radius-sm: 0.125rem; + --radius-md: 0.125rem; + --radius-lg: 0.1875rem; + --radius-xl: 0.25rem; + --radius-2xl: 0.375rem; + + --shadow-card: 0 0 0 1px rgb(139 49 240 / 0.16), 0 0 22px rgb(139 49 240 / 0.2); + --shadow-lift: 0 0 0 1px rgb(139 49 240 / 0.3), 0 0 40px rgb(139 49 240 / 0.35); + + --font-sans: 'Chakra Petch', 'Avenir Next', 'Nunito Sans', ui-sans-serif, system-ui, sans-serif; + --font-display: 'Chakra Petch', 'Avenir Next', 'Nunito Sans', ui-sans-serif, system-ui, sans-serif; + + /* Die getoenten Pillen aus .dark ins Neon gezogen. */ + --color-sky-50: #04202e; + --color-sky-200: #0b4a63; + --color-sky-700: #22d3ee; + + --color-amber-50: #2b1f04; + --color-amber-200: #57400a; + --color-amber-700: #ffcc4d; + + --color-violet-50: #1d1140; + --color-violet-200: #382068; + --color-violet-700: #d0a2ff; + + --color-red-50: #2c0e1c; + --color-red-200: #5c1c34; + --color-red-700: #ff6b8a; +} + +/* + * Cyberpunk, hell: Konzern bei Tageslicht. Kuehles Blaugrau statt warmem + * Weiss, dasselbe Violett - nur dunkler, damit weisse Schrift darauf haelt. + * Die scharfen Kanten bleiben, das Leuchten wird zu einem knappen kalten + * Schatten: ein Halo auf hellem Grund sieht nach Fehler aus, nicht nach Neon. + */ +html.theme-cyberpunk:not(.dark) { + --color-canvas: #eceff7; + --color-surface: #ffffff; + + --color-brand-50: #f3ecff; + --color-brand-100: #e6d9ff; + --color-brand-200: #c9adff; + --color-brand-300: #a97bff; + --color-brand-400: #8d4df0; + --color-brand-500: #7326d6; + --color-brand-600: #5d1cb0; + --color-brand-700: #4a1590; + --color-brand-800: #380f6e; + --color-brand-900: #290a52; + --color-brand-accent: #7326d6; + + --color-ink-50: #f2f4fb; + --color-ink-100: #e4e8f4; + --color-ink-200: #c3cbe0; + --color-ink-300: #98a2bd; + --color-ink-400: #5d6786; + --color-ink-500: #4a5473; + --color-ink-600: #3a4260; + --color-ink-700: #2c334d; + --color-ink-800: #1e2438; + --color-ink-900: #121728; + --color-ink-950: #0a0d18; + + --radius-sm: 0.125rem; + --radius-md: 0.125rem; + --radius-lg: 0.1875rem; + --radius-xl: 0.25rem; + --radius-2xl: 0.375rem; + + --shadow-card: 0 1px 2px rgb(18 23 40 / 0.06), 0 6px 18px rgb(115 38 214 / 0.1); + --shadow-lift: 0 12px 30px rgb(115 38 214 / 0.18); + + --font-sans: 'Chakra Petch', 'Avenir Next', 'Nunito Sans', ui-sans-serif, system-ui, sans-serif; + --font-display: 'Chakra Petch', 'Avenir Next', 'Nunito Sans', ui-sans-serif, system-ui, sans-serif; +} + @layer base { html, body { @@ -414,6 +535,23 @@ html.theme-pixel.dark { font-smooth: never; } + /* + * Cyberpunk laesst die Ueberschriften atmen: die negative Laufweite zieht + * Chakra Petch unangenehm zusammen, gesperrt liest es sich nach Anzeigetafel. + */ + html.theme-cyberpunk h1, + html.theme-cyberpunk h2, + html.theme-cyberpunk h3, + html.theme-cyberpunk h4, + html.theme-cyberpunk h5, + html.theme-cyberpunk h6 { + letter-spacing: 0.015em; + } + + html.theme-cyberpunk .field-label { + letter-spacing: 0.12em; + } + /* Fokus-Ring durchgaengig in CI-Gruen */ :focus-visible { outline: 2px solid var(--color-brand-500); diff --git a/src/types.ts b/src/types.ts index d2a0b6f..04ff008 100644 --- a/src/types.ts +++ b/src/types.ts @@ -88,7 +88,7 @@ export type Role = 'admin' | 'user'; * Tippfehler hier nicht still zum Standarddesign fuehrt. * server/test/userFields.test.mjs prueft, dass beide Listen gleich bleiben. */ -export const THEME_PALETTES = ['perinco', 'pixel', 'outdoor'] as const; +export const THEME_PALETTES = ['perinco', 'pixel', 'outdoor', 'cyberpunk'] as const; export type ThemePalette = (typeof THEME_PALETTES)[number]; @@ -97,6 +97,10 @@ export const THEME_PALETTE_LABELS: Record