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:
@@ -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 (
|
||||
<main className="flex flex-1 items-center justify-center p-6">
|
||||
<div className="shadow-card w-full max-w-sm rounded-2xl border bg-card p-8">
|
||||
<CertviaLogo variant="lockup" theme="dark" height={34} />
|
||||
<CraftviaLogo variant="horizontal" height={34} />
|
||||
<p className="mt-5 font-heading text-lg font-semibold">Passwort ändern</p>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
Ihr Konto wurde mit einem Initialpasswort angelegt. Bitte vergeben Sie jetzt ein eigenes Passwort, um fortzufahren.
|
||||
|
||||
@@ -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 (
|
||||
<main className="flex flex-1 items-center justify-center p-6">
|
||||
<div className="shadow-card w-full max-w-md rounded-2xl border bg-card p-8">
|
||||
<CertviaLogo variant="lockup" theme="dark" height={34} />
|
||||
<CraftviaLogo variant="horizontal" height={34} />
|
||||
<p className="mt-5 font-heading text-lg font-semibold">Zwei-Faktor-Authentifizierung einrichten</p>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
Ihre Organisation verlangt eine Zwei-Faktor-Authentifizierung. Scannen Sie den QR-Code mit
|
||||
|
||||
+2
-4
@@ -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 (
|
||||
<main className="flex flex-1 items-center justify-center p-6">
|
||||
<div className="shadow-card w-full max-w-sm rounded-2xl border bg-card p-8 text-center">
|
||||
<CertviaLogo variant="lockup" theme="dark" height={34} className="mx-auto" />
|
||||
<CraftviaLogo variant="horizontal" height={34} className="mx-auto" />
|
||||
<p className="mt-6 font-heading text-lg font-semibold">Es ist ein Fehler aufgetreten</p>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
Die Aktion konnte nicht abgeschlossen werden. Bitte versuchen Sie es erneut oder wenden
|
||||
@@ -31,7 +30,6 @@ export default function Error({
|
||||
<Button onClick={reset} className="mt-6 w-full justify-center">
|
||||
Erneut versuchen
|
||||
</Button>
|
||||
<PoweredByGefim className="mt-7 border-t border-[var(--panel-brd)] pt-4" />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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 (
|
||||
<main className="flex flex-1 items-center justify-center p-6">
|
||||
<div className="shadow-card w-full max-w-sm rounded-2xl border bg-card p-8">
|
||||
<CertviaLogo variant="lockup" theme="dark" height={36} />
|
||||
<CraftviaLogo variant="horizontal" height={36} />
|
||||
<p className="mt-5 font-heading text-lg font-semibold">Passwort vergessen</p>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
{domain === "platform"
|
||||
@@ -40,8 +39,6 @@ export default async function ForgotPasswordPage({
|
||||
Zurück zur Anmeldung
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
<PoweredByGefim className="mt-6 border-t border-[var(--panel-brd)] pt-4" />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
||||
+78
-269
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<main className="flex flex-1 items-center justify-center p-6">
|
||||
<div className="shadow-card w-full max-w-sm rounded-2xl border bg-card p-8">
|
||||
<CertviaLogo variant="lockup" theme="dark" height={36} />
|
||||
<CraftviaLogo variant="horizontal" height={36} />
|
||||
<p className="mt-5 font-heading text-lg font-semibold">Konto einrichten</p>
|
||||
|
||||
{valid ? (
|
||||
@@ -57,8 +56,6 @@ export default async function InvitePage({
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
|
||||
<PoweredByGefim className="mt-7 border-t border-[var(--panel-brd)] pt-4" />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
||||
+12
-40
@@ -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 (
|
||||
<html
|
||||
lang={locale}
|
||||
className={`${openSans.variable} ${poppins.variable} h-full antialiased`}
|
||||
>
|
||||
<html lang={locale} className={`${inter.variable} h-full antialiased`}>
|
||||
<body className="min-h-full flex flex-col bg-background text-foreground">
|
||||
<NextIntlClientProvider>{children}</NextIntlClientProvider>
|
||||
</body>
|
||||
|
||||
@@ -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 (
|
||||
<main className="flex flex-1 items-center justify-center p-6">
|
||||
<div className="shadow-card w-full max-w-sm rounded-2xl border bg-card p-8">
|
||||
<CertviaLogo variant="lockup" theme="dark" height={40} />
|
||||
<CraftviaLogo variant="horizontal" height={40} />
|
||||
<p className="mt-5 font-heading text-lg font-semibold">Bestätigung (2. Schritt)</p>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
Geben Sie den 6-stelligen Code aus Ihrer Authenticator-App oder einen Recovery-Code ein.
|
||||
@@ -81,8 +80,6 @@ export default async function LoginMfaPage({
|
||||
<p className="mt-4 text-center text-[12.5px]">
|
||||
<Link href="/login" className="text-muted-foreground hover:text-foreground">Zurück zur Anmeldung</Link>
|
||||
</p>
|
||||
|
||||
<PoweredByGefim className="mt-7 border-t border-[var(--panel-brd)] pt-4" />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -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 (
|
||||
<main className="flex flex-1 items-center justify-center p-6">
|
||||
<div className="shadow-card w-full max-w-sm rounded-2xl border bg-card p-8">
|
||||
<CertviaLogo variant="lockup" theme="dark" height={42} />
|
||||
<CraftviaLogo variant="horizontal" height={42} />
|
||||
<p className="mt-3 font-heading text-[13px] font-medium text-[var(--band-text)]">
|
||||
{t("tagline")}
|
||||
</p>
|
||||
@@ -122,8 +121,6 @@ export default async function LoginPage({
|
||||
{t("forgotPassword")}
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
<PoweredByGefim className="mt-7 border-t border-[var(--panel-brd)] pt-4" />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -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 (
|
||||
<main className="flex flex-1 items-center justify-center p-6">
|
||||
<div className="shadow-card w-full max-w-sm rounded-2xl border bg-card p-8 text-center">
|
||||
<CertviaLogo variant="lockup" theme="dark" height={34} className="mx-auto" />
|
||||
<CraftviaLogo variant="horizontal" height={34} className="mx-auto" />
|
||||
<p className="mt-6 font-heading text-4xl font-bold text-[var(--primary)]">404</p>
|
||||
<p className="mt-2 font-heading text-lg font-semibold">Seite nicht gefunden</p>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
@@ -21,7 +20,6 @@ export default function NotFound() {
|
||||
>
|
||||
Zum Dashboard
|
||||
</Button>
|
||||
<PoweredByGefim className="mt-7 border-t border-[var(--panel-brd)] pt-4" />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -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 (
|
||||
<main className="flex flex-1 items-center justify-center p-6">
|
||||
<div className="shadow-card w-full max-w-md rounded-2xl border bg-card p-8">
|
||||
<CertviaLogo variant="lockup" theme="dark" height={34} />
|
||||
<CraftviaLogo variant="horizontal" height={34} />
|
||||
<p className="mt-5 font-heading text-lg font-semibold">Zwei-Faktor-Authentifizierung einrichten</p>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
Für Plattform-Administratoren verpflichtend. Scannen Sie den QR-Code mit einer
|
||||
|
||||
@@ -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 (
|
||||
<main className="flex flex-1 items-center justify-center p-6">
|
||||
<div className="shadow-card w-full max-w-sm rounded-2xl border bg-card p-8">
|
||||
<CertviaLogo variant="lockup" theme="dark" height={38} />
|
||||
<CraftviaLogo variant="horizontal" height={38} />
|
||||
<p className="mt-4 font-heading text-lg font-semibold">Plattform-Administration</p>
|
||||
<h2 className="mt-1 text-sm font-normal text-muted-foreground">
|
||||
Betreiberzugang — getrennt vom Mandanten-Login
|
||||
@@ -84,8 +83,6 @@ export default async function PlatformLoginPage({
|
||||
Passwort vergessen?
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
<PoweredByGefim className="mt-7 border-t border-[var(--panel-brd)] pt-4" />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -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 (
|
||||
<main className="flex flex-1 items-center justify-center p-6">
|
||||
<div className="shadow-card w-full max-w-sm rounded-2xl border bg-card p-8">
|
||||
<CertviaLogo variant="lockup" theme="dark" height={36} />
|
||||
<CraftviaLogo variant="horizontal" height={36} />
|
||||
<p className="mt-5 font-heading text-lg font-semibold">Neues Passwort vergeben</p>
|
||||
|
||||
{valid ? (
|
||||
@@ -74,8 +73,6 @@ export default async function ResetPage({
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
|
||||
<PoweredByGefim className="mt-7 border-t border-[var(--panel-brd)] pt-4" />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -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 (
|
||||
<main className="flex flex-1 items-center justify-center p-6">
|
||||
<div className="shadow-card w-full max-w-sm rounded-2xl border bg-card p-8">
|
||||
<CertviaLogo variant="lockup" theme="dark" height={36} />
|
||||
<CraftviaLogo variant="horizontal" height={36} />
|
||||
<p className="mt-5 font-heading text-lg font-semibold">Mandant wählen</p>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
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() {
|
||||
<form action={async () => { "use server"; await signOut({ redirectTo: "/login" }); }} className="mt-5">
|
||||
<Button type="submit" variant="ghost" className="w-full justify-center text-[13px]">Abmelden</Button>
|
||||
</form>
|
||||
|
||||
<PoweredByGefim className="mt-7 border-t border-[var(--panel-brd)] pt-4" />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -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 (
|
||||
<main className="flex flex-1 items-center justify-center p-6">
|
||||
<div className="shadow-card w-full max-w-sm rounded-2xl border bg-card p-8">
|
||||
<CertviaLogo variant="lockup" theme="dark" height={36} />
|
||||
<CraftviaLogo variant="horizontal" height={36} />
|
||||
<p className="mt-5 font-heading text-lg font-semibold">E-Mail-Adresse bestätigen</p>
|
||||
|
||||
<p
|
||||
@@ -52,8 +51,6 @@ export default async function VerifyEmailPage({
|
||||
>
|
||||
Zur Anmeldung →
|
||||
</Link>
|
||||
|
||||
<PoweredByGefim className="mt-7 border-t border-[var(--panel-brd)] pt-4" />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user