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:
+49
-68
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user