diff --git a/docs/BRANDING-CERTVIA.md b/docs/BRANDING-CERTVIA.md deleted file mode 100644 index c02fb00..0000000 --- a/docs/BRANDING-CERTVIA.md +++ /dev/null @@ -1,149 +0,0 @@ -# Branding-Umstellung auf Certvia — Inventar & Umsetzung - -> Branch `dev-branding-certvia` (Basis `dev`) · Quelle: `Aufgabenpaket-Branding-Certvia.md` + `Certvia-Assets/` -> Leitplanke: **GEFIM bleibt Dachmarke** („Ein Produkt von GEFIM"), nur die **Produktmarke** wird Certvia. -> Reines Branding — **keine Funktionsänderung**, kein Umbau von Logik oder Datenmodell. - ---- - -## 1. Design-Tokens (verbindliche Referenz) - -**Markenfarben** (Logo, Print, Verläufe — `--brand-*`): - -| Rolle | Token | Hex | -|---|---|---| -| Violett (Marke) | `--brand-violet` | `#5d52a3` | -| Magenta (Akzent „via", Haken) | `--brand-magenta` | `#812d80` | -| Hellblau | `--brand-blue` | `#8dc4e0` | -| Anthrazit (Text/Headlines) | `--brand-anthracite` | `#3b3b3a` | -| Violett auf Dunkel | `--brand-violet-on-dark` | `#7d6fd6` | -| Magenta auf Dunkel | `--brand-magenta-on-dark` | `#d17bcf` | - -**UI Dark-Theme** (unverändert — **ist** die Certvia-Produktpalette, nicht „korrigieren"): -`--ui-primary #7d6fd6` · `--ui-primary-deep #5d52a3` · `--ui-blue #8dc4e0` · `--ui-magenta #b45bb0` · -`--bg-0 #0e1220` · `--bg-1 #141a2e` · `--elevated #1a2138` · `--panel rgba(30,38,64,.72)` · -`--panel-brd rgba(120,135,180,.18)` · `--foreground #e8ecf7` · `--muted-foreground #8b93ad` · -`--ok #39c07f` · `--warn #f0ad4e` · `--risk #ff6b6b` · Primär-Verlauf `linear-gradient(135deg,#5d52a3,#7d6fd6)` -· Fokus-Ring `rgba(125,111,214,.35)`. - -Single Source of Truth: **`src/app/globals.css`** (CSS) und **`src/lib/brand.ts`** (JS/TS — für alles, -was CSS-Variablen nicht auflösen kann: Canvas-/PNG-Export, Metadaten, künftige Dokument-Exporte). - -**Typografie:** Headlines/Wortmarke **Poppins**, Fließtext **Open Sans** — self-hosted (`src/app/fonts/`). - ---- - -## 2. Inventar (Story S0) - -### 2.1 Produktmarke → Certvia - -| Datei | Fundstelle | Maßnahme | Story | -|---|---|---|---| -| `src/app/globals.css` | `--gefim-violet/-deep/-blue/-magenta`, Kommentare „GEFIM-Theme" | Umbenannt auf `--brand-*` / `--ui-*`, Kommentare auf Certvia | S1/S9 | -| `src/app/globals.css` | Flächenfarben direkt als Hex (`#0e1220`, `#141a2e`, …) | Über `--bg-0`/`--bg-1`/`--elevated` geführt, semantische Tokens referenzieren sie | S1 | -| `src/components/risk-modals.tsx` | `bg-[#0e1220]` (Rest-Risiko-Marker, Legende) | → `bg-[var(--bg-0)]` | S1 | -| `src/app/(app)/risks/page.tsx` | `bg-[#0e1220]/75`, `hover:bg-[#0e1220]` (Heatmap-Chip) | → Token `--chip-overlay` / `var(--bg-0)` | S1 | -| `src/components/dependency-graph.tsx` | `#8dc4e0`, `#ff6b6b`, `#141a2e`, `#0e1220`, `#4a5372`, `#8b93ad` | CSS-Vars wo möglich; PNG-Export nutzt `BRAND.ui` aus `src/lib/brand.ts` (html-to-image löst keine CSS-Vars auf) | S1 | -| `public/gefim-logo.png` | Produktlogo in Sidebar + Login | Verschoben nach `public/assets/logo/gefim-logo.png`, dient nur noch der **Dachmarke** | S2 | -| `src/app/(app)/layout.tsx` | Sidebar-Logo `/gefim-logo.png`, Alt-Text „GEFIM …" | `` über den Branding-Resolver | S2/S8 | -| `src/app/login/page.tsx` | Logo `/gefim-logo.png`, Alt-Text | Certvia-Lockup + Tagline + Dachmarken-Fußnote | S2/S7 | -| `src/app/platform/login/page.tsx` | kein Logo | Certvia-Lockup + „Plattform-Administration" | S2/S7 | -| `src/app/(platform)/layout.tsx` | `ShieldCheck` + „ISMS · Plattform-Administration" | Certvia-Mark + „Certvia · Plattform-Administration" | S2/S3 | -| `src/app/layout.tsx` | `title: "ISMS-Tool"`, keine Icons/OG/Manifest | Vollständige Certvia-Metadaten inkl. Icons, Manifest, `themeColor` | S3 | -| `src/app/favicon.ico` | Next-Default-Favicon | Entfernt; Icons laufen über `public/favicon/` + `metadata.icons` | S2 | -| `messages/de.json`, `messages/en.json` | `common.appName` = „ISMS-Tool"/„ISMS Tool" | → „Certvia" (+ `appTagline`, `appByline`) | S3 | -| `src/server/mfa.ts` | TOTP-Issuer `"ISMS-Tool · Plattform"` | → `"Certvia · Plattform"` | S3 | -| `src/components/ui/button.tsx` | Kommentar „GEFIM-Verlauf" | → „Certvia-Verlauf" | S9 | -| `src/components/mockup-ui.tsx` | Kommentar-Referenz auf Mockup-Datei | Formulierung entschärft (Dateiname bleibt, s. u.) | S9 | -| `src/lib/control-titles.ts` | Kommentar „aus dem GEFIM-Mockup übernommen" | → neutral „aus dem UI-Mockup" | S9 | -| `src/app/(app)/settings/page.tsx` | Hinweis „Logo-Upload folgt in Phase 2" | Ergänzt um Certvia-Default-Aussage | S8 | -| `src/proxy.ts` | Matcher schloss `.webmanifest` nicht aus | `/site.webmanifest` lieferte die Login-HTML statt JSON → Endung ergänzt (siehe §5) | S3 | - -### 2.2 Dachmarke GEFIM → bleibt bewusst erhalten - -| Ort | Grund | -|---|---| -| `public/assets/logo/gefim-logo.png` | Dachmarken-Logo für „Ein Produkt von GEFIM" | -| `src/components/brand/powered-by-gefim.tsx` | Neue Komponente, die genau diesen Hinweis rendert (Login, Plattform-Login, Print-/Export-Fußzeile, Mail-Footer) | -| `docs/ISMS-*-GEFIM.html`, Verweise in `README.md`/`AGENTS.md`/`docs/SPEC.md` | Historische Mockups/Spezifikation — Projektartefakte, keine Produkt-UI | -| `prisma/seed.ts` (`ORG_NAME` „GEFIM Demo GmbH") | **Demo-Mandantendaten** (fiktiver Kunde), nicht der Produktname | -| `prisma/import-managed.ts` (`portal.gefim.example`) | Demo-Registerzeile, nicht der Produktname | - -Nach der Umstellung liefert `rg -i "gefim"` ausschließlich diese Kategorien. - ---- - -## 3. Umgesetzte Stories - -| Story | Inhalt | Status | -|---|---|---| -| **S0** | Inventar (dieses Dokument) | ✅ | -| **S1** | Token-Zentralisierung `--brand-*` / `--ui-*`, `src/lib/brand.ts`, hartkodierte Farben ersetzt | ✅ | -| **S2** | Assets eingebunden, ``, Favicon/PWA-Icons, Sidebar + beide Logins | ✅ | -| **S3** | App-Name/Meta/OG/Manifest/i18n/MFA-Issuer auf Certvia | ✅ | -| **S4** | Poppins + Open Sans self-hosted bestätigt (waren es bereits), Fallback-Stack + `swap` geprüft | ✅ | -| **S7** | Login, Plattform-Login, Change-Password, Enroll-MFA, Konto-deaktiviert, 404, 500 gebranded | ✅ | -| **S9** | Produkt-Referenzen bereinigt, Dachmarke belassen | ✅ | -| **S5** | Branding-Layer für Dokument-/Export-CD (`src/lib/brand.ts` + Print-Stylesheet) | ✅ (Andockpunkt) | -| **S8** | Branding-Resolver mit Certvia-Default, Custom überschreibt nur wenn gesetzt | ✅ | -| **S6** | Brandfähige E-Mail-Template-Basis (SMTP selbst folgt in Paket 4) | ✅ (Basis) | - ---- - -## 4. Wo liegt was (neu) - -| Pfad | Inhalt | -|---|---| -| `src/lib/brand.ts` | Marken-/UI-Farben als JS-Konstanten, Produktname/Tagline/Byline, Asset-Pfade, `resolveTenantBranding()` | -| `src/lib/document-brand.ts` | Dokument-CD (helles Theme, Kopf-/Fußzeilen-Bausteine) — Andockpunkt für DOCX/PDF | -| `src/lib/email-brand.ts` | Brandfähige HTML-/Text-Mail-Basis für Paket 4 | -| `src/components/brand/certvia-logo.tsx` | `` als Inline-SVG | -| `src/components/brand/tenant-brand.tsx` | Mandanten-Marke mit Certvia-Fallback | -| `src/components/brand/powered-by-gefim.tsx` | Dachmarken-Hinweis inkl. GEFIM-Logo | -| `public/assets/logo/` | Certvia-SVGs/PNGs + GEFIM-Dachmarkenlogo | -| `public/favicon/`, `public/favicon.ico`, `public/site.webmanifest` | App-Icons und PWA-Manifest | -| `docs/branding/` | Aufgabenpaket + Logo-README aus dem Design-Paket | -| `src/app/globals.css` (`@media print`) | Druck-/Dokument-CD | - ---- - -## 5. Beim Einbau aufgefallen (Abweichungen vom Aufgabenpaket) - -- **Lockup-SVG trägt Poppins jetzt eingebettet.** `public/assets/logo/certvia-lockup-positiv.svg` - enthält Poppins 300 + 700 als woff2-Data-URI (≈22 KB). Damit erscheint die Wortmarke auch - außerhalb der App korrekt (Druck, Export, Mail) — der offene Punkt „Text in Pfade wandeln" aus - `docs/branding/README-Logo.md` ist damit erledigt. Geometrie und Farben sind unverändert. - In der App rendert `` das Zeichen ohnehin inline und nutzt die App-Schrift direkt. -- **`/site.webmanifest` war nicht erreichbar.** Der Route-Gate-Matcher in `src/proxy.ts` nahm nur - `svg|png|jpg|ico` aus — für `.webmanifest` lieferte der Server die Login-Seite statt JSON, das - PWA-Manifest wäre also wirkungslos geblieben. Endung ergänzt. Bewusst **keine** Verzeichnis- - Ausnahme für `assets/`: das ist zugleich die App-Route des Asset-Inventars und bleibt hinter dem - Gate (nachgeprüft: `/assets`, `/assets/xyz` → 307 auf `/login`). -- **`src/app/favicon.ico` entfernt.** Die Datei-Konvention von Next.js und eine `public/favicon.ico` - schließen sich gegenseitig aus; die Icons laufen jetzt vollständig über `metadata.icons`. -- **404-/500-Seiten neu angelegt** (`src/app/not-found.tsx`, `src/app/error.tsx`) — es gab bisher - keine. Sie erscheinen für angemeldete Nutzer; anonyme Aufrufe fängt weiterhin das Login-Gate ab. - -### Kontrast (WCAG AA, geprüft) - -| Kombination | Kontrast | Bewertung | -|---|---|---| -| Magenta `#812d80` auf Weiß (Wortmarke/Print) | 8,0 : 1 | AA + AAA | -| Magenta `#d17bcf` auf Karte `#141a2e` | 6,1 : 1 | AA | -| Magenta `#d17bcf` auf Seitengrund `#0e1220` | 6,6 : 1 | AA | - ---- - -## 6. Bewusste Abgrenzungen - -- **UI-Palette unverändert.** Die Dark-Theme-Farben wurden nur konsolidiert/umbenannt, nicht geändert. - `--brand-violet` (`#5d52a3`) gilt für Logo/Print/Verläufe, `--ui-primary` (`#7d6fd6`) für Interaktion. -- **Kein Light-Mode.** Die App ist bewusst dark-only; `` existiert für - Dokument-/Druck-/Mail-Kontexte, wird in der UI aber nicht verwendet. -- **Poppins 400** ist nicht als woff2 im Repo (vorhanden: 300/500/600/700). Aktuell nutzt nichts - Poppins 400 — Headlines laufen auf 600/700, die Wortmarke auf 300/700. Sollte 400 gebraucht werden, - muss die woff2 ergänzt werden; ein CDN-Fetch bleibt ausgeschlossen. -- **Logo-Upload je Mandant** braucht Objektspeicher (Admin Phase 2). Der Resolver ist bereits darauf - vorbereitet (`logoUrl` optional), das Feld existiert im Schema noch nicht — bewusst keine Migration. -- **DOCX/PDF-Export** existiert noch nicht. S5 liefert den Branding-Layer (Konstanten + Kopf-/Fußzeile - + Print-CSS), an den das Export-Feature andockt. diff --git a/docs/branding/Aufgabenpaket-Branding-Certvia.md b/docs/branding/Aufgabenpaket-Branding-Certvia.md deleted file mode 100644 index f46a01a..0000000 --- a/docs/branding/Aufgabenpaket-Branding-Certvia.md +++ /dev/null @@ -1,107 +0,0 @@ -# Umsetzungspaket — Vollständige Branding-Umstellung auf **Certvia** - -> Claude-Code-Prompt für **einen** Entwickler. Basis-Branch **`dev`**, Feature-Branch **`dev/branding-certvia`** (PR-Ziel `dev`). -> Quelle der Wahrheit: **Certvia Brand-Board** (`Certvia-Brand-Board-final.html`) + Logo-SVGs (`certvia-mark-positiv/negativ/grau/anthrazit.svg`, `certvia-favicon.svg`, `certvia-lockup-positiv.svg`) aus `Certvia-Design-Uebergabe.zip`. - -## 0. Ziel & Leitplanken -Die App vollständig auf den **Certvia-Styleguide** umstellen — Logo, Wortmarke, App-Name, Favicon/Icons, Typografie, Marken-/UI-Farben, Dokument-/Export-Design und alle Texte. -**Wichtige Leitplanken:** -- **GEFIM bleibt Dachmarke.** „**Ein Produkt von GEFIM**" (inkl. GEFIM-Logo) bleibt im Footer/Impressum/Export-Fußzeile erhalten. Nur die **Produktmarke** wird Certvia. -- **UI-Farben bleiben wie initial festgelegt** (Dark-Theme-Primär `#7d6fd6` etc.) — sie **sind** die Certvia-UI-Palette. Nicht „korrigieren". Marken-Violett `#5d52a3` gilt für Logo/Print/Verläufe, UI-Interaktiv bleibt `#7d6fd6`. -- **Keine Funktionsänderung** — reines Branding/Design. Kein Umbau von Logik/Datenmodell. - ---- - -## 1. Design-Tokens (verbindliche Referenz) - -**Markenfarben (Logo, Print, Verläufe, Akzent):** -| Rolle | Hex | -|---|---| -| Violett (Marke) | `#5d52a3` | -| Magenta (Akzent: „via", Haken, Highlights) | `#812d80` | -| Hellblau | `#8dc4e0` | -| Anthrazit (Text/Headlines) | `#3b3b3a` | - -**UI Dark-Theme (unverändert, = Certvia-Produktpalette):** -`--violet:#7d6fd6` · `--violet-deep:#5d52a3` · `--blue:#8dc4e0` · `--magenta:#b45bb0` · `--bg0:#0e1220` · `--bg1:#141a2e` · `--panel:rgba(30,38,64,.72)` · `--panel-b:rgba(120,135,180,.18)` · `--elev:#1a2138` · `--txt:#e8ecf7` · `--muted:#8b93ad` · `--ok:#39c07f` · `--warn:#f0ad4e` · `--risk:#ff6b6b` · Primär-Verlauf `linear-gradient(135deg,#5d52a3,#7d6fd6)` · Fokus-Ring `rgba(125,111,214,.35)`. - -**Typografie:** Headlines/Wortmarke **Poppins**, Fließtext **Open Sans** (bereits im Einsatz — bestätigen, **selbst hosten**, kein ungefragtes Google-Fonts-CDN). - -**Logo/Marke:** C-Monogramm (offenes „C" Violett `#5d52a3` + Haken Magenta `#812d80`, Haken schließt bündig an die C-Öffnung). Wortmarke „Cert" (Poppins Light, Anthrazit) + „**via**" (Poppins Bold, Magenta). Auf dunklem Grund: C `#7d6fd6`, Haken `#d17bcf`, Text weiß. - ---- - -## 2. Story B0 — Bestandsaufnahme / Branding-Inventar (zuerst!) (S) -**Als** Entwickler **möchte ich** alle Branding-Berührungspunkte finden, **damit** nichts übersehen wird. -- Repo-weit suchen und als kurze Inventarliste (Datei → Fundstelle → Maßnahme) dokumentieren: - ```bash - rg -i "gefim" -l # Produktname vs. Dachmarke unterscheiden - rg -i "shield|logo|brand|favicon|wordmark|app[_-]?name|siteName|title" src public - rg -i "#5d52a3|#7d6fd6|#812d80|#8dc4e0|#b45bb0|#0e1220" src # hartkodierte Farben außerhalb der Tokens - rg -i "poppins|open sans|fonts.googleapis" src public - ``` -- **AK:** Inventarliste liegt vor; jede Fundstelle ist einer der Stories S1–S9 zugeordnet; klar getrennt „Produktmarke → Certvia" vs. „Dachmarke → GEFIM bleibt". - ---- - -## 3. Stories - -### S1 — Design-Tokens zentralisieren & Certvia-Palette verankern (M) -- Eine **zentrale Token-Quelle** (CSS-Variablen/`theme`/Tailwind-Config) als Single Source of Truth; hartkodierte Farben aus S0 durch Tokens ersetzen. -- Marken- vs. UI-Tokens klar benennen (`--brand-violet` `#5d52a3` vs. `--ui-primary` `#7d6fd6`), damit Logo/Print und UI nicht vermischt werden. -- **AK:** keine hartkodierten Markenfarben mehr in Komponenten; Umschalten zentral möglich; UI optisch unverändert (nur konsolidiert). - -### S2 — Logo, Wortmarke & Icons (M) -- **App-Header/Sidebar**, **Login** (`/login`) und **Plattform-Login** (`/platform/login`): GEFIM-Schild → **Certvia-Logo** (Lockup C-Monogramm + „Certvia"). Dark-Variante nutzen. -- **Favicon** + **PWA/Manifest-Icons** (`public/`, `manifest.webmanifest`, `apple-touch-icon`) auf `certvia-favicon.svg` (C auf Violett-Tile) und generierte PNGs (32/180/192/512). -- `meta-theme-color` = `#5d52a3` (schon so auf der Website). -- SVG-Assets aus dem Design-Paket in `public/assets/logo/` bzw. `src/components/brand/` einbinden; **Logo als React-Komponente** ``. -- **AK:** in allen App-Bereichen erscheint das Certvia-Logo; Favicon/Tab-Icon = Certvia; keine GEFIM-Schild-Reste in Produkt-UI. - -### S3 — App-Name, Titel, Meta & i18n-Texte (S–M) -- App-Name/``/OG-/Twitter-Meta, PWA-Name, Sidebar-Kopf, „Über"/Version-Info → **Certvia**. -- i18n (`de`/`en`): alle Produktname-Strings auf „Certvia"; Tagline „Informationssicherheit. Endlich einfach." wo passend. -- **AK:** Browser-Tab, Meta, Manifest und sichtbare Produktnamen zeigen „Certvia"; „ein Produkt von GEFIM" bleibt erhalten. - -### S4 — Typografie bestätigen & selbst hosten (S) -- Poppins (300/400/500/600/700) + Open Sans (400/600/700) **self-hosted** (woff2) statt CDN; Fallback-Stack; `font-display: swap`. -- **AK:** keine externen Font-Requests; Wortmarke „Cert**via**" korrekt (Light + Bold), Headlines Poppins, Fließtext Open Sans. - -### S5 — Dokument-/Export-Corporate-Design (M–L) -- **DOCX/PDF/Print-Export** (Richtlinien, VDA-ISA-Katalog, Reports) auf **Certvia-Corporate-Design**: Deckblatt/Kopf mit Certvia-Logo + Farben, Fußzeile „Certvia — ein Produkt von GEFIM" + GEFIM-Logo, Poppins/Open Sans. -- Betrifft den (offenen) DOCX/PDF-Export — Branding-Layer so bauen, dass er beim Export-Feature andockt. -- **AK:** exportierte Dokumente tragen Certvia-Branding; GEFIM als Dachmarke in der Fußzeile. - -### S6 — E-Mail-/Benachrichtigungs-Templates (S–M, falls vorhanden) -- Sobald SMTP/E-Mail (Paket 4) existiert bzw. für Einladungs-/Reset-Mails: Header/Footer/Farben/Logo auf Certvia; Absender-/Signatur-Text „Certvia — ein Produkt von GEFIM". -- Falls E-Mail noch nicht aktiv: nur die **Template-Basis** brandfähig vorbereiten. -- **AK:** vorhandene Mail-Templates gebranded oder brandfähige Basis vorbereitet. - -### S7 — Auth-, Fehler- & Systemseiten (S) -- Login, Change-Password, deaktiviertes Konto, 404/500, Wartungs-/Status-Banner, Loading-/Empty-States: Certvia-Logo + Palette. -- **AK:** keine ungebrandeten/gemischten Screens mehr. - -### S8 — Mandanten-Branding-Default = Certvia (S–M) -- Der Whitelabel-/Branding-Fallback je Mandant (Admin Phase 2 „Logo-Upload") ist **Certvia** als Default; Custom-Logo überschreibt nur, wenn gesetzt. -- **AK:** ohne Custom-Logo zeigt jeder Mandant Certvia; „ein Produkt von GEFIM" bleibt unabhängig davon. - -### S9 — Restliche GEFIM-Referenzen bereinigen (S) -- Alle in S0 gefundenen **Produkt**-Referenzen „GEFIM" → „Certvia" (Kommentare, Alt-Texte, aria-labels, Klassennamen unkritisch). **Dachmarken-Hinweise bewusst belassen.** -- **AK:** `rg -i "gefim"` liefert nur noch Dachmarken-Kontext (Footer/Impressum/Export-Fußzeile/Assets `gefim/`). - ---- - -## 4. QA / Definition of Done -- **Visuelles Review** der Kernscreens (Dashboard, Richtlinien, Assets, Risiken, Lieferanten, Aufgaben, Admin, beide Logins, Export, Fehlerseiten) hell **und** dunkel — Screenshots im PR. -- **Kontrast prüfen:** Magenta `#812d80` auf Weiß und `#d17bcf` auf Dunkel (WCAG AA für Text/aktive Elemente). -- `rg -i "gefim"` = nur Dachmarken-Kontext; keine externen Font-/Logo-Requests; keine hartkodierten Markenfarben. -- `npx tsc --noEmit` → `npm run lint` → `npm run build` grün; Demo-Umgebung lauffähig. -- **Reines Branding-PR** — keine funktionalen Diffs. - -## 5. Reihenfolge -S0 (Inventar) → S1 (Tokens) → S2 (Logo/Icons) → S3 (Name/Meta/i18n) → S4 (Fonts) → S7 (Auth/Fehler) → S9 (Cleanup) → S5 (Export) → S8 (Mandanten-Default) → S6 (Mail, falls vorhanden). - -## 6. Assets (mitliefern an den Entwickler) -Aus `Certvia-Design-Uebergabe.zip`: `logo/certvia-mark-positiv.svg`, `-negativ.svg`, `-grau.svg`, `-anthrazit.svg`, `certvia-favicon.svg`, `certvia-lockup-positiv.svg`, `logo/README-Logo.md`, `Brand-Board.html` (Farb-/Typo-Referenz) sowie `gefim-dachmarke/` (GEFIM-Logo für den „ein Produkt von GEFIM"-Hinweis). - -> Hinweis: Falls PNG-Rastergrößen (Favicon/Apple-Touch/PWA 32/180/192/512) benötigt werden, kann ich sie aus dem SVG erzeugen und beilegen — sag kurz Bescheid. diff --git a/docs/branding/README-Logo.md b/docs/branding/README-Logo.md deleted file mode 100644 index 22f7c6b..0000000 --- a/docs/branding/README-Logo.md +++ /dev/null @@ -1,29 +0,0 @@ -# Certvia — Logo-Dateien - -Logo = **C-Monogramm + Haken** (Entwurf C) in GEFIM-Farben. Alle Marken (außer Lockup) sind quadratisch (viewBox 64×64), vektoriell und frei skalierbar. - -## Dateien -| Datei | Verwendung | -|---|---| -| `certvia-mark-positiv.svg` | Standard auf hellem Grund (C Violett, Haken Magenta) | -| `certvia-mark-negativ.svg` | Auf farbigem/dunklem Grund (weiß) | -| `certvia-mark-grau.svg` | Graustufen | -| `certvia-mark-anthrazit.svg` | Einfarbig Anthrazit (Dokumente/Druck s/w) | -| `certvia-favicon.svg` | App-Icon/Favicon (Monogramm auf violettem Tile) | -| `certvia-lockup-positiv.svg` | Wortmarke horizontal (Zeichen + „Certvia") | - -## Farben -- **Violett (C):** `#5d52a3` -- **Magenta (Haken & „via"):** `#812d80` -- **Anthrazit (Wortmarke „Cert"):** `#3b3b3a` -- Auf dunklem Grund/UI: C `#7d6fd6`, Haken `#d17bcf`. - -## Konstruktion / Regeln -- „C" ist **offen nach rechts**; der **Haken** liegt in der Öffnung („cert"). -- **Kein Verlauf** im Zeichen. „C" nicht schließen, nicht verzerren, Seitenverhältnis wahren. -- **Schutzraum:** mind. Höhe des Haken-Elements rundum frei. -- Wortmarke „Cert" (Poppins Light/300, Anthrazit) + „**via**" (Poppins Bold/700, Magenta). -- Hinweis: SVG-Lockup nutzt Poppins per `font-family` — beim finalen Export ggf. **Text in Pfade wandeln**, damit die Schrift überall korrekt erscheint. - -## Schrift -- **Poppins** (Wortmarke/Headlines), **Open Sans** (Fließtext). diff --git a/docs/craftvia/BRANDING.md b/docs/craftvia/BRANDING.md new file mode 100644 index 0000000..fbe9fc2 --- /dev/null +++ b/docs/craftvia/BRANDING.md @@ -0,0 +1,68 @@ +# Craftvia — Branding in der Anwendung + +Maßgeblich ist das Brandbook: [BRANDBOOK.md](BRANDBOOK.md) (§11.4 Farben, §11.5 Typografie). +Referenzbild: [assets/craftvia-brand-identity-v1.png](assets/craftvia-brand-identity-v1.png). +Dieses Dokument beschreibt, **wo** die Marke im Code verankert ist. + +## Quellen der Wahrheit + +| Was | Datei | +|---|---| +| CSS-Tokens (Browser) | `src/app/globals.css` — `--brand-*` (Kernfarben), `--ui-*` (UI-Rollen), shadcn-Tokens abgeleitet | +| JS-Konstanten (Metadaten, Mail, Dokumente) | `src/lib/brand.ts` — `BRAND`, `BRAND_COLORS`, `STATUS_COLORS`, `BRAND_FONTS` | +| Logo (Inline-SVG) | `src/components/brand/craftvia-logo.tsx` | +| Mandanten-Logo-Override | `src/components/brand/tenant-brand.tsx` + `resolveTenantBranding()` | +| E-Mail-Layout | `src/lib/email-brand.ts` | +| Dokument-/Druck-CD | `src/lib/document-brand.ts`, `@media print` in `globals.css` | +| Favicon/PWA-Icons | `scripts/generate-brand-icons.ts` → `public/favicon/*`, `public/favicon.ico` | +| Manifest | `public/site.webmanifest` (`theme_color` #082E5B) | + +## Farben + +| Rolle | Name | Hex | Token | +|---|---|---|---| +| Primär | Lotsenblau | `#082E5B` | `--brand-lotsenblau` → `--primary` | +| Akzent/CTA | Signalorange | `#FF6A00` | `--brand-signalorange` → `--ui-accent` (`bg-cta`) | +| Text | Graphit | `#34383D` | `--foreground` | +| Fläche | Hafengrau | `#F3F5F7` | `--background` | +| Sekundärtext | Stahlgrau | `#66727D` | `--muted-foreground` | +| Linien | Zink | `#D9E0E5` | `--border`, `--input` | +| Erfolg / Warnung / Fehler / Info | | `#23845D` / `#C87912` / `#C64242` / `#2F6FA3` | `--ok` / `--warn` / `--risk` / `--info` | + +Regeln: Signalorange sparsam (eine Hauptaktion je Ansicht, aktive Elemente). Farbe nie +als einziges Statussignal — immer mit Text oder Icon. Keine Verläufe, keine Schlagschatten +(`--shadow-card` ist eine feine Kontur); die Utility-Namen `bg-grad*` zeigen Vollfarbe. + +## Logo + +`<CraftviaLogo />` — Props: + +- `variant`: `"horizontal"` (Signet + Wortmarke, Default) | `"signet"` +- `tone`: `"color"` (Default) | `"mono"` (einfarbig Lotsenblau) | `"reversed"` (weiß, für dunkle Flächen) +- `tile` (nur Signet): App-Icon-Kachel in Lotsenblau mit weißem C und orangem Haken +- `tagline`: „Handwerk. Digital auf Kurs." unter der Wortmarke +- `height`: Renderhöhe in px + +Konstruktion (viewBox 64×64, exportiert als `SIGNET`): kräftiges C (Kreisbogen r=19, +Strichstärke 11) mit Öffnung rechts oben; oranger Haken startet links außerhalb, durchquert +das C (dort per Maske freigestellt) und endet als Pfeil nach rechts oben. Wortmarke +„Craft" Lotsenblau + „via" Signalorange, Gewicht 800. + +Nach Änderungen an der Geometrie Icons neu erzeugen: + +```bash +npx tsx scripts/generate-brand-icons.ts +``` + +## Typografie + +Inter (Brandbook §11.5), selbst gehostet über `next/font/local` +(`src/app/fonts/inter-variable.ttf`, SIL Open Font License) — kein Laden aus dem Netz. +TODO: auf woff2-Subsets umstellen (Dateigröße). + +## Produktidentität + +- Name: **Craftvia**, Tagline: **Handwerk. Digital auf Kurs.** +- In-Product-Assistent: **Lotse** (eigene Figur, getrennt vom Logo; Modul `lotse`) +- TOTP-Issuer: `Craftvia · Plattform` (`src/server/mfa.ts`), WebAuthn-RP-Name: `Craftvia` +- Mail-Absender-Default: `MAIL_FROM_NAME` → `Craftvia` diff --git a/public/assets/logo/certvia-favicon.svg b/public/assets/logo/certvia-favicon.svg deleted file mode 100644 index 69cff9d..0000000 --- a/public/assets/logo/certvia-favicon.svg +++ /dev/null @@ -1,5 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="Certvia"> - <rect x="0" y="0" width="64" height="64" rx="14" fill="#5d52a3"/> - <path d="M46 20 A17 17 0 1 0 46 44" fill="none" stroke="#ffffff" stroke-width="6" stroke-linecap="round"/> - <path d="M27 32 l6 6 l12 -15" fill="none" stroke="#f2a7ef" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/> -</svg> diff --git a/public/assets/logo/certvia-lockup-positiv-1024.png b/public/assets/logo/certvia-lockup-positiv-1024.png deleted file mode 100644 index b0e613f..0000000 Binary files a/public/assets/logo/certvia-lockup-positiv-1024.png and /dev/null differ diff --git a/public/assets/logo/certvia-lockup-positiv.svg b/public/assets/logo/certvia-lockup-positiv.svg deleted file mode 100644 index 6348447..0000000 --- a/public/assets/logo/certvia-lockup-positiv.svg +++ /dev/null @@ -1,34 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 380 96" width="380" height="96" role="img" aria-label="Certvia"> - <title>Certvia - - - - - - - - - - Certvia - - diff --git a/public/assets/logo/certvia-mark-anthrazit.svg b/public/assets/logo/certvia-mark-anthrazit.svg deleted file mode 100644 index 0b644d0..0000000 --- a/public/assets/logo/certvia-mark-anthrazit.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/public/assets/logo/certvia-mark-grau.svg b/public/assets/logo/certvia-mark-grau.svg deleted file mode 100644 index d172546..0000000 --- a/public/assets/logo/certvia-mark-grau.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/public/assets/logo/certvia-mark-negativ-512.png b/public/assets/logo/certvia-mark-negativ-512.png deleted file mode 100644 index abd05be..0000000 Binary files a/public/assets/logo/certvia-mark-negativ-512.png and /dev/null differ diff --git a/public/assets/logo/certvia-mark-negativ.svg b/public/assets/logo/certvia-mark-negativ.svg deleted file mode 100644 index 5eaa8a9..0000000 --- a/public/assets/logo/certvia-mark-negativ.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/public/assets/logo/certvia-mark-positiv-512.png b/public/assets/logo/certvia-mark-positiv-512.png deleted file mode 100644 index 7ae2f5c..0000000 Binary files a/public/assets/logo/certvia-mark-positiv-512.png and /dev/null differ diff --git a/public/assets/logo/certvia-mark-positiv.svg b/public/assets/logo/certvia-mark-positiv.svg deleted file mode 100644 index c0656bb..0000000 --- a/public/assets/logo/certvia-mark-positiv.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/public/assets/logo/gefim-logo.png b/public/assets/logo/gefim-logo.png deleted file mode 100644 index 92b0e96..0000000 Binary files a/public/assets/logo/gefim-logo.png and /dev/null differ diff --git a/public/favicon.ico b/public/favicon.ico index e5be691..7a5a3e5 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/favicon/apple-touch-icon-180.png b/public/favicon/apple-touch-icon-180.png index 00ac4f9..37f1195 100644 Binary files a/public/favicon/apple-touch-icon-180.png and b/public/favicon/apple-touch-icon-180.png differ diff --git a/public/favicon/craftvia-app-icon.svg b/public/favicon/craftvia-app-icon.svg new file mode 100644 index 0000000..c3f98f5 --- /dev/null +++ b/public/favicon/craftvia-app-icon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/favicon/craftvia-signet.svg b/public/favicon/craftvia-signet.svg new file mode 100644 index 0000000..e129fdd --- /dev/null +++ b/public/favicon/craftvia-signet.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/favicon/icon-16.png b/public/favicon/icon-16.png index ffadec2..f30b438 100644 Binary files a/public/favicon/icon-16.png and b/public/favicon/icon-16.png differ diff --git a/public/favicon/icon-32.png b/public/favicon/icon-32.png index 266529b..b378252 100644 Binary files a/public/favicon/icon-32.png and b/public/favicon/icon-32.png differ diff --git a/public/favicon/icon-48.png b/public/favicon/icon-48.png index b91ba0c..91693a3 100644 Binary files a/public/favicon/icon-48.png and b/public/favicon/icon-48.png differ diff --git a/public/favicon/icon-512.png b/public/favicon/icon-512.png deleted file mode 100644 index bc28282..0000000 Binary files a/public/favicon/icon-512.png and /dev/null differ diff --git a/public/favicon/icon-64.png b/public/favicon/icon-64.png deleted file mode 100644 index 811aa74..0000000 Binary files a/public/favicon/icon-64.png and /dev/null differ diff --git a/public/favicon/maskable-512.png b/public/favicon/maskable-512.png index f2e8ebd..3048b12 100644 Binary files a/public/favicon/maskable-512.png and b/public/favicon/maskable-512.png differ diff --git a/public/favicon/pwa-192.png b/public/favicon/pwa-192.png index 25438d5..eb2ea06 100644 Binary files a/public/favicon/pwa-192.png and b/public/favicon/pwa-192.png differ diff --git a/public/favicon/pwa-512.png b/public/favicon/pwa-512.png index bc28282..c3d29d2 100644 Binary files a/public/favicon/pwa-512.png and b/public/favicon/pwa-512.png differ diff --git a/public/site.webmanifest b/public/site.webmanifest index d18c52e..cfe1191 100644 --- a/public/site.webmanifest +++ b/public/site.webmanifest @@ -1,11 +1,15 @@ { - "name": "Certvia", - "short_name": "Certvia", - "description": "Informationssicherheit. Endlich einfach.", - "theme_color": "#5d52a3", - "background_color": "#0e1220", + "name": "Craftvia", + "short_name": "Craftvia", + "description": "Handwerk. Digital auf Kurs.", + "lang": "de", + "start_url": "/dashboard", + "scope": "/", + "theme_color": "#082E5B", + "background_color": "#F3F5F7", "display": "standalone", "icons": [ + { "src": "/favicon/craftvia-app-icon.svg", "sizes": "any", "type": "image/svg+xml" }, { "src": "/favicon/pwa-192.png", "sizes": "192x192", "type": "image/png" }, { "src": "/favicon/pwa-512.png", "sizes": "512x512", "type": "image/png" }, { "src": "/favicon/maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" } diff --git a/scripts/generate-brand-icons.ts b/scripts/generate-brand-icons.ts new file mode 100644 index 0000000..fa433e1 --- /dev/null +++ b/scripts/generate-brand-icons.ts @@ -0,0 +1,93 @@ +/** + * Erzeugt Favicon/PWA-Icons aus dem Craftvia-Signet (src/components/brand/craftvia-logo.tsx, + * Geometrie `SIGNET`). Keine Netzwerkzugriffe; PNGs via `sharp` (liegt über Next.js bei). + * + * Lauf: npx tsx scripts/generate-brand-icons.ts + * Ausgabe: public/favicon/*.svg|png, public/favicon.ico (PNG-in-ICO, 32×32) + */ +import { mkdirSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import sharp from "sharp"; +import { BRAND_COLORS } from "../src/lib/brand"; +import { SIGNET } from "../src/components/brand/craftvia-logo"; + +const OUT = join(process.cwd(), "public", "favicon"); +mkdirSync(OUT, { recursive: true }); + +function signetSvg(opts: { tile: boolean; padding?: number; radius?: number }): string { + const arc = opts.tile ? BRAND_COLORS.white : BRAND_COLORS.lotsenblau; + const check = BRAND_COLORS.signalorange; + const pad = opts.padding ?? 9.6; + const scale = (64 - 2 * pad) / 64; + const bg = opts.tile + ? `` + : ""; + return ` + + + + + + + ${bg} + + + + + + +`; +} + +async function png(svg: string, size: number, file: string) { + await sharp(Buffer.from(svg), { density: 72 * (size / 64) * 2 }) + .resize(size, size) + .png() + .toFile(join(OUT, file)); +} + +/** Minimaler ICO-Container mit einem eingebetteten PNG (von allen Browsern unterstützt). */ +function icoFromPng(pngBuf: Buffer, size: number): Buffer { + const header = Buffer.alloc(6); + header.writeUInt16LE(0, 0); + header.writeUInt16LE(1, 2); + header.writeUInt16LE(1, 4); + const entry = Buffer.alloc(16); + entry.writeUInt8(size >= 256 ? 0 : size, 0); + entry.writeUInt8(size >= 256 ? 0 : size, 1); + entry.writeUInt8(0, 2); + entry.writeUInt8(0, 3); + entry.writeUInt16LE(1, 4); + entry.writeUInt16LE(32, 6); + entry.writeUInt32LE(pngBuf.length, 8); + entry.writeUInt32LE(6 + 16, 12); + return Buffer.concat([header, entry, pngBuf]); +} + +async function main() { + // Favicon: freistehendes Signet (transparent) als SVG — skaliert verlustfrei. + const plain = signetSvg({ tile: false, padding: 2 }); + writeFileSync(join(OUT, "craftvia-signet.svg"), plain); + // App-Icon-Kachel (Lotsenblau) für PWA/Apple/OG. + const tile = signetSvg({ tile: true }); + writeFileSync(join(OUT, "craftvia-app-icon.svg"), tile); + // Maskable: volle Fläche ohne Rundung, Signet in der Safe-Zone (80 %). + const maskable = signetSvg({ tile: true, padding: 14, radius: 0 }); + + await png(plain, 16, "icon-16.png"); + await png(plain, 32, "icon-32.png"); + await png(plain, 48, "icon-48.png"); + await png(tile, 180, "apple-touch-icon-180.png"); + await png(tile, 192, "pwa-192.png"); + await png(tile, 512, "pwa-512.png"); + await png(maskable, 512, "maskable-512.png"); + + const ico32 = await sharp(Buffer.from(plain), { density: 144 }).resize(32, 32).png().toBuffer(); + writeFileSync(join(process.cwd(), "public", "favicon.ico"), icoFromPng(ico32, 32)); + console.log("✓ Craftvia-Icons erzeugt in public/favicon (+ public/favicon.ico)"); +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/src/app/change-password/page.tsx b/src/app/change-password/page.tsx index b5317dc..b5a34be 100644 --- a/src/app/change-password/page.tsx +++ b/src/app/change-password/page.tsx @@ -3,7 +3,7 @@ import { auth } from "@/server/auth"; import { dbForTenant, prisma } from "@/server/db"; import { resolvePasswordPolicy, describePasswordPolicy } from "@/lib/password-policy"; import { ForcePasswordChangeForm } from "@/components/force-password-change-form"; -import { CertviaLogo } from "@/components/brand/certvia-logo"; +import { CraftviaLogo } from "@/components/brand/craftvia-logo"; /** * Erzwungener Passwortwechsel beim ersten Login (Force-Change). Liegt außerhalb der @@ -29,7 +29,7 @@ export default async function ChangePasswordPage() { return (
- +

Passwort ändern

Ihr Konto wurde mit einem Initialpasswort angelegt. Bitte vergeben Sie jetzt ein eigenes Passwort, um fortzufahren. diff --git a/src/app/enroll-mfa/page.tsx b/src/app/enroll-mfa/page.tsx index 96a89b0..cdadee5 100644 --- a/src/app/enroll-mfa/page.tsx +++ b/src/app/enroll-mfa/page.tsx @@ -7,7 +7,7 @@ import { newTotpSecret, totpUri } from "@/server/mfa"; import { encryptSecret, decryptSecret } from "@/server/secret-crypto"; import { TenantMfaEnrollForm } from "@/components/tenant-mfa-enroll-form"; import { PasskeyManager } from "@/components/passkey-manager"; -import { CertviaLogo } from "@/components/brand/certvia-logo"; +import { CraftviaLogo } from "@/components/brand/craftvia-logo"; /** * SEC3-a: Pflicht-MFA-Einrichtung für Mandanten-Nutzer, wenn der Mandant MFA verlangt @@ -37,7 +37,7 @@ export default async function EnrollMfaPage() { return (

- +

Zwei-Faktor-Authentifizierung einrichten

Ihre Organisation verlangt eine Zwei-Faktor-Authentifizierung. Scannen Sie den QR-Code mit diff --git a/src/app/error.tsx b/src/app/error.tsx index 71b4437..0e2fcc7 100644 --- a/src/app/error.tsx +++ b/src/app/error.tsx @@ -1,8 +1,7 @@ "use client"; import { Button } from "@/components/ui/button"; -import { CertviaLogo } from "@/components/brand/certvia-logo"; -import { PoweredByGefim } from "@/components/brand/powered-by-gefim"; +import { CraftviaLogo } from "@/components/brand/craftvia-logo"; /** * Gebrandete Fehlerseite (Story S7). Zeigt bewusst keine technischen Details — @@ -19,7 +18,7 @@ export default function Error({ return (

- +

Es ist ein Fehler aufgetreten

Die Aktion konnte nicht abgeschlossen werden. Bitte versuchen Sie es erneut oder wenden @@ -31,7 +30,6 @@ export default function Error({ -

); diff --git a/src/app/fonts/inter-variable.ttf b/src/app/fonts/inter-variable.ttf new file mode 100644 index 0000000..ec3164e Binary files /dev/null and b/src/app/fonts/inter-variable.ttf differ diff --git a/src/app/fonts/open-sans-300.woff2 b/src/app/fonts/open-sans-300.woff2 deleted file mode 100644 index d6c8549..0000000 Binary files a/src/app/fonts/open-sans-300.woff2 and /dev/null differ diff --git a/src/app/fonts/open-sans-400.woff2 b/src/app/fonts/open-sans-400.woff2 deleted file mode 100644 index e2d3fa4..0000000 Binary files a/src/app/fonts/open-sans-400.woff2 and /dev/null differ diff --git a/src/app/fonts/open-sans-600.woff2 b/src/app/fonts/open-sans-600.woff2 deleted file mode 100644 index f48dda7..0000000 Binary files a/src/app/fonts/open-sans-600.woff2 and /dev/null differ diff --git a/src/app/fonts/open-sans-700.woff2 b/src/app/fonts/open-sans-700.woff2 deleted file mode 100644 index f5977a4..0000000 Binary files a/src/app/fonts/open-sans-700.woff2 and /dev/null differ diff --git a/src/app/fonts/poppins-300.woff2 b/src/app/fonts/poppins-300.woff2 deleted file mode 100644 index 962b734..0000000 Binary files a/src/app/fonts/poppins-300.woff2 and /dev/null differ diff --git a/src/app/fonts/poppins-500.woff2 b/src/app/fonts/poppins-500.woff2 deleted file mode 100644 index c660336..0000000 Binary files a/src/app/fonts/poppins-500.woff2 and /dev/null differ diff --git a/src/app/fonts/poppins-600.woff2 b/src/app/fonts/poppins-600.woff2 deleted file mode 100644 index 921e962..0000000 Binary files a/src/app/fonts/poppins-600.woff2 and /dev/null differ diff --git a/src/app/fonts/poppins-700.woff2 b/src/app/fonts/poppins-700.woff2 deleted file mode 100644 index bf022fc..0000000 Binary files a/src/app/fonts/poppins-700.woff2 and /dev/null differ diff --git a/src/app/forgot-password/page.tsx b/src/app/forgot-password/page.tsx index 479c0a8..e22065e 100644 --- a/src/app/forgot-password/page.tsx +++ b/src/app/forgot-password/page.tsx @@ -1,6 +1,5 @@ import Link from "next/link"; -import { CertviaLogo } from "@/components/brand/certvia-logo"; -import { PoweredByGefim } from "@/components/brand/powered-by-gefim"; +import { CraftviaLogo } from "@/components/brand/craftvia-logo"; import { ForgotPasswordForm } from "@/components/auth-recovery-forms"; import { loginPathOf } from "@/server/auth-selfservice"; @@ -25,7 +24,7 @@ export default async function ForgotPasswordPage({ return (
- +

Passwort vergessen

{domain === "platform" @@ -40,8 +39,6 @@ export default async function ForgotPasswordPage({ Zurück zur Anmeldung

- -
); diff --git a/src/app/globals.css b/src/app/globals.css index 4a55682..523eb41 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -7,9 +7,9 @@ @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); - --font-sans: var(--font-open-sans); + --font-sans: var(--font-inter); --font-mono: ui-monospace, monospace; - --font-heading: var(--font-poppins); + --font-heading: var(--font-inter); --color-sidebar-ring: var(--sidebar-ring); --color-sidebar-border: var(--sidebar-border); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); @@ -39,7 +39,7 @@ --color-popover: var(--popover); --color-card-foreground: var(--card-foreground); --color-card: var(--card); - /* Semantische Zusatzflächen (dunkles Certvia-Theme) */ + /* Semantische Zusatzflächen */ --color-panel: var(--panel); --color-panel-brd: var(--panel-brd); --color-elevated: var(--elevated); @@ -47,6 +47,8 @@ --color-band: var(--band); --color-band-text: var(--band-text); --color-band-brd: var(--band-brd); + --color-cta: var(--ui-accent); + --color-cta-foreground: var(--ui-accent-foreground); --color-ok: var(--ok); --color-warn: var(--warn); --color-risk: var(--risk); @@ -61,133 +63,109 @@ } /* - * Certvia — zentrale Token-Definition (Single Source of Truth für Farben). + * Craftvia — zentrale Token-Definition (Single Source of Truth für Farben). + * Quelle: docs/craftvia/BRANDBOOK.md §11.4. JS-Pendant: src/lib/brand.ts. * - * Zwei bewusst getrennte Ebenen: - * --brand-* Markenfarben aus dem Certvia-Styleguide (Logo, Print, Verläufe, - * Dokument-/Export-CD). NICHT für UI-Flächen verwenden. - * --ui-* Produktpalette des dunklen App-Themes (Interaktion, Flächen). - * Sie ist die festgelegte Certvia-UI-Palette und wird nicht an die - * Markenfarben „angeglichen" — Marken-Violett #5d52a3 gilt fürs Logo, - * UI-Interaktiv bleibt #7d6fd6. + * Zwei Ebenen: + * --brand-* Kernfarben der Marke (Logo, Print, Dokumente). + * --ui-* Rollen im hellen App-Theme (Interaktion, Flächen), abgeleitet aus --brand-*. * * Die semantischen shadcn-Tokens (--primary, --background, …) leiten sich unten - * ausschließlich aus --ui-* / --bg-* ab; Hex-Werte stehen nur noch hier. - * Ein späterer Light-Mode kann über dieselben Token-Namen unter .light ergänzt werden. - * JS-seitiges Pendant (Canvas-/PNG-Export, Metadaten): src/lib/brand.ts. + * ausschließlich aus --brand-* / --ui-* ab; Hex-Werte stehen nur in diesem Block. + * Farbe nie als einziges Statussignal — immer mit Text/Icon kombinieren. */ :root { - /* ---- Marke (Logo / Print / Export) ---- */ - --brand-violet: #5d52a3; - --brand-magenta: #812d80; - --brand-blue: #8dc4e0; - --brand-anthracite: #3b3b3a; - /* Marken-Varianten auf dunklem Grund (Styleguide) */ - --brand-violet-on-dark: #7d6fd6; - --brand-magenta-on-dark: #d17bcf; + /* ---- Marke ---- */ + --brand-lotsenblau: #082e5b; + --brand-signalorange: #ff6a00; + --brand-graphit: #34383d; + --brand-hafengrau: #f3f5f7; + --brand-stahlgrau: #66727d; + --brand-zink: #d9e0e5; + --brand-white: #ffffff; - /* ---- UI-Palette (dunkles Produkt-Theme) ---- */ - --ui-primary: #7d6fd6; - --ui-primary-deep: #5d52a3; - --ui-blue: #8dc4e0; - --ui-magenta: #b45bb0; - --ui-primary-soft: rgba(125, 111, 214, 0.18); - --ui-primary-ring: rgba(125, 111, 214, 0.35); + /* ---- Funktionsfarben ---- */ + --ok: #23845d; + --warn: #c87912; + --risk: #c64242; + --info: #2f6fa3; + + /* ---- UI-Rollen (helles Produkt-Theme) ---- */ + --ui-primary: var(--brand-lotsenblau); + --ui-primary-hover: #0b3c75; + --ui-primary-soft: rgba(8, 46, 91, 0.08); + --ui-primary-ring: rgba(8, 46, 91, 0.3); + /* Akzent/CTA: Signalorange — sparsam für die wichtigste Aktion je Ansicht. */ + --ui-accent: var(--brand-signalorange); + --ui-accent-foreground: #ffffff; /* Grundflächen */ - --bg-0: #0e1220; - --bg-1: #141a2e; - --bg-2: #1a2138; - --bg-3: #1e2640; - --txt: #e8ecf7; - --txt-muted: #8b93ad; - --line: rgba(120, 135, 180, 0.18); + --bg-0: var(--brand-hafengrau); + --bg-1: var(--brand-white); + --bg-2: #eef1f4; + --txt: var(--brand-graphit); + --txt-muted: var(--brand-stahlgrau); + --line: var(--brand-zink); - /* Verläufe */ - --grad: var(--grad-primary); - --grad-primary: linear-gradient( - 135deg, - var(--ui-primary-deep) 0%, - var(--ui-primary) 100% - ); - --grad-soft: var(--grad-primary); - --grad-critical: linear-gradient(90deg, var(--risk) 0%, var(--ui-magenta) 100%); + /* Keine Verläufe im Craftvia-CD: die Utility-Namen bleiben, zeigen aber Vollfarbe. */ + --grad-primary: var(--ui-primary); - /* Status */ - --ok: #39c07f; - --warn: #f0ad4e; - --risk: #ff6b6b; - --info: #5aa9e6; - - /* Flächen (semantisch → leiten aus den Tokens oben ab) */ + /* Flächen (semantisch) */ --background: var(--bg-0); --foreground: var(--txt); --card: var(--bg-1); --card-foreground: var(--txt); - --popover: var(--bg-2); + --popover: var(--bg-1); --popover-foreground: var(--txt); --primary: var(--ui-primary); --primary-foreground: #ffffff; --secondary: var(--bg-2); - --secondary-foreground: #cfc9ee; + --secondary-foreground: var(--brand-lotsenblau); --muted: var(--bg-2); --muted-foreground: var(--txt-muted); - --accent: var(--bg-3); - --accent-foreground: #cfc9ee; + --accent: var(--ui-primary-soft); + --accent-foreground: var(--brand-lotsenblau); --destructive: var(--risk); --border: var(--line); - --input: rgba(120, 135, 180, 0.22); + --input: var(--line); --ring: var(--ui-primary); - --chart-1: var(--ui-primary); - --chart-2: var(--ui-blue); - --chart-3: var(--ui-magenta); + --chart-1: var(--brand-lotsenblau); + --chart-2: var(--brand-signalorange); + --chart-3: var(--info); --chart-4: var(--ok); --chart-5: var(--warn); - --radius: 0.7rem; + --radius: 0.6rem; - /* Panels / Glas / erhöhte Flächen */ - --panel: rgba(30, 38, 64, 0.72); + /* Panels / erhöhte Flächen */ + --panel: rgba(255, 255, 255, 0.92); --panel-brd: var(--line); --elevated: var(--bg-2); - /* weiche Akzentkarte (früher #faf9fd) */ - --surface-soft: rgba(125, 111, 214, 0.08); - /* Info-Band (früher #f2f0f9 / #e6e2f3 / #5a4e86) */ - --band: rgba(125, 111, 214, 0.1); - --band-text: #c3bdec; - --band-brd: rgba(125, 111, 214, 0.22); - /* Chip auf farbiger Fläche (Risiko-Heatmap) — dunkler Grund, halbtransparent */ - --chip-overlay: rgba(14, 18, 32, 0.75); - - /* Graph (React Flow) — Linien/Labels, die als SVG-Attribute gesetzt werden */ - --graph-edge: #4a5372; - --graph-edge-critical: var(--risk); + --surface-soft: var(--ui-primary-soft); + --band: rgba(47, 111, 163, 0.08); + --band-text: var(--info); + --band-brd: rgba(47, 111, 163, 0.25); /* Sidebar / Topbar */ --sidebar: var(--bg-1); - --sidebar-foreground: #b6bdd4; + --sidebar-foreground: var(--brand-graphit); --sidebar-primary: var(--ui-primary); --sidebar-primary-foreground: #ffffff; --sidebar-accent: var(--ui-primary-soft); - --sidebar-accent-foreground: #cfc9ee; + --sidebar-accent-foreground: var(--brand-lotsenblau); --sidebar-border: var(--line); --sidebar-ring: var(--ui-primary); --focus-ring: 0 0 0 3px var(--ui-primary-ring); - --radius-card: 16px; - --radius-ctrl: 10px; - --dot-grid: radial-gradient(rgba(140, 155, 200, 0.1) 1px, transparent 1px); - --glow: - radial-gradient(1200px 700px at 20% -10%, #1c2340 0%, transparent 60%), - radial-gradient(900px 600px at 100% 110%, #1a2138 0%, transparent 55%); - --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.3), 0 8px 28px rgba(0, 0, 0, 0.28); + --radius-card: 12px; + --radius-ctrl: 8px; + /* Flaches CD: nur eine feine Kontur statt Schlagschatten. */ + --shadow-card: 0 1px 2px rgba(8, 46, 91, 0.06); } body { - font-family: var(--font-sans), "Open Sans", system-ui, sans-serif; + font-family: var(--font-sans), Inter, system-ui, "Segoe UI", Roboto, Arial, sans-serif; font-size: 14px; background-color: var(--background); - background-image: var(--glow); - background-attachment: fixed; } @layer base { @@ -204,149 +182,29 @@ body { h2, h3, h4 { - font-family: var(--font-heading), "Poppins", sans-serif; - font-weight: 600; + font-family: var(--font-heading), Inter, system-ui, sans-serif; + font-weight: 700; + color: var(--brand-lotsenblau); } } @layer utilities { - .bg-grad { - background: var(--grad-primary); - } + .bg-grad, .bg-grad-soft { background: var(--grad-primary); } - .bg-grad-critical { - background: var(--grad-critical); - } .shadow-card { box-shadow: var(--shadow-card); } - .dot-grid { - background-image: var(--dot-grid); - background-size: 26px 26px; - } -} - -/* React Flow — Dark-Anpassungen + Glow für kritische Kanten (Teil B) */ -.react-flow__edge.critical-edge .react-flow__edge-path { - filter: drop-shadow(0 0 4px rgba(255, 107, 107, 0.7)); -} -.react-flow__controls-button { - background: var(--elevated) !important; - border-color: var(--panel-brd) !important; - color: var(--muted-foreground) !important; -} -.react-flow__controls-button:hover { - background: var(--accent) !important; -} -.react-flow__controls-button svg { - fill: currentColor; -} -.react-flow__edge-text { - fill: var(--muted-foreground); -} - -/* Gerenderte Richtlinien/Verfahren (Lesemodus) — Typografie ohne Plugin */ -.policy-prose { - font-size: 13.5px; - line-height: 1.7; - color: var(--foreground); -} -.policy-prose h2 { - font-family: var(--font-heading, inherit); - font-size: 16px; - font-weight: 700; - margin: 1.6em 0 0.5em; - padding-top: 0.9em; - border-top: 1px solid var(--border); - scroll-margin-top: 1rem; -} -.policy-prose h2:first-child { - border-top: 0; - padding-top: 0; - margin-top: 0; -} -.policy-prose h3 { - font-family: var(--font-heading, inherit); - font-size: 14px; - font-weight: 700; - margin: 1.3em 0 0.4em; -} -.policy-prose h4 { - font-size: 13px; - font-weight: 700; - margin: 1em 0 0.3em; -} -.policy-prose p { - margin: 0.55em 0; -} -.policy-prose ul, -.policy-prose ol { - margin: 0.5em 0; - padding-left: 1.3em; - list-style: revert; -} -.policy-prose li { - margin: 0.25em 0; -} -.policy-prose strong { - font-weight: 700; -} -.policy-prose a { - color: var(--info); - text-decoration: none; -} -.policy-prose a:hover { - text-decoration: underline; -} -.policy-prose blockquote { - border-left: 3px solid var(--band-brd); - background: var(--band); - color: var(--band-text); - padding: 0.5em 0.9em; - margin: 0.8em 0; - border-radius: 0 8px 8px 0; - font-size: 12.5px; -} -.policy-prose table { - width: 100%; - border-collapse: collapse; - margin: 0.9em 0; - font-size: 12.5px; -} -.policy-prose th, -.policy-prose td { - border: 1px solid var(--border); - padding: 6px 10px; - text-align: left; - vertical-align: top; -} -.policy-prose th { - background: var(--elevated); - font-weight: 700; -} -.policy-prose code { - background: var(--elevated); - padding: 1px 5px; - border-radius: 4px; - font-size: 12px; } /* ========================================================================= - * Dokument-/Druck-Corporate-Design (Story S5) - * - * Ausgaben außerhalb der App laufen im hellen Certvia-CD: Marken-Violett/ - * Magenta auf Weiß, Poppins für Überschriften, Open Sans im Fließtext. - * Kopfzeile = Certvia-Lockup (das SVG trägt Poppins eingebettet, siehe - * public/assets/logo/certvia-lockup-positiv.svg), Fußzeile = Dachmarken- - * Hinweis „Certvia — ein Produkt von GEFIM" auf jeder Seite. - * - * Farb-/Textreferenz für den späteren DOCX/PDF-Export: src/lib/document-brand.ts. + * Dokument-/Druck-Corporate-Design (Einsatzberichte, Druckansicht) + * Farb-/Textreferenz für den PDF-Export: src/lib/document-brand.ts. * ========================================================================= */ @page { size: A4; - margin: 24mm 16mm 22mm; + margin: 20mm 16mm 20mm; } @media print { @@ -361,82 +219,33 @@ body { :root { --background: #ffffff; - --foreground: var(--brand-anthracite); --card: #ffffff; - --card-foreground: var(--brand-anthracite); - --muted-foreground: #6b6b70; - --primary: var(--brand-violet); - --border: #d9d7e4; --panel: #ffffff; - --panel-brd: #d9d7e4; - --elevated: #f4f2fa; - --band: #f4f2fa; - --band-text: var(--brand-violet); - --band-brd: #d9d7e4; - --info: var(--brand-violet); } body { background: #ffffff !important; - background-image: none !important; - color: var(--brand-anthracite); + color: var(--brand-graphit); } main { padding: 0 !important; } - /* Kopf: Certvia-Lockup, einmal am Dokumentanfang. */ - body::before { - content: ""; - display: block; - height: 13mm; - margin-bottom: 6mm; - padding-bottom: 4mm; - border-bottom: 1px solid #d9d7e4; - background: url("/assets/logo/certvia-lockup-positiv.svg") left center / auto 11mm no-repeat; - } - - /* Fuß: Dachmarken-Hinweis, auf jeder Seite (fixed wiederholt beim Druck). */ + /* Fuß: Produkthinweis auf jeder Seite. */ body::after { - content: "Certvia — ein Produkt von GEFIM"; + content: "Erstellt mit Craftvia"; position: fixed; bottom: 0; left: 0; right: 0; padding-top: 2mm; - border-top: 1px solid #d9d7e4; - font-family: var(--font-sans), "Open Sans", sans-serif; + border-top: 1px solid var(--brand-zink); font-size: 9pt; - color: #6b6b70; + color: var(--brand-stahlgrau); } - .shadow-card, - .bg-grad, - .bg-grad-soft { + .shadow-card { box-shadow: none !important; - background: #ffffff !important; - color: var(--brand-anthracite) !important; - } - - .policy-prose { - color: var(--brand-anthracite); - font-size: 10.5pt; - } - .policy-prose h2, - .policy-prose h3 { - color: var(--brand-violet); - break-after: avoid; - } - .policy-prose table, - .policy-prose blockquote { - break-inside: avoid; - } - .policy-prose th { - background: #f4f2fa; - } - .policy-prose a { - color: var(--brand-violet); - text-decoration: none; } } diff --git a/src/app/invite/page.tsx b/src/app/invite/page.tsx index 6c19425..dd4cc52 100644 --- a/src/app/invite/page.tsx +++ b/src/app/invite/page.tsx @@ -1,7 +1,6 @@ import Link from "next/link"; import { prisma } from "@/server/db"; -import { CertviaLogo } from "@/components/brand/certvia-logo"; -import { PoweredByGefim } from "@/components/brand/powered-by-gefim"; +import { CraftviaLogo } from "@/components/brand/craftvia-logo"; import { ResetPasswordForm } from "@/components/auth-recovery-forms"; import { checkInvitationToken } from "@/server/actions/auth-recovery"; import { describePasswordPolicy, resolvePasswordPolicy } from "@/lib/password-policy"; @@ -37,7 +36,7 @@ export default async function InvitePage({ return (
- +

Konto einrichten

{valid ? ( @@ -57,8 +56,6 @@ export default async function InvitePage({ )} - -
); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f24d097..dae7d33 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -5,30 +5,13 @@ import { getLocale } from "next-intl/server"; import { BRAND, BRAND_ASSETS, BRAND_COLORS } from "@/lib/brand"; import "./globals.css"; -// Selbst gehostet über next/font/local (committete woff2 in ./fonts) — kein -// Google-Fonts-Fetch zur Build- oder Laufzeit. DSGVO-konform und macht den -// Produktiv-Build reproduzierbar/offline (next/font/google lud die Schriften -// sonst zur Build-Zeit von Google, was flakig fehlschlagen kann). -const openSans = localFont({ - variable: "--font-open-sans", +// Inter (Brandbook §11.5), selbst gehostet über next/font/local — kein Google-Fonts-Fetch +// zur Build- oder Laufzeit (DSGVO, reproduzierbarer Offline-Build). Variable Font +// (Gewichte 100–900 in einer Datei). TODO(brand): auf woff2-Subsets umstellen (kleiner). +const inter = localFont({ + variable: "--font-inter", display: "swap", - src: [ - { path: "./fonts/open-sans-300.woff2", weight: "300", style: "normal" }, - { path: "./fonts/open-sans-400.woff2", weight: "400", style: "normal" }, - { path: "./fonts/open-sans-600.woff2", weight: "600", style: "normal" }, - { path: "./fonts/open-sans-700.woff2", weight: "700", style: "normal" }, - ], -}); - -const poppins = localFont({ - variable: "--font-poppins", - display: "swap", - src: [ - { path: "./fonts/poppins-300.woff2", weight: "300", style: "normal" }, - { path: "./fonts/poppins-500.woff2", weight: "500", style: "normal" }, - { path: "./fonts/poppins-600.woff2", weight: "600", style: "normal" }, - { path: "./fonts/poppins-700.woff2", weight: "700", style: "normal" }, - ], + src: [{ path: "./fonts/inter-variable.ttf", weight: "100 900", style: "normal" }], }); export const metadata: Metadata = { @@ -37,35 +20,27 @@ export const metadata: Metadata = { description: BRAND.description, applicationName: BRAND.name, manifest: "/site.webmanifest", - // Alle Icons aus dem Certvia-Asset-Bundle (public/favicon + public/assets/logo). + // Icons aus dem Craftvia-Signet erzeugt (scripts/generate-brand-icons.ts). icons: { icon: [ { url: "/favicon.ico", sizes: "any" }, { url: BRAND_ASSETS.favicon, type: "image/svg+xml" }, - { url: "/favicon/icon-16.png", type: "image/png", sizes: "16x16" }, { url: "/favicon/icon-32.png", type: "image/png", sizes: "32x32" }, ], - apple: [{ url: "/favicon/apple-touch-icon-180.png", sizes: "180x180" }], + apple: [{ url: BRAND_ASSETS.appleTouchIcon, sizes: "180x180" }], }, - appleWebApp: { capable: true, title: BRAND.name, statusBarStyle: "black-translucent" }, + appleWebApp: { capable: true, title: BRAND.name, statusBarStyle: "default" }, openGraph: { type: "website", siteName: BRAND.name, title: `${BRAND.name} — ${BRAND.tagline}`, description: BRAND.description, - images: [{ url: "/assets/logo/certvia-lockup-positiv-1024.png", width: 1024, height: 259 }], - }, - twitter: { - card: "summary_large_image", - title: `${BRAND.name} — ${BRAND.tagline}`, - description: BRAND.description, - images: ["/assets/logo/certvia-lockup-positiv-1024.png"], + images: [{ url: BRAND_ASSETS.ogImage, width: 512, height: 512 }], }, }; export const viewport: Viewport = { - // Marken-Violett (wie auf der Certvia-Website), nicht die UI-Primärfarbe. - themeColor: BRAND_COLORS.violet, + themeColor: BRAND_COLORS.lotsenblau, }; export default async function RootLayout({ @@ -76,10 +51,7 @@ export default async function RootLayout({ const locale = await getLocale(); return ( - + {children} diff --git a/src/app/login/mfa/page.tsx b/src/app/login/mfa/page.tsx index bc14e2f..98718b3 100644 --- a/src/app/login/mfa/page.tsx +++ b/src/app/login/mfa/page.tsx @@ -8,8 +8,7 @@ import { verifyMfaPending, signLoginTicket, MFA_PENDING_COOKIE } from "@/server/ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { CertviaLogo } from "@/components/brand/certvia-logo"; -import { PoweredByGefim } from "@/components/brand/powered-by-gefim"; +import { CraftviaLogo } from "@/components/brand/craftvia-logo"; /** * WS5 (Option C) — Two-Step-Login, Schritt 2: MFA. Erreichbar NUR mit gültigem, @@ -57,7 +56,7 @@ export default async function LoginMfaPage({ return (
- +

Bestätigung (2. Schritt)

Geben Sie den 6-stelligen Code aus Ihrer Authenticator-App oder einen Recovery-Code ein. @@ -81,8 +80,6 @@ export default async function LoginMfaPage({

Zurück zur Anmeldung

- -
); diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 6345005..1b136f3 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -9,8 +9,7 @@ import { signMfaPending, signLoginTicket, MFA_PENDING_COOKIE } from "@/server/lo import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { CertviaLogo } from "@/components/brand/certvia-logo"; -import { PoweredByGefim } from "@/components/brand/powered-by-gefim"; +import { CraftviaLogo } from "@/components/brand/craftvia-logo"; import { PasskeyLoginButton } from "@/components/passkey-login-button"; export default async function LoginPage({ @@ -78,7 +77,7 @@ export default async function LoginPage({ return (
- +

{t("tagline")}

@@ -122,8 +121,6 @@ export default async function LoginPage({ {t("forgotPassword")}

- -
); diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx index ce500f6..c7b5568 100644 --- a/src/app/not-found.tsx +++ b/src/app/not-found.tsx @@ -1,14 +1,13 @@ import Link from "next/link"; import { Button } from "@/components/ui/button"; -import { CertviaLogo } from "@/components/brand/certvia-logo"; -import { PoweredByGefim } from "@/components/brand/powered-by-gefim"; +import { CraftviaLogo } from "@/components/brand/craftvia-logo"; /** Gebrandete 404-Seite (Story S7 — keine ungebrandeten Systemscreens). */ export default function NotFound() { return (
- +

404

Seite nicht gefunden

@@ -21,7 +20,6 @@ export default function NotFound() { > Zum Dashboard -

); diff --git a/src/app/platform/enroll-mfa/page.tsx b/src/app/platform/enroll-mfa/page.tsx index fe0ef24..0d158f5 100644 --- a/src/app/platform/enroll-mfa/page.tsx +++ b/src/app/platform/enroll-mfa/page.tsx @@ -5,7 +5,7 @@ import { prisma } from "@/server/db"; import { newTotpSecret, totpUri } from "@/server/mfa"; import { encryptSecret, decryptSecret } from "@/server/secret-crypto"; import { PlatformEnrollForm } from "@/components/platform-enroll-form"; -import { CertviaLogo } from "@/components/brand/certvia-logo"; +import { CraftviaLogo } from "@/components/brand/craftvia-logo"; /** * MFA-Einrichtung für Plattform-Administratoren (Pflicht beim ersten Login). @@ -30,7 +30,7 @@ export default async function EnrollMfaPage() { return (
- +

Zwei-Faktor-Authentifizierung einrichten

Für Plattform-Administratoren verpflichtend. Scannen Sie den QR-Code mit einer diff --git a/src/app/platform/login/page.tsx b/src/app/platform/login/page.tsx index 1832e28..d907b33 100644 --- a/src/app/platform/login/page.tsx +++ b/src/app/platform/login/page.tsx @@ -5,8 +5,7 @@ import { platformAuth, platformSignIn } from "@/server/platform-auth"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { CertviaLogo } from "@/components/brand/certvia-logo"; -import { PoweredByGefim } from "@/components/brand/powered-by-gefim"; +import { CraftviaLogo } from "@/components/brand/craftvia-logo"; /** * Getrennter Login für Plattform-Administratoren (Phase-1-Härtung Paket 2). @@ -41,7 +40,7 @@ export default async function PlatformLoginPage({ return (

- +

Plattform-Administration

Betreiberzugang — getrennt vom Mandanten-Login @@ -84,8 +83,6 @@ export default async function PlatformLoginPage({ Passwort vergessen?

- -

); diff --git a/src/app/reset/page.tsx b/src/app/reset/page.tsx index bdf8680..8b7275b 100644 --- a/src/app/reset/page.tsx +++ b/src/app/reset/page.tsx @@ -1,7 +1,6 @@ import Link from "next/link"; import { prisma } from "@/server/db"; -import { CertviaLogo } from "@/components/brand/certvia-logo"; -import { PoweredByGefim } from "@/components/brand/powered-by-gefim"; +import { CraftviaLogo } from "@/components/brand/craftvia-logo"; import { ResetPasswordForm } from "@/components/auth-recovery-forms"; import { checkResetToken } from "@/server/actions/auth-recovery"; import { loginPathOf } from "@/server/auth-selfservice"; @@ -43,7 +42,7 @@ export default async function ResetPage({ return (
- +

Neues Passwort vergeben

{valid ? ( @@ -74,8 +73,6 @@ export default async function ResetPage({ )} - -
); diff --git a/src/app/select-tenant/page.tsx b/src/app/select-tenant/page.tsx index c6534a2..8fd6c0c 100644 --- a/src/app/select-tenant/page.tsx +++ b/src/app/select-tenant/page.tsx @@ -2,8 +2,7 @@ import { redirect } from "next/navigation"; import { auth, signOut } from "@/server/auth"; import { setActiveTenant } from "@/server/actions/tenant-switch"; import { Button } from "@/components/ui/button"; -import { CertviaLogo } from "@/components/brand/certvia-logo"; -import { PoweredByGefim } from "@/components/brand/powered-by-gefim"; +import { CraftviaLogo } from "@/components/brand/craftvia-logo"; /** * WS2 (Option C) — Mandantenauswahl nach dem Login, wenn eine Person mehreren @@ -22,7 +21,7 @@ export default async function SelectTenantPage() { return (
- +

Mandant wählen

Ihr Konto gehört mehreren Mandanten an. Bitte wählen Sie, in welchem Sie arbeiten möchten. @@ -48,8 +47,6 @@ export default async function SelectTenantPage() {

{ "use server"; await signOut({ redirectTo: "/login" }); }} className="mt-5">
- -
); diff --git a/src/app/verify-email/page.tsx b/src/app/verify-email/page.tsx index 9e9ad44..7962520 100644 --- a/src/app/verify-email/page.tsx +++ b/src/app/verify-email/page.tsx @@ -1,6 +1,5 @@ import Link from "next/link"; -import { CertviaLogo } from "@/components/brand/certvia-logo"; -import { PoweredByGefim } from "@/components/brand/powered-by-gefim"; +import { CraftviaLogo } from "@/components/brand/craftvia-logo"; import { confirmEmailChange } from "@/server/actions/auth-recovery"; import { loginPathOf } from "@/server/auth-selfservice"; @@ -32,7 +31,7 @@ export default async function VerifyEmailPage({ return (
- +

E-Mail-Adresse bestätigen

Zur Anmeldung → - -

); diff --git a/src/components/brand/certvia-logo.tsx b/src/components/brand/certvia-logo.tsx deleted file mode 100644 index b982973..0000000 --- a/src/components/brand/certvia-logo.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import { BRAND, BRAND_COLORS } from "@/lib/brand"; - -/** - * Certvia-Logo als Inline-SVG. - * - * Bewusst inline statt : die Wortmarke setzt „Cert" in Poppins - * Light und „via" in Poppins Bold. Ein extern geladenes SVG hat keinen Zugriff auf - * die per next/font/local eingebundene Schrift (gehashter Family-Name) und würde - * auf eine System-Schrift zurückfallen. Inline greift `var(--font-poppins)`. - * - * Zeichen-Konstruktion (unverändert aus dem Design-Paket, viewBox 64×64): - * „C" nach rechts offen, der Haken liegt bündig in der Öffnung. Kein Verlauf. - * - * theme="light" → heller Grund (C Violett, Haken/„via" Magenta, „Cert" Anthrazit) - * theme="dark" → dunkler Grund (C #7d6fd6, Haken #d17bcf, „Cert" weiß) - */ - -type LogoTheme = "light" | "dark"; - -const PALETTE: Record = { - light: { - arc: BRAND_COLORS.violet, - hook: BRAND_COLORS.magenta, - word: BRAND_COLORS.anthracite, - accent: BRAND_COLORS.magenta, - }, - dark: { - arc: BRAND_COLORS.violetOnDark, - hook: BRAND_COLORS.magentaOnDark, - word: BRAND_COLORS.white, - accent: BRAND_COLORS.magentaOnDark, - }, -}; - -/** Das Monogramm allein — Pfade aus `certvia-mark-*.svg`. */ -function Monogram({ arc, hook }: { arc: string; hook: string }) { - return ( - <> - - - - ); -} - -export function CertviaLogo({ - variant = "lockup", - theme = "dark", - height = 40, - className, - title = BRAND.name, -}: { - variant?: "lockup" | "mark"; - theme?: LogoTheme; - /** Renderhöhe in px; die Breite ergibt sich aus dem Seitenverhältnis. */ - height?: number; - className?: string; - title?: string; -}) { - const c = PALETTE[theme]; - - if (variant === "mark") { - return ( - - {title} - - - ); - } - - return ( - - {title} - - - - - - Cert - - - via - - - - ); -} diff --git a/src/components/brand/craftvia-logo.tsx b/src/components/brand/craftvia-logo.tsx new file mode 100644 index 0000000..74d7cc8 --- /dev/null +++ b/src/components/brand/craftvia-logo.tsx @@ -0,0 +1,137 @@ +import { useId } from "react"; +import { BRAND, BRAND_COLORS } from "@/lib/brand"; + +/** + * Craftvia-Logo als Inline-SVG (Referenz: docs/craftvia/assets/craftvia-brand-identity-v1.png). + * + * Signet: kräftiges „C" in Lotsenblau, rechts oben geöffnet. Ein Haken in Signalorange + * startet links außerhalb, durchquert das C (das C ist dort freigestellt) und läuft als + * Pfeil nach rechts oben durch die Öffnung hinaus („auf Kurs"). + * Wortmarke: „Craft" Lotsenblau + „via" Signalorange, fett; Tagline optional. + * Keine Schatten, keine Verläufe. + * + * Inline statt : die Wortmarke nutzt die per next/font eingebundene App-Schrift. + * + * Varianten: + * variant="horizontal" (Default) | "signet" + * tone="color" (Default) | "mono" (einfarbig Lotsenblau) | "reversed" (weiß auf dunklem Grund) + * tile (nur signet): App-Icon — Lotsenblau-Kachel, weißes C, oranger Haken + */ + +export type CraftviaLogoTone = "color" | "mono" | "reversed"; + +/** Geometrie des Signets (viewBox 0 0 64 64) — auch von scripts/generate-brand-icons.ts genutzt. */ +export const SIGNET = { + /** C: Kreisbogen um (30,34), r=19, von -70° gegen den Uhrzeigersinn bis +10° (Öffnung rechts oben). */ + arc: "M36.5 16.14 A19 19 0 1 0 48.71 37.3", + arcWidth: 11, + /** Haken: Schwanz links außerhalb → Tal → Richtung Öffnung. */ + check: "M6 28 L22 42 L44 21", + checkWidth: 7, + /** Pfeilspitze nach rechts oben. */ + arrow: "M52.7 12.7 L47.73 27.8 L37.37 16.96 Z", +} as const; + +function palette(tone: CraftviaLogoTone, tile: boolean) { + if (tile) return { arc: BRAND_COLORS.white, check: BRAND_COLORS.signalorange, craft: BRAND_COLORS.white, via: BRAND_COLORS.signalorange, tagline: BRAND_COLORS.white }; + switch (tone) { + case "mono": + return { arc: BRAND_COLORS.lotsenblau, check: BRAND_COLORS.lotsenblau, craft: BRAND_COLORS.lotsenblau, via: BRAND_COLORS.lotsenblau, tagline: BRAND_COLORS.lotsenblau }; + case "reversed": + return { arc: BRAND_COLORS.white, check: BRAND_COLORS.white, craft: BRAND_COLORS.white, via: BRAND_COLORS.white, tagline: BRAND_COLORS.white }; + default: + return { arc: BRAND_COLORS.lotsenblau, check: BRAND_COLORS.signalorange, craft: BRAND_COLORS.lotsenblau, via: BRAND_COLORS.signalorange, tagline: BRAND_COLORS.lotsenblau }; + } +} + +function Signet({ maskId, arc, check }: { maskId: string; arc: string; check: string }) { + return ( + <> + + {/* Freistellung: das C ist dort unterbrochen, wo der Haken es durchquert. */} + + + + + + + + + + ); +} + +export function CraftviaLogo({ + variant = "horizontal", + tone = "color", + tile = false, + tagline = false, + height = 36, + className, + title = BRAND.name, +}: { + variant?: "horizontal" | "signet"; + tone?: CraftviaLogoTone; + /** Nur für variant="signet": App-Icon-Kachel in Lotsenblau. */ + tile?: boolean; + /** Tagline „Handwerk. Digital auf Kurs." unter der Wortmarke (nur horizontal). */ + tagline?: boolean; + /** Renderhöhe in px; die Breite ergibt sich aus dem Seitenverhältnis. */ + height?: number; + className?: string; + title?: string; +}) { + const maskId = `cv-mask-${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`; + const isTile = variant === "signet" && tile; + const c = palette(tone, isTile); + + if (variant === "signet") { + return ( + + {title} + {isTile ? ( + <> + + + + + + ) : ( + + )} + + ); + } + + const vbW = 292; + const vbH = tagline ? 82 : 64; + return ( + + {title} + + + Craft + via + + {tagline && ( + + {BRAND.tagline} + + )} + + ); +} diff --git a/src/components/brand/powered-by-gefim.tsx b/src/components/brand/powered-by-gefim.tsx deleted file mode 100644 index 8b44610..0000000 --- a/src/components/brand/powered-by-gefim.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import Image from "next/image"; -import { BRAND, BRAND_ASSETS } from "@/lib/brand"; - -/** - * Dachmarken-Hinweis „Ein Produkt von GEFIM". - * - * Bewusste Leitplanke der Branding-Umstellung: Certvia ist die Produktmarke, - * GEFIM bleibt Dachmarke und erscheint weiterhin in Fußzeilen, auf den - * Anmeldeseiten sowie später in Export- und Mail-Fußzeilen. - * Siehe docs/BRANDING-CERTVIA.md. - */ -export function PoweredByGefim({ - withLogo = true, - className, -}: { - withLogo?: boolean; - className?: string; -}) { - return ( -

- {BRAND.byline} - {withLogo && ( - {BRAND.parentBrand} - )} -

- ); -} diff --git a/src/components/brand/tenant-brand.tsx b/src/components/brand/tenant-brand.tsx index 95ac53d..8b11550 100644 --- a/src/components/brand/tenant-brand.tsx +++ b/src/components/brand/tenant-brand.tsx @@ -1,13 +1,12 @@ -import { CertviaLogo } from "@/components/brand/certvia-logo"; +import { CraftviaLogo } from "@/components/brand/craftvia-logo"; import type { TenantBranding } from "@/lib/brand"; /** - * Kopfbereichs-Marke eines Mandanten (Story S8). + * Kopfbereichs-Marke eines Mandanten. * - * Default ist **Certvia**; ein mandanteneigenes Logo überschreibt nur, wenn es - * gesetzt ist. Der Upload je Mandant kommt mit Admin Phase 2 (Objektspeicher) — - * bis dahin liefert `resolveTenantBranding` konstant `logoUrl: null`, die - * Verzweigung ist aber bereits vorhanden und muss dann nicht nachgezogen werden. + * Default ist **Craftvia**; ein mandanteneigenes Logo überschreibt nur, wenn es + * gesetzt ist (TenantSettings.logoKey → Upload folgt). Bis dahin liefert + * `resolveTenantBranding` konstant `logoUrl: null`. */ export function TenantBrand({ branding, @@ -20,8 +19,7 @@ export function TenantBrand({ }) { if (branding.logoUrl) { return ( - // Mandanten-Logos liegen später im Objektspeicher (beliebige Domain) — - // daher bewusst statt next/image ohne Remote-Pattern-Konfiguration. + // Mandanten-Logos liegen im Objektspeicher — daher bewusst statt next/image. // eslint-disable-next-line @next/next/no-img-element ); } - return ; + return ; } diff --git a/src/components/mockup-ui.tsx b/src/components/mockup-ui.tsx index d22c6d5..9319971 100644 --- a/src/components/mockup-ui.tsx +++ b/src/components/mockup-ui.tsx @@ -1,10 +1,9 @@ import { cn } from "@/lib/utils"; /** - * Basisbausteine im Look des UI-Mockups (docs/ISMS-Prototyp-GEFIM.html — - * historisches Projektartefakt, der Dateiname bleibt bewusst unverändert): - * PageHead (Crumb/Titel/Sub + Aktionen), KPI-Kapsel, C/I/A-Quadrate, - * Tag-Chip, Owner-Chip, Status-Pille. + * UI-Basisbausteine (Craftvia-CD): PageHead (Crumb/Titel/Sub + Aktionen), KPI-Kapsel, + * Tag-Chip, Owner-Chip, Status-Pille. Farbe nie als einziges Statussignal — Pillen + * tragen immer Text. */ export function PageHead({ @@ -56,80 +55,9 @@ export function KpiCard({ ); } -// Segmentbalken-Farben je Stufe 1–4 als Ampel: Grün → Gelb → Orange → Rot -const CIA_BAR_COLORS: Record = { - 1: "bg-[#2e9e6b]", - 2: "bg-[#e5b000]", - 3: "bg-[#e07d2e]", - 4: "bg-[#d64c4c]", -}; - -/** Ein Schutzziel als 4er-Segmentbalken, gefüllt bis `level`. */ -function CiaSegment({ level, label }: { level: number; label?: string }) { - const color = CIA_BAR_COLORS[level] ?? CIA_BAR_COLORS[1]; - return ( - - - {[1, 2, 3, 4].map((seg) => ( - - ))} - - {label && ( - - {label} - - )} - - ); -} - -/** - * Schutzbedarf / Schadenshöhe als drei Segmentbalken (C/I/A). Mit `labels` - * werden VER/INT/VFB unter den Balken gezeigt (Detailkarten); ohne für - * kompakte Tabellenzellen. - */ -export function CiaBadge({ - c, - i, - a, - labels = false, -}: { - c: number; - i: number; - a: number; - labels?: boolean; -}) { - return ( - - - - - - ); -} - -/** VER/INT/VFB-Beschriftung passend zur Balkenbreite — einmal pro Tabelle als Überschrift. */ -export function CiaLegend() { - return ( - - {["VER", "INT", "VFB"].map((l) => ( - - {l} - - ))} - - ); -} - export function Tag({ children }: { children: React.ReactNode }) { return ( - + {children} ); @@ -153,15 +81,15 @@ export function OwnerChip({ name, noOwnerLabel }: { name?: string | null; noOwne ); } -// Status-Chips: ~16–20 % Alpha der Statusfarbe auf Dunkel (Referenz-Vorgabe) +// Status-Chips: ~12 % Alpha der Funktionsfarbe auf hellem Grund (Brandbook §11.4). const PILL_TONES = { - ok: "bg-[rgba(57,192,127,0.16)] text-[var(--ok)]", - warn: "bg-[rgba(240,173,78,0.16)] text-[var(--warn)]", - orange: "bg-[rgba(240,140,60,0.16)] text-[#f0a35a]", - risk: "bg-[rgba(255,107,107,0.16)] text-[var(--risk)]", - info: "bg-[rgba(90,169,230,0.16)] text-[var(--info)]", - mut: "bg-[rgba(139,147,173,0.16)] text-muted-foreground", - violet: "bg-[rgba(125,111,214,0.2)] text-[#c3bdec]", + ok: "bg-[rgba(35,132,93,0.12)] text-[var(--ok)]", + warn: "bg-[rgba(200,121,18,0.12)] text-[var(--warn)]", + orange: "bg-[rgba(255,106,0,0.12)] text-[#b34a00]", + risk: "bg-[rgba(198,66,66,0.12)] text-[var(--risk)]", + info: "bg-[rgba(47,111,163,0.12)] text-[var(--info)]", + mut: "bg-[rgba(102,114,125,0.12)] text-muted-foreground", + violet: "bg-[rgba(8,46,91,0.1)] text-[var(--primary)]", } as const; export function Pill({ @@ -183,12 +111,6 @@ export function Pill({ ); } -/** Kritikalität 1–4 als Pille in Mockup-Farben (info/warn/risk). */ -export function CriticalityPill({ level, label }: { level: number; label: string }) { - const tone = level >= 4 ? "risk" : level === 3 ? "warn" : level === 2 ? "info" : "ok"; - return {label}; -} - export function SectTitle({ title, sub }: { title: string; sub?: string }) { return (
diff --git a/src/lib/brand.ts b/src/lib/brand.ts index 2e11e27..d307a74 100644 --- a/src/lib/brand.ts +++ b/src/lib/brand.ts @@ -1,106 +1,88 @@ /** - * Certvia — Marken- und Produktkonstanten (JS-Pendant zu den CSS-Tokens in - * `src/app/globals.css`). + * Craftvia — Marken- und Produktkonstanten (JS-Pendant zu den CSS-Tokens in + * `src/app/globals.css`). Quelle: docs/craftvia/BRANDBOOK.md §11.4/§11.5. * * Hier steht nur, was CSS-Variablen NICHT auflösen können: - * - Metadaten (``, OG/Twitter, Manifest, TOTP-Issuer) - * - Canvas-/PNG-Export (html-to-image rendert außerhalb des CSS-Kontexts) - * - Dokument-/Export-Corporate-Design (DOCX/PDF, Print-Kopf/-Fußzeile) + * - Metadaten (`<title>`, OG, Manifest, TOTP-Issuer, WebAuthn-RP-Name) + * - Dokument-/Export-Corporate-Design (PDF-Berichte, Print) * - E-Mail-Templates (Inline-Styles, kein CSS-Var-Support in Mail-Clients) + * - Inline-SVG-Logo (src/components/brand/craftvia-logo.tsx) * * Für alles, was im Browser gerendert wird, gelten die CSS-Tokens als Quelle — * dieses Modul spiegelt sie nur, damit beide Seiten nicht auseinanderlaufen. - * - * Leitplanke: GEFIM bleibt Dachmarke. Produktmarke = Certvia. */ -/** Markenfarben aus dem Certvia-Styleguide (Logo, Print, Verläufe). */ +/** Kernfarben aus dem Craftvia-Brandbook (§11.4). */ export const BRAND_COLORS = { - violet: "#5d52a3", - magenta: "#812d80", - blue: "#8dc4e0", - anthracite: "#3b3b3a", - /** Varianten auf dunklem Grund */ - violetOnDark: "#7d6fd6", - magentaOnDark: "#d17bcf", - white: "#ffffff", + /** Primär — Logo, Navigation, Headlines */ + lotsenblau: "#082E5B", + /** Sekundär — „via", Haken-/Weg-Signet, CTA, aktive Elemente */ + signalorange: "#FF6A00", + /** Dunkel — Fließtext, Footer */ + graphit: "#34383D", + /** Hell — Flächen, Seitenhintergrund */ + hafengrau: "#F3F5F7", + /** Neutral — Sekundärtext, Icons */ + stahlgrau: "#66727D", + /** Linie — Trennlinien, Eingabefelder */ + zink: "#D9E0E5", + white: "#FFFFFF", } as const; -/** - * UI-Palette des dunklen Produkt-Themes. Bewusst getrennt von BRAND_COLORS: - * Marken-Violett #5d52a3 gilt fürs Logo, UI-Interaktiv bleibt #7d6fd6. - */ -export const UI_COLORS = { - primary: "#7d6fd6", - primaryDeep: "#5d52a3", - blue: "#8dc4e0", - magenta: "#b45bb0", - bg0: "#0e1220", - bg1: "#141a2e", - bg2: "#1a2138", - text: "#e8ecf7", - textMuted: "#8b93ad", - line: "rgba(120, 135, 180, 0.18)", - graphEdge: "#4a5372", - ok: "#39c07f", - warn: "#f0ad4e", - risk: "#ff6b6b", - info: "#5aa9e6", +/** Funktionsfarben (§11.4). Farbe nie als einziges Statussignal einsetzen. */ +export const STATUS_COLORS = { + success: "#23845D", + warning: "#C87912", + error: "#C64242", + info: "#2F6FA3", } as const; /** Produktidentität — überall dort, wo der Name als Text erscheint. */ export const BRAND = { - /** Produktmarke */ - name: "Certvia", - tagline: "Informationssicherheit. Endlich einfach.", + name: "Craftvia", + tagline: "Handwerk. Digital auf Kurs.", description: - "Certvia — Informationssicherheits-Managementsystem für ISO/IEC 27001:2022 und TISAX/VDA-ISA.", - /** Dachmarke (bleibt erhalten, siehe docs/BRANDING-CERTVIA.md) */ - parentBrand: "GEFIM", - /** Fußzeilen-/Signaturhinweis für UI, Export und E-Mail */ - byline: "Ein Produkt von GEFIM", + "Craftvia — die Einsatz-App für Handwerks- und Montagebetriebe: Aufträge, Teams, Einsätze und Berichte in einem System.", + /** Name des In-Product-Assistenten (separat vom Logo, §Lotse) */ + assistantName: "Lotse", /** Kompakte Form für Dokument-Fußzeilen */ - documentByline: "Certvia — ein Produkt von GEFIM", + documentByline: "Erstellt mit Craftvia", } as const; -/** Asset-Pfade unter `public/` (Logo-Bundle aus dem Design-Paket). */ +/** Asset-Pfade unter `public/` (aus dem Inline-SVG-Logo erzeugt). */ export const BRAND_ASSETS = { - markPositive: "/assets/logo/certvia-mark-positiv.svg", - markNegative: "/assets/logo/certvia-mark-negativ.svg", - markGrey: "/assets/logo/certvia-mark-grau.svg", - markAnthracite: "/assets/logo/certvia-mark-anthrazit.svg", - lockupPositive: "/assets/logo/certvia-lockup-positiv.svg", - favicon: "/assets/logo/certvia-favicon.svg", - /** Dachmarke */ - parentLogo: "/assets/logo/gefim-logo.png", -} as const; - -/** Schriftstapel für Kontexte ohne Tailwind/CSS-Vars (Export, E-Mail, Print). */ -export const BRAND_FONTS = { - heading: "'Poppins', 'Segoe UI', system-ui, sans-serif", - body: "'Open Sans', system-ui, -apple-system, sans-serif", + favicon: "/favicon/craftvia-signet.svg", + appleTouchIcon: "/favicon/apple-touch-icon-180.png", + ogImage: "/favicon/pwa-512.png", } as const; /** - * Standard-Branding eines Mandanten (Story S8). Ohne eigenes Logo/Akzent zeigt - * jeder Mandant Certvia; ein späterer Logo-Upload (Admin Phase 2) überschreibt - * ausschließlich die gesetzten Felder. + * Schriftstapel für Kontexte ohne Tailwind/CSS-Vars (Export, E-Mail, Print). + * Primärschrift laut Brandbook: Inter. TODO(brand): Inter selbst hosten (woff2 in + * src/app/fonts) — bis dahin nutzt die App die vorhandenen selbst gehosteten Schriften. + */ +export const BRAND_FONTS = { + heading: "Inter, system-ui, 'Segoe UI', Roboto, Arial, sans-serif", + body: "Inter, system-ui, 'Segoe UI', Roboto, Arial, sans-serif", +} as const; + +/** + * Standard-Branding eines Mandanten. Ohne eigenes Logo/Akzent zeigt jeder Mandant + * Craftvia; ein späterer Logo-Upload überschreibt ausschließlich die gesetzten Felder. */ export type TenantBranding = { /** Produktname im Kopfbereich */ productName: string; - /** Custom-Logo des Mandanten; `null` ⇒ Certvia-Lockup */ + /** Custom-Logo des Mandanten; `null` ⇒ Craftvia-Logo */ logoUrl: string | null; /** Akzentfarbe; `null` ⇒ UI-Primärfarbe */ accent: string | null; - /** Dachmarken-Hinweis, unabhängig vom Mandanten-Branding */ - byline: string; }; const HEX_RE = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i; /** - * Löst das effektive Branding eines Mandanten auf. Certvia ist der Default; + * Löst das effektive Branding eines Mandanten auf. Craftvia ist der Default; * mandanteneigene Werte überschreiben nur, wenn sie gesetzt und plausibel sind. */ export function resolveTenantBranding( @@ -112,6 +94,5 @@ export function resolveTenantBranding( productName: BRAND.name, logoUrl: logoUrl ? logoUrl : null, accent: accent && HEX_RE.test(accent) ? accent : null, - byline: BRAND.byline, }; } diff --git a/src/lib/document-brand.ts b/src/lib/document-brand.ts index d2e4ef1..7de6fd8 100644 --- a/src/lib/document-brand.ts +++ b/src/lib/document-brand.ts @@ -1,28 +1,23 @@ import { BRAND, BRAND_COLORS, BRAND_FONTS } from "@/lib/brand"; /** - * Branding-Layer für Dokument-Ausgaben (Story S5). + * Branding-Layer für Dokument-Ausgaben (Einsatzberichte, Druckansicht, PDF-Export). * - * Ausgaben außerhalb der App-UI — Druckansicht, künftiger DOCX-/PDF-Export von - * Richtlinien, VDA-ISA-Katalog und Reports — laufen im **hellen** Corporate - * Design: Marken-Violett/Magenta auf Weiß, Poppins für Überschriften, Open Sans - * für Fließtext, Fußzeile mit dem Dachmarken-Hinweis. - * - * Bewusst ohne Abhängigkeit zu einer Export-Bibliothek: das Export-Feature - * (DOCX/PDF) existiert noch nicht. Dieses Modul liefert die Kopf-/Fußzeilen- - * Bausteine und die Farb-/Schriftreferenz, an die es dann andockt — damit das - * CD nicht ein zweites Mal definiert werden muss. + * Ausgaben außerhalb der App-UI laufen im hellen Craftvia-CD: Lotsenblau für + * Überschriften, Signalorange als sparsamer Akzent, Graphit für Fließtext. + * Bewusst ohne Abhängigkeit zu einer Export-Bibliothek — das Berichtsmodul dockt + * an diese Farb-/Schriftreferenz an. */ /** Farb-/Schriftreferenz für Dokumentausgaben (heller Grund). */ export const DOCUMENT_THEME = { - pageBackground: "#ffffff", - text: BRAND_COLORS.anthracite, - textMuted: "#6b6b70", - accent: BRAND_COLORS.violet, - accentStrong: BRAND_COLORS.magenta, - rule: "#d9d7e4", - tableHeaderBackground: "#f4f2fa", + pageBackground: BRAND_COLORS.white, + text: BRAND_COLORS.graphit, + textMuted: BRAND_COLORS.stahlgrau, + accent: BRAND_COLORS.lotsenblau, + accentStrong: BRAND_COLORS.signalorange, + rule: BRAND_COLORS.zink, + tableHeaderBackground: BRAND_COLORS.hafengrau, headingFont: BRAND_FONTS.heading, bodyFont: BRAND_FONTS.body, } as const; @@ -30,7 +25,7 @@ export const DOCUMENT_THEME = { export type DocumentHeader = { /** Produktmarke — steht im Kopf jeder Ausgabe */ brand: string; - /** Dokumenttitel, z. B. „Richtlinie Zugriffskontrolle" */ + /** Dokumenttitel, z. B. „Einsatzbericht A-2026-0012" */ title: string; /** Organisation des Mandanten */ organisation?: string; @@ -41,10 +36,8 @@ export type DocumentHeader = { }; export type DocumentFooter = { - /** „Certvia — ein Produkt von GEFIM" */ + /** „Erstellt mit Craftvia" */ byline: string; - /** Logo-Pfad der Dachmarke für Ausgaben, die Bilder einbetten können */ - parentLogo: string; /** Optionaler Vertraulichkeitsvermerk */ confidentiality?: string; }; @@ -54,18 +47,14 @@ export function documentHeader(input: Omit<DocumentHeader, "brand">): DocumentHe return { brand: BRAND.name, ...input }; } -/** Fußzeilen-Daten — der Dachmarken-Hinweis ist nicht abschaltbar. */ +/** Fußzeilen-Daten einer Dokumentausgabe. */ export function documentFooter(confidentiality?: string): DocumentFooter { - return { - byline: BRAND.documentByline, - parentLogo: "/assets/logo/gefim-logo.png", - confidentiality, - }; + return { byline: BRAND.documentByline, confidentiality }; } /** * Einzeiler für Ausgabeformate, die nur eine Textfußzeile kennen - * (CSV-Kopfkommentar, Plaintext-Mail, DOCX-Footer ohne Bild). + * (CSV-Kopfkommentar, Plaintext, PDF-Footer ohne Bild). */ export function documentFooterLine(confidentiality?: string): string { const f = documentFooter(confidentiality); diff --git a/src/lib/email-brand.ts b/src/lib/email-brand.ts index 03bd5d9..ea02c47 100644 --- a/src/lib/email-brand.ts +++ b/src/lib/email-brand.ts @@ -5,7 +5,7 @@ import { DOCUMENT_THEME } from "@/lib/document-brand"; * Brandfähige E-Mail-Template-Basis (Story S6). * * SMTP und der Einladungs-/Reset-Flow kommen erst mit Paket 4 — dieses Modul - * liefert vorab das Certvia-Gerüst, damit die Templates dann nur noch Inhalt + * liefert das Craftvia-Gerüst, damit die Templates nur noch Inhalt * einsetzen müssen und das CD nicht neu erfunden wird. * * Bewusste Einschränkungen für E-Mail-Clients: @@ -39,7 +39,7 @@ export type EmailContent = { * E-Mail-Clients nicht. */ function baseUrl(): string { - return (process.env.AUTH_URL ?? process.env.NEXTAUTH_URL ?? "https://app.certvia.de").replace( + return (process.env.AUTH_URL ?? process.env.NEXTAUTH_URL ?? "http://localhost:3000").replace( /\/+$/, "" ); @@ -58,13 +58,13 @@ export function emailSender(address: string): string { return `${BRAND.name} <${address}>`; } -/** Signatur-/Fußzeilentext — enthält immer den Dachmarken-Hinweis. */ +/** Signatur-/Fußzeilentext. */ export function emailSignature(): string { - return `${BRAND.name} — ${BRAND.byline}`; + return `${BRAND.name} — ${BRAND.tagline}`; } /** - * Rendert eine Mail im Certvia-CD (heller Grund, Marken-Violett, Poppins-Kopf). + * Rendert eine Mail im Craftvia-CD (Hafengrau-Grund, Lotsenblau, Signalorange-CTA). * Rückgabe ist ein vollständiges HTML-Dokument. */ export function renderHtmlEmail(content: EmailContent): string { @@ -80,7 +80,7 @@ export function renderHtmlEmail(content: EmailContent): string { const action = content.action ? `<table role="presentation" cellpadding="0" cellspacing="0" style="margin:24px 0;"> - <tr><td style="background:${BRAND_COLORS.violet};border-radius:8px;"> + <tr><td style="background:${BRAND_COLORS.signalorange};border-radius:8px;"> <a href="${escapeHtml(content.action.url)}" style="display:inline-block;padding:12px 22px;font-family:${t.headingFont};font-size:15px;font-weight:600;color:#ffffff;text-decoration:none;">${escapeHtml(content.action.label)}</a> </td></tr> </table>` @@ -93,12 +93,13 @@ export function renderHtmlEmail(content: EmailContent): string { return `<!doctype html> <html lang="de"> <head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${escapeHtml(content.subject)} - - + +
- ${BRAND.name} + ${BRAND.name} + Craftvia

${escapeHtml(content.heading)}

diff --git a/src/server/mail/config.ts b/src/server/mail/config.ts index d35b446..0608c5c 100644 --- a/src/server/mail/config.ts +++ b/src/server/mail/config.ts @@ -90,7 +90,7 @@ export function resetMailConfigCache(): void { cached = null; } -/** Absenderzeile `Certvia `. */ +/** Absenderzeile `Craftvia `. */ export function mailFrom(config: MailConfig): string { return `${config.fromName} <${config.from}>`; } diff --git a/src/server/webauthn.ts b/src/server/webauthn.ts index f08bf57..4ee85da 100644 --- a/src/server/webauthn.ts +++ b/src/server/webauthn.ts @@ -9,6 +9,7 @@ import type { AuthenticationResponseJSON, AuthenticatorTransportFuture, } from "@simplewebauthn/types"; +import { BRAND } from "@/lib/brand"; /** * SEC3-b: WebAuthn/Passkey-Basis. Kapselt die RP-/Origin-Konfiguration (aus AUTH_URL bzw. @@ -17,7 +18,7 @@ import type { * (Cookie) und Persistenz. */ -export const RP_NAME = "Certvia"; +export const RP_NAME = BRAND.name; /** Cookie-Name der kurzlebigen Login-Challenge (pre-session Passkey-Login). */ export const LOGIN_CHALLENGE_COOKIE = "wa_login_challenge";