Basis: Certvia dev@a48c5fb als Fundament für Craftvia
CI / build-and-check (push) Canceled after 0s
CI / audit (push) Canceled after 0s
CI / sbom (push) Canceled after 0s

Unveränderter Stand von certvia/dev (a48c5fb) plus Craftvia-Spezifikation
und Brandbook unter docs/craftvia/. ISMS-Module werden im Folgecommit entfernt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-14 11:05:39 +02:00
co-authored by Claude Opus 5
commit c8e6f30a27
720 changed files with 140143 additions and 0 deletions
+119
View File
@@ -0,0 +1,119 @@
import { BRAND, BRAND_COLORS } from "@/lib/brand";
/**
* Certvia-Logo als Inline-SVG.
*
* Bewusst inline statt <Image src="…svg">: 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<LogoTheme, { arc: string; hook: string; word: string; accent: string }> = {
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 (
<>
<path
d="M46 20 A17 17 0 1 0 46 44"
fill="none"
stroke={arc}
strokeWidth={6}
strokeLinecap="round"
/>
<path
d="M27 32 l6 6 l12 -15"
fill="none"
stroke={hook}
strokeWidth={5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</>
);
}
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 (
<svg
viewBox="0 0 64 64"
width={height}
height={height}
role="img"
aria-label={title}
className={className}
>
<title>{title}</title>
<Monogram arc={c.arc} hook={c.hook} />
</svg>
);
}
return (
<svg
viewBox="0 0 380 96"
width={(height * 380) / 96}
height={height}
role="img"
aria-label={title}
className={className}
>
<title>{title}</title>
<g transform="translate(6,8) scale(1.25)">
<Monogram arc={c.arc} hook={c.hook} />
</g>
<text
x={112}
y={62}
fontSize={52}
letterSpacing={-1}
style={{ fontFamily: "var(--font-poppins), Poppins, 'Segoe UI', sans-serif" }}
>
<tspan fontWeight={300} fill={c.word}>
Cert
</tspan>
<tspan fontWeight={700} fill={c.accent}>
via
</tspan>
</text>
</svg>
);
}
+37
View File
@@ -0,0 +1,37 @@
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 (
<p
className={`flex items-center justify-center gap-2 text-[11px] text-muted-foreground ${className ?? ""}`}
>
<span>{BRAND.byline}</span>
{withLogo && (
<Image
src={BRAND_ASSETS.parentLogo}
alt={BRAND.parentBrand}
// Seitenverhältnis der Quelldatei (1000×353)
width={76}
height={27}
// Negativdarstellung auf dunklem Grund
className="opacity-80 brightness-0 invert"
/>
)}
</p>
);
}
+35
View File
@@ -0,0 +1,35 @@
import { CertviaLogo } from "@/components/brand/certvia-logo";
import type { TenantBranding } from "@/lib/brand";
/**
* Kopfbereichs-Marke eines Mandanten (Story S8).
*
* 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.
*/
export function TenantBrand({
branding,
height = 34,
className,
}: {
branding: TenantBranding;
height?: number;
className?: string;
}) {
if (branding.logoUrl) {
return (
// Mandanten-Logos liegen später im Objektspeicher (beliebige Domain) —
// daher bewusst <img> statt next/image ohne Remote-Pattern-Konfiguration.
// eslint-disable-next-line @next/next/no-img-element
<img
src={branding.logoUrl}
alt={branding.productName}
style={{ height, width: "auto" }}
className={className}
/>
);
}
return <CertviaLogo variant="lockup" theme="dark" height={height} className={className} />;
}