Fundament: Branding Craftvia

- Farb-Tokens laut Brandbook §11.4 (Lotsenblau, Signalorange, Graphit, Hafengrau,
  Stahlgrau, Zink, Funktionsfarben) in globals.css + src/lib/brand.ts; helles Theme
- Inline-SVG-Logo src/components/brand/craftvia-logo.tsx (horizontal/signet,
  color/mono/reversed, App-Icon-Kachel, optionale Tagline); Certvia-/GEFIM-Logos entfernt
- Favicon/PWA-Icons aus dem Signet erzeugt (scripts/generate-brand-icons.ts),
  site.webmanifest (Craftvia, theme_color #082E5B)
- Inter selbst gehostet (next/font/local, lokale OFL-Datei), Open Sans/Poppins entfernt
- Metadaten, WebAuthn-RP-Name, Mail-/Dokument-CD auf Craftvia umgestellt
- docs/craftvia/BRANDING.md ersetzt docs/BRANDING-CERTVIA.md

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-14 11:39:52 +02:00
co-authored by Claude Opus 5
parent 8491c7f173
commit 9ec7fa5356
62 changed files with 549 additions and 1070 deletions
+49 -68
View File
@@ -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 (`<title>`, 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,
};
}
+17 -28
View File
@@ -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);
+10 -9
View File
@@ -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)}</title></head>
<body style="margin:0;padding:0;background:#f4f2fa;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#f4f2fa;padding:28px 12px;">
<body style="margin:0;padding:0;background:${BRAND_COLORS.hafengrau};">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:${BRAND_COLORS.hafengrau};padding:28px 12px;">
<tr><td align="center">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="max-width:560px;background:${t.pageBackground};border-radius:14px;overflow:hidden;border:1px solid ${t.rule};">
<tr><td style="padding:26px 30px 0;">
<img src="${url}/assets/logo/certvia-lockup-positiv.svg" alt="${BRAND.name}" height="34" style="height:34px;width:auto;display:block;border:0;">
<img src="${url}/favicon/pwa-192.png" alt="${BRAND.name}" height="34" width="34" style="height:34px;width:34px;display:inline-block;vertical-align:middle;border:0;border-radius:8px;">
<span style="display:inline-block;vertical-align:middle;margin-left:10px;font-family:${t.headingFont};font-size:22px;font-weight:800;color:${BRAND_COLORS.lotsenblau};">Craft<span style="color:${BRAND_COLORS.signalorange};">via</span></span>
</td></tr>
<tr><td style="padding:22px 30px 4px;">
<h1 style="margin:0 0 14px;font-family:${t.headingFont};font-size:20px;font-weight:600;color:${t.text};">${escapeHtml(content.heading)}</h1>