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